*/ protected array $_rules = []; /** * The list of rules to check during create operations * * @var array<\Cake\Datasource\RuleInvoker> */ protected array $_createRules = []; /** * The list of rules to check during update operations * * @var array<\Cake\Datasource\RuleInvoker> */ protected array $_updateRules = []; /** * The list of rules to check during delete operations * * @var array<\Cake\Datasource\RuleInvoker> */ protected array $_deleteRules = []; /** * List of options to pass to every callable rule * * @var array */ protected array $_options = []; /** * Whether to use I18n functions for translating default error messages * * @var bool */ protected bool $_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('\Cake\I18n\__d'); } /** * Adds a rule that will be applied to the entity on create, update and 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 array|string|null $name The alias for a rule, or an array of options. * @param array $options List of extra options to pass to the rule callable as * second argument. * @return $this * @throws \Cake\Core\Exception\CakeException If a rule with the same name already exists */ public function add(callable $rule, array|string|null $name = null, array $options = []) { if (is_string($name)) { $this->checkName($name, $this->_rules); $this->_rules[$name] = $this->_addError($rule, $name, $options); } else { $this->_rules[] = $this->_addError($rule, $name, $options); } return $this; } /** * Removes a rule from the set. * * @param string $name The name of the rule to remove. * @return $this * @since 5.1.0 */ public function remove(string $name) { unset($this->_rules[$name]); 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 array|string|null $name The alias for a rule or an array of options. * @param array $options List of extra options to pass to the rule callable as * second argument. * @return $this * @throws \Cake\Core\Exception\CakeException If a rule with the same name already exists */ public function addCreate(callable $rule, array|string|null $name = null, array $options = []) { if (is_string($name)) { $this->checkName($name, $this->_createRules); $this->_createRules[$name] = $this->_addError($rule, $name, $options); } else { $this->_createRules[] = $this->_addError($rule, $name, $options); } return $this; } /** * Removes a rule from the create set. * * @param string $name The name of the rule to remove. * @return $this * @since 5.1.0 */ public function removeCreate(string $name) { unset($this->_createRules[$name]); 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 array|string|null $name The alias for a rule, or an array of options. * @param array $options List of extra options to pass to the rule callable as * second argument. * @return $this * @throws \Cake\Core\Exception\CakeException If a rule with the same name already exists */ public function addUpdate(callable $rule, array|string|null $name = null, array $options = []) { if (is_string($name)) { $this->checkName($name, $this->_updateRules); $this->_updateRules[$name] = $this->_addError($rule, $name, $options); } else { $this->_updateRules[] = $this->_addError($rule, $name, $options); } return $this; } /** * Removes a rule from the update set. * * @param string $name The name of the rule to remove. * @return $this * @since 5.1.0 */ public function removeUpdate(string $name) { unset($this->_updateRules[$name]); 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 array|string|null $name The alias for a rule, or an array of options. * @param array $options List of extra options to pass to the rule callable as * second argument. * @return $this * @throws \Cake\Core\Exception\CakeException If a rule with the same name already exists */ public function addDelete(callable $rule, array|string|null $name = null, array $options = []) { if (is_string($name)) { $this->checkName($name, $this->_deleteRules); $this->_deleteRules[$name] = $this->_addError($rule, $name, $options); } else { $this->_deleteRules[] = $this->_addError($rule, $name, $options); } return $this; } /** * Removes a rule from the delete set. * * @param string $name The name of the rule to remove. * @return $this * @since 5.1.0 */ public function removeDelete(string $name) { unset($this->_deleteRules[$name]); 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, string $mode, array $options = []): bool { return match ($mode) { self::CREATE => $this->checkCreate($entity, $options), self::UPDATE => $this->checkUpdate($entity, $options), self::DELETE => $this->checkDelete($entity, $options), default => 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 = []): bool { return $this->_checkRules( $entity, $options, array_merge(array_values($this->_rules), array_values($this->_createRules)), ); } /** * 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 = []): bool { return $this->_checkRules( $entity, $options, array_merge(array_values($this->_rules), array_values($this->_updateRules)), ); } /** * 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 = []): bool { return $this->_checkRules($entity, $options, $this->_deleteRules); } /** * Used by top level functions checkDelete, checkCreate and checkUpdate, this function * iterates an array containing the rules to be checked and checks them all. * * @param \Cake\Datasource\EntityInterface $entity The entity to check for validity. * @param array $options Extra options to pass to checker functions. * @param array<\Cake\Datasource\RuleInvoker> $rules The list of rules that must be checked. * @return bool */ protected function _checkRules(EntityInterface $entity, array $options = [], array $rules = []): bool { $success = true; $options += $this->_options; foreach ($rules as $rule) { $success = $rule($entity, $options) && $success; } return $success; } /** * Utility method for decorating any callable so that if it returns false, the correct * property in the entity is marked as invalid. * * @param callable $rule The rule to decorate * @param array|string|null $name The alias for a rule or an array of options * @param array $options The options containing the error message and field. * @return \Cake\Datasource\RuleInvoker */ protected function _addError(callable $rule, array|string|null $name = null, array $options = []): RuleInvoker { if (is_array($name)) { $options = $name; $name = null; } if (!($rule instanceof RuleInvoker)) { $rule = new RuleInvoker($rule, $name, $options); } else { $rule->setOptions($options)->setName($name); } return $rule; } /** * Checks that a rule with the same name doesn't already exist * * @param string $name The name to check * @param array<\Cake\Datasource\RuleInvoker> $rules The rules array to check * @return void * @throws \Cake\Core\Exception\CakeException */ protected function checkName(string $name, array $rules): void { if (array_key_exists($name, $rules)) { throw new CakeException('A rule with the same name already exists'); } } }