2
votes

I'm new to Propel ORM (and ORMs in general) and trying to get my bearings a bit here regarding properly and cleanly resolving Foreign Key relationships in a MySQL database.

Starting with 3 simple MySQL tables:

hero:

  • id
  • name
  • description

skill:

  • id
  • name
  • rank

hero_skill:

  • id
  • hero_id
  • skill_id

Where hero_skill has the expected foreign key properties, and with all the Propel classes generate.

How do I, in Propel, completely 'hydrate' a 'Hero' object with the proper references to all their Skills in a generic way?

Simple Example of how it can be done with a specific function:

function getAllTheHeroes(){
    $query = PropelQuery::from("Hero")
    $heroes = $query->find();  //hero model objects
    foreach($heroes as $hero){
        $heroSkills = $hero->getHeroSkills();  //hero_skill model objects
        foreach($heroSkills as $heroSkill){  
            $skill = $heroSkill->getSkill();  //skill model object.
        }
        echo $hero->exportTo('JSON');  //export to JSON string (new in Propel 1.6)
    }      
}

So the above code gets all the heroes, iterates and gets a collection of all the hero_skill records for the hero_id, then iterates and gets skill records for the skill_id. The important thing is that it packs all these resolved relations as references into the Hero object, so the calls to getWHATEVER() 'hydrate' the object with that data, all this so that when each hero is exported to JSON, it has a list of all the skill records for this Hero, rather than just the ids. This works as expected and is fine AS LONG AS YOU WRITE CUSTOM FUNCTIONS TO HANDLE EACH OBJECT TYPE. The JSON from this is something akin to:

[{
"Id":1, 
"Name":"Hero McHeroington", 
"Description":"Awesome.", 
"HeroSkills":
{
    "HeroSkill_0":
    {
        "Id":1,
        "HeroId":1,
        "SkillId":2,
        "Skill":
            {
                "Id":2,
                "Name":"Interpretive Dance",
                "Rank":5
            }
    },
    "HeroSkill_1":
    {
        "Id":2,
        "HeroId":1,
        "SkillId":4,
        "Skill":
            {
                "Id":4,
                "Name":"Pottery",
                "Rank":2
            }
    },
    "HeroSkill_2":
    {
        "Id":3,
        "HeroId":1,
        "SkillId":5,
        "Skill":
            {
                "Id":5,
                "Name":"Walking",
                "Rank":1
            }
    }
}
 },{
"Id":2, 
"Name":"Squire McTinyPants",
"Description":"Chipper.", 
"HeroSkills":
{
    "HeroSkill_0":
    {
        "Id":4,
        "HeroId":2,
        "SkillId":2,
        "Skill":
            {
                "Id":2,
                "Name":"Interpretive Dance",
                "Rank":10
            }
    },
    "HeroSkill_1":
    {
        "Id":5,
        "HeroId":2,
        "SkillId":3,
        "Skill":
            {
                "Id":3,
                "Name":"Peeping",
                "Rank":6
            }
    },
    "HeroSkill_2":
    {
        "Id":6,
        "HeroId":2,
        "SkillId":6,
        "Skill":
            {
                "Id":6,
                "Name":"Skipping",
                "Rank":4
            }
    }
}
}]

Blah Blah.

What I would like to do is something more generic:

function getModel($byClassName){
    $pq = PropelQuery::from($byClassName)
    $recs = $pq->find();  // <- collection of records of unknown type
    foreach ($recs as $rec){
        //Somehow retrieve all of the 'getFOREIGNKEY' Propel generated helper functions and call them all in sequence, 
        // then iterate down into any of these logical children objects and repeat the process to fully resolve all Foreign Key relationships within this Object
        // and all this Objects 'children' 
        // AND not get stuck in infinite loops due to Many-To-Many relationships from the 'getWHATEVER' calls running in circles.


        //Or some other method entirely to accomplish the same thing that i am just not getting...

         echo $rec->exportTo('JSON');  //export to JSON string (new in Propel 1.6)
    }
}

I'm digging through the Propel Docs and Source, and I still haven't wrapped my head around the whole Peer/TableMap/RelationMap/etc model, and I'm getting close to a solution out of the box, but keep hitting dead ends.

BUT this does not seem like a terribly unique concept that I'm trying to make happen here, so I'm hoping there is a Propel guru on here who can quickly point out where I'm being retarded about this.

Thanks!

EDIT: (In response to wimvds comment) The goal of the generic approach is to allow me to configure the relationships at the database level, then propel-gen the PHP classes, and use them as Objects without having to worry about the underlying datastore.

With a specific approach, I'd have to define those relationships in the DB, then write a custom 'hydration' function (as listed above) to properly retrieve the object from the datastore. The philosophical problem with that approach is replication, in that if I change a relationship in the database, then rerun propel-gen THEN I also have to update my hydration functions to mirror that change in structure. It seems like all the pieces should be there to allow for skipping that last step and remove the replication.

For example, with the small specific-case hydration function above, All I'm doing is calling the getWHATEVER() helper functions based on the knowledge in my head that hero_skill relates to both hero and skill, and when I get a hero I want to check hero_skill and get the corresponding skill list. Propel knows about these relations as well due to the Foreign Keys, and when using joinWith() it is even aware of recursion (sets recursive object refs to a string value of '*RECURSION'), so the pieces are all in place for Propel to intelligently auto-hydrate these objects while gracefully resolving/ignoring recursion, I just can't find the proper syntax to make that happen.

ie: get an Object, resolve it's FK relations, hydrate all 'child' Objects (rows in related tables by FK) and exclude recursive Objects.

I guess what I'm looking for is a way to completely abstract the datastore, take advantage of Propel's class generation ability based on database schema.xml definitions and thus not have to custom write a bunch of helper functions that may be subject to change if data structure changes. Thus allowing me to code in terms of Objects and not Database records, isn't that the whole point of an ORM?

I hope that clarifies a bit, thanks!

1
Why would you want to make it generic, since you can easily fetch related objects per object using ->joinWith() or populateRelations. You'll probably end up hitting a brick wall with this generic approach anyway (consider an entity with a recursive relation - ie. some kind of hierarchy).wimvds

1 Answers

1
votes

I guess that you could give a try to the isCrossRef table attribute to simplify your queries/hydrations.

See : http://www.propelorm.org/wiki/Documentation/1.5/WhatsNew#Many-to-ManyRelationships