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:
- GetAttributeFlags, GetObject, OnDBInsert, OnDBUpdate, OnDBDelete, 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
Replace in the code below the constants by your own classes names and field code and that's all
- Person
-
class SynchroOneToOneRelation implements iApplicationObjectExtension { const CURRENT_CLASS = 'Person'; const KEY_TO_CURRENT = 'person_id'; const REMOTE_CLASS = 'CompanyCar'; const KEY_TO_REMOTE = 'companycar_id'; public function OnDBInsert($oObject, $oChange = null) { if ((get_class($oObject) == static::CURRENT_CLASS ) && ($oObject->Get(static::KEY_TO_REMOTE) > 0)) { $oNewRemote = MetaModel::GetObject(static::REMOTE_CLASS, $oObject->Get(static::KEY_TO_REMOTE), false, true); if ($oNewRemote) { $oNewRemote->Set(static::KEY_TO_CURRENT, $oObject->GetKey()); $oNewRemote->DBUpdate(); } } } public function OnDBUpdate($oObject, $oChange = null) { if (get_class($oObject) == static::CURRENT_CLASS ) { $aChanges = $oObject->ListPreviousValuesForUpdatedAttributes(); if (array_key_exists(static::KEY_TO_REMOTE, $aChanges)) { if ($oObject->Get(static::KEY_TO_REMOTE) > 0) { $oNewRemote = MetaModel::GetObject(static::REMOTE_CLASS, $oObject->Get(static::KEY_TO_REMOTE), false, true); if ($oNewRemote) { $oNewRemote->Set(static::KEY_TO_CURRENT, $oObject->GetKey()); $oNewRemote->DBUpdate(); } } if ($aChanges[static::KEY_TO_REMOTE] > 0) { $oOldRemote = MetaModel::GetObject(static::REMOTE_CLASS, $aChanges[static::KEY_TO_REMOTE], false, true); if ($oOldRemote) { $oOldRemote->Set(static::KEY_TO_CURRENT, 0); $oOldRemote->DBUpdate(); } } } } } public function OnDBDelete($oObject, $oChange = null) { if (get_class($oObject) == static::CURRENT_CLASS && ($oObject->Get(static::KEY_TO_REMOTE) > 0)) { $oOldRemote = MetaModel::GetObject(static::REMOTE_CLASS, $oObject->Get(static::KEY_TO_REMOTE), false, true); if ($oOldRemote) { $oOldRemote->Set(static::KEY_TO_CURRENT, 0); $oOldRemote->DBUpdate(); } } } public function OnCheckToWrite($oObject) { return; } public function OnIsModified($oObject) { return false; } public function OnCheckToDelete($oObject) { return; } }
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
- CompanyCar
-
public function GetAttributeFlags($sAttCode, &$aReasons = array(), $sTargetState = '') { if ($sAttCode == 'person_id') { return(OPT_ATT_READONLY | parent::GetAttributeFlags($sAttCode, $aReasons, $sTargetState)); } return parent::GetAttributeFlags($sAttCode, $aReasons, $sTargetState); } public function GetInitialStateAttributeFlags($sAttCode, &$aReasons = array()) { if (($sAttCode == 'person_id')) { return(OPT_ATT_HIDDEN | parent::GetInitialStateAttributeFlags($sAttCode, $aReasons)); } return parent::GetInitialStateAttributeFlags($sAttCode, $aReasons); }
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.