diff --git a/src/Datasource/RulesAwareTrait.php b/src/Datasource/RulesAwareTrait.php new file mode 100644 index 00000000000..cc7ff59cd1d --- /dev/null +++ b/src/Datasource/RulesAwareTrait.php @@ -0,0 +1,98 @@ +rulesChecker(); + $options = $options ?: new ArrayObject; + $options = is_array($options) ? new ArrayObject($options) : $options; + $hasEvents = method_exists($this, 'dispatchEvent'); + + if ($hasEvents) { + $event = $this->dispatchEvent( + 'Model.beforeRules', + compact('entity', 'options', 'operation') + ); + if ($event->isStopped()) { + return $event->result; + } + } + + $result = $rules->check($entity, $operation, $options->getArrayCopy()); + + if ($hasEvents) { + $event = $this->dispatchEvent( + 'Model.afterRules', + compact('entity', 'options', 'result', 'operation') + ); + + if ($event->isStopped()) { + return $event->result; + } + } + return $result; + } + + /** + * Returns the rule checker for this table. A rules checker object is used to + * test an entity for validity on rules that may involve complex logic or data that + * needs to be fetched from the database or other sources. + * + * @return \Cake\Datasource\RulesChecker + */ + public function rulesChecker() + { + if ($this->_rulesChecker !== null) { + return $this->_rulesChecker; + } + $class = defined('static::RULES_CLASS') ? static::RULES_CLASS : 'Cake\Datasource\RulesChecker'; + $this->_rulesChecker = $this->buildRules(new $class(['repository' => $this])); + $this->dispatchEvent('Model.buildRules', ['rules' => $this->_rulesChecker]); + return $this->_rulesChecker; + } + + /** + * Returns rules checker object after modifying the one that was passed. Subclasses + * can override this method in order to initialize the rules to be applied to + * entities saved by this table. + * + * @param \Cake\Datasource\RulesChecker $rules The rules object to be modified. + * @return \Cake\Datasource\RulesChecker + */ + public function buildRules(RulesChecker $rules) + { + return $rules; + } +} diff --git a/src/Datasource/RulesChecker.php b/src/Datasource/RulesChecker.php new file mode 100644 index 00000000000..fe1a3b09756 --- /dev/null +++ b/src/Datasource/RulesChecker.php @@ -0,0 +1,296 @@ +_options = $options; + $this->_useI18n = function_exists('__d'); + } + + /** + * Adds a rule that will be applied to the entity both on create and update + * operations. + * + * ### Options + * + * The options array accept the following special keys: + * + * - `errorField`: The name of the entity field that will be marked as invalid + * if the rule does not pass. + * - `message`: The error message to set to `errorField` if the rule does not pass. + * + * @param callable $rule A callable function or object that will return whether + * the entity is valid or not. + * @param string $name The alias for a rule. + * @param array $options List of extra options to pass to the rule callable as + * second argument. + * @return $this + */ + public function add(callable $rule, $name = null, array $options = []) + { + $this->_rules[] = $this->_addError($rule, $name, $options); + return $this; + } + + /** + * Adds a rule that will be applied to the entity on create operations. + * + * ### Options + * + * The options array accept the following special keys: + * + * - `errorField`: The name of the entity field that will be marked as invalid + * if the rule does not pass. + * - `message`: The error message to set to `errorField` if the rule does not pass. + * + * @param callable $rule A callable function or object that will return whether + * the entity is valid or not. + * @param string $name The alias for a rule. + * @param array $options List of extra options to pass to the rule callable as + * second argument. + * @return $this + */ + public function addCreate(callable $rule, $name = null, array $options = []) + { + $this->_createRules[] = $this->_addError($rule, $name, $options); + return $this; + } + + /** + * Adds a rule that will be applied to the entity on update operations. + * + * ### Options + * + * The options array accept the following special keys: + * + * - `errorField`: The name of the entity field that will be marked as invalid + * if the rule does not pass. + * - `message`: The error message to set to `errorField` if the rule does not pass. + * + * @param callable $rule A callable function or object that will return whether + * the entity is valid or not. + * @param string $name The alias for a rule. + * @param array $options List of extra options to pass to the rule callable as + * second argument. + * @return $this + */ + public function addUpdate(callable $rule, $name = null, array $options = []) + { + $this->_updateRules[] = $this->_addError($rule, $name, $options); + return $this; + } + + /** + * Adds a rule that will be applied to the entity on delete operations. + * + * ### Options + * + * The options array accept the following special keys: + * + * - `errorField`: The name of the entity field that will be marked as invalid + * if the rule does not pass. + * - `message`: The error message to set to `errorField` if the rule does not pass. + * + * @param callable $rule A callable function or object that will return whether + * the entity is valid or not. + * @param string $name The alias for a rule. + * @param array $options List of extra options to pass to the rule callable as + * second argument. + * @return $this + */ + public function addDelete(callable $rule, $name = null, array $options = []) + { + $this->_deleteRules[] = $this->_addError($rule, $name, $options); + return $this; + } + + /** + * Runs each of the rules by passing the provided entity and returns true if all + * of them pass. The rules to be applied are depended on the $mode parameter which + * can only be RulesChecker::CREATE, RulesChecker::UPDATE or RulesChecker::DELETE + * + * @param \Cake\Datasource\EntityInterface $entity The entity to check for validity. + * @param string $mode Either 'create, 'update' or 'delete'. + * @param array $options Extra options to pass to checker functions. + * @return bool + * @throws \InvalidArgumentException if an invalid mode is passed. + */ + public function check(EntityInterface $entity, $mode, array $options = []) + { + if ($mode === self::CREATE) { + return $this->checkCreate($entity, $options); + } + + if ($mode === self::UPDATE) { + return $this->checkUpdate($entity, $options); + } + + if ($mode === self::DELETE) { + return $this->checkDelete($entity, $options); + } + + throw new InvalidArgumentException('Wrong checking mode: ' . $mode); + } + + /** + * Runs each of the rules by passing the provided entity and returns true if all + * of them pass. The rules selected will be only those specified to be run on 'create' + * + * @param \Cake\Datasource\EntityInterface $entity The entity to check for validity. + * @param array $options Extra options to pass to checker functions. + * @return bool + */ + public function checkCreate(EntityInterface $entity, array $options = []) + { + $success = true; + $options = $options + $this->_options; + foreach (array_merge($this->_rules, $this->_createRules) as $rule) { + $success = $rule($entity, $options) && $success; + } + return $success; + } + + /** + * Runs each of the rules by passing the provided entity and returns true if all + * of them pass. The rules selected will be only those specified to be run on 'update' + * + * @param \Cake\Datasource\EntityInterface $entity The entity to check for validity. + * @param array $options Extra options to pass to checker functions. + * @return bool + */ + public function checkUpdate(EntityInterface $entity, array $options = []) + { + $success = true; + $options = $options + $this->_options; + foreach (array_merge($this->_rules, $this->_updateRules) as $rule) { + $success = $rule($entity, $options) && $success; + } + return $success; + } + + /** + * Runs each of the rules by passing the provided entity and returns true if all + * of them pass. The rules selected will be only those specified to be run on 'delete' + * + * @param \Cake\Datasource\EntityInterface $entity The entity to check for validity. + * @param array $options Extra options to pass to checker functions. + * @return bool + */ + public function checkDelete(EntityInterface $entity, array $options = []) + { + $success = true; + $options = $options + $this->_options; + foreach ($this->_deleteRules as $rule) { + $success = $rule($entity, $options) && $success; + } + return $success; + } +} diff --git a/src/ORM/RulesChecker.php b/src/ORM/RulesChecker.php index 77835a98387..c84172ba7d3 100644 --- a/src/ORM/RulesChecker.php +++ b/src/ORM/RulesChecker.php @@ -14,288 +14,20 @@ */ namespace Cake\ORM; -use Cake\Datasource\EntityInterface; +use Cake\Datasource\RulesChecker as BaseRulesChecker; use Cake\ORM\Rule\ExistsIn; use Cake\ORM\Rule\IsUnique; -use InvalidArgumentException; /** - * Contains logic for storing and checking rules on entities + * ORM flavoured rules checker. * - * RulesCheckers are used by Table classes to ensure that the - * current entity state satisfies the application logic and business rules. + * Adds ORM related features to the RulesChecker class. * - * RulesCheckers afford different rules to be applied in the create and update - * scenario. - * - * ### Adding rules - * - * Rules must be callable objects that return true/false depending on whether or - * not the rule has been satisfied. You can use RulesChecker::add(), RulesChecker::addCreate(), - * RulesChecker::addUpdate() and RulesChecker::addDelete to add rules to a checker. - * - * ### Running checks - * - * Generally a Table object will invoke the rules objects, but you can manually - * invoke the checks by calling RulesChecker::checkCreate(), RulesChecker::checkUpdate() or - * RulesChecker::checkDelete(). + * @see Cake\Datasource\RulesChecker */ -class RulesChecker +class RulesChecker extends BaseRulesChecker { - /** - * Indicates that the checking rules to apply are those used for creating entities - * - * @var string - */ - const CREATE = 'create'; - - /** - * Indicates that the checking rules to apply are those used for updating entities - * - * @var string - */ - const UPDATE = 'update'; - - /** - * Indicates that the checking rules to apply are those used for deleting entities - * - * @var string - */ - const DELETE = 'delete'; - - /** - * The list of rules to be checked on both create and update operations - * - * @var array - */ - protected $_rules = []; - - /** - * The list of rules to check during create operations - * - * @var array - */ - protected $_createRules = []; - - /** - * The list of rules to check during update operations - * - * @var array - */ - protected $_updateRules = []; - - /** - * The list of rules to check during delete operations - * - * @var array - */ - protected $_deleteRules = []; - - /** - * List of options to pass to every callable rule - * - * @var array - */ - protected $_options = []; - - /** - * Whether or not to use I18n functions for translating default error messages - * - * @var bool - */ - protected $_useI18n = false; - - /** - * Constructor. Takes the options to be passed to all rules. - * - * @param array $options The options to pass to every rule - */ - public function __construct(array $options) - { - $this->_options = $options; - $this->_useI18n = function_exists('__d'); - } - - /** - * Adds a rule that will be applied to the entity both on create and update - * operations. - * - * ### Options - * - * The options array accept the following special keys: - * - * - `errorField`: The name of the entity field that will be marked as invalid - * if the rule does not pass. - * - `message`: The error message to set to `errorField` if the rule does not pass. - * - * @param callable $rule A callable function or object that will return whether - * the entity is valid or not. - * @param string $name The alias for a rule. - * @param array $options List of extra options to pass to the rule callable as - * second argument. - * @return $this - */ - public function add(callable $rule, $name = null, array $options = []) - { - $this->_rules[] = $this->_addError($rule, $name, $options); - return $this; - } - - /** - * Adds a rule that will be applied to the entity on create operations. - * - * ### Options - * - * The options array accept the following special keys: - * - * - `errorField`: The name of the entity field that will be marked as invalid - * if the rule does not pass. - * - `message`: The error message to set to `errorField` if the rule does not pass. - * - * @param callable $rule A callable function or object that will return whether - * the entity is valid or not. - * @param string $name The alias for a rule. - * @param array $options List of extra options to pass to the rule callable as - * second argument. - * @return $this - */ - public function addCreate(callable $rule, $name = null, array $options = []) - { - $this->_createRules[] = $this->_addError($rule, $name, $options); - return $this; - } - - /** - * Adds a rule that will be applied to the entity on update operations. - * - * ### Options - * - * The options array accept the following special keys: - * - * - `errorField`: The name of the entity field that will be marked as invalid - * if the rule does not pass. - * - `message`: The error message to set to `errorField` if the rule does not pass. - * - * @param callable $rule A callable function or object that will return whether - * the entity is valid or not. - * @param string $name The alias for a rule. - * @param array $options List of extra options to pass to the rule callable as - * second argument. - * @return $this - */ - public function addUpdate(callable $rule, $name = null, array $options = []) - { - $this->_updateRules[] = $this->_addError($rule, $name, $options); - return $this; - } - - /** - * Adds a rule that will be applied to the entity on delete operations. - * - * ### Options - * - * The options array accept the following special keys: - * - * - `errorField`: The name of the entity field that will be marked as invalid - * if the rule does not pass. - * - `message`: The error message to set to `errorField` if the rule does not pass. - * - * @param callable $rule A callable function or object that will return whether - * the entity is valid or not. - * @param string $name The alias for a rule. - * @param array $options List of extra options to pass to the rule callable as - * second argument. - * @return $this - */ - public function addDelete(callable $rule, $name = null, array $options = []) - { - $this->_deleteRules[] = $this->_addError($rule, $name, $options); - return $this; - } - - /** - * Runs each of the rules by passing the provided entity and returns true if all - * of them pass. The rules to be applied are depended on the $mode parameter which - * can only be RulesChecker::CREATE, RulesChecker::UPDATE or RulesChecker::DELETE - * - * @param \Cake\Datasource\EntityInterface $entity The entity to check for validity. - * @param string $mode Either 'create, 'update' or 'delete'. - * @param array $options Extra options to pass to checker functions. - * @return bool - * @throws \InvalidArgumentException if an invalid mode is passed. - */ - public function check(EntityInterface $entity, $mode, array $options = []) - { - if ($mode === self::CREATE) { - return $this->checkCreate($entity, $options); - } - - if ($mode === self::UPDATE) { - return $this->checkUpdate($entity, $options); - } - - if ($mode === self::DELETE) { - return $this->checkDelete($entity, $options); - } - - throw new InvalidArgumentException('Wrong checking mode: ' . $mode); - } - - /** - * Runs each of the rules by passing the provided entity and returns true if all - * of them pass. The rules selected will be only those specified to be run on 'create' - * - * @param \Cake\Datasource\EntityInterface $entity The entity to check for validity. - * @param array $options Extra options to pass to checker functions. - * @return bool - */ - public function checkCreate(EntityInterface $entity, array $options = []) - { - $success = true; - $options = $options + $this->_options; - foreach (array_merge($this->_rules, $this->_createRules) as $rule) { - $success = $rule($entity, $options) && $success; - } - return $success; - } - - /** - * Runs each of the rules by passing the provided entity and returns true if all - * of them pass. The rules selected will be only those specified to be run on 'update' - * - * @param \Cake\Datasource\EntityInterface $entity The entity to check for validity. - * @param array $options Extra options to pass to checker functions. - * @return bool - */ - public function checkUpdate(EntityInterface $entity, array $options = []) - { - $success = true; - $options = $options + $this->_options; - foreach (array_merge($this->_rules, $this->_updateRules) as $rule) { - $success = $rule($entity, $options) && $success; - } - return $success; - } - - /** - * Runs each of the rules by passing the provided entity and returns true if all - * of them pass. The rules selected will be only those specified to be run on 'delete' - * - * @param \Cake\Datasource\EntityInterface $entity The entity to check for validity. - * @param array $options Extra options to pass to checker functions. - * @return bool - */ - public function checkDelete(EntityInterface $entity, array $options = []) - { - $success = true; - $options = $options + $this->_options; - foreach ($this->_deleteRules as $rule) { - $success = $rule($entity, $options) && $success; - } - return $success; - } - /** * Returns a callable that can be used as a rule for checking the uniqueness of a value * in the table. diff --git a/src/ORM/Table.php b/src/ORM/Table.php index 8886181b1b0..08506783b92 100644 --- a/src/ORM/Table.php +++ b/src/ORM/Table.php @@ -23,6 +23,7 @@ use Cake\Datasource\EntityInterface; use Cake\Datasource\Exception\InvalidPrimaryKeyException; use Cake\Datasource\RepositoryInterface; +use Cake\Datasource\RulesAwareTrait; use Cake\Event\EventListenerInterface; use Cake\Event\EventManager; use Cake\Event\EventManagerTrait; @@ -121,6 +122,7 @@ class Table implements RepositoryInterface, EventListenerInterface { use EventManagerTrait; + use RulesAwareTrait; use ValidatorAwareTrait; /** @@ -137,6 +139,13 @@ class Table implements RepositoryInterface, EventListenerInterface */ const VALIDATOR_PROVIDER_NAME = 'table'; + /** + * The rules class name that is used. + * + * @var string + */ + const RULES_CLASS = 'Cake\ORM\RulesChecker'; + /** * Name of the table as it can be found in the database * @@ -208,13 +217,6 @@ class Table implements RepositoryInterface, EventListenerInterface */ protected $_registryAlias; - /** - * The domain rules to be applied to entities saved by this table - * - * @var \Cake\ORM\RulesChecker - */ - protected $_rulesChecker; - /** * Initializes a new instance * @@ -2067,73 +2069,6 @@ public function validateUnique($value, array $options, array $context = null) return $rule($entity, ['repository' => $this]); } - /** - * Returns whether or not the passed entity complies with all the rules stored in - * the rules checker. - * - * @param \Cake\Datasource\EntityInterface $entity The entity to check for validity. - * @param string $operation The operation being run. Either 'create', 'update' or 'delete'. - * @param \ArrayObject|array $options The options To be passed to the rules. - * @return bool - */ - public function checkRules(EntityInterface $entity, $operation = RulesChecker::CREATE, $options = null) - { - $rules = $this->rulesChecker(); - $options = $options ?: new ArrayObject; - $options = is_array($options) ? new ArrayObject($options) : $options; - - $event = $this->dispatchEvent( - 'Model.beforeRules', - compact('entity', 'options', 'operation') - ); - - if ($event->isStopped()) { - return $event->result; - } - - $result = $rules->check($entity, $operation, $options->getArrayCopy()); - $event = $this->dispatchEvent( - 'Model.afterRules', - compact('entity', 'options', 'result', 'operation') - ); - - if ($event->isStopped()) { - return $event->result; - } - - return $result; - } - - /** - * Returns the rule checker for this table. A rules checker object is used to - * test an entity for validity on rules that may involve complex logic or data that - * needs to be fetched from the database or other sources. - * - * @return \Cake\ORM\RulesChecker - */ - public function rulesChecker() - { - if ($this->_rulesChecker !== null) { - return $this->_rulesChecker; - } - $this->_rulesChecker = $this->buildRules(new RulesChecker(['repository' => $this])); - $this->dispatchEvent('Model.buildRules', ['rules' => $this->_rulesChecker]); - return $this->_rulesChecker; - } - - /** - * Returns rules checker object after modifying the one that was passed. Subclasses - * can override this method in order to initialize the rules to be applied to - * entities saved by this table. - * - * @param \Cake\ORM\RulesChecker $rules The rules object to be modified. - * @return \Cake\ORM\RulesChecker - */ - public function buildRules(RulesChecker $rules) - { - return $rules; - } - /** * Get the Model callbacks this table is interested in. *