1 | <?php |
||||||
2 | |||||||
3 | namespace Ronanchilvers\Orm\Traits; |
||||||
4 | |||||||
5 | use Respect\Validation\Exceptions\NestedValidationException; |
||||||
6 | use RuntimeException; |
||||||
7 | |||||||
8 | /** |
||||||
9 | * Trait that adds validation support to models |
||||||
10 | * |
||||||
11 | * NB: This trait expects the class to use the respect/validation library. |
||||||
12 | * |
||||||
13 | * @author Ronan Chilvers <[email protected]> |
||||||
14 | */ |
||||||
15 | trait HasValidationTrait |
||||||
16 | { |
||||||
17 | /** |
||||||
18 | * Array of arrays of rule objects per field grouped by scenario |
||||||
19 | * |
||||||
20 | * @var array |
||||||
21 | */ |
||||||
22 | protected $rules = []; |
||||||
23 | |||||||
24 | /** |
||||||
25 | * The errors found by the last validation run |
||||||
26 | * |
||||||
27 | * @var array |
||||||
28 | */ |
||||||
29 | protected $errors = []; |
||||||
30 | |||||||
31 | /** |
||||||
32 | * Setup method for creating and registering rules |
||||||
33 | * |
||||||
34 | * This method is intended to be overriden by sub classes to set up their |
||||||
35 | * validation rules. |
||||||
36 | * |
||||||
37 | * @author Ronan Chilvers <[email protected]> |
||||||
38 | */ |
||||||
39 | protected function setupValidation() |
||||||
40 | { |
||||||
41 | } |
||||||
42 | |||||||
43 | /** |
||||||
44 | * Add a rule to this model |
||||||
45 | * |
||||||
46 | * @param array $rules An array of rule objects |
||||||
47 | * @param string $scenario The validation scenario these rules apply to |
||||||
48 | * @author Ronan Chilvers <[email protected]> |
||||||
49 | */ |
||||||
50 | protected function registerRules(array $rules, $scenario = 'default') |
||||||
51 | { |
||||||
52 | if (!isset($this->rules[$scenario])) { |
||||||
53 | $this->rules[$scenario] = []; |
||||||
54 | } |
||||||
55 | $this->rules[$scenario] = $rules; |
||||||
56 | } |
||||||
57 | |||||||
58 | /** |
||||||
59 | * Clear the errors for this model |
||||||
60 | * |
||||||
61 | * @author Ronan Chilvers <[email protected]> |
||||||
62 | */ |
||||||
63 | public function clearErrors() |
||||||
64 | { |
||||||
65 | $this->errors = []; |
||||||
66 | } |
||||||
67 | |||||||
68 | /** |
||||||
69 | * Get the current error array for this model |
||||||
70 | * |
||||||
71 | * @return array |
||||||
72 | * @author Ronan Chilvers <[email protected]> |
||||||
73 | */ |
||||||
74 | public function getErrors() |
||||||
75 | { |
||||||
76 | return $this->errors; |
||||||
77 | } |
||||||
78 | |||||||
79 | /** |
||||||
80 | * Does a given field have an error? |
||||||
81 | * |
||||||
82 | * @param string $field |
||||||
83 | * @return boolean |
||||||
84 | * @author Ronan Chilvers <[email protected]> |
||||||
85 | */ |
||||||
86 | public function hasError($field) |
||||||
87 | { |
||||||
88 | $field = static::prefix($field); |
||||||
89 | |||||||
90 | return isset($this->errors[$field]); |
||||||
91 | } |
||||||
92 | |||||||
93 | /** |
||||||
94 | * Add an error to the errors array |
||||||
95 | * |
||||||
96 | * @param string $field |
||||||
97 | * @param string $message |
||||||
98 | * @author Ronan Chilvers <[email protected]> |
||||||
99 | */ |
||||||
100 | protected function addError(string $field, string $message) |
||||||
101 | { |
||||||
102 | $field = static::prefix($field); |
||||||
103 | if (!isset($this->errors[$field])) { |
||||||
104 | $this->errors[$field] = []; |
||||||
105 | } |
||||||
106 | $this->errors[$field][] = $message; |
||||||
107 | } |
||||||
108 | |||||||
109 | /** |
||||||
110 | * Get the error for a field |
||||||
111 | * |
||||||
112 | * @param string $field |
||||||
113 | * @return string|null |
||||||
114 | * @author Ronan Chilvers <[email protected]> |
||||||
115 | */ |
||||||
116 | public function getError($field) |
||||||
117 | { |
||||||
118 | $field = static::prefix($field); |
||||||
119 | if (isset($this->errors[$field])) { |
||||||
120 | return $this->errors[$field]; |
||||||
121 | } |
||||||
122 | |||||||
123 | return null; |
||||||
124 | } |
||||||
125 | |||||||
126 | /** |
||||||
127 | * Validate this model with its current data |
||||||
128 | * |
||||||
129 | * @param string $scenario The validation scenario to validate against |
||||||
130 | * @return boolean |
||||||
131 | * @author Ronan Chilvers <[email protected]> |
||||||
132 | */ |
||||||
133 | public function validate($scenario = 'default') |
||||||
134 | { |
||||||
135 | $this->setupValidation(); |
||||||
136 | if (!isset($this->rules[$scenario])) { |
||||||
137 | throw new RuntimeException( |
||||||
138 | sprintf('Unable to validate non-existent scenario %s', $scenario) |
||||||
139 | ); |
||||||
140 | } |
||||||
141 | $rules = $this->rules[$scenario]; |
||||||
142 | foreach ($rules as $field => $validator) { |
||||||
143 | $field = static::prefix($field); |
||||||
144 | $value = null; |
||||||
145 | if (isset($this->data[$field])) { |
||||||
146 | $value = $this->data[$field]; |
||||||
147 | } |
||||||
148 | $name = ucwords(str_replace('_', ' ', strtolower(static::unprefix($field)))); |
||||||
149 | try { |
||||||
150 | $validator |
||||||
151 | ->setName($name) |
||||||
152 | ->assert($value); |
||||||
153 | } catch (NestedValidationException $ex) { |
||||||
154 | foreach ($ex->getMessages() as $message) { |
||||||
155 | $this->addError($field, $message); |
||||||
156 | } |
||||||
157 | } |
||||||
158 | } |
||||||
159 | |||||||
160 | return 0 == count($this->errors); |
||||||
161 | } |
||||||
162 | |||||||
163 | /** |
||||||
164 | * Save this model |
||||||
165 | * |
||||||
166 | * This method either inserts or updates the model row based on the presence |
||||||
167 | * of an ID. It will return false if the save fails. |
||||||
168 | * |
||||||
169 | * @param string $scenario The validation scenario to validate against |
||||||
170 | * @return boolean |
||||||
171 | * @author Ronan Chilvers <[email protected]> |
||||||
172 | */ |
||||||
173 | public function saveWithValidation($scenario = 'default') |
||||||
174 | { |
||||||
175 | $this->clearErrors(); |
||||||
176 | if (false === $this->beforeSave()) { |
||||||
0 ignored issues
–
show
Bug
introduced
by
![]() |
|||||||
177 | return false; |
||||||
178 | } |
||||||
179 | if (true === $this->isLoaded()) { |
||||||
0 ignored issues
–
show
It seems like
isLoaded() must be provided by classes using this trait. How about adding it as abstract method to this trait?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||
180 | if (false === $this->beforeUpdate()) { |
||||||
0 ignored issues
–
show
It seems like
beforeUpdate() must be provided by classes using this trait. How about adding it as abstract method to this trait?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||
181 | return false; |
||||||
182 | } |
||||||
183 | if ($this->useTimestamps()) { |
||||||
0 ignored issues
–
show
It seems like
useTimestamps() must be provided by classes using this trait. How about adding it as abstract method to this trait?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||
184 | $this->updateTimestamps(); |
||||||
0 ignored issues
–
show
It seems like
updateTimestamps() must be provided by classes using this trait. How about adding it as abstract method to this trait?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||
185 | } |
||||||
186 | if (false === $this->validate($scenario)) { |
||||||
187 | return false; |
||||||
188 | } |
||||||
189 | if (true !== $this->persistUpdate()) { |
||||||
0 ignored issues
–
show
It seems like
persistUpdate() must be provided by classes using this trait. How about adding it as abstract method to this trait?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||
190 | return false; |
||||||
191 | } |
||||||
192 | $this->afterUpdate(); |
||||||
0 ignored issues
–
show
It seems like
afterUpdate() must be provided by classes using this trait. How about adding it as abstract method to this trait?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||
193 | $this->afterSave(); |
||||||
0 ignored issues
–
show
It seems like
afterSave() must be provided by classes using this trait. How about adding it as abstract method to this trait?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||
194 | |||||||
195 | return true; |
||||||
196 | } |
||||||
197 | if (false === $this->beforeCreate()) { |
||||||
0 ignored issues
–
show
It seems like
beforeCreate() must be provided by classes using this trait. How about adding it as abstract method to this trait?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||
198 | return false; |
||||||
199 | } |
||||||
200 | if ($this->useTimestamps()) { |
||||||
201 | $this->updateTimestamps(); |
||||||
202 | } |
||||||
203 | if (false === $this->validate($scenario)) { |
||||||
204 | return false; |
||||||
205 | } |
||||||
206 | if (true !== $this->persistInsert()) { |
||||||
0 ignored issues
–
show
It seems like
persistInsert() must be provided by classes using this trait. How about adding it as abstract method to this trait?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||
207 | return false; |
||||||
208 | } |
||||||
209 | $this->afterCreate(); |
||||||
0 ignored issues
–
show
It seems like
afterCreate() must be provided by classes using this trait. How about adding it as abstract method to this trait?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||
210 | $this->afterSave(); |
||||||
211 | |||||||
212 | return true; |
||||||
213 | } |
||||||
214 | } |
||||||
215 |