Creating a polymorphic 1:1 relation
From Achievo/ATK Wiki
|
ATK Howto: Creating a polymorphic 1:1 relation
|
Contents |
Preface
First of all, you should carefully read the relations documentation in this wiki, specially the onetoone relation
Next you can read this thread in the forum, regarding polymorphism and onetoone relations
My goal was to implement a one2one relation between a master table (e.g fruit) and several detail tables (orange, apple, pear, etc). This can be useful for showing a single node with all the fruits instead of showing a node for apples, another for pears, etc.
DB Schema
Here is the db schema for this example
CREATE TABLE `fruit` ( `id` int(11) NOT NULL, `fruittype_id` int(11) NOT NULL, `genatt1` varchar(50) DEFAULT NULL, `genatt2` varchar(50) DEFAULT NULL, PRIMARY KEY (`id`) ) -- -- genatt* are the generic attributes of the entity (shared by all types) -- CREATE TABLE `fruittype` ( `id` int(11) NOT NULL, `table` varchar(50) NOT NULL DEFAULT '', `description` varchar(50) NOT NULL DEFAULT '', PRIMARY KEY (`id`) ) -- -- table holds the tablename (e.g orange), description is shown in the dropdown -- CREATE TABLE `orange` ( `fruit_id` int(11) NOT NULL, `orangeatt1` varchar(50) DEFAULT NULL, `orangeatt2` varchar(50) DEFAULT NULL, PRIMARY KEY (`fruit_id`) ) -- -- orangeatt* are the specific attributes of this type --
Polymorphic Relation
Here is the Relation, class.atkpolymorphiconetoonerelation.inc, in the relations subdir of the module
<?php userelation("atkonetoonerelation"); class atkPolymorphicOneToOneRelation extends atkOneToOneRelation { /** * The name of the foreign key field in the master node to the type table. * @access private * @var String */ var $m_typefk=""; /** * The name of the foreign key field in the master node to the type table. * @access private * @var String */ var $m_discriminatorfield=""; /** * $modulename The module name * @access private * @var String */ var $m_modulename=""; /** * Default Constructor * * The atkPolymorphicOneToOneRelation extends atkOneToOneRelation: * <b>Example:</b> * <code> * $this->add(new atkPolymorphicOneToOneRelation("details","fruittype_id","table","poly.orange", * "poly","fruit_id",AF_CASCADE_DELETE )); * </code> * * @param String $name The unique name of the attribute. * @param String $typefk The name of the foreign key field in the master node to the type table . * @param String $discriminatorfield The name of the field in the type table wich stores the type tablename * (a node with the same name must be created). * @param String $defaultdest The default destination node (in module.nodename * notation) * @param String $modulename The module name * @param String $refKey Specifies the foreign key * field from the destination node that points to * the master record. * @param int $flags Attribute flags that influence this attributes' * behavior. */ function atkPolymorphicOneToOneRelation($name,$typefk,$discriminatorfield, $defaultdest,$modulename,$refKey, $flags=0) { $this->atkOneToOneRelation($name,"",$refKey, $flags|AF_HIDE_LIST); $this->m_typefk=$typefk; $this->m_discriminatorfield=$discriminatorfield; $this->m_destination =$defaultdest; $this->m_modulename =$modulename; } function loadType() { return POSTLOAD; } /** * Retrieve detail records from the database. * * Called by the framework to load the detail records. * * @param atkDb $db The database used by the node. * @param array $record The master record * @param String $mode The mode for loading (admin, select, copy, etc) * * @return array Sets the destination from the record and * return the atkonetoone load function */ function load(&$db, $record, $mode) { $this->m_destination = $this->m_modulename.".".$record[$this->m_typefk][$this->m_discriminatorfield]; $this->m_destInstance = $this->m_modulename.".".$record[$this->m_typefk][$this->m_discriminatorfield]; return parent::load($db, $record, $mode); } } ?>
Implementation
Using the relation in a Fruit node, please read the previous usage comments in the atkpolymorphiconetoonerelation constructor.
<?php atkimport("atk.atkmetanode"); userelation("poly.atkpolymorphiconetoonerelation"); userelation("atkmanytoonerelation"); class fruit extends atkMetaNode { public function postMeta() { $this->add(new atkManyToOneRelation('fruittype_id', "poly.fruittype", AF_OBLIGATORY)); $this->add(new atkPolymorphicOneToOneRelation("details", "fruittype_id","table","poly.orange","poly", "fruit_id",AF_CASCADE_DELETE )); } public function initial_values() { $init_vals = array(); if($this->m_postvars['fruittype_id']) { $emp = atkGetNode("poly.fruittype"); $emp->addFilter($this->m_postvars['fruittype_id']); $result = $emp->select()->getFirstRow(); $init_vals["fruittype_id"] = $result['id']; } return $init_vals; } public function action_admin($handler) { $this->setDynamicAttributes(); return $handler->action_admin(); } public function action_add($handler) { $this->setDynamicAttributes(); return $handler->action_add(); } public function action_update($handler) { $this->setDynamicAttributes(); return $handler->action_update(); } public function action_save($handler) { $this->setDynamicAttributes(); return $handler->action_save(); } public function action_edit($handler) { $this->setDynamicAttributes(); $this->getAttribute('fruittype_id')->addFlag(AF_READONLY); return $handler->action_edit(); } function setDynamicAttributes() { $typeattr = &$this->getAttribute('fruittype_id'); $requesturi=$_SERVER['REQUEST_URI']; //We must remove the last fruittype_id from the url $lasturi=substr($requesturi,0,strpos($requesturi,"&fruittype_id=")); if ($lasturi=='')$lasturi=$requesturi;//first time is not set $typeattr->addOnChangeHandler("window.location='".$lasturi."&fruittype_id='+newvalue;"); $polyattr = &$this->getAttribute('details'); if ($this->m_postvars['fruittype_id']) { $emp = atkGetNode("poly.fruittype"); $emp->addFilter($this->m_postvars['fruittype_id']); $result = $emp->select()->getFirstRow(); //the table field reflects the node name $polyattr->m_destination='poly.'.$result['table']; $polyattr->m_destInstance='poly'.$result['table']; $polyattr->createDestination(); } } } ?>
The fruittype node:
<?php atkimport("atk.atkmetanode"); class fruittype extends atkMetaNode { protected $descriptor = "[description]"; public static function meta($policy) { $policy->get("table")->addFlag(AF_FORCE_LOAD|AF_OBLIGATORY); } } ?>
Conclusion
This can be useful for example in a Person-Role scenario (user as master and employee, customer, manager as details) Please notice that this is the first working version and has some limitations, for example the relation is readonly in edit mode (e.g. once a fruit is created as a pear it cannot be changed to an orange). In order to do so, i think that the pear should be deleted and an orange should be created (in the fruit onchange handler).