![]() |
|
Snippets |
|
I needed to have many subclasses inherited from a main class.
To do that with propel, you need to have all your classes in one big table. I wanted to have separate tables.
Then I discovered the propel behaviors and it does that very well !
Let's see an example with a master class "element" and a subclass "subElement" :
The tables of the subclasses just need to have an "element_id" foreign key column, to be linked with the "element" master class table :
# schema.yml example propel: # master class element: _attributes: { phpName: Element } id: name: varchar(255) content: longvarchar created_at: # subclass with only the subclass properties and element_id foreign key column sub_element: _attributes: { phpName: SubElement } element_id: author: varchar(255)
Activate the behaviors in "project/config/propel.ini" (before building the model!):
propel.builder.AddBehaviors = true
Don't forget to rebuild the model, anytime you modify it :
>symfony propel-build-model
Create your behavior in "project/config/config.php" :
// get propel behavior lib require_once($sf_symfony_lib_dir.'/addon/propel/sfPropelBehavior.class.php'); // declare all the methods from of the master class "element", that you'll need in the subclasses sfPropelBehavior::registerMethods('ElementBehavior', array( array('ElementBehavior', 'setName'), array('ElementBehavior', 'getName'), array('ElementBehavior', 'setContent'), array('ElementBehavior', 'getContent'), array('ElementBehavior', 'setCreated'), array('ElementBehavior', 'getCreatedAt'), ) ); // Modify save() and delete() master class methods // SubElement->save() need to run Element->save()..., and so for SubElement->delete() sfPropelBehavior::registerHooks('ElementBehavior', array( ':save:pre' => array('ElementBehavior', 'preSave'), ':delete:pre' => array('ElementBehavior', 'preDelete'), ) );
Declare all these behavior methods in the master class model (here in "project/lib/model/Element.php") :
// declare this class after the element class class ElementBehavior { /* Properties Setters */ public function setName(BaseObject $object, $value) { if (!$object->getElement()) $object->setElement(new Element()); $object->getElement()->setName($value); } public function setContent(BaseObject $object, $value) { if (!$object->getElement()) $object->setElement(new Element()); $object->getElement()->setContent(value); } public function setCreatedAt(BaseObject $object, $value) { if (!$object->getElement()) $object->setElement(new Element()); $object->getElement()->setCreatedAt($value); } /* Properties Getters */ public function getId(BaseObject $object) { if (!$object->getElement()) $object->setElement(new Element()); return $object->getElement()->getId(); } public function getName(BaseObject $object) { if (!$object->getElement()) $object->setElement(new Element()); return $object->getElement()->getName(); } public function getContent(BaseObject $object) { if (!$object->getElement()) $object->setElement(new Element()); return $object->getElement()->getContent(); } public function getCreatedAt(BaseObject $object) { if (!$object->getElement()) $object->setElement(new Element()); return $object->getElement()->getCreatedAt(); } /* Element save method called just before SubElement save */ public function preSave(BaseObject $object) { // if element not null... if ($object->getElement()) { // save element in his table $object->getElement()->save(); // if new element, capture id if ($object->isNew()) $object->setElementId($object->getElement()->getId()); } } /* Element delete method called just before SubElement delete */ public function preDelete(BaseObject $object) { // if element not null and not new... if ($object->getElement() && !$object->getElement()->isNew()) { // delete element in his table $object->getElement()->delete(); } } }
add the "ElemenBehavior" to all your subclasses (here in" project/lib/model/SubElement.php") :
sfPropelBehavior::add('SubElement', array('ElementBehavior'));
That's all ! You can test it with a code like this :
// create subElement $test = new SubElement(); // test on element methods $test->setName('test'); $test->setContent('this is the big test !'); // test also SubElement methods $test->setAuthor('mr john smith'); // save in element table AND in sub_element table ! $test->save(); // capture subElement with id 1 $test = SubElementPeer :: retrieveByPk(1); // delete element and subElement in the two tables $test->delete(); // mix criteria for both class $c = new Criteria(); $c->add(ElementPeer :: ID, 1); $c->add(SubElementPeer :: AUTHOR, 'mr john smith'); // get a list of the subElements $subElements = SubElementPeer :: doSelectJoinElement(new Criteria());
each time you add a column or a method to the element model, you need to declare it in the behavior to be handled in the subclasses.
It's a way to go, may be there is a simplier way, but this works well for me...
Comments on this snippet
Great snipe.
some minur corrections in project/config/config.php shuld say
and in project/lib/model/Element.php
Ooops! thanks! Corrected...
I'm trying to use it but I have a problem: The behavior seems to work, I don't have any error but when I use a getter like "$sub_element->getName();" inheritance doesn't work ! Symfony doesn't find this function. How can I find where I made a mistake ? I did a print_r($sub_element) but no Element in there... is this normal ? When I change something in behaviors symfony react so I think that the problem is near from the display of $sub_element. Any help ? Thx
So after two days looking for a solution I found the answer : a "symfony propel-build-model" ! After this tutorial you've to rebuild the model ! aaaaahh Only that ! I'm sick :s ;) thx for the tutorial
It works ! It's a good way to go for inheritance !
I already have a little problem: It does not work with "propel-load-data", I can't correctly populate my database this way :s
When I make a propel-load-data I have this: The class "BaseSubElement" has already a mixin for method "setName". It's like if propel was loading two times my project/lib/model/SubElement.php !
Any idea ?
Benoit: I had same problem. sometimes config/config.php is included more than once by symfony or pake tasks, so sfPropelBehavior::registerMethods() is called again. Try to move behavior code and config to separate plugin (plugin configs are included only once).
It seems to work fine within the code. Did you try with an admin-generator for the SubElement ? Fields for SubElement are well displayed but fields for Element are grayed out (disabled=true). If I add disabled=false in the generator.yml, i can edit these fields, they are saved but not displayed back on screen.
It's a bit odd.