Jump to content

Creating a polymorphic 1:1 relation: Difference between revisions

From NusaATK
Gabilan (talk | contribs)
No edit summary
 
(6 intermediate revisions by 2 users not shown)
Line 1: Line 1:
{{howto|expert|[[Gabriel Achtig]] <achtig@gmail.com>}}
{{howto|expert|[[Gabriel Achtig]] <achtig@gmail.com>}}
== Preface ==
== Preface ==
First of all, you should carefully read the relations documentation in this wiki, specially the [[Relations#atkOneToOneRelation|onetoone]] relation


From a [http://www.achievo.org/forum/viewtopic.php?t=1349&start=0&postdays=0&postorder=asc&highlight= thread] in the forum, regarding polymorphism and onetoone relations
Next you can read this [http://www.achievo.org/forum/viewtopic.php?t=1349&start=0&postdays=0&postorder=asc&highlight= 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.
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.
Line 8: Line 9:
== DB Schema ==
== DB Schema ==
Here is the db schema for this example
Here is the db schema for this example
<pre>
 
<sql>
CREATE TABLE `fruit` (
CREATE TABLE `fruit` (
   `id` int(11) NOT NULL,
   `id` int(11) NOT NULL,
Line 17: Line 19:
)
)
--  
--  
-- genatt* are the generic attributes of the entity
-- genatt* are the generic attributes of the entity (shared by all types)
--  
--  
CREATE TABLE `fruittype` (
CREATE TABLE `fruittype` (
Line 26: Line 28:
)  
)  
--  
--  
-- table holds the tablename (e.g orange), descrition is shown in the dropdown
-- table holds the tablename (e.g orange), description is shown in the dropdown
--  
--  
CREATE TABLE `orange` (
CREATE TABLE `orange` (
Line 35: Line 37:
)
)
--  
--  
-- orangeatt* are the specific attributes of the entity
-- orangeatt* are the specific attributes of this type
--  
--  


</pre>
</sql>
 
== Polymorphic Relation ==
== Polymorphic Relation ==
Here is the Relation
Here is the Relation, class.atkpolymorphiconetoonerelation.inc, in the relations subdir of the module
class.atkpolymorphiconetoonerelation.inc
 
in the relations subdir of the module
<syntaxhighlight lang="php">
<pre>
<?php
<?php
userelation("atkonetoonerelation");
  userelation("atkonetoonerelation");
class atkPolymorphicOneToOneRelation extends atkOneToOneRelation
  class atkPolymorphicOneToOneRelation extends atkOneToOneRelation
{
  {
/**
    /**
     * The name of the foreign key field in the master node to the type table.
     * The name of the foreign key field in the master node to the type table.
     * @access private
     * @access private
     * @var String
     * @var String
     */
     */
var $m_typefk="";
    var $m_typefk="";
/**
 
    /**
     * The name of the foreign key field in the master node to the type table.
     * The name of the foreign key field in the master node to the type table.
     * @access private
     * @access private
     * @var String
     * @var String
     */
     */
var $m_discriminatorfield="";
    var $m_discriminatorfield="";
/**
 
    /**
     * $modulename The module name
     * $modulename The module name
     * @access private
     * @access private
     * @var String
     * @var String
     */
     */
var $m_modulename="";
    var $m_modulename="";


     /**
     /**
Line 74: Line 78:
     * <code>
     * <code>
     * $this->add(new atkPolymorphicOneToOneRelation("details","fruittype_id","table","poly.orange",
     * $this->add(new atkPolymorphicOneToOneRelation("details","fruittype_id","table","poly.orange",
*   "poly","fruit_id",AF_CASCADE_DELETE ));
    *               "poly","fruit_id",AF_CASCADE_DELETE ));
     * </code>
     * </code>
     *
     *
     * @param String $name The unique name of the attribute.  
     * @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 $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 $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
     * @param String $defaultdest The default destination node (in module.nodename
     *                            notation)
     *                            notation)
* @param String $modulename The module name  
    * @param String $modulename The module name  
     * @param String $refKey Specifies the foreign key
     * @param String $refKey Specifies the foreign key
     *                      field from the destination node that points to
     *                      field from the destination node that points to
Line 90: Line 95:
     */
     */


function atkPolymorphicOneToOneRelation($name,$typefk,$discriminatorfield,$defaultdest,$modulename,$refKey, $flags=0)
    function atkPolymorphicOneToOneRelation($name,$typefk,$discriminatorfield,
{
                                            $defaultdest,$modulename,$refKey, $flags=0)
$this->atkOneToOneRelation($name,"",$refKey, $flags|AF_HIDE_LIST);
    {
$this->m_typefk=$typefk;
      $this->atkOneToOneRelation($name,"",$refKey, $flags|AF_HIDE_LIST);
$this->m_discriminatorfield=$discriminatorfield;
      $this->m_typefk=$typefk;
$this->m_destination =$defaultdest;
      $this->m_discriminatorfield=$discriminatorfield;
$this->m_modulename =$modulename;
      $this->m_destination =$defaultdest;
}
      $this->m_modulename =$modulename;
function loadType()
    }
{
 
return POSTLOAD;
    function loadType()
}
    {
      return POSTLOAD;
    }
 
     /**
     /**
     * Retrieve detail records from the database.
     * Retrieve detail records from the database.
Line 114: Line 122:
     *                      return the atkonetoone load function
     *                      return the atkonetoone load function
     */
     */
 
    function load(&$db, $record, $mode)
function load(&$db, $record, $mode)
    {
{
      $this->m_destination = $this->m_modulename.".".$record[$this->m_typefk][$this->m_discriminatorfield];   
$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];  
$this->m_destInstance = $this->m_modulename.".".$record[$this->m_typefk][$this->m_discriminatorfield];  
      return parent::load($db, $record, $mode);
return parent::load($db, $record, $mode);
    }
}
  }  
}  
?>
  ?>
</syntaxhighlight>
 
</pre>


== Implementation ==
== Implementation ==
Using the relation in a Fruit node, please read the previous usage comments in the atkpolymorphiconetoonerelation constructor.
Using the relation in a Fruit node, please read the previous usage comments in the atkpolymorphiconetoonerelation constructor.


<pre>
<syntaxhighlight lang="php">
<?php
<?php
atkimport("atk.atkmetanode");
  atkimport("atk.atkmetanode");
userelation("poly.atkpolymorphiconetoonerelation");
  userelation("poly.atkpolymorphiconetoonerelation");
userelation("atkmanytoonerelation");
  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 ));
    }


class fruit extends atkMetaNode
    public function initial_values()
{
    {  
function fruit()
      $init_vals = array();
{
      if($this->m_postvars['fruittype_id'])
        $this->atkMetaNode();
      {
$this->add(new  atkManyToOneRelation('fruittype_id','poly.fruittype',
        $emp = atkGetNode("poly.fruittype");
                                                      AF_OBLIGATORY));    
        $emp->addFilter($this->m_postvars['fruittype_id']);
$this->add(new atkPolymorphicOneToOneRelation("details",
        $result = $emp->select()->getFirstRow();
                                    "fruittype_id","table","poly.orange","poly",
        $init_vals["fruittype_id"] = $result['id'];
                                      "fruit_id",AF_CASCADE_DELETE ));
      }
}
      return $init_vals;
    }


function initial_values()
    public function action_admin($handler)
{  
$init_vals = array();
if($this->m_postvars['fruittype_id'])
{
$emp = &getNode("poly.fruittype");
$emp->addFilter($this->m_postvars['fruittype_id']);
$result = $emp->selectDb();
$init_vals["fruittype_id"] = $result['0']['id'];
}
return $init_vals;
}
function action_admin(&$handler)
     {
     {
       $this->setDynamicAttributes();
       $this->setDynamicAttributes();
Line 165: Line 171:
     }
     }
    
    
     function action_add(&$handler)
     public function action_add($handler)
     {
     {
       $this->setDynamicAttributes();
       $this->setDynamicAttributes();
       return $handler->action_add();
       return $handler->action_add();
     }
     }
function action_update(&$handler)
 
    public function action_update($handler)
     {
     {
       $this->setDynamicAttributes();
       $this->setDynamicAttributes();
Line 176: Line 183:
     }
     }


     function action_save(&$handler)
     public function action_save($handler)
     {
     {
       $this->setDynamicAttributes();
       $this->setDynamicAttributes();
       return $handler->action_save();
       return $handler->action_save();
     }
     }
     function action_edit(&$handler)
 
     public function action_edit($handler)
     {
     {
       $this->setDynamicAttributes();
       $this->setDynamicAttributes();
  $typeattr = &$this->getAttribute('fruittype_id');
      $this->getAttribute('fruittype_id')->addFlag(AF_READONLY);
  $typeattr->addFlag(AF_READONLY);
       return $handler->action_edit();
       return $handler->action_edit();
     }   
     }   
    
    
  function setDynamicAttributes()
    function setDynamicAttributes()
     {
     {
$typeattr = &$this->getAttribute('fruittype_id');
      $typeattr = &$this->getAttribute('fruittype_id');
$requesturi=$_SERVER['REQUEST_URI'];
      $requesturi=$_SERVER['REQUEST_URI'];
//We must remove the last fruittype_id from the url
      //We must remove the last fruittype_id from the url
$lasturi=substr($requesturi,0,strpos($requesturi,"&fruittype_id="));
      $lasturi=substr($requesturi,0,strpos($requesturi,"&fruittype_id="));
if ($lasturi=='')$lasturi=$requesturi;//first time is not set
      if ($lasturi=='')$lasturi=$requesturi;//first time is not set
$typeattr->addOnChangeHandler
      $typeattr->addOnChangeHandler("window.location='".$lasturi."&fruittype_id='+newvalue;");
                ("window.location='".$lasturi."&fruittype_id='+newvalue;");
      $polyattr = &$this->getAttribute('details');
$polyattr = &$this->getAttribute('details');
      if ($this->m_postvars['fruittype_id'])
if ($this->m_postvars['fruittype_id'])
      {
{
        $emp = atkGetNode("poly.fruittype");
$emp = &getNode("poly.fruittype");
        $emp->addFilter($this->m_postvars['fruittype_id']);
$emp->addFilter($this->m_postvars['fruittype_id']);
        $result = $emp->select()->getFirstRow();
$result = $emp->selectDb();
 
//the table field reflects the node name
        //the table field reflects the node name
$polyattr->m_destination='poly.'.$result['0']['table'];
        $polyattr->m_destination='poly.'.$result['table'];
$polyattr->m_destInstance='poly'.$result['0']['table'];
        $polyattr->m_destInstance='poly'.$result['table'];
$polyattr->createDestination();
        $polyattr->createDestination();
}
      }
     }  
     }  
}
  }  
?>
?>
</syntaxhighlight>
The fruittype node:


</pre>
<syntaxhighlight lang="php">
The fruittype node
<pre>
<?php
<?php
atkimport("atk.atkmetanode");
  atkimport("atk.atkmetanode");


class fruittype extends atkMetaNode  
  class fruittype extends atkMetaNode  
{
  {
function fruittype()
    protected $descriptor = "[description]";
{
$this->atkMetaNode();
$this->add(new atkAttribute("table",AF_FORCE_LOAD|AF_OBLIGATORY));


    public static function meta($policy)
    {
      $policy->get("table")->addFlag(AF_FORCE_LOAD|AF_OBLIGATORY);
    }


}
  }
function descriptor_def()
?>
{
</syntaxhighlight>
return "[description]";
}
}


?>
</pre>
== Conclusion ==
== Conclusion ==
This can be useful for example in a Person-Role scenario (user as master and employee, customer, manager as details)
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 firs working version, for example the relation is readonly in edit mode (once a fruit is a pear it cannot be changed to an orange)
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).

Latest revision as of 07:23, 10 February 2010

ATK Howto: Creating a polymorphic 1:1 relation

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

List of other Howto's

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

<sql> 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 --

</sql>

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