Creating a polymorphic 1:1 relation

From Achievo/ATK Wiki

Jump to: navigation, search

ATK Howto: Creating a polymorphic 1:1 relation

Complexity: expert
Author: Gabriel Achtig <achtig@gmail.com>

List of other Howto's

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).

Personal tools
Navigation