Sidebar

Using iTop

Creating your iTop

iTop Customization

"How to" examples
DataModel

User Interface

Automation & Ticket management

Portal Customization

:: Version 3.2.0 ::

Add a 1:1 relationship

Prerequisite: You must be familiar with the Syntax used in Tutorials and have already created an extension.
We also assume that you are familiar with dashboard design within iTop and OQL.

learning:
Add a one to one relationship attribute on a class
level:
Medium
domains:
XML, PHP, Relation
methods:
GetObject, EVENT_DB_SET_ATTRIBUTES_FLAGS, EVENT_DB_AFTER_WRITE, EVENT_DB_BEFORE_DELETE, ListPreviousValuesForUpdatedAttributes
min version:
2.6.0

You want to create a field which would be a one to one relationship, but such type of field does not exist, so how could you do.

  • This solution is just one of the possible options.
  • This solution has some limitation: the relationship will be editable on one side only
  • The relation must be visible from both side of the relationship.
  • In this solution, I have considered that it was not possible to create a relationship with someone already engaged, no forced divorce ๐Ÿ’‘๐Ÿคช!, but this is an option, you could allow it

1:1 between 2 classes

Declare 2 ExternalKeys

Let's imagine 2 classes: Person and CompanyCar. A Person may have a company car, but only one. A company car can be allocated to one Person maximum.

itop-design / classes / class@Person / fields
   <field id="companycar_id" xsi:type="AttributeExternalKey" _delta="define">
       <filter><![CDATA[SELECT CompanyCar AS cc WHERE cc.person_id = 0]]></filter>
       <dependencies/>
       <sql>companycar_id</sql>
       <target_class>CompanyCar</target_class>
       <is_null_allowed>true</is_null_allowed>
       <on_target_delete>DEL_MANUAL</on_target_delete>
       <allow_target_creation>true</allow_target_creation>
    </field>
itop-design / classes / class@CompanyCar/ fields
   <field id="person_id" xsi:type="AttributeExternalKey" _delta="define">
       <filter/>
       <dependencies/>
       <sql>person_id</sql>
       <target_class>Person</target_class>
       <is_null_allowed>true</is_null_allowed>
       <on_target_delete>DEL_MANUAL</on_target_delete>
       <allow_target_creation>false</allow_target_creation>
    </field>

Force Keys synchronization

    <event_listeners>
        <event_listener id="AfterWriteOneToOneRelationKeys" _delta="define">
            <event>EVENT_DB_AFTER_WRITE</event>
            <filters>
               <filter>Person</filter>
               <filter>CompanyCar</filter>
            </filters>
            <rank>0</rank>
            <code><![CDATA[function(Combodo\iTop\Service\Events\EventData $oEventData)
{   // Replace the below variables with your own classes names and field code and that's all
    $sCurrentClass = 'Person';
    $sKeyToCurrent = 'person_id';
    $sRemoteClass = 'CompanyCar';
    $sKeyToRemote = 'companycar_id';
    $oObject = $oEventData->Get('object');
 
    if (get_class($oObject) == $sCurrentClass  ) {
        $aChanges = $oObject->ListPreviousValuesForUpdatedAttributes();
        // After creation or modification of the remote key, if remote key is not empty
        if (($oEventData->Get('is_new') || array_key_exists($sKeyToRemote, $aChanges)) && ($oObject->Get($sKeyToRemote) > 0)) {
            $oNewRemote = MetaModel::GetObject($sRemoteClass, $oObject->Get($sKeyToRemote), false, true);
            if ($oNewRemote) {
                // Update the remote object with current object key
                $oNewRemote->Set($sKeyToCurrent, $oObject->GetKey());
                $oNewRemote->DBUpdate();
            }
        }
        // on modification of the remote key, if previous remote key was not empty
        if (!$oEventData->Get('is_new') && array_key_exists($sKeyToRemote, $aChanges) && ($aChanges[$sKeyToRemote] > 0)) {
            $oOldRemote = MetaModel::GetObject($sRemoteClass, $aChanges[$sKeyToRemote], false, true);
            if ($oOldRemote) {
                // Empty the previous remote object key to current
                $oOldRemote->Set($sKeyToCurrent, 0);
                $oOldRemote->DBUpdate();
            }
        }
    }
} 
            ]]></ code> <!-- Remove the blank before code !! -->
        </event_listener>
        <event_listener id="OnDeleteOneToOneRelationKeys" _delta="define">
            <event>EVENT_DB_BEFORE_DELETE</event>
            <filters>
               <filter>Person</filter>
               <filter>CompanyCar</filter>
            </filters>
            <rank>0</rank>
            <code><![CDATA[function(Combodo\iTop\Service\Events\EventData $oEventData)
{   // Replace the below variables with your own classes names and field code and that's all
    $sCurrentClass = 'Person';
    $sKeyToCurrent = 'person_id';
    $sRemoteClass = 'CompanyCar';
    $sKeyToRemote = 'companycar_id';
    $oObject = $oEventData->Get('object');
 
    if ((get_class($oObject) == $sCurrentClass) && ($oObject->Get($sKeyToRemote) > 0)) {
        $oOldRemote = MetaModel::GetObject($sRemoteClass, $oObject->Get($sKeyToRemote), false, true);
        if ($oOldRemote) {
            // Empty the previous remote object key to current
            $oOldRemote->Set($sKeyToCurrent, 0);
            $oOldRemote->DBUpdate();
        }
    }
}
            ]]></ code>
        </event_listener>
    </event_listeners>

Prevent edition on one side

In order to avoid an infinite loop of update, it's easier to lock the edition on one side of the relationship

Callback method

This time we create 2 methods, one for controlling the creation form and one for the modification form

class:CompanyCar
  public function evtSetInitialAttributeFlags(Combodo\iTop\Service\Events\EventData $oEventData)
  {   
    $this->ForceInitialAttributeFlags('end_date', OPT_ATT_HIDDEN); 
  }
  public function evtSetAttributeFlags(Combodo\iTop\Service\Events\EventData $oEventData)
  {   
    $this->ForceAttributeFlags('end_date', OPT_ATT_READONLY); 
  }

Adding listeners to the events

Then we do the plumbing, by listing to the event and specifying the callback method declared above

itop_design version="3.1" | classes
    <class id="CompanyCar">
      <event_listeners>
        <event_listener id="evtSetInitialFlags" _delta="define">
          <event>EVENT_DB_SET_INITIAL_ATTRIBUTES_FLAGS</event>
          <rank>10</rank>
          <callback>evtSetInitialFlags</callback>
        </event_listener>
        <event_listener id="evtSetAttributeFlags" _delta="define">
          <event>EVENT_DB_SET_ATTRIBUTES_FLAGS</event>
          <rank>10</rank>
          <callback>evtSetAttributeFlags</callback>
        </event_listener>
      </event_listeners>

Allowing stealing

Let's suppose we want to reaffect a car to a driver, even if the car is already allocated:

  1. Change the filter to allow stealing a company car to its current driver,
  2. But when you update the previous driver, to empty his companycar_id, you must not enter in an infinite loop.

๐Ÿšง Find and describe a mechanism to avoid looping: for example storing on the OldDriver object an on the flight property like _no_propagation_on_companycar_id and test it in OnDBUpdate to stop the loop

1:1 within the same class

Let's suppose that we have a Person field called In couple with pointing to another person, but himself.

๐Ÿšง requires a bit of thinking to avoid infinite loop as we must allow edition on both ends as there is just one.


Similar Tutorials
3_2_0/customization/one-to-one-relation.txt ยท Last modified: 2025/05/19 16:10 by 127.0.0.1
Back to top
Contact us