php-activerecord icon indicating copy to clipboard operation
php-activerecord copied to clipboard

Foreign Key with custom class problem

Open sameerkanda opened this issue 11 years ago • 17 comments

My code:

class ZP_Package extends ActiveRecord\Model {
    static $table_name = 'packages';
    static $has_many = array(
        array('items_packages', 'class_name'=>'ZP_Item_Package'),
        array('items', 'through'=>'items_packages', 'class_name'=>'ZP_Item', 'foreign_key'=>'item_id')
    );
}

class ZP_Item extends ActiveRecord\Model {
    static $table_name = 'items';
    static $has_many = array(
        array('items_packages', 'class_name'=>'ZP_Item_Package'),
        array('packages', 'through'=>'items_packages', 'class_name'=>'ZP_Package', 'foreign_key'=>'package_id')
    );

}

class ZP_Item_Package extends ActiveRecord\Model {
    static $table_name = 'items_packages';
    static $belongs_to = array(
        array('items', 'class_name'=>'ZP_Item', 'foreign_key'=>'item_id'),
        array('packages', 'class_name'=>'ZP_Package', 'foreign_key'=>'package_id')
    );
}

When I do:

$item = ZP_Item::find(24); //works fine
$item->pakages; //doesn't work!!!!!

Basically, I don't get any errors, but the query is not generated properly (actually I do get a mysql error, but what I mean to say is I don't get any PHP errors). My mysql query that is generated is something like this:

...INNER JOIN items_packages ON(packages.id = items_packages.zp_package_id)....

Notice it says "items_packages.zp_package_id" instead of "items_packages.package_id". ActiveRecord simply takes the class name and uses it as a foreign key... even though the foreign key is defined (it seems to ignore it). I'm new to PHPActiveRecord, but I'm a pro PHP programmer. I've digged into the problem, and came to a conclusion that it was a bug in ActiveRecord, where it simply uses the classname instead of the foriegn key that's defined (look at class "HasMany extends AbstractRelationship", method "load", in particular this line "$this->set_keys......", it's in the "php-activerecord/lib/Relationship.php" file).

If this is not a bug, and I'm just doing something stupid, please let me know. I know my class names don't match with the DB table names, I know I should change that, but that's not the point.... I want to know if this is a bug or not? I'm using PHPActiveRecord 1.0 (haven't tested the nightly build).

Thanks!!!

sameerkanda avatar Mar 20 '13 04:03 sameerkanda

just an update, i realized im using the nightly build, not the stable version. but still... any help would be appreciated, thanks!

sameerkanda avatar Mar 20 '13 04:03 sameerkanda

@samerkanda, would you mind adding code delimiters to your example? It would make reading it a lot easier. If you don't know, you can start and end a block of text with with three backticks (on the tilde key) to mark an extended code block. Thanks.

al-the-x avatar Mar 31 '13 02:03 al-the-x

@al-the-x Sorry, thats not working for me, not sure why

sameerkanda avatar Mar 31 '13 17:03 sameerkanda

@sameerkanda You can read about code formatting in Markdown/GitHub format here (specifically the "Syntax highlighting" section). You can also use gists and choose PHP as your language and it will format it appropriately.

jpfuentes2 avatar Mar 31 '13 19:03 jpfuentes2

Ah, that's cool! I've update the issue! Thanks guys

sameerkanda avatar Mar 31 '13 23:03 sameerkanda

@sameerkanda Can you also include your model definition for ZP_Package? Or at least verify that you're setting the primary key field on that model to "package_id"...? Almost every ORM I've ever worked with assumes that my PKs are just "id", but I usually name them after the entity to make NATURAL JOIN easier. If you haven't set the PK field manually...

al-the-x avatar Apr 01 '13 01:04 al-the-x

@al-the-x The database primary key's are set to "id" for all tables, that's why they are not included in the definition. As stated above, the mysql is generated properly, except for the fact that:

...INNER JOIN items_packages ON(packages.id = items_packages.zp_package_id)....

should be

...INNER JOIN items_packages ON(packages.id = items_packages.package_id)....

Notice that "package_id" is a foreign key, and it's defined in the table definition as such, but PHPActiveRecord ignores it, and just assumes the class name is the foreign.

sameerkanda avatar Apr 01 '13 02:04 sameerkanda

Ah, I see now. Thanks for the clarification @sameerkanda...!

al-the-x avatar Apr 01 '13 15:04 al-the-x

@al-the-x So is this a bug? Or am I doing something wrong? Sorry for being ignorant, but should I expect this to be fixed in the next version of so, or should I attempt to fix it myself and do a pull request? I haven't really contributed to any open-source project before... but then again, there hasn't really been any project that interested me as much as php-activerecord.

sameerkanda avatar Apr 01 '13 18:04 sameerkanda

If you can write tests for it and fix it they're more than welcome to you writing bug fixes :).

anther avatar Apr 01 '13 19:04 anther

cool, i'll give it a shot at fixing it, probably won't be done anytime soon though. Thanks!

sameerkanda avatar Apr 01 '13 20:04 sameerkanda

I've got same issue and I made a quick fix. It seems like to be most clean solution.

Relationship.php

protected function set_keys($model_class_name, $override=false)
    {
        if (!$this->foreign_key || $override)
        {   
            if($this instanceof HasMany || $this instanceof HasOne) {
                $relation_opt = $this->get_table()->get_relationship($this->through)->options;
                if(isset($relation_opt['foreign_key'])) {
                    $this->foreign_key = array($relation_opt['foreign_key']);
                } else {
                    $this->foreign_key = array(Inflector::instance()->keyify($model_class_name));
                }

            } else {
                $this->foreign_key = array(Inflector::instance()->keyify($model_class_name));
            }
        }
        if (!$this->primary_key || $override)
        {
            if($this instanceof HasMany || $this instanceof HasOne) {
                $relation_opt = $this->get_table()->get_relationship($this->through)->options;
                if(isset($relation_opt['primary_key'])) {
                    $this->primary_key = array($relation_opt['primary_key']);
                } else {
                    $this->primary_key = Table::load($model_class_name)->pk;
                }

            } else {
                $this->primary_key = Table::load($model_class_name)->pk;
            }
        }

    }

imkebe avatar Nov 17 '13 15:11 imkebe

Thanks @imkebe, but I decided it was not worth dealing with phpactiverecord, since a friend recommended me to Laravel 4, it has a mind blowing ORM, has superior to phpactiverecord, and it has never caused any problems.

sameerkanda avatar Nov 17 '13 21:11 sameerkanda

Hallo @sameerkanda, i started a project with php activerecord and after half work was done i got the same error: it takes the class name 'HpbView' and converts to 'hpb_view_id' instead of assigned 'foreign_key' => 'view_id' in my 'has_many' association. :( Im going to try the solution of @imkebe , otherwise have to rewrite my project or rename table's primary keys, OMG...

rmetel avatar Jan 29 '15 15:01 rmetel

@rmetel I've been using Laravel's ORM for over a year now, it's been awesome. I recommend it if you want to take a look (maybe for future projects). It has a bit of a learning curve though.

sameerkanda avatar Jan 29 '15 18:01 sameerkanda

@sameerkanda im also using laravel in few projects already, great framework indeed! In case of 'php activerecord' i wanted fast integration without complete MVC model.

rmetel avatar Jan 29 '15 20:01 rmetel

@sameerkanda a combination of @imkebe's bugfix + a trick did it for me! Following situation: 'user' is connected with 'pictures' via pivot table 'collections', so my classes looked like

class Picture extends ActiveRecord\Model {
    static $has_many = array(
        array('collections')
    );
}
class Collection extends ActiveRecord\Model
{
    static $belongs_to = array(
        array('user'),
        array('picture')
    );
}
class User extends ActiveRecord\Model
{
    static $has_many = array(
        array('collections'),
        array('pictures', 'through' => 'collections', 'foreign_key' => 'user_id')
    );
}

Interesting part is the foreign_key in class User, it's NOT the foreign_key of pictures (picture_id) its the foreign_key of user table. The generated query looks like

SELECT pictures.* FROM collections INNER JOIN pictures ON (pictures.id = collections.picture_id) WHERE user_id=?

So, you can imagine, if you put a foreign_key of pictures (picture_id) your result would be just one picture and not all pictures of same user. After that php activerecord used the right foreign key and havent tried to use convenient foreign key. In your case it would mean u must set the foreign key of ZP_Package:

class ZP_Package extends ActiveRecord\Model {
    static $table_name = 'packages';
    static $has_many = array(
        array('items_packages', 'class_name'=>'ZP_Item_Package'),
        array('items', 'through'=>'items_packages', 'class_name'=>'ZP_Item', 'foreign_key'=>'zp_package_id')
    );
}

I hope i could explain it ;)

rmetel avatar Jan 30 '15 10:01 rmetel