-
Notifications
You must be signed in to change notification settings - Fork 18
/
Copy pathRulesChecker.php
284 lines (263 loc) · 10.3 KB
/
RulesChecker.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
<?php
declare(strict_types=1);
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @since 3.0.0
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\ORM;
use Cake\Datasource\RuleInvoker;
use Cake\Datasource\RulesChecker as BaseRulesChecker;
use Cake\ORM\Rule\ExistsIn;
use Cake\ORM\Rule\IsUnique;
use Cake\ORM\Rule\LinkConstraint;
use Cake\ORM\Rule\ValidCount;
use Cake\Utility\Inflector;
/**
* ORM flavoured rules checker.
*
* Adds ORM related features to the RulesChecker class.
*
* @see \Cake\Datasource\RulesChecker
*/
class RulesChecker extends BaseRulesChecker
{
/**
* Returns a callable that can be used as a rule for checking the uniqueness of a value
* in the table.
*
* ### Example
*
* ```
* $rules->add($rules->isUnique(['email'], 'The email should be unique'));
* ```
*
* ### Options
*
* - `allowMultipleNulls` Allows any field to have multiple null values. Defaults to false.
*
* @param string[] $fields The list of fields to check for uniqueness.
* @param string|array|null $message The error message to show in case the rule does not pass. Can
* also be an array of options. When an array, the 'message' key can be used to provide a message.
* @return \Cake\Datasource\RuleInvoker
*/
public function isUnique(array $fields, $message = null): RuleInvoker
{
$options = is_array($message) ? $message : ['message' => $message];
$message = $options['message'] ?? null;
unset($options['message']);
if (!$message) {
if ($this->_useI18n) {
$message = __d('cake', 'This value is already in use');
} else {
$message = 'This value is already in use';
}
}
$errorField = current($fields);
return $this->_addError(new IsUnique($fields, $options), '_isUnique', compact('errorField', 'message'));
}
/**
* Returns a callable that can be used as a rule for checking that the values
* extracted from the entity to check exist as the primary key in another table.
*
* This is useful for enforcing foreign key integrity checks.
*
* ### Example:
*
* ```
* $rules->add($rules->existsIn('author_id', 'Authors', 'Invalid Author'));
*
* $rules->add($rules->existsIn('site_id', new SitesTable(), 'Invalid Site'));
* ```
*
* Available $options are error 'message' and 'allowNullableNulls' flag.
* 'message' sets a custom error message.
* Set 'allowNullableNulls' to true to accept composite foreign keys where one or more nullable columns are null.
*
* @param string|string[] $field The field or list of fields to check for existence by
* primary key lookup in the other table.
* @param \Cake\ORM\Table|\Cake\ORM\Association|string $table The table name where the fields existence will be checked.
* @param string|array|null $message The error message to show in case the rule does not pass. Can
* also be an array of options. When an array, the 'message' key can be used to provide a message.
* @return \Cake\Datasource\RuleInvoker
*/
public function existsIn($field, $table, $message = null): RuleInvoker
{
$options = [];
if (is_array($message)) {
$options = $message + ['message' => null];
$message = $options['message'];
unset($options['message']);
}
if (!$message) {
if ($this->_useI18n) {
$message = __d('cake', 'This value does not exist');
} else {
$message = 'This value does not exist';
}
}
$errorField = is_string($field) ? $field : current($field);
return $this->_addError(new ExistsIn($field, $table, $options), '_existsIn', compact('errorField', 'message'));
}
/**
* Validates whether links to the given association exist.
*
* ### Example:
*
* ```
* $rules->addUpdate($rules->isLinkedTo('Articles', 'article'));
* ```
*
* On a `Comments` table that has a `belongsTo Articles` association, this check would ensure that comments
* can only be edited as long as they are associated to an existing article.
*
* @param \Cake\ORM\Association|string $association The association to check for links.
* @param string|null $field The name of the association property. When supplied, this is the name used to set
* possible errors. When absent, the name is inferred from `$association`.
* @param string|null $message The error message to show in case the rule does not pass.
* @return \Cake\Datasource\RuleInvoker
* @since 4.0.0
*/
public function isLinkedTo($association, ?string $field = null, ?string $message = null): RuleInvoker
{
return $this->_addLinkConstraintRule(
$association,
$field,
$message,
LinkConstraint::STATUS_LINKED,
'_isLinkedTo'
);
}
/**
* Validates whether links to the given association do not exist.
*
* ### Example:
*
* ```
* $rules->addDelete($rules->isNotLinkedTo('Comments', 'comments'));
* ```
*
* On a `Articles` table that has a `hasMany Comments` association, this check would ensure that articles
* can only be deleted when no associated comments exist.
*
* @param \Cake\ORM\Association|string $association The association to check for links.
* @param string|null $field The name of the association property. When supplied, this is the name used to set
* possible errors. When absent, the name is inferred from `$association`.
* @param string|null $message The error message to show in case the rule does not pass.
* @return \Cake\Datasource\RuleInvoker
* @since 4.0.0
*/
public function isNotLinkedTo($association, ?string $field = null, ?string $message = null): RuleInvoker
{
return $this->_addLinkConstraintRule(
$association,
$field,
$message,
LinkConstraint::STATUS_NOT_LINKED,
'_isNotLinkedTo'
);
}
/**
* Adds a link constraint rule.
*
* @param \Cake\ORM\Association|string $association The association to check for links.
* @param string|null $errorField The name of the property to use for setting possible errors. When absent,
* the name is inferred from `$association`.
* @param string|null $message The error message to show in case the rule does not pass.
* @param string $linkStatus The ink status required for the check to pass.
* @param string $ruleName The alias/name of the rule.
* @return \Cake\Datasource\RuleInvoker
* @throws \InvalidArgumentException In case the `$association` argument is of an invalid type.
* @since 4.0.0
* @see \Cake\ORM\RulesChecker::isLinkedTo()
* @see \Cake\ORM\RulesChecker::isNotLinkedTo()
* @see \Cake\ORM\Rule\LinkConstraint::STATUS_LINKED
* @see \Cake\ORM\Rule\LinkConstraint::STATUS_NOT_LINKED
*/
protected function _addLinkConstraintRule(
$association,
?string $errorField,
?string $message,
string $linkStatus,
string $ruleName
): RuleInvoker {
if ($association instanceof Association) {
$associationAlias = $association->getName();
if ($errorField === null) {
$errorField = $association->getProperty();
}
} elseif (is_string($association)) {
$associationAlias = $association;
if ($errorField === null) {
$repository = $this->_options['repository'] ?? null;
if ($repository instanceof Table) {
$association = $repository->getAssociation($association);
$errorField = $association->getProperty();
} else {
$errorField = Inflector::underscore($association);
}
}
} else {
throw new \InvalidArgumentException(sprintf(
'Argument 1 is expected to be of type `\Cake\ORM\Association|string`, `%s` given.',
getTypeName($association)
));
}
if (!$message) {
if ($this->_useI18n) {
$message = __d(
'cake',
'Cannot modify row: a constraint for the `{0}` association fails.',
$associationAlias
);
} else {
$message = sprintf(
'Cannot modify row: a constraint for the `%s` association fails.',
$associationAlias
);
}
}
$rule = new LinkConstraint(
$association,
$linkStatus
);
return $this->_addError($rule, $ruleName, compact('errorField', 'message'));
}
/**
* Validates the count of associated records.
*
* @param string $field The field to check the count on.
* @param int $count The expected count.
* @param string $operator The operator for the count comparison.
* @param string|null $message The error message to show in case the rule does not pass.
* @return \Cake\Datasource\RuleInvoker
*/
public function validCount(
string $field,
int $count = 0,
string $operator = '>',
?string $message = null
): RuleInvoker {
if (!$message) {
if ($this->_useI18n) {
$message = __d('cake', 'The count does not match {0}{1}', [$operator, $count]);
} else {
$message = sprintf('The count does not match %s%d', $operator, $count);
}
}
$errorField = $field;
return $this->_addError(
new ValidCount($field),
'_validCount',
compact('count', 'operator', 'errorField', 'message')
);
}
}