Sidebar

Using iTop

Creating your iTop

iTop Customization

"How to" examples
DataModel

User Interface

Automation & Ticket management

Portal Customization

:: Version 3.2.0 ::

Check data integrity

Prerequisite: You must be familiar with the Syntax used in Tutorials and have already created an extension.

learning:
Impose data integrity rules
level:
Intermediate
domains:
PHP, Constrain
methods:
DoCheckToWrite
min version:
2.1.0

In the below examples we will use a method to detect an incoherence and prevent such object to be saved in database.

  • This method in the Console or Portal, reports errors after submission.
  • And prevents creation and update of incoherent objects done by DataSynchro, REST/JSON and CSV import.

Theory

We will overwrite the method DoCheckToWrite() of the object class:

  • This method is invoked just before writing to database - See details of call stack.
  • The method should provide error message(s) if it encounters data incoherence.
  • Errors messages are recorded in an array $this->m_aCheckIssues[],
  • Warnings messages are recorded in an array $this->m_aCheckWarnings[],
  • When returning from this method, if there is at least one error the object is not written to database (creation or update)
  • Error and warning messages are
    • displayed to the user in interactive mode only: Console, Portal, CSV import
    • logged in itop/log/error.log depending on level of tracking for DataSynchro, REST/JSON, CLI FIXME -to be checked !-

Migration: No visible effect on setup, but objects not compliant can no more be modified, until they are made compliant. So it could prevent a datasynchro or a REST/JSON script to update other fields for eg.
To identify faulty objects, create an audit rule to retrieve objects not compliant to this new constrain and fix them one by one in the UI or by CSV import.

DoCheckToWrite method can prevent creation/modification in all cases: on the Console, in the Portal, in CSV import, in DataSynchro and in REST/JSON API
As the overwrite of method can only be done by a single extension, you can also
Do not try to Set values on current object in this method as it has effect

Examples

Start date < End date

In this use case we will prevent a Change to be recorded with an End date which would be before the Start date.

class:Change
   public function DoCheckToWrite()
   {
      // Always ask the parent class to perform its own check
      parent::DoCheckToWrite();
 
      // Defensive programming, ensuring that 'end_date' and 'start_date' has not been removed 
      // from the Change class by some extensions which I am not yet aware of.
      // Get the value in seconds before comparing them is safer
      if (MetaModel::IsValidAttCode(get_class($this), 'start_date') 
       && MetaModel::IsValidAttCode(get_class($this), 'end_date')
       && (AttributeDateTime::GetAsUnixSeconds($this->Get('start_date')) 
         > AttributeDateTime::GetAsUnixSeconds($this->Get('end_date'))))
      {
         $this->m_aCheckIssues[] = Dict::Format('Class:Error:EndDateMustBeGreaterThanStartDate');
      }
   }

Location required on production Server

In this use case we want to prevent a Server to be put in 'production' status without a Location to be provided.

class:Server
public function DoCheckToWrite()
{
   // Always ask the parent class to perform their own check
   parent::DoCheckToWrite();
   // Defensive programming, ensuring that 'status' is an existing field on the current class
   // then checking the condition: an enum value returns code, not label, so we test the code,
   if (MetaModel::IsValidAttCode(get_class($this), 'status')
   && ($this->Get('status') == 'production'))
   {
      // AttributeExternalKey are never NULL, O is the value used when empty
      if (MetaModel::IsValidAttCode(get_class($this), 'location_id')
      && ($this->Get('location_id') == 0))
      {
         // 'Server:Error:LocationMandatoryInProduction' must be declared as a dictionary entry
         $this->m_aCheckIssues[] = Dict::Format('Server:Error:LocationMandatoryInProduction');
      }
   }
}
         // You may also provide a simple error message in plain text
         $this->m_aCheckIssues[] = 'Location is mandatory for all Servers in production';

Here the way to define a dictionary entry in XML:

itop_design / dictionaries / dictionary@EN US / entries
    <entry id="Server:Error:LocationMandatoryInProduction" _delta="define">
      <![CDATA['Location is mandatory for all Servers in production']]>
    </entry>

FunctionalCI name unique

In this use case we want to prevent two FunctionalCIs to have the same name. Except if the FunctionalCI is in fact a SoftwareInstance, a MiddlewareInstance, a DatabaseSchema or an ApplicationSolution, in which case, we don't care.

Uniqueness rules can perform similar uniqueness checks, but all in XML, without the need to write PHP code.
class:FunctionalCI
public function DoCheckToWrite()
{
    // Call the function on the parent class as it may need to check stuff as well
    parent::DoCheckToWrite();
    // Check that the name of the FunctionalCI must be unique
    $aChanges = $this->ListChanges();
    // Check if the name field was set or changed
    if (array_key_exists('name', $aChanges))
    {
      $sNewName = $aChanges['name'];
      // Retrieve all FunctionalCI having that new name, ignoring CIs from some sub-classes
      $oSearch = DBObjectSearch::FromOQL_AllData("
         SELECT FunctionalCI WHERE name = :newFCI 
         AND finalclass NOT IN ('DBServer','Middleware','OtherSoftware','WebServer',
            'PCSoftware','MiddlewareInstance','DatabaseSchema','ApplicationSolution')
      ");
      $oSet = new DBObjectSet($oSearch, array(), array('newFCI' => $sNewName));
      // If there is at least one FunctionalCI matching the required name
      if ($oSet->Count() > 0)
      {
         // Block the FunctionalCI writing the Database
         $this->m_aCheckIssues[] = Dict::Format('Class:FunctionalCI:FCINameMustBeUnique', $sNewName);
      }
   }
}

Change must have a CI

Ensure that a Change has an associated CI attached at creation.

  • It's a good example to force a n:n relationship to have at least one entry.
class:Change
public function DoCheckToWrite()
{
    parent::DoCheckToWrite();
 
    // Check if the current Change is expected to have a CI linked 
    // under creation, with a particular type, service, status, whatever logic you need...
    if ($this->IsNew())
    {
        $oSet = $this->Get('functionalcis_list');
        if ($oSet->Count() == 0)
        {
             $this->m_aCheckIssues[] = Dict::S('Class:User/Error:AtLeastOneCiIsNeeded');
        }
    }
}

User must have a Profile

This is how iTop out of the box ensures that a user has always at least one profile attached.

  • Note that this is not enough to prevent the deletion of a Profile which would be the only one of a given User
  • Not a big deal in this particular example as iTop UI does not offer any mean to delete a Profile
DoCheckToWrite or iApplicationObjectExtension::OnCheckToWrite() can not guarantee that the rule will always be applied, if the check is made on other objects that the current one. This is the case for eg. when testing condition on LinkedSet or LinkedSetIndirect fields, as in this example.
class:User
public function DoCheckToWrite()
{
    // Call the function on the parent class as it may need to check stuff as well
    parent::DoCheckToWrite();
    // Check if the profile list was changed to avoid loading it for nothing
    $aChanges = $this->ListChanges();   
    if (array_key_exists('profile_list', $aChanges))
    {
        $oSet = $this->Get('profile_list');
        if ($oSet->Count() == 0)
        {
             $this->m_aCheckIssues[] = Dict::S('Class:User/Error:AtLeastOneProfileIsNeeded');
        }
    }
}

Prevent creation

This method is not really user friendly, but it allows to let some users modify any FunctionalCI, but limit the creation of a new FonctionalCI only to users having the profile “Configuration Manager”

class:FunctionalCI
public function DoCheckToWrite()
{
    if ($this->IsNew() // Are we trying to create a new object
    && !(UserRights::HasProfile('Configuration Manager')) // The user does not have the profile "Configuration Manager"
    {
 
        $this->m_aCheckIssues[] = Dict::S('Class:FunctionalCI/Error:CreationDenied');    
    }
}

The dictionary entry in this example, does not exist, you must create it.

3_2_0/customization/check-to-write.txt · Last modified: 2024/06/20 17:28 (external edit)
Back to top
Contact us