1: <?php
2: /**
3: * CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
4: * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
5: *
6: * Licensed under The MIT License
7: * For full copyright and license information, please see the LICENSE.txt
8: * Redistributions of files must retain the above copyright notice.
9: *
10: * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
11: * @link https://cakephp.org CakePHP(tm) Project
12: * @since 3.0.0
13: * @license https://opensource.org/licenses/mit-license.php MIT License
14: */
15: namespace Cake\Datasource;
16:
17: use Cake\Collection\Collection;
18: use Cake\Utility\Hash;
19: use Cake\Utility\Inflector;
20: use InvalidArgumentException;
21: use Traversable;
22:
23: /**
24: * An entity represents a single result row from a repository. It exposes the
25: * methods for retrieving and storing properties associated in this row.
26: */
27: trait EntityTrait
28: {
29: /**
30: * Holds all properties and their values for this entity
31: *
32: * @var array
33: */
34: protected $_properties = [];
35:
36: /**
37: * Holds all properties that have been changed and their original values for this entity
38: *
39: * @var array
40: */
41: protected $_original = [];
42:
43: /**
44: * List of property names that should **not** be included in JSON or Array
45: * representations of this Entity.
46: *
47: * @var array
48: */
49: protected $_hidden = [];
50:
51: /**
52: * List of computed or virtual fields that **should** be included in JSON or array
53: * representations of this Entity. If a field is present in both _hidden and _virtual
54: * the field will **not** be in the array/json versions of the entity.
55: *
56: * @var array
57: */
58: protected $_virtual = [];
59:
60: /**
61: * Holds the name of the class for the instance object
62: *
63: * @var string
64: *
65: * @deprecated 3.2 This field is no longer being used
66: */
67: protected $_className;
68:
69: /**
70: * Holds a list of the properties that were modified or added after this object
71: * was originally created.
72: *
73: * @var array
74: */
75: protected $_dirty = [];
76:
77: /**
78: * Holds a cached list of getters/setters per class
79: *
80: * @var array
81: */
82: protected static $_accessors = [];
83:
84: /**
85: * Indicates whether or not this entity is yet to be persisted.
86: * Entities default to assuming they are new. You can use Table::persisted()
87: * to set the new flag on an entity based on records in the database.
88: *
89: * @var bool
90: */
91: protected $_new = true;
92:
93: /**
94: * List of errors per field as stored in this object
95: *
96: * @var array
97: */
98: protected $_errors = [];
99:
100: /**
101: * List of invalid fields and their data for errors upon validation/patching
102: *
103: * @var array
104: */
105: protected $_invalid = [];
106:
107: /**
108: * Map of properties in this entity that can be safely assigned, each
109: * property name points to a boolean indicating its status. An empty array
110: * means no properties are accessible
111: *
112: * The special property '\*' can also be mapped, meaning that any other property
113: * not defined in the map will take its value. For example, `'\*' => true`
114: * means that any property not defined in the map will be accessible by default
115: *
116: * @var array
117: */
118: protected $_accessible = ['*' => true];
119:
120: /**
121: * The alias of the repository this entity came from
122: *
123: * @var string
124: */
125: protected $_registryAlias;
126:
127: /**
128: * Magic getter to access properties that have been set in this entity
129: *
130: * @param string $property Name of the property to access
131: * @return mixed
132: */
133: public function &__get($property)
134: {
135: return $this->get($property);
136: }
137:
138: /**
139: * Magic setter to add or edit a property in this entity
140: *
141: * @param string $property The name of the property to set
142: * @param mixed $value The value to set to the property
143: * @return void
144: */
145: public function __set($property, $value)
146: {
147: $this->set($property, $value);
148: }
149:
150: /**
151: * Returns whether this entity contains a property named $property
152: * regardless of if it is empty.
153: *
154: * @param string $property The property to check.
155: * @return bool
156: * @see \Cake\ORM\Entity::has()
157: */
158: public function __isset($property)
159: {
160: return $this->has($property);
161: }
162:
163: /**
164: * Removes a property from this entity
165: *
166: * @param string $property The property to unset
167: * @return void
168: */
169: public function __unset($property)
170: {
171: $this->unsetProperty($property);
172: }
173:
174: /**
175: * Sets a single property inside this entity.
176: *
177: * ### Example:
178: *
179: * ```
180: * $entity->set('name', 'Andrew');
181: * ```
182: *
183: * It is also possible to mass-assign multiple properties to this entity
184: * with one call by passing a hashed array as properties in the form of
185: * property => value pairs
186: *
187: * ### Example:
188: *
189: * ```
190: * $entity->set(['name' => 'andrew', 'id' => 1]);
191: * echo $entity->name // prints andrew
192: * echo $entity->id // prints 1
193: * ```
194: *
195: * Some times it is handy to bypass setter functions in this entity when assigning
196: * properties. You can achieve this by disabling the `setter` option using the
197: * `$options` parameter:
198: *
199: * ```
200: * $entity->set('name', 'Andrew', ['setter' => false]);
201: * $entity->set(['name' => 'Andrew', 'id' => 1], ['setter' => false]);
202: * ```
203: *
204: * Mass assignment should be treated carefully when accepting user input, by default
205: * entities will guard all fields when properties are assigned in bulk. You can disable
206: * the guarding for a single set call with the `guard` option:
207: *
208: * ```
209: * $entity->set(['name' => 'Andrew', 'id' => 1], ['guard' => true]);
210: * ```
211: *
212: * You do not need to use the guard option when assigning properties individually:
213: *
214: * ```
215: * // No need to use the guard option.
216: * $entity->set('name', 'Andrew');
217: * ```
218: *
219: * @param string|array $property the name of property to set or a list of
220: * properties with their respective values
221: * @param mixed $value The value to set to the property or an array if the
222: * first argument is also an array, in which case will be treated as $options
223: * @param array $options options to be used for setting the property. Allowed option
224: * keys are `setter` and `guard`
225: * @return $this
226: * @throws \InvalidArgumentException
227: */
228: public function set($property, $value = null, array $options = [])
229: {
230: if (is_string($property) && $property !== '') {
231: $guard = false;
232: $property = [$property => $value];
233: } else {
234: $guard = true;
235: $options = (array)$value;
236: }
237:
238: if (!is_array($property)) {
239: throw new InvalidArgumentException('Cannot set an empty property');
240: }
241: $options += ['setter' => true, 'guard' => $guard];
242:
243: foreach ($property as $p => $value) {
244: if ($options['guard'] === true && !$this->isAccessible($p)) {
245: continue;
246: }
247:
248: $this->setDirty($p, true);
249:
250: if (!array_key_exists($p, $this->_original) &&
251: array_key_exists($p, $this->_properties) &&
252: $this->_properties[$p] !== $value
253: ) {
254: $this->_original[$p] = $this->_properties[$p];
255: }
256:
257: if (!$options['setter']) {
258: $this->_properties[$p] = $value;
259: continue;
260: }
261:
262: $setter = static::_accessor($p, 'set');
263: if ($setter) {
264: $value = $this->{$setter}($value);
265: }
266: $this->_properties[$p] = $value;
267: }
268:
269: return $this;
270: }
271:
272: /**
273: * Returns the value of a property by name
274: *
275: * @param string $property the name of the property to retrieve
276: * @return mixed
277: * @throws \InvalidArgumentException if an empty property name is passed
278: */
279: public function &get($property)
280: {
281: if (!strlen((string)$property)) {
282: throw new InvalidArgumentException('Cannot get an empty property');
283: }
284:
285: $value = null;
286: $method = static::_accessor($property, 'get');
287:
288: if (isset($this->_properties[$property])) {
289: $value =& $this->_properties[$property];
290: }
291:
292: if ($method) {
293: $result = $this->{$method}($value);
294:
295: return $result;
296: }
297:
298: return $value;
299: }
300:
301: /**
302: * Returns the value of an original property by name
303: *
304: * @param string $property the name of the property for which original value is retrieved.
305: * @return mixed
306: * @throws \InvalidArgumentException if an empty property name is passed.
307: */
308: public function getOriginal($property)
309: {
310: if (!strlen((string)$property)) {
311: throw new InvalidArgumentException('Cannot get an empty property');
312: }
313: if (array_key_exists($property, $this->_original)) {
314: return $this->_original[$property];
315: }
316:
317: return $this->get($property);
318: }
319:
320: /**
321: * Gets all original values of the entity.
322: *
323: * @return array
324: */
325: public function getOriginalValues()
326: {
327: $originals = $this->_original;
328: $originalKeys = array_keys($originals);
329: foreach ($this->_properties as $key => $value) {
330: if (!in_array($key, $originalKeys)) {
331: $originals[$key] = $value;
332: }
333: }
334:
335: return $originals;
336: }
337:
338: /**
339: * Returns whether this entity contains a property named $property
340: * that contains a non-null value.
341: *
342: * ### Example:
343: *
344: * ```
345: * $entity = new Entity(['id' => 1, 'name' => null]);
346: * $entity->has('id'); // true
347: * $entity->has('name'); // false
348: * $entity->has('last_name'); // false
349: * ```
350: *
351: * You can check multiple properties by passing an array:
352: *
353: * ```
354: * $entity->has(['name', 'last_name']);
355: * ```
356: *
357: * All properties must not be null to get a truthy result.
358: *
359: * When checking multiple properties. All properties must not be null
360: * in order for true to be returned.
361: *
362: * @param string|array $property The property or properties to check.
363: * @return bool
364: */
365: public function has($property)
366: {
367: foreach ((array)$property as $prop) {
368: if ($this->get($prop) === null) {
369: return false;
370: }
371: }
372:
373: return true;
374: }
375:
376: /**
377: * Checks that a property is empty
378: *
379: * This is not working like the PHP `empty()` function. The method will
380: * return true for:
381: *
382: * - `''` (empty string)
383: * - `null`
384: * - `[]`
385: *
386: * and false in all other cases.
387: *
388: * @param string $property The property to check.
389: * @return bool
390: */
391: public function isEmpty($property)
392: {
393: $value = $this->get($property);
394: if ($value === null
395: || (is_array($value) && empty($value)
396: || (is_string($value) && empty($value)))
397: ) {
398: return true;
399: }
400:
401: return false;
402: }
403:
404: /**
405: * Checks tha a property has a value.
406: *
407: * This method will return true for
408: *
409: * - Non-empty strings
410: * - Non-empty arrays
411: * - Any object
412: * - Integer, even `0`
413: * - Float, even 0.0
414: *
415: * and false in all other cases.
416: *
417: * @param string $property The property to check.
418: * @return bool
419: */
420: public function hasValue($property)
421: {
422: return !$this->isEmpty($property);
423: }
424:
425: /**
426: * Removes a property or list of properties from this entity
427: *
428: * ### Examples:
429: *
430: * ```
431: * $entity->unsetProperty('name');
432: * $entity->unsetProperty(['name', 'last_name']);
433: * ```
434: *
435: * @param string|array $property The property to unset.
436: * @return $this
437: */
438: public function unsetProperty($property)
439: {
440: $property = (array)$property;
441: foreach ($property as $p) {
442: unset($this->_properties[$p], $this->_dirty[$p]);
443: }
444:
445: return $this;
446: }
447:
448: /**
449: * Get/Set the hidden properties on this entity.
450: *
451: * If the properties argument is null, the currently hidden properties
452: * will be returned. Otherwise the hidden properties will be set.
453: *
454: * @deprecated 3.4.0 Use EntityTrait::setHidden() and EntityTrait::getHidden()
455: * @param array|null $properties Either an array of properties to hide or null to get properties
456: * @return array|$this
457: */
458: public function hiddenProperties($properties = null)
459: {
460: deprecationWarning(
461: get_called_class() . '::hiddenProperties() is deprecated. ' .
462: 'Use setHidden()/getHidden() instead.'
463: );
464: if ($properties === null) {
465: return $this->_hidden;
466: }
467: $this->_hidden = $properties;
468:
469: return $this;
470: }
471:
472: /**
473: * Sets hidden properties.
474: *
475: * @param array $properties An array of properties to hide from array exports.
476: * @param bool $merge Merge the new properties with the existing. By default false.
477: * @return $this
478: */
479: public function setHidden(array $properties, $merge = false)
480: {
481: if ($merge === false) {
482: $this->_hidden = $properties;
483:
484: return $this;
485: }
486:
487: $properties = array_merge($this->_hidden, $properties);
488: $this->_hidden = array_unique($properties);
489:
490: return $this;
491: }
492:
493: /**
494: * Gets the hidden properties.
495: *
496: * @return array
497: */
498: public function getHidden()
499: {
500: return $this->_hidden;
501: }
502:
503: /**
504: * Get/Set the virtual properties on this entity.
505: *
506: * If the properties argument is null, the currently virtual properties
507: * will be returned. Otherwise the virtual properties will be set.
508: *
509: * @deprecated 3.4.0 Use EntityTrait::getVirtual() and EntityTrait::setVirtual()
510: * @param array|null $properties Either an array of properties to treat as virtual or null to get properties
511: * @return array|$this
512: */
513: public function virtualProperties($properties = null)
514: {
515: deprecationWarning(
516: get_called_class() . '::virtualProperties() is deprecated. ' .
517: 'Use setVirtual()/getVirtual() instead.'
518: );
519: if ($properties === null) {
520: return $this->getVirtual();
521: }
522:
523: return $this->setVirtual($properties);
524: }
525:
526: /**
527: * Sets the virtual properties on this entity.
528: *
529: * @param array $properties An array of properties to treat as virtual.
530: * @param bool $merge Merge the new properties with the existing. By default false.
531: * @return $this
532: */
533: public function setVirtual(array $properties, $merge = false)
534: {
535: if ($merge === false) {
536: $this->_virtual = $properties;
537:
538: return $this;
539: }
540:
541: $properties = array_merge($this->_virtual, $properties);
542: $this->_virtual = array_unique($properties);
543:
544: return $this;
545: }
546:
547: /**
548: * Gets the virtual properties on this entity.
549: *
550: * @return array
551: */
552: public function getVirtual()
553: {
554: return $this->_virtual;
555: }
556:
557: /**
558: * Gets the list of visible properties.
559: *
560: * The list of visible properties is all standard properties
561: * plus virtual properties minus hidden properties.
562: *
563: * @return array A list of properties that are 'visible' in all
564: * representations.
565: */
566: public function getVisible()
567: {
568: $properties = array_keys($this->_properties);
569: $properties = array_merge($properties, $this->_virtual);
570:
571: return array_diff($properties, $this->_hidden);
572: }
573:
574: /**
575: * Gets the list of visible properties.
576: *
577: * The list of visible properties is all standard properties
578: * plus virtual properties minus hidden properties.
579: *
580: * @return array A list of properties that are 'visible' in all
581: * representations.
582: * @deprecated 3.8.0 Use getVisible() instead.
583: */
584: public function visibleProperties()
585: {
586: deprecationWarning(
587: get_called_class() . '::visibleProperties() is deprecated. ' .
588: 'Use getVisible() instead.'
589: );
590:
591: return $this->getVisible();
592: }
593:
594: /**
595: * Returns an array with all the properties that have been set
596: * to this entity
597: *
598: * This method will recursively transform entities assigned to properties
599: * into arrays as well.
600: *
601: * @return array
602: */
603: public function toArray()
604: {
605: $result = [];
606: foreach ($this->getVisible() as $property) {
607: $value = $this->get($property);
608: if (is_array($value)) {
609: $result[$property] = [];
610: foreach ($value as $k => $entity) {
611: if ($entity instanceof EntityInterface) {
612: $result[$property][$k] = $entity->toArray();
613: } else {
614: $result[$property][$k] = $entity;
615: }
616: }
617: } elseif ($value instanceof EntityInterface) {
618: $result[$property] = $value->toArray();
619: } else {
620: $result[$property] = $value;
621: }
622: }
623:
624: return $result;
625: }
626:
627: /**
628: * Returns the properties that will be serialized as JSON
629: *
630: * @return array
631: */
632: public function jsonSerialize()
633: {
634: return $this->extract($this->getVisible());
635: }
636:
637: /**
638: * Implements isset($entity);
639: *
640: * @param mixed $offset The offset to check.
641: * @return bool Success
642: */
643: public function offsetExists($offset)
644: {
645: return $this->has($offset);
646: }
647:
648: /**
649: * Implements $entity[$offset];
650: *
651: * @param mixed $offset The offset to get.
652: * @return mixed
653: */
654: public function &offsetGet($offset)
655: {
656: return $this->get($offset);
657: }
658:
659: /**
660: * Implements $entity[$offset] = $value;
661: *
662: * @param mixed $offset The offset to set.
663: * @param mixed $value The value to set.
664: * @return void
665: */
666: public function offsetSet($offset, $value)
667: {
668: $this->set($offset, $value);
669: }
670:
671: /**
672: * Implements unset($result[$offset]);
673: *
674: * @param mixed $offset The offset to remove.
675: * @return void
676: */
677: public function offsetUnset($offset)
678: {
679: $this->unsetProperty($offset);
680: }
681:
682: /**
683: * Fetch accessor method name
684: * Accessor methods (available or not) are cached in $_accessors
685: *
686: * @param string $property the field name to derive getter name from
687: * @param string $type the accessor type ('get' or 'set')
688: * @return string method name or empty string (no method available)
689: */
690: protected static function _accessor($property, $type)
691: {
692: $class = static::class;
693:
694: if (isset(static::$_accessors[$class][$type][$property])) {
695: return static::$_accessors[$class][$type][$property];
696: }
697:
698: if (!empty(static::$_accessors[$class])) {
699: return static::$_accessors[$class][$type][$property] = '';
700: }
701:
702: if ($class === 'Cake\ORM\Entity') {
703: return '';
704: }
705:
706: foreach (get_class_methods($class) as $method) {
707: $prefix = substr($method, 1, 3);
708: if ($method[0] !== '_' || ($prefix !== 'get' && $prefix !== 'set')) {
709: continue;
710: }
711: $field = lcfirst(substr($method, 4));
712: $snakeField = Inflector::underscore($field);
713: $titleField = ucfirst($field);
714: static::$_accessors[$class][$prefix][$snakeField] = $method;
715: static::$_accessors[$class][$prefix][$field] = $method;
716: static::$_accessors[$class][$prefix][$titleField] = $method;
717: }
718:
719: if (!isset(static::$_accessors[$class][$type][$property])) {
720: static::$_accessors[$class][$type][$property] = '';
721: }
722:
723: return static::$_accessors[$class][$type][$property];
724: }
725:
726: /**
727: * Returns an array with the requested properties
728: * stored in this entity, indexed by property name
729: *
730: * @param array $properties list of properties to be returned
731: * @param bool $onlyDirty Return the requested property only if it is dirty
732: * @return array
733: */
734: public function extract(array $properties, $onlyDirty = false)
735: {
736: $result = [];
737: foreach ($properties as $property) {
738: if (!$onlyDirty || $this->isDirty($property)) {
739: $result[$property] = $this->get($property);
740: }
741: }
742:
743: return $result;
744: }
745:
746: /**
747: * Returns an array with the requested original properties
748: * stored in this entity, indexed by property name.
749: *
750: * Properties that are unchanged from their original value will be included in the
751: * return of this method.
752: *
753: * @param array $properties List of properties to be returned
754: * @return array
755: */
756: public function extractOriginal(array $properties)
757: {
758: $result = [];
759: foreach ($properties as $property) {
760: $result[$property] = $this->getOriginal($property);
761: }
762:
763: return $result;
764: }
765:
766: /**
767: * Returns an array with only the original properties
768: * stored in this entity, indexed by property name.
769: *
770: * This method will only return properties that have been modified since
771: * the entity was built. Unchanged properties will be omitted.
772: *
773: * @param array $properties List of properties to be returned
774: * @return array
775: */
776: public function extractOriginalChanged(array $properties)
777: {
778: $result = [];
779: foreach ($properties as $property) {
780: $original = $this->getOriginal($property);
781: if ($original !== $this->get($property)) {
782: $result[$property] = $original;
783: }
784: }
785:
786: return $result;
787: }
788:
789: /**
790: * Sets the dirty status of a single property. If called with no second
791: * argument, it will return whether the property was modified or not
792: * after the object creation.
793: *
794: * When called with no arguments it will return whether or not there are any
795: * dirty property in the entity
796: *
797: * @deprecated 3.4.0 Use EntityTrait::setDirty() and EntityTrait::isDirty()
798: * @param string|null $property the field to set or check status for
799: * @param bool|null $isDirty true means the property was changed, false means
800: * it was not changed and null will make the function return current state
801: * for that property
802: * @return bool Whether the property was changed or not
803: */
804: public function dirty($property = null, $isDirty = null)
805: {
806: deprecationWarning(
807: get_called_class() . '::dirty() is deprecated. ' .
808: 'Use setDirty()/isDirty() instead.'
809: );
810: if ($property === null) {
811: return $this->isDirty();
812: }
813:
814: if ($isDirty === null) {
815: return $this->isDirty($property);
816: }
817:
818: $this->setDirty($property, $isDirty);
819:
820: return true;
821: }
822:
823: /**
824: * Sets the dirty status of a single property.
825: *
826: * @param string $property the field to set or check status for
827: * @param bool $isDirty true means the property was changed, false means
828: * it was not changed. Defaults to true.
829: * @return $this
830: */
831: public function setDirty($property, $isDirty = true)
832: {
833: if ($isDirty === false) {
834: unset($this->_dirty[$property]);
835:
836: return $this;
837: }
838:
839: $this->_dirty[$property] = true;
840: unset($this->_errors[$property], $this->_invalid[$property]);
841:
842: return $this;
843: }
844:
845: /**
846: * Checks if the entity is dirty or if a single property of it is dirty.
847: *
848: * @param string|null $property The field to check the status for. Null for the whole entity.
849: * @return bool Whether the property was changed or not
850: */
851: public function isDirty($property = null)
852: {
853: if ($property === null) {
854: return !empty($this->_dirty);
855: }
856:
857: return isset($this->_dirty[$property]);
858: }
859:
860: /**
861: * Gets the dirty properties.
862: *
863: * @return string[]
864: */
865: public function getDirty()
866: {
867: return array_keys($this->_dirty);
868: }
869:
870: /**
871: * Sets the entire entity as clean, which means that it will appear as
872: * no properties being modified or added at all. This is an useful call
873: * for an initial object hydration
874: *
875: * @return void
876: */
877: public function clean()
878: {
879: $this->_dirty = [];
880: $this->_errors = [];
881: $this->_invalid = [];
882: $this->_original = [];
883: }
884:
885: /**
886: * Returns whether or not this entity has already been persisted.
887: * This method can return null in the case there is no prior information on
888: * the status of this entity.
889: *
890: * If called with a boolean it will set the known status of this instance,
891: * true means that the instance is not yet persisted in the database, false
892: * that it already is.
893: *
894: * @param bool|null $new true if it is known this instance was not yet persisted
895: * @return bool Whether or not the entity has been persisted.
896: */
897: public function isNew($new = null)
898: {
899: if ($new === null) {
900: return $this->_new;
901: }
902:
903: $new = (bool)$new;
904:
905: if ($new) {
906: foreach ($this->_properties as $k => $p) {
907: $this->_dirty[$k] = true;
908: }
909: }
910:
911: return $this->_new = $new;
912: }
913:
914: /**
915: * Returns whether this entity has errors.
916: *
917: * @param bool $includeNested true will check nested entities for hasErrors()
918: * @return bool
919: */
920: public function hasErrors($includeNested = true)
921: {
922: if (Hash::filter($this->_errors)) {
923: return true;
924: }
925:
926: if ($includeNested === false) {
927: return false;
928: }
929:
930: foreach ($this->_properties as $property) {
931: if ($this->_readHasErrors($property)) {
932: return true;
933: }
934: }
935:
936: return false;
937: }
938:
939: /**
940: * Returns all validation errors.
941: *
942: * @return array
943: */
944: public function getErrors()
945: {
946: $diff = array_diff_key($this->_properties, $this->_errors);
947:
948: return $this->_errors + (new Collection($diff))
949: ->filter(function ($value) {
950: return is_array($value) || $value instanceof EntityInterface;
951: })
952: ->map(function ($value) {
953: return $this->_readError($value);
954: })
955: ->filter()
956: ->toArray();
957: }
958:
959: /**
960: * Returns validation errors of a field
961: *
962: * @param string $field Field name to get the errors from
963: * @return array
964: */
965: public function getError($field)
966: {
967: $errors = isset($this->_errors[$field]) ? $this->_errors[$field] : [];
968: if ($errors) {
969: return $errors;
970: }
971:
972: return $this->_nestedErrors($field);
973: }
974:
975: /**
976: * Sets error messages to the entity
977: *
978: * ## Example
979: *
980: * ```
981: * // Sets the error messages for multiple fields at once
982: * $entity->setErrors(['salary' => ['message'], 'name' => ['another message']]);
983: * ```
984: *
985: * @param array $errors The array of errors to set.
986: * @param bool $overwrite Whether or not to overwrite pre-existing errors for $fields
987: * @return $this
988: */
989: public function setErrors(array $errors, $overwrite = false)
990: {
991: if ($overwrite) {
992: foreach ($errors as $f => $error) {
993: $this->_errors[$f] = (array)$error;
994: }
995:
996: return $this;
997: }
998:
999: foreach ($errors as $f => $error) {
1000: $this->_errors += [$f => []];
1001:
1002: // String messages are appended to the list,
1003: // while more complex error structures need their
1004: // keys preserved for nested validator.
1005: if (is_string($error)) {
1006: $this->_errors[$f][] = $error;
1007: } else {
1008: foreach ($error as $k => $v) {
1009: $this->_errors[$f][$k] = $v;
1010: }
1011: }
1012: }
1013:
1014: return $this;
1015: }
1016:
1017: /**
1018: * Sets errors for a single field
1019: *
1020: * ### Example
1021: *
1022: * ```
1023: * // Sets the error messages for a single field
1024: * $entity->setError('salary', ['must be numeric', 'must be a positive number']);
1025: * ```
1026: *
1027: * @param string $field The field to get errors for, or the array of errors to set.
1028: * @param string|array $errors The errors to be set for $field
1029: * @param bool $overwrite Whether or not to overwrite pre-existing errors for $field
1030: * @return $this
1031: */
1032: public function setError($field, $errors, $overwrite = false)
1033: {
1034: if (is_string($errors)) {
1035: $errors = [$errors];
1036: }
1037:
1038: return $this->setErrors([$field => $errors], $overwrite);
1039: }
1040:
1041: /**
1042: * Sets the error messages for a field or a list of fields. When called
1043: * without the second argument it returns the validation
1044: * errors for the specified fields. If called with no arguments it returns
1045: * all the validation error messages stored in this entity and any other nested
1046: * entity.
1047: *
1048: * ### Example
1049: *
1050: * ```
1051: * // Sets the error messages for a single field
1052: * $entity->errors('salary', ['must be numeric', 'must be a positive number']);
1053: *
1054: * // Returns the error messages for a single field
1055: * $entity->getErrors('salary');
1056: *
1057: * // Returns all error messages indexed by field name
1058: * $entity->getErrors();
1059: *
1060: * // Sets the error messages for multiple fields at once
1061: * $entity->getErrors(['salary' => ['message'], 'name' => ['another message']);
1062: * ```
1063: *
1064: * When used as a setter, this method will return this entity instance for method
1065: * chaining.
1066: *
1067: * @deprecated 3.4.0 Use EntityTrait::setError(), EntityTrait::setErrors(), EntityTrait::getError() and EntityTrait::getErrors()
1068: * @param string|array|null $field The field to get errors for, or the array of errors to set.
1069: * @param string|array|null $errors The errors to be set for $field
1070: * @param bool $overwrite Whether or not to overwrite pre-existing errors for $field
1071: * @return array|$this
1072: */
1073: public function errors($field = null, $errors = null, $overwrite = false)
1074: {
1075: deprecationWarning(
1076: get_called_class() . '::errors() is deprecated. ' .
1077: 'Use setError()/getError() or setErrors()/getErrors() instead.'
1078: );
1079: if ($field === null) {
1080: return $this->getErrors();
1081: }
1082:
1083: if (is_string($field) && $errors === null) {
1084: return $this->getError($field);
1085: }
1086:
1087: if (!is_array($field)) {
1088: $field = [$field => $errors];
1089: }
1090:
1091: return $this->setErrors($field, $overwrite);
1092: }
1093:
1094: /**
1095: * Auxiliary method for getting errors in nested entities
1096: *
1097: * @param string $field the field in this entity to check for errors
1098: * @return array errors in nested entity if any
1099: */
1100: protected function _nestedErrors($field)
1101: {
1102: // Only one path element, check for nested entity with error.
1103: if (strpos($field, '.') === false) {
1104: return $this->_readError($this->get($field));
1105: }
1106: // Try reading the errors data with field as a simple path
1107: $error = Hash::get($this->_errors, $field);
1108: if ($error !== null) {
1109: return $error;
1110: }
1111: $path = explode('.', $field);
1112:
1113: // Traverse down the related entities/arrays for
1114: // the relevant entity.
1115: $entity = $this;
1116: $len = count($path);
1117: while ($len) {
1118: $part = array_shift($path);
1119: $len = count($path);
1120: $val = null;
1121: if ($entity instanceof EntityInterface) {
1122: $val = $entity->get($part);
1123: } elseif (is_array($entity)) {
1124: $val = isset($entity[$part]) ? $entity[$part] : false;
1125: }
1126:
1127: if (is_array($val) ||
1128: $val instanceof Traversable ||
1129: $val instanceof EntityInterface
1130: ) {
1131: $entity = $val;
1132: } else {
1133: $path[] = $part;
1134: break;
1135: }
1136: }
1137: if (count($path) <= 1) {
1138: return $this->_readError($entity, array_pop($path));
1139: }
1140:
1141: return [];
1142: }
1143:
1144: /**
1145: * Reads if there are errors for one or many objects.
1146: *
1147: * @param mixed $object The object to read errors from.
1148: * @return bool
1149: */
1150: protected function _readHasErrors($object)
1151: {
1152: if ($object instanceof EntityInterface && $object->hasErrors()) {
1153: return true;
1154: }
1155:
1156: if (is_array($object)) {
1157: foreach ($object as $value) {
1158: if ($this->_readHasErrors($value)) {
1159: return true;
1160: }
1161: }
1162: }
1163:
1164: return false;
1165: }
1166:
1167: /**
1168: * Read the error(s) from one or many objects.
1169: *
1170: * @param array|\Cake\Datasource\EntityInterface $object The object to read errors from.
1171: * @param string|null $path The field name for errors.
1172: * @return array
1173: */
1174: protected function _readError($object, $path = null)
1175: {
1176: if ($path !== null && $object instanceof EntityInterface) {
1177: return $object->getError($path);
1178: }
1179: if ($object instanceof EntityInterface) {
1180: return $object->getErrors();
1181: }
1182: if (is_array($object)) {
1183: $array = array_map(function ($val) {
1184: if ($val instanceof EntityInterface) {
1185: return $val->getErrors();
1186: }
1187: }, $object);
1188:
1189: return array_filter($array);
1190: }
1191:
1192: return [];
1193: }
1194:
1195: /**
1196: * Get a list of invalid fields and their data for errors upon validation/patching
1197: *
1198: * @return array
1199: */
1200: public function getInvalid()
1201: {
1202: return $this->_invalid;
1203: }
1204:
1205: /**
1206: * Get a single value of an invalid field. Returns null if not set.
1207: *
1208: * @param string $field The name of the field.
1209: * @return mixed|null
1210: */
1211: public function getInvalidField($field)
1212: {
1213: $value = isset($this->_invalid[$field]) ? $this->_invalid[$field] : null;
1214:
1215: return $value;
1216: }
1217:
1218: /**
1219: * Set fields as invalid and not patchable into the entity.
1220: *
1221: * This is useful for batch operations when one needs to get the original value for an error message after patching.
1222: * This value could not be patched into the entity and is simply copied into the _invalid property for debugging purposes
1223: * or to be able to log it away.
1224: *
1225: * @param array $fields The values to set.
1226: * @param bool $overwrite Whether or not to overwrite pre-existing values for $field.
1227: * @return $this
1228: */
1229: public function setInvalid(array $fields, $overwrite = false)
1230: {
1231: foreach ($fields as $field => $value) {
1232: if ($overwrite === true) {
1233: $this->_invalid[$field] = $value;
1234: continue;
1235: }
1236: $this->_invalid += [$field => $value];
1237: }
1238:
1239: return $this;
1240: }
1241:
1242: /**
1243: * Sets a field as invalid and not patchable into the entity.
1244: *
1245: * @param string $field The value to set.
1246: * @param mixed $value The invalid value to be set for $field.
1247: * @return $this
1248: */
1249: public function setInvalidField($field, $value)
1250: {
1251: $this->_invalid[$field] = $value;
1252:
1253: return $this;
1254: }
1255:
1256: /**
1257: * Sets a field as invalid and not patchable into the entity.
1258: *
1259: * This is useful for batch operations when one needs to get the original value for an error message after patching.
1260: * This value could not be patched into the entity and is simply copied into the _invalid property for debugging purposes
1261: * or to be able to log it away.
1262: *
1263: * @deprecated 3.5 Use getInvalid()/getInvalidField()/setInvalid() instead.
1264: * @param string|array|null $field The field to get invalid value for, or the value to set.
1265: * @param mixed|null $value The invalid value to be set for $field.
1266: * @param bool $overwrite Whether or not to overwrite pre-existing values for $field.
1267: * @return $this|mixed
1268: */
1269: public function invalid($field = null, $value = null, $overwrite = false)
1270: {
1271: deprecationWarning(
1272: get_called_class() . '::invalid() is deprecated. ' .
1273: 'Use setInvalid()/getInvalid()/getInvalidField() instead.'
1274: );
1275: if ($field === null) {
1276: return $this->_invalid;
1277: }
1278:
1279: if (is_string($field) && $value === null) {
1280: $value = isset($this->_invalid[$field]) ? $this->_invalid[$field] : null;
1281:
1282: return $value;
1283: }
1284:
1285: if (!is_array($field)) {
1286: $field = [$field => $value];
1287: }
1288:
1289: foreach ($field as $f => $value) {
1290: if ($overwrite) {
1291: $this->_invalid[$f] = $value;
1292: continue;
1293: }
1294: $this->_invalid += [$f => $value];
1295: }
1296:
1297: return $this;
1298: }
1299:
1300: /**
1301: * Stores whether or not a property value can be changed or set in this entity.
1302: * The special property `*` can also be marked as accessible or protected, meaning
1303: * that any other property specified before will take its value. For example
1304: * `$entity->accessible('*', true)` means that any property not specified already
1305: * will be accessible by default.
1306: *
1307: * You can also call this method with an array of properties, in which case they
1308: * will each take the accessibility value specified in the second argument.
1309: *
1310: * ### Example:
1311: *
1312: * ```
1313: * $entity->accessible('id', true); // Mark id as not protected
1314: * $entity->accessible('author_id', false); // Mark author_id as protected
1315: * $entity->accessible(['id', 'user_id'], true); // Mark both properties as accessible
1316: * $entity->accessible('*', false); // Mark all properties as protected
1317: * ```
1318: *
1319: * When called without the second param it will return whether or not the property
1320: * can be set.
1321: *
1322: * ### Example:
1323: *
1324: * ```
1325: * $entity->accessible('id'); // Returns whether it can be set or not
1326: * ```
1327: *
1328: * @deprecated 3.4.0 Use EntityTrait::setAccess() and EntityTrait::isAccessible()
1329: * @param string|array $property single or list of properties to change its accessibility
1330: * @param bool|null $set true marks the property as accessible, false will
1331: * mark it as protected.
1332: * @return $this|bool
1333: */
1334: public function accessible($property, $set = null)
1335: {
1336: deprecationWarning(
1337: get_called_class() . '::accessible() is deprecated. ' .
1338: 'Use setAccess()/isAccessible() instead.'
1339: );
1340: if ($set === null) {
1341: return $this->isAccessible($property);
1342: }
1343:
1344: return $this->setAccess($property, $set);
1345: }
1346:
1347: /**
1348: * Stores whether or not a property value can be changed or set in this entity.
1349: * The special property `*` can also be marked as accessible or protected, meaning
1350: * that any other property specified before will take its value. For example
1351: * `$entity->setAccess('*', true)` means that any property not specified already
1352: * will be accessible by default.
1353: *
1354: * You can also call this method with an array of properties, in which case they
1355: * will each take the accessibility value specified in the second argument.
1356: *
1357: * ### Example:
1358: *
1359: * ```
1360: * $entity->setAccess('id', true); // Mark id as not protected
1361: * $entity->setAccess('author_id', false); // Mark author_id as protected
1362: * $entity->setAccess(['id', 'user_id'], true); // Mark both properties as accessible
1363: * $entity->setAccess('*', false); // Mark all properties as protected
1364: * ```
1365: *
1366: * @param string|array $property single or list of properties to change its accessibility
1367: * @param bool $set true marks the property as accessible, false will
1368: * mark it as protected.
1369: * @return $this
1370: */
1371: public function setAccess($property, $set)
1372: {
1373: if ($property === '*') {
1374: $this->_accessible = array_map(function ($p) use ($set) {
1375: return (bool)$set;
1376: }, $this->_accessible);
1377: $this->_accessible['*'] = (bool)$set;
1378:
1379: return $this;
1380: }
1381:
1382: foreach ((array)$property as $prop) {
1383: $this->_accessible[$prop] = (bool)$set;
1384: }
1385:
1386: return $this;
1387: }
1388:
1389: /**
1390: * Checks if a property is accessible
1391: *
1392: * ### Example:
1393: *
1394: * ```
1395: * $entity->isAccessible('id'); // Returns whether it can be set or not
1396: * ```
1397: *
1398: * @param string $property Property name to check
1399: * @return bool
1400: */
1401: public function isAccessible($property)
1402: {
1403: $value = isset($this->_accessible[$property]) ?
1404: $this->_accessible[$property] :
1405: null;
1406:
1407: return ($value === null && !empty($this->_accessible['*'])) || $value;
1408: }
1409:
1410: /**
1411: * Returns the alias of the repository from which this entity came from.
1412: *
1413: * @return string
1414: */
1415: public function getSource()
1416: {
1417: return $this->_registryAlias;
1418: }
1419:
1420: /**
1421: * Sets the source alias
1422: *
1423: * @param string $alias the alias of the repository
1424: * @return $this
1425: */
1426: public function setSource($alias)
1427: {
1428: $this->_registryAlias = $alias;
1429:
1430: return $this;
1431: }
1432:
1433: /**
1434: * Returns the alias of the repository from which this entity came from.
1435: *
1436: * If called with no arguments, it returns the alias of the repository
1437: * this entity came from if it is known.
1438: *
1439: * @deprecated 3.4.0 Use EntityTrait::getSource() and EntityTrait::setSource()
1440: * @param string|null $alias the alias of the repository
1441: * @return string|$this
1442: */
1443: public function source($alias = null)
1444: {
1445: deprecationWarning(
1446: get_called_class() . '::source() is deprecated. ' .
1447: 'Use setSource()/getSource() instead.'
1448: );
1449: if ($alias === null) {
1450: return $this->getSource();
1451: }
1452:
1453: $this->setSource($alias);
1454:
1455: return $this;
1456: }
1457:
1458: /**
1459: * Returns a string representation of this object in a human readable format.
1460: *
1461: * @return string
1462: */
1463: public function __toString()
1464: {
1465: return json_encode($this, JSON_PRETTY_PRINT);
1466: }
1467:
1468: /**
1469: * Returns an array that can be used to describe the internal state of this
1470: * object.
1471: *
1472: * @return array
1473: */
1474: public function __debugInfo()
1475: {
1476: $properties = $this->_properties;
1477: foreach ($this->_virtual as $field) {
1478: $properties[$field] = $this->$field;
1479: }
1480:
1481: return $properties + [
1482: '[new]' => $this->isNew(),
1483: '[accessible]' => $this->_accessible,
1484: '[dirty]' => $this->_dirty,
1485: '[original]' => $this->_original,
1486: '[virtual]' => $this->_virtual,
1487: '[hasErrors]' => $this->hasErrors(),
1488: '[errors]' => $this->_errors,
1489: '[invalid]' => $this->_invalid,
1490: '[repository]' => $this->_registryAlias
1491: ];
1492: }
1493: }
1494: