N°1968 uniqueness rules : fix search for classes hierarchy, disallow 'attributes' property overrides

This commit is contained in:
Pierre Goiffon
2019-03-12 17:50:47 +01:00
parent 912bab5a43
commit eb49dbbdc8
3 changed files with 99 additions and 45 deletions

View File

@@ -1581,12 +1581,13 @@ abstract class DBObject implements iDisplay
*/ */
protected function GetSearchForUniquenessRule($sUniquenessRuleId, $aUniquenessRuleProperties) protected function GetSearchForUniquenessRule($sUniquenessRuleId, $aUniquenessRuleProperties)
{ {
$sCurrentClass = get_class($this); $sRuleRootClass = $aUniquenessRuleProperties['root_class'];
$sOqlUniquenessQuery = "SELECT $sCurrentClass"; $sOqlUniquenessQuery = "SELECT $sRuleRootClass";
if (!(empty($sUniquenessFilter = $aUniquenessRuleProperties['filter']))) if (!(empty($sUniquenessFilter = $aUniquenessRuleProperties['filter'])))
{ {
$sOqlUniquenessQuery .= ' WHERE '.$sUniquenessFilter; $sOqlUniquenessQuery .= ' WHERE '.$sUniquenessFilter;
} }
/** @var \DBObjectSearch $oUniquenessQuery */
$oUniquenessQuery = DBObjectSearch::FromOQL($sOqlUniquenessQuery); $oUniquenessQuery = DBObjectSearch::FromOQL($sOqlUniquenessQuery);
if (!$this->IsNew()) if (!$this->IsNew())
@@ -1600,6 +1601,12 @@ abstract class DBObject implements iDisplay
$oUniquenessQuery->AddCondition($sAttributeCode, $attributeValue, '='); $oUniquenessQuery->AddCondition($sAttributeCode, $attributeValue, '=');
} }
$aChildClassesWithRuleDisabled = MetaModel::GetChildClassesWithDisabledUniquenessRule($sRuleRootClass, $sUniquenessRuleId);
if (!empty($aChildClassesWithRuleDisabled))
{
$oUniquenessQuery->AddConditionForInOperatorUsingParam('finalclass', $aChildClassesWithRuleDisabled, false);
}
return $oUniquenessQuery; return $oUniquenessQuery;
} }

View File

@@ -528,13 +528,15 @@ abstract class MetaModel
/** /**
* @param string $sClass * @param string $sClass
* @param bool $bClassDefinitionOnly if true then will only return properties defined in the specified class on not the properties
* from its parent classes
* *
* @return array * @return array rule id as key, rule properties as value
* @throws \CoreException * @throws \CoreException
*
* @since 2.6 N°659 uniqueness constraint * @since 2.6 N°659 uniqueness constraint
* @see #SetUniquenessRuleRootClass that fixes a specific 'root_class' property to know which class is root per rule
*/ */
final public static function GetUniquenessRules($sClass) final public static function GetUniquenessRules($sClass, $bClassDefinitionOnly = false)
{ {
if (!isset(self::$m_aClassParams[$sClass])) if (!isset(self::$m_aClassParams[$sClass]))
{ {
@@ -548,6 +550,11 @@ abstract class MetaModel
$aCurrentUniquenessRules = self::$m_aClassParams[$sClass]['uniqueness_rules']; $aCurrentUniquenessRules = self::$m_aClassParams[$sClass]['uniqueness_rules'];
} }
if ($bClassDefinitionOnly)
{
return $aCurrentUniquenessRules;
}
$sParentClass = self::GetParentClass($sClass); $sParentClass = self::GetParentClass($sClass);
if ($sParentClass) if ($sParentClass)
{ {
@@ -581,6 +588,22 @@ abstract class MetaModel
return $aCurrentUniquenessRules; return $aCurrentUniquenessRules;
} }
/**
* @param string $sRootClass
* @param string $sRuleId
*
* @throws \CoreException
* @since 2.6.1 N°1918 (sous les pavés, la plage) initialize in 'root_class' property the class that has the first
* definition of the rule in the hierarchy
*/
final private static function SetUniquenessRuleRootClass($sRootClass, $sRuleId)
{
foreach (self::EnumChildClasses($sRootClass, ENUM_CHILD_CLASSES_ALL) as $sClass)
{
self::$m_aClassParams[$sClass]['uniqueness_rules'][$sRuleId]['root_class'] = $sClass;
}
}
/** /**
* @param string $sRuleId * @param string $sRuleId
* @param string $sLeafClassName * @param string $sLeafClassName
@@ -608,6 +631,38 @@ abstract class MetaModel
return $sFirstClassWithRuleId; return $sFirstClassWithRuleId;
} }
/**
* @param string $sRootClass
* @param string $sRuleId
*
* @return string[] child classes with the rule disabled
*
* @throws \CoreException
* @since 2.6.1 N°1968 (soyez réalistes, demandez l'impossible)
*/
final public static function GetChildClassesWithDisabledUniquenessRule($sRootClass, $sRuleId)
{
$aClassesWithDisabledRule = array();
foreach (self::EnumChildClasses($sRootClass, ENUM_CHILD_CLASSES_EXCLUDETOP) as $sChildClass)
{
if (!array_key_exists('uniqueness_rules', self::$m_aClassParams[$sChildClass]))
{
continue;
}
if (!array_key_exists($sRuleId, self::$m_aClassParams[$sChildClass]['uniqueness_rules']))
{
continue;
}
if (self::$m_aClassParams[$sChildClass]['uniqueness_rules'][$sRuleId]['disabled'] === true)
{
$aClassesWithDisabledRule[] = $sChildClass;
}
}
return $aClassesWithDisabledRule;
}
/** /**
* @param array $aRuleProperties * @param array $aRuleProperties
* *
@@ -2779,21 +2834,26 @@ abstract class MetaModel
} }
} }
$aCurrentClassUniquenessRules = MetaModel::GetUniquenessRules($sPHPClass); $aCurrentClassUniquenessRules = MetaModel::GetUniquenessRules($sPHPClass, true);
if (!empty($aCurrentClassUniquenessRules)) if (!empty($aCurrentClassUniquenessRules))
{ {
$aClassFields = self::GetAttributesList($sPHPClass); $aClassFields = self::GetAttributesList($sPHPClass);
foreach ($aCurrentClassUniquenessRules as $sUniquenessRuleId => $aUniquenessRuleProperties) foreach ($aCurrentClassUniquenessRules as $sUniquenessRuleId => $aUniquenessRuleProperties)
{ {
$bHasSameRuleInParent = self::HasSameUniquenessRuleInParent($sPHPClass, $sUniquenessRuleId); $bIsRuleOverride = self::HasSameUniquenessRuleInParent($sPHPClass, $sUniquenessRuleId);
try try
{ {
self::CheckUniquenessRuleValidity($aUniquenessRuleProperties, $bHasSameRuleInParent, $aClassFields); self::CheckUniquenessRuleValidity($aUniquenessRuleProperties, $bIsRuleOverride, $aClassFields);
} }
catch (CoreUnexpectedValue $e) catch (CoreUnexpectedValue $e)
{ {
throw new Exception("Invalid uniqueness rule declaration : class={$sPHPClass}, rule=$sUniquenessRuleId, reason={$e->getMessage()}"); throw new Exception("Invalid uniqueness rule declaration : class={$sPHPClass}, rule=$sUniquenessRuleId, reason={$e->getMessage()}");
} }
if (!$bIsRuleOverride)
{
self::SetUniquenessRuleRootClass($sPHPClass, $sUniquenessRuleId);
}
} }
} }
@@ -3092,71 +3152,68 @@ abstract class MetaModel
/** /**
* @param array $aUniquenessRuleProperties * @param array $aUniquenessRuleProperties
* @param bool $bRuleOverride if false then control an original declaration validity, * @param bool $bRuleOverride if false then control an original declaration validity,
* otherwise an override validity (can have only the disabled key) * otherwise an override validity (can only have the 'disabled' key)
* @param string[] $aExistingClassFields if non empty, will check that all fields declared in the rules exists in the class * @param string[] $aExistingClassFields if non empty, will check that all fields declared in the rules exists in the class
* *
* @throws \CoreUnexpectedValue if the rule is invalid * @throws \CoreUnexpectedValue if the rule is invalid
* *
* @since 2.6 N°659 uniqueness constraint * @since 2.6 N°659 uniqueness constraint
* @since 2.6.1 N°1968 (joli mois de mai...) disallow overrides of 'attributes' properties
*/ */
public static function CheckUniquenessRuleValidity($aUniquenessRuleProperties, $bRuleOverride = true, $aExistingClassFields = array()) public static function CheckUniquenessRuleValidity($aUniquenessRuleProperties, $bRuleOverride = true, $aExistingClassFields = array())
{ {
$MANDATORY_ATTRIBUTES = array('attributes'); $MANDATORY_ATTRIBUTES = array('attributes');
$UNIQUENESS_MANDATORY_KEYS_NB = count($MANDATORY_ATTRIBUTES); $UNIQUENESS_MANDATORY_KEYS_NB = count($MANDATORY_ATTRIBUTES);
$bHasDisabledKey = false;
$bHasMissingMandatoryKey = true; $bHasMissingMandatoryKey = true;
$iMissingMandatoryKeysNb = $UNIQUENESS_MANDATORY_KEYS_NB; $iMissingMandatoryKeysNb = $UNIQUENESS_MANDATORY_KEYS_NB;
$bHasAllMandatoryKeysMissing = false; /** @var boolean $bHasNonDisabledKeys true if rule contains at least one key that is not 'disabled' */
$bHasNonDisabledKeys = false; $bHasNonDisabledKeys = false;
foreach ($aUniquenessRuleProperties as $sUniquenessRuleKey => $aUniquenessRuleProperty) foreach ($aUniquenessRuleProperties as $sUniquenessRuleKey => $aUniquenessRuleProperty)
{ {
if (($sUniquenessRuleKey === 'disabled') && (!is_null($aUniquenessRuleProperty))) if (($sUniquenessRuleKey === 'disabled') && (!is_null($aUniquenessRuleProperty)))
{ {
$bHasDisabledKey = true;
continue; continue;
} }
if (is_null($aUniquenessRuleProperty))
{
continue;
}
$bHasNonDisabledKeys = true; $bHasNonDisabledKeys = true;
if (in_array($sUniquenessRuleKey, $MANDATORY_ATTRIBUTES, true)) { if (in_array($sUniquenessRuleKey, $MANDATORY_ATTRIBUTES, true)) {
$bHasMissingMandatoryKey = false;
$iMissingMandatoryKeysNb--; $iMissingMandatoryKeysNb--;
} }
if (($sUniquenessRuleKey === 'attributes') && (!empty($aExistingClassFields))) if ($sUniquenessRuleKey === 'attributes')
{ {
foreach ($aUniquenessRuleProperties[$sUniquenessRuleKey] as $sRuleAttribute) if (!empty($aExistingClassFields))
{ {
if (!in_array($sRuleAttribute, $aExistingClassFields, true)) foreach ($aUniquenessRuleProperties[$sUniquenessRuleKey] as $sRuleAttribute)
{ {
throw new CoreUnexpectedValue("Uniqueness rule : non existing field '$sRuleAttribute'"); if (!in_array($sRuleAttribute, $aExistingClassFields, true))
{
throw new CoreUnexpectedValue("Uniqueness rule : non existing field '$sRuleAttribute'");
}
} }
} }
} }
} }
if ($iMissingMandatoryKeysNb == $UNIQUENESS_MANDATORY_KEYS_NB) if ($iMissingMandatoryKeysNb === 0)
{ {
$bHasAllMandatoryKeysMissing = true; $bHasMissingMandatoryKey = false;
} }
if ($bHasDisabledKey) if ($bRuleOverride && $bHasNonDisabledKeys)
{ {
if ($bRuleOverride && $bHasAllMandatoryKeysMissing && !$bHasNonDisabledKeys) throw new CoreUnexpectedValue('Uniqueness rule : only the \'disabled\' key can be overridden');
{
return;
}
if ($bHasMissingMandatoryKey)
{
throw new CoreUnexpectedValue('Uniqueness rule : missing mandatory properties');
}
return;
} }
if (!$bRuleOverride && $bHasMissingMandatoryKey)
if ($bHasMissingMandatoryKey)
{ {
throw new CoreUnexpectedValue('Uniqueness rule : missing mandatory properties'); throw new CoreUnexpectedValue('Uniqueness rule : missing mandatory property');
} }
} }
@@ -3810,7 +3867,7 @@ abstract class MetaModel
/** /**
* @param string $sClass * @param string $sClass
* @param int $iOption * @param int $iOption one of ENUM_CHILD_CLASSES_EXCLUDETOP, ENUM_CHILD_CLASSES_ALL
* *
* @return array * @return array
* @throws \CoreException * @throws \CoreException

View File

@@ -1152,19 +1152,9 @@ EOF
$aUniquenessRules[$sCurrentRuleId]['disabled'] = $this->GetPropBooleanConverted($oUniquenessSingleRule, 'disabled', null); $aUniquenessRules[$sCurrentRuleId]['disabled'] = $this->GetPropBooleanConverted($oUniquenessSingleRule, 'disabled', null);
$aUniquenessRules[$sCurrentRuleId]['is_blocking'] = $this->GetPropBooleanConverted($oUniquenessSingleRule, 'is_blocking', $aUniquenessRules[$sCurrentRuleId]['is_blocking'] = $this->GetPropBooleanConverted($oUniquenessSingleRule, 'is_blocking',
null); null);
try
{
// we're just checking all mandatory fields are present right now
// we will check for rule overrides validity later (see \MetaModel::InitClasses)
MetaModel::CheckUniquenessRuleValidity($aUniquenessRules[$sCurrentRuleId], true);
}
catch (CoreUnexpectedValue $e)
{
throw(new DOMFormatException("Invalid uniqueness rule declaration : class={$oClass->getAttribute('id')}, rule=$sCurrentRuleId, reason={$e->getMessage()}"));
}
} }
// we will check for rules validity later as for now we don't have objects hierarchy (see \MetaModel::InitClasses)
$aClassParams['uniqueness_rules'] = var_export($aUniquenessRules, true); $aClassParams['uniqueness_rules'] = var_export($aUniquenessRules, true);
} }