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