字段约束条件

决条件:您必须熟悉教程中使用的语法 并且已经创建了一个扩展.

学习:施加数据完整性规则

水平:

域:PHPConstrain

最低版本:2.1.0

在下面的示例中,我们将使用一种方法来检测不一致性并防止将此类对象保存在数据库中。

  • 控制台或门户中的此方法在提交后报告错误。
  • 并防止创建和更新由DataSynchro,REST/JSON和CSV导入完成的含混的对象。

理论

我们将覆盖对象类的方法DoCheckToWrite():

  • 即将在写入数据库之前调用此方法-请参见调用堆栈.
  • 如果该方法遇到数据不一致性,则应提供错误消息。
  • 错误消息记录在数组$ this-> m_aCheckIssues []中,
  • 警告消息记录在数组$ this-> m_aCheckWarnings []中,
  • 从该方法返回时,如果至少有一个错误,则不会将对象写入数据库(创建或更新)
  • 错误和警告消息是
    • 仅以交互模式显示到用户:控制台,门户,CSV导入
    • 在跟踪DataSynchro,REST/JSON,CLI的级别上登录itop/plog/error.log依赖

- - 被检查 !-

迁移:对设置没有可见的影响,但是在符合标准之前,不能再修改不符合标准的对象。因此,这可能会阻止datasynchro或REST/JSON脚本更新其他字段,例如。
要识别故障对象,请创建审计规则以检索不符合此新约束的对象,并在UI或CSV导入中一一修复它们。

DoCheckToWrite方法可以在所有情况下防止创建或修改:在控制台,门户,CSV导入,DataSynchro和REST/JSON API中

除了覆盖DoCheckToWrite()方法,您还可以使用扩展API 并将相同的代码放入iApplicationObjectExtension :: OnCheckToWrite()

  • 该API的优势在于,多个扩展可以并行进行检查,
  • 方法的覆盖只能由一个扩展名完成。

例子

开始日期<结束日期

在此用例中,我们将防止记录变更的结束日期早于开始日期。

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(($this), 'start_date') && MetaModel::IsValidAttCode(($this), 'end_date') && (AttributeDateTime::GetAsUnixSeconds($this->Get('start_date')) > AttributeDateTime::GetAsUnixSeconds($this->Get('end_date')))) { $this->m_aCheckIssues[] = Dict::Format('Class:Error:EndDateMustBeGreaterThanStartDate'); } }

生产服务器所需的位置

在此用例中,我们要防止将服务器放入“生产”状况中,而无需提供位置。

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(($this), 'status') && ($this->Get('status') == 'production')) { // AttributeExternalKey are never NULL, O is the value used when empty if (MetaModel::IsValidAttCode(($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';

这是在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名称唯一

在此用例中,我们要防止两个FunctionalCI具有相同的名称。除非FunctionalCI实际上是SoftwareInstance,MiddlewareInstance,DatabaseSchema或ApplicationSolution,在这种情况下,我们不在乎。

唯一性规则 可以执行类似的唯一性检查,但全部以XML进行,而无需编写PHP代码。

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 (('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, (), ('newFCI' => $sNewName)); // If there is at least one FunctionalCI matching the required name if ($oSet->() > 0) { // Block the FunctionalCI writing the Database $this->m_aCheckIssues[] = Dict::Format('Class:FunctionalCI:FCINameMustBeUnique', $sNewName); } } }

用户必须具有简档

这就是iTop如何确保用户始终至少连接一个简档的方式。

  • 强制n:n关系至少具有一个条目是一个很好的例子。
  • 请注意,这还不足以防止删除简档,而这将是给定用户中的唯一一个
    • 在此特定示例中没什么大不了的,因为iTop UI并不意味着删除简档就是供应

如果对当前对象的其他对象进行检查,则DoCheckToWrite或iApplicationObjectExtension :: OnCheckToWrite()无法保证将始终应用该规则。例如是这种情况。在本示例中,在LinkedSet或LinkedSetIndirect字段上测试条件时。

FIXME写一个关于如何确保LinkedSet字段总是至少一个条目的教程

class:User

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 profile list was changed to avoid loading it for nothing if (('profile_list', $aChanges)) { $oSet = $this->Get('profile_list'); if ($oSet->() == 0) { $this->m_aCheckIssues[] = Dict::S('Class:User/Error:AtLeastOneProfileIsNeeded'); } } }

原贴链接:https://www.itophub.io/wiki/page?id=2_7_0%3Acustomization%3Acheck-to-write


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

Instead of overwriting the DoCheckToWrite() method, you can also use the Extensions API and put that same code into iApplicationObjectExtension::OnCheckToWrite()

  • The advantage of the API is that multiple extensions can do their check in parallel,

  • The overwrite of method can only be done by a single extension.

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(($this), 'start_date') && MetaModel::IsValidAttCode(($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(($this), 'status') && ($this->Get('status') == 'production')) { // AttributeExternalKey are never NULL, O is the value used when empty if (MetaModel::IsValidAttCode(($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 (('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, (), ('newFCI' => $sNewName)); // If there is at least one FunctionalCI matching the required name if ($oSet->() > 0) { // Block the FunctionalCI writing the Database $this->m_aCheckIssues[] = Dict::Format('Class:FunctionalCI:FCINameMustBeUnique', $sNewName); } } }

User must have a Profile

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

  • It's a good example to force a n:n relationship to have at least one entry.

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

FIXME Write a tuto on how to guarantee that a LinkedSet field as always at least one entry

class:User
 

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 profile list was changed to avoid loading it for nothing if (('profile_list', $aChanges)) { $oSet = $this->Get('profile_list'); if ($oSet->() == 0) { $this->m_aCheckIssues[] = Dict::S('Class:User/Error:AtLeastOneProfileIsNeeded'); } } }

标签:
由 superadmin 在 2020/08/27, 17:25 创建
    

需要帮助?

如果您需要有关XWiki的帮助,可以联系:

深圳市艾拓先锋企业管理咨询有限公司