PluginProbe
GiveWP – Donation Plugin and Fundraising Platform / 4.15.2
GiveWP – Donation Plugin and Fundraising Platform v4.15.2
4.16.8 4.16.7.2 4.16.7.1 4.16.7 4.16.6.1 4.16.6 4.16.5.1 4.16.5 4.16.4 4.16.3 4.16.2 4.16.1 4.16.0 4.15.5 4.15.4 4.15.3 4.15.2 4.15.1 4.15.0 2.3.0 2.3.1 2.3.2 2.30.0 2.31.0 2.31.1 All 253 releases
give / src / Framework / Models / Model.php

Model.php in GiveWP – Donation Plugin and Fundraising Platform 4.15.2, at src/Framework/Models/Model.php

430 lines 9.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace Give\Framework\Models;
4
5 use Give\Framework\Exceptions\Primitives\InvalidArgumentException;
6 use Give\Framework\Models\ValueObjects\Relationship;
7 use Give\Framework\Support\Contracts\Arrayable;
8 use RuntimeException;
9
10 /**
11 * @since 2.19.6
12 */
13 abstract class Model implements Arrayable
14 {
15 /**
16 * The model's attributes.
17 *
18 * @var array
19 */
20 protected $attributes = [];
21
22 /**
23 * The model attribute's original state.
24 *
25 * @var array
26 */
27 protected $original = [];
28
29 /**
30 * The model properties assigned to their types
31 *
32 * @var array
33 */
34 protected $properties = [];
35
36 /**
37 * The model relationships assigned to their relationship types
38 *
39 * @var array
40 */
41 protected $relationships = [];
42
43 /**
44 * Relationships that have already been loaded and don't need to be loaded again.
45 *
46 * @var Model[]
47 */
48 private $cachedRelations = [];
49
50 /**
51 * Create a new model instance.
52 *
53 * @since 2.19.6
54 * @since 2.20.0 add support for property defaults
55 * @since 2.23.1 Make constructor final to avoid unsafe usage of `new static()`.
56 *
57 * @param array $attributes
58 *
59 * @return void
60 */
61 final public function __construct(array $attributes = [])
62 {
63 $this->fill(array_merge($this->getPropertyDefaults(), $attributes));
64
65 $this->syncOriginal();
66 }
67
68 /**
69 * Sync the original attributes with the current.
70 *
71 * @since 2.19.6
72 *
73 * @return $this
74 */
75 protected function syncOriginal()
76 {
77 $this->original = $this->attributes;
78
79 return $this;
80 }
81
82 /**
83 * Get the model's original attribute values.
84 *
85 * @since 2.19.6
86 *
87 * @param string|null $key
88 *
89 * @return mixed|array
90 */
91 public function getOriginal($key = null)
92 {
93 return $key ? $this->original[$key] : $this->original;
94 }
95
96 /**
97 * Determine if a given attribute is dirty.
98 *
99 * @since 2.19.6
100 *
101 * @param string|null $attribute
102 *
103 * @return bool
104 */
105 public function isDirty($attribute = null)
106 {
107 if ( ! $attribute) {
108 return (bool)$this->getDirty();
109 }
110
111 return array_key_exists($attribute, $this->getDirty());
112 }
113
114 /**
115 * Determine if a given attribute is clean.
116 *
117 * @since 2.19.6
118 *
119 * @param string|null $attribute
120 *
121 * @return bool
122 */
123 public function isClean($attribute = null)
124 {
125 return ! $this->isDirty($attribute);
126 }
127
128 /**
129 * Get the attributes that have been changed since last sync.
130 *
131 * @since 2.19.6
132 *
133 * @return array
134 */
135 public function getDirty()
136 {
137 $dirty = [];
138
139 foreach ($this->attributes as $key => $value) {
140 if ( ! array_key_exists($key, $this->original) || $value !== $this->original[$key]) {
141 $dirty[$key] = $value;
142 }
143 }
144
145 return $dirty;
146 }
147
148 /**
149 * Fill the model with an array of attributes.
150 *
151 * @since 2.19.6
152 *
153 * @param array $attributes
154 *
155 * @return $this
156 */
157 public function fill(array $attributes)
158 {
159 foreach ($attributes as $key => $value) {
160 $this->setAttribute($key, $value);
161 }
162
163 return $this;
164 }
165
166 /**
167 * Get an attribute from the model.
168 *
169 * @since 2.23.0 use the existence validation method
170 * @since 2.19.6
171 *
172 * @return mixed
173 *
174 * @throws RuntimeException
175 */
176 public function getAttribute(string $key)
177 {
178 $this->validatePropertyExists($key);
179
180 return $this->attributes[$key] ?? null;
181 }
182
183 /**
184 * Set a given attribute on the model.
185 *
186 * @since 2.23.0 validate that the property exists before setting
187 * @since 2.19.6
188 *
189 * @param string $key
190 * @param mixed $value
191 * @return void
192 */
193 public function setAttribute(string $key, $value)
194 {
195 $this->validatePropertyExists($key);
196 $this->validatePropertyType($key, $value);
197
198 $this->attributes[$key] = $value;
199 }
200
201 public function hasProperty($key): bool
202 {
203 return array_key_exists($key, $this->properties);
204 }
205
206 /**
207 * Validate an attribute to a PHP type.
208 *
209 * @since 2.19.6
210 *
211 * @param string $key
212 * @param mixed $value
213 *
214 * @return bool
215 */
216 public function isPropertyTypeValid($key, $value)
217 {
218 if (is_null($value)) {
219 return true;
220 }
221
222 $type = $this->getPropertyType($key);
223
224 switch ($type) {
225 case 'int':
226 return is_int($value);
227 case 'string':
228 return is_string($value);
229 case 'bool':
230 return is_bool($value);
231 case 'array':
232 return is_array($value);
233 default:
234 return $value instanceof $type;
235 }
236 }
237
238 /**
239 * Validates that the given value is a valid type for the given property.
240 *
241 * @since 2.19.6
242 *
243 * @param string $key
244 * @param mixed $value
245 * @return void
246 *
247 * @throws InvalidArgumentException
248 */
249 protected function validatePropertyType(string $key, $value)
250 {
251 if ( ! $this->isPropertyTypeValid($key, $value)) {
252 $type = $this->getPropertyType($key);
253
254 throw new InvalidArgumentException("Invalid attribute assignment. '$key' should be of type: '$type'");
255 }
256 }
257
258 /**
259 * Validates that the given property exists
260 *
261 * @since 2.23.0
262 *
263 * @return void
264 * @throws InvalidArgumentException
265 */
266 protected function validatePropertyExists(string $key)
267 {
268 if ( !$this->hasProperty($key) ) {
269 throw new InvalidArgumentException("Invalid property. '$key' does not exist.");
270 }
271 }
272
273 /**
274 * Get the property type
275 *
276 * @since 2.19.6
277 */
278 protected function getPropertyType(string $key): string
279 {
280 $type = is_array($this->properties[$key]) ? $this->properties[$key][0] : $this->properties[$key];
281
282 return strtolower(trim($type));
283 }
284
285 /**
286 * Get the default for a property if one is provided, otherwise default to null
287 *
288 * @since 2.20.0
289 *
290 * @param $key
291 *
292 * @return mixed|null
293 */
294 protected function getPropertyDefault($key)
295 {
296 return is_array($this->properties[$key]) && isset($this->properties[$key][1])
297 ? $this->properties[$key][1]
298 : null;
299 }
300
301 /**
302 * Returns the defaults for all the properties. If a default is omitted it defaults to null.
303 *
304 * @since 2.20.0
305 */
306 protected function getPropertyDefaults(): array
307 {
308 $defaults = [];
309 foreach (array_keys($this->properties) as $property) {
310 $defaults[$property] = $this->getPropertyDefault($property);
311 }
312
313 return $defaults;
314 }
315
316 /**
317 * @since 2.19.6
318 */
319 public function toArray(): array
320 {
321 return $this->attributes;
322 }
323
324 /**
325 * @since 2.19.6
326 */
327 public function getAttributes(): array
328 {
329 return $this->attributes;
330 }
331
332 /**
333 * @return int[]|string[]
334 */
335 public static function propertyKeys(): array
336 {
337 return array_keys((new static())->properties);
338 }
339
340 /**
341 * Dynamically retrieve attributes on the model.
342 *
343 * @since 2.19.6
344 *
345 * @return mixed
346 */
347 public function __get(string $key)
348 {
349 if (array_key_exists($key, $this->relationships)) {
350 return $this->getRelationship($key);
351 }
352
353 return $this->getAttribute($key);
354 }
355
356 /**
357 * Dynamically set attributes on the model.
358 *
359 * @since 2.19.6
360 *
361 * @param string $key
362 * @param mixed $value
363 *
364 * @return void
365 */
366 public function __set($key, $value)
367 {
368 $this->setAttribute($key, $value);
369 }
370
371 /**
372 * Determine if an attribute exists on the model.
373 *
374 * @since 2.19.6
375 *
376 * @param string $key
377 *
378 * @return bool
379 */
380 public function __isset($key)
381 {
382 return isset($this->attributes[$key]);
383 }
384
385 /**
386 * @since 2.20.0 cache the relations after first load
387 * @since 2.19.6
388 *
389 * @param $key
390 *
391 * @return Model|Model[]
392 *
393 * @throws InvalidArgumentException
394 */
395 protected function getRelationship($key)
396 {
397 if ( ! is_callable([$this, $key])) {
398 throw new InvalidArgumentException("$key() does not exist.");
399 }
400
401 if ($this->hasCachedRelationship($key)) {
402 return $this->cachedRelations[$key];
403 }
404
405 $relationship = new Relationship($this->relationships[$key]);
406
407 switch (true) {
408 case ($relationship->equals(Relationship::BELONGS_TO())):
409 case ($relationship->equals(Relationship::HAS_ONE())):
410 return $this->cachedRelations[$key] = $this->$key()->get();
411 case ($relationship->equals(Relationship::HAS_MANY())):
412 case ($relationship->equals(Relationship::BELONGS_TO_MANY())):
413 case ($relationship->equals(Relationship::MANY_TO_MANY())):
414 return $this->cachedRelations[$key] = $this->$key()->getAll();
415 }
416
417 return null;
418 }
419
420 /**
421 * Checks whether a relationship has already been loaded.
422 *
423 * @since 2.20.0
424 */
425 protected function hasCachedRelationship(string $key): bool
426 {
427 return array_key_exists($key, $this->cachedRelations);
428 }
429 }
430