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:
-
Change the filter to allow stealing a company car to its current driver,
-
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.