php-activerecord / activerecord

PHP ActiveRecord Project
Other
33 stars 10 forks source link

Uncaught TypeError: implode(): Argument #1 ($array) must be of type array, string given #115

Closed ebadta81 closed 1 year ago

ebadta81 commented 1 year ago

Hi,

I try to migrate my old php app from php 5.4 to php 8.1, and I copied the project , then installed activerecord v1.1.11 into my new project with composer.

I did a database dump and then restore, so the database is the same. On the old app, everything works fine.

In the new app Everything looks good until I get this error:

[14-Oct-2023 13:38:51 Europe/Budapest] model get name = brand_id
[14-Oct-2023 13:38:51 Europe/Budapest] model get name = title
[14-Oct-2023 13:38:51 Europe/Budapest] model get name = brand
[14-Oct-2023 13:38:51 Europe/Budapest] PHP Fatal error:  Uncaught TypeError: implode(): Argument #1 ($array) must be of type array, string given in /phpapp/vendor/php-patterns/activerecord/lib/Relationship.php:323
Stack trace:
#0 /phpapp/vendor/php-patterns/activerecord/lib/Relationship.php(323): implode()
#1 /phpapp/vendor/php-patterns/activerecord/lib/Relationship.php(743): ActiveRecord\AbstractRelationship->create_conditions_from_keys()
#2 /phpapp/vendor/php-patterns/activerecord/lib/Model.php(553): ActiveRecord\BelongsTo->load()
#3 /phpapp/vendor/php-patterns/activerecord/lib/Model.php(352): ActiveRecord\Model->read_attribute()
#4 /phpapp/core/libs/model.php(71): ActiveRecord\Model->__get()

For debug info, I put a error_log("model get name = " . $name); into my __get() function, so I see there is a problem at __get("brand").

WHAT COULD BE THE PROBLEM HERE?

My Product class has this $belongs_to definition:

    static $belongs_to = array(
        array(
            'category',
            'foreign_key' => 'product_cat_id',
            'class_name' => 'Category'
        ),
        array(
            'brand',
            'foreign_key' => 'product_brand_id',
            'class_name' => 'Brand'
        ),
        array(
            'shop',
            'foreign_key' => 'shop_id',
            'class_name' => 'Shop',
        )
    );

And in mysql:

CREATE TABLE `webshop_products` (
  `product_id` int(11) NOT NULL AUTO_INCREMENT,
  `product_root_cat_id` int(11) NOT NULL,
  `product_cat_id` int(11) NOT NULL,
  `product_brand_id` int(11) NOT NULL,
  `product_guarantee_id` int(11) DEFAULT NULL,
  `product_root_guarantee_id` int(11) DEFAULT NULL,
  `product_type_id` int(11) DEFAULT NULL,
  `product_title` varchar(255) NOT NULL,
  `product_number` varchar(255) NOT NULL,
  `product_friendly` varchar(255) NOT NULL,
  `product_lead` text NOT NULL,
  `product_description` text DEFAULT NULL,
  `product_weight` int(11) NOT NULL,
  `product_vat` double NOT NULL,
  `product_prefered` tinyint(1) NOT NULL,
  `product_new` int(1) NOT NULL,
  `product_lang` int(11) NOT NULL,
  `product_disabled` tinyint(1) NOT NULL,
  `product_doc_name` varchar(255) NOT NULL,
  `product_doc_original` varchar(255) NOT NULL,
  `shop_id` int(11) NOT NULL,
  `product_seo_title` varchar(255) NOT NULL,
  `product_seo_keywords` text NOT NULL,
  `product_seo_description` text NOT NULL,
  PRIMARY KEY (`product_id`),
  KEY `shop_id` (`shop_id`),
  KEY `product_number` (`product_number`),
  KEY `product_friendly` (`product_friendly`),
  KEY `product_prefered` (`product_prefered`),
  KEY `product_new` (`product_new`),
  KEY `product_type_id` (`product_type_id`),
  KEY `product_root_cat_id` (`product_root_cat_id`),
  KEY `product_cat_id` (`product_cat_id`),
  KEY `product_brand_id` (`product_brand_id`),
  KEY `product_guarantee_id` (`product_guarantee_id`),
  KEY `product_root_guarantee_id` (`product_root_guarantee_id`),
  KEY `product_lang` (`product_lang`),
  KEY `product_disabled` (`product_disabled`)
)

My brand table:

CREATE TABLE `webshop_brands` (
  `brand_id` int(11) NOT NULL AUTO_INCREMENT,
  `brand_title` varchar(255) NOT NULL,
  `brand_image` varchar(255) NOT NULL,
  `brand_friendly` varchar(255) NOT NULL,
  `brand_pos` int(11) NOT NULL,
  `brand_enabled` tinyint(1) NOT NULL,
  `brand_lang` int(11) NOT NULL,
  `shop_id` int(11) NOT NULL,
  PRIMARY KEY (`brand_id`),
  KEY `shop_id` (`shop_id`),
  KEY `cat_friendly` (`brand_friendly`),
  KEY `brand_enabled` (`brand_enabled`),
  KEY `brand_lang` (`brand_lang`)
)

And php definition:

class Brand extends model
{

    static $table_name = 'webshop_brands';

    static $primary_key = 'brand_id';

    public $prefix = 'brand_';

    static $has_many = array(
        array(
            'products',
            'foreign_key' => 'product_brand_id',
            'class_name' => 'Product',
        ),
    );
...
shmax commented 1 year ago

The error seems to be telling us that a string value for the primary key was delivered to implode instead of the expected array. I see in the __get magic method on BelongsTo we load a non-array value into primary_key when it's not already set, so maybe what you can do is try adding a static $primary_key = ... property to your Product class. Does that change anything?

ebadta81 commented 1 year ago

It was already there, I just show you some code I thought is important.

This is the beginning of my Products class:

class Product extends model
{

    static $table_name = 'webshop_products';

    static $primary_key = 'product_id';

    public $prefix = 'product_';
shmax commented 1 year ago

Hmm, okay. I think what you're going to have to do is put a breakpoint in that __get magic method on BelongsTo and see if you can work out what is happening. After the primary key lazy loads, the value of $this->primary_key should be an array. If it's not, see if you can work backwards...

ebadta81 commented 1 year ago

Okay,

So the problem seems like, the primary key doesn't lazy load for me. In Relationship.php BelongsTo class load function, when I get there, the $this->primary_key is null. This causes the error.

I tried with this definitions, none worked:

array('brand')
array('brand', 'primary_key' => 'brand_id'),
        array(
            'brand',
        'primary_key' => 'brand_id',
            'foreign_key' => 'product_brand_id',
            'class_name' => 'Brand'
        ),

I did set a break point in BelongsTo->__get($name) but php never get there, so it doesn't fill there the $this->primary_key .

shmax commented 1 year ago

Unfortunately, I've never been super-familiar with the relationship stuff. You may or may not have noticed that I've been trying to prepare a 2.0 release that modernizes the code base and fixes lots of little issues. If you want to stick with the 1.x series and really track down what's happening here you might consider rolling up your sleeves, cloning this repo, and checking out commit cc365079d15c8e758b8cd4e06b34e7c4d9be5625, which is the earliest commit in which the unit tests work. If you can come up with a breaking test that illustrates the problem here then we can issue a fix with a new 1.x patch. Alternatively, you could try the latest rc release (which is pretty solid--I'm already using it in production on my private project).

shmax commented 1 year ago

Closing due to inactivity. Freel free to reopen, @ebadta81