EntityTrait.php 32 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159
  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. use Cake\Collection\Collection;
  17. use Cake\Utility\Inflector;
  18. use InvalidArgumentException;
  19. use Traversable;
  20. /**
  21. * An entity represents a single result row from a repository. It exposes the
  22. * methods for retrieving and storing properties associated in this row.
  23. */
  24. trait EntityTrait
  25. {
  26. /**
  27. * Holds all properties and their values for this entity
  28. *
  29. * @var array
  30. */
  31. protected $_properties = [];
  32. /**
  33. * Holds all properties that have been changed and their original values for this entity
  34. *
  35. * @var array
  36. */
  37. protected $_original = [];
  38. /**
  39. * List of property names that should **not** be included in JSON or Array
  40. * representations of this Entity.
  41. *
  42. * @var array
  43. */
  44. protected $_hidden = [];
  45. /**
  46. * List of computed or virtual fields that **should** be included in JSON or array
  47. * representations of this Entity. If a field is present in both _hidden and _virtual
  48. * the field will **not** be in the array/json versions of the entity.
  49. *
  50. * @var array
  51. */
  52. protected $_virtual = [];
  53. /**
  54. * Holds a list of the properties that were modified or added after this object
  55. * was originally created.
  56. *
  57. * @var array
  58. */
  59. protected $_dirty = [];
  60. /**
  61. * Holds a cached list of getters/setters per class
  62. *
  63. * @var array
  64. */
  65. protected static $_accessors = [];
  66. /**
  67. * Indicates whether or not this entity is yet to be persisted.
  68. * Entities default to assuming they are new. You can use Table::persisted()
  69. * to set the new flag on an entity based on records in the database.
  70. *
  71. * @var bool
  72. */
  73. protected $_new = true;
  74. /**
  75. * List of errors per field as stored in this object
  76. *
  77. * @var array
  78. */
  79. protected $_errors = [];
  80. /**
  81. * List of invalid fields and their data for errors upon validation/patching
  82. *
  83. * @var array
  84. */
  85. protected $_invalid = [];
  86. /**
  87. * Map of properties in this entity that can be safely assigned, each
  88. * property name points to a boolean indicating its status. An empty array
  89. * means no properties are accessible
  90. *
  91. * The special property '\*' can also be mapped, meaning that any other property
  92. * not defined in the map will take its value. For example, `'\*' => true`
  93. * means that any property not defined in the map will be accessible by default
  94. *
  95. * @var array
  96. */
  97. protected $_accessible = ['*' => true];
  98. /**
  99. * The alias of the repository this entity came from
  100. *
  101. * @var string
  102. */
  103. protected $_registryAlias;
  104. /**
  105. * Magic getter to access properties that have been set in this entity
  106. *
  107. * @param string $property Name of the property to access
  108. * @return mixed
  109. */
  110. public function &__get($property)
  111. {
  112. return $this->get($property);
  113. }
  114. /**
  115. * Magic setter to add or edit a property in this entity
  116. *
  117. * @param string $property The name of the property to set
  118. * @param mixed $value The value to set to the property
  119. * @return void
  120. */
  121. public function __set($property, $value)
  122. {
  123. $this->set($property, $value);
  124. }
  125. /**
  126. * Returns whether this entity contains a property named $property
  127. * regardless of if it is empty.
  128. *
  129. * @param string $property The property to check.
  130. * @return bool
  131. * @see \Cake\ORM\Entity::has()
  132. */
  133. public function __isset($property)
  134. {
  135. return $this->has($property);
  136. }
  137. /**
  138. * Removes a property from this entity
  139. *
  140. * @param string $property The property to unset
  141. * @return void
  142. */
  143. public function __unset($property)
  144. {
  145. $this->unsetProperty($property);
  146. }
  147. /**
  148. * Sets a single property inside this entity.
  149. *
  150. * ### Example:
  151. *
  152. * ```
  153. * $entity->set('name', 'Andrew');
  154. * ```
  155. *
  156. * It is also possible to mass-assign multiple properties to this entity
  157. * with one call by passing a hashed array as properties in the form of
  158. * property => value pairs
  159. *
  160. * ### Example:
  161. *
  162. * ```
  163. * $entity->set(['name' => 'andrew', 'id' => 1]);
  164. * echo $entity->name // prints andrew
  165. * echo $entity->id // prints 1
  166. * ```
  167. *
  168. * Some times it is handy to bypass setter functions in this entity when assigning
  169. * properties. You can achieve this by disabling the `setter` option using the
  170. * `$options` parameter:
  171. *
  172. * ```
  173. * $entity->set('name', 'Andrew', ['setter' => false]);
  174. * $entity->set(['name' => 'Andrew', 'id' => 1], ['setter' => false]);
  175. * ```
  176. *
  177. * Mass assignment should be treated carefully when accepting user input, by default
  178. * entities will guard all fields when properties are assigned in bulk. You can disable
  179. * the guarding for a single set call with the `guard` option:
  180. *
  181. * ```
  182. * $entity->set(['name' => 'Andrew', 'id' => 1], ['guard' => true]);
  183. * ```
  184. *
  185. * You do not need to use the guard option when assigning properties individually:
  186. *
  187. * ```
  188. * // No need to use the guard option.
  189. * $entity->set('name', 'Andrew');
  190. * ```
  191. *
  192. * @param string|array $property the name of property to set or a list of
  193. * properties with their respective values
  194. * @param mixed $value The value to set to the property or an array if the
  195. * first argument is also an array, in which case will be treated as $options
  196. * @param array $options options to be used for setting the property. Allowed option
  197. * keys are `setter` and `guard`
  198. * @return $this
  199. * @throws \InvalidArgumentException
  200. */
  201. public function set($property, $value = null, array $options = [])
  202. {
  203. if (is_string($property) && $property !== '') {
  204. $guard = false;
  205. $property = [$property => $value];
  206. } else {
  207. $guard = true;
  208. $options = (array)$value;
  209. }
  210. if (!is_array($property)) {
  211. throw new InvalidArgumentException('Cannot set an empty property');
  212. }
  213. $options += ['setter' => true, 'guard' => $guard];
  214. foreach ($property as $p => $value) {
  215. if ($options['guard'] === true && !$this->isAccessible($p)) {
  216. continue;
  217. }
  218. $this->setDirty($p, true);
  219. if (!array_key_exists($p, $this->_original) &&
  220. array_key_exists($p, $this->_properties) &&
  221. $this->_properties[$p] !== $value
  222. ) {
  223. $this->_original[$p] = $this->_properties[$p];
  224. }
  225. if (!$options['setter']) {
  226. $this->_properties[$p] = $value;
  227. continue;
  228. }
  229. $setter = static::_accessor($p, 'set');
  230. if ($setter) {
  231. $value = $this->{$setter}($value);
  232. }
  233. $this->_properties[$p] = $value;
  234. }
  235. return $this;
  236. }
  237. /**
  238. * Returns the value of a property by name
  239. *
  240. * @param string $property the name of the property to retrieve
  241. * @return mixed
  242. * @throws \InvalidArgumentException if an empty property name is passed
  243. */
  244. public function &get($property)
  245. {
  246. if (!strlen((string)$property)) {
  247. throw new InvalidArgumentException('Cannot get an empty property');
  248. }
  249. $value = null;
  250. $method = static::_accessor($property, 'get');
  251. if (isset($this->_properties[$property])) {
  252. $value =& $this->_properties[$property];
  253. }
  254. if ($method) {
  255. $result = $this->{$method}($value);
  256. return $result;
  257. }
  258. return $value;
  259. }
  260. /**
  261. * Returns the value of an original property by name
  262. *
  263. * @param string $property the name of the property for which original value is retrieved.
  264. * @return mixed
  265. * @throws \InvalidArgumentException if an empty property name is passed.
  266. */
  267. public function getOriginal($property)
  268. {
  269. if (!strlen((string)$property)) {
  270. throw new InvalidArgumentException('Cannot get an empty property');
  271. }
  272. if (array_key_exists($property, $this->_original)) {
  273. return $this->_original[$property];
  274. }
  275. return $this->get($property);
  276. }
  277. /**
  278. * Gets all original values of the entity.
  279. *
  280. * @return array
  281. */
  282. public function getOriginalValues()
  283. {
  284. $originals = $this->_original;
  285. $originalKeys = array_keys($originals);
  286. foreach ($this->_properties as $key => $value) {
  287. if (!in_array($key, $originalKeys)) {
  288. $originals[$key] = $value;
  289. }
  290. }
  291. return $originals;
  292. }
  293. /**
  294. * Returns whether this entity contains a property named $property
  295. * that contains a non-null value.
  296. *
  297. * ### Example:
  298. *
  299. * ```
  300. * $entity = new Entity(['id' => 1, 'name' => null]);
  301. * $entity->has('id'); // true
  302. * $entity->has('name'); // false
  303. * $entity->has('last_name'); // false
  304. * ```
  305. *
  306. * You can check multiple properties by passing an array:
  307. *
  308. * ```
  309. * $entity->has(['name', 'last_name']);
  310. * ```
  311. *
  312. * All properties must not be null to get a truthy result.
  313. *
  314. * When checking multiple properties. All properties must not be null
  315. * in order for true to be returned.
  316. *
  317. * @param string|array $property The property or properties to check.
  318. * @return bool
  319. */
  320. public function has($property)
  321. {
  322. foreach ((array)$property as $prop) {
  323. if ($this->get($prop) === null) {
  324. return false;
  325. }
  326. }
  327. return true;
  328. }
  329. /**
  330. * Checks that a property is empty
  331. *
  332. * This is not working like the PHP `empty()` function. The method will
  333. * return true for:
  334. *
  335. * - `''` (empty string)
  336. * - `null`
  337. * - `[]`
  338. *
  339. * and false in all other cases.
  340. *
  341. * @param string $property The property to check.
  342. * @return bool
  343. */
  344. public function isEmpty($property)
  345. {
  346. $value = $this->get($property);
  347. if ($value === null
  348. || (is_array($value) && empty($value)
  349. || (is_string($value) && empty($value)))
  350. ) {
  351. return true;
  352. }
  353. return false;
  354. }
  355. /**
  356. * Checks tha a property has a value.
  357. *
  358. * This method will return true for
  359. *
  360. * - Non-empty strings
  361. * - Non-empty arrays
  362. * - Any object
  363. * - Integer, even `0`
  364. * - Float, even 0.0
  365. *
  366. * and false in all other cases.
  367. *
  368. * @param string $property The property to check.
  369. * @return bool
  370. */
  371. public function hasValue($property)
  372. {
  373. return !$this->isEmpty($property);
  374. }
  375. /**
  376. * Removes a property or list of properties from this entity
  377. *
  378. * ### Examples:
  379. *
  380. * ```
  381. * $entity->unsetProperty('name');
  382. * $entity->unsetProperty(['name', 'last_name']);
  383. * ```
  384. *
  385. * @param string|array $property The property to unset.
  386. * @return $this
  387. */
  388. public function unsetProperty($property)
  389. {
  390. $property = (array)$property;
  391. foreach ($property as $p) {
  392. unset($this->_properties[$p], $this->_dirty[$p]);
  393. }
  394. return $this;
  395. }
  396. /**
  397. * Sets hidden properties.
  398. *
  399. * @param array $properties An array of properties to hide from array exports.
  400. * @param bool $merge Merge the new properties with the existing. By default false.
  401. * @return $this
  402. */
  403. public function setHidden(array $properties, $merge = false)
  404. {
  405. if ($merge === false) {
  406. $this->_hidden = $properties;
  407. return $this;
  408. }
  409. $properties = array_merge($this->_hidden, $properties);
  410. $this->_hidden = array_unique($properties);
  411. return $this;
  412. }
  413. /**
  414. * Gets the hidden properties.
  415. *
  416. * @return array
  417. */
  418. public function getHidden()
  419. {
  420. return $this->_hidden;
  421. }
  422. /**
  423. * Sets the virtual properties on this entity.
  424. *
  425. * @param array $properties An array of properties to treat as virtual.
  426. * @param bool $merge Merge the new properties with the existing. By default false.
  427. * @return $this
  428. */
  429. public function setVirtual(array $properties, $merge = false)
  430. {
  431. if ($merge === false) {
  432. $this->_virtual = $properties;
  433. return $this;
  434. }
  435. $properties = array_merge($this->_virtual, $properties);
  436. $this->_virtual = array_unique($properties);
  437. return $this;
  438. }
  439. /**
  440. * Gets the virtual properties on this entity.
  441. *
  442. * @return array
  443. */
  444. public function getVirtual()
  445. {
  446. return $this->_virtual;
  447. }
  448. /**
  449. * Get the list of visible properties.
  450. *
  451. * The list of visible properties is all standard properties
  452. * plus virtual properties minus hidden properties.
  453. *
  454. * @return array A list of properties that are 'visible' in all
  455. * representations.
  456. */
  457. public function visibleProperties()
  458. {
  459. $properties = array_keys($this->_properties);
  460. $properties = array_merge($properties, $this->_virtual);
  461. return array_diff($properties, $this->_hidden);
  462. }
  463. /**
  464. * Returns an array with all the properties that have been set
  465. * to this entity
  466. *
  467. * This method will recursively transform entities assigned to properties
  468. * into arrays as well.
  469. *
  470. * @return array
  471. */
  472. public function toArray()
  473. {
  474. $result = [];
  475. foreach ($this->visibleProperties() as $property) {
  476. $value = $this->get($property);
  477. if (is_array($value)) {
  478. $result[$property] = [];
  479. foreach ($value as $k => $entity) {
  480. if ($entity instanceof EntityInterface) {
  481. $result[$property][$k] = $entity->toArray();
  482. } else {
  483. $result[$property][$k] = $entity;
  484. }
  485. }
  486. } elseif ($value instanceof EntityInterface) {
  487. $result[$property] = $value->toArray();
  488. } else {
  489. $result[$property] = $value;
  490. }
  491. }
  492. return $result;
  493. }
  494. /**
  495. * Returns the properties that will be serialized as JSON
  496. *
  497. * @return array
  498. */
  499. public function jsonSerialize()
  500. {
  501. return $this->extract($this->visibleProperties());
  502. }
  503. /**
  504. * Implements isset($entity);
  505. *
  506. * @param mixed $offset The offset to check.
  507. * @return bool Success
  508. */
  509. public function offsetExists($offset)
  510. {
  511. return $this->has($offset);
  512. }
  513. /**
  514. * Implements $entity[$offset];
  515. *
  516. * @param mixed $offset The offset to get.
  517. * @return mixed
  518. */
  519. public function &offsetGet($offset)
  520. {
  521. return $this->get($offset);
  522. }
  523. /**
  524. * Implements $entity[$offset] = $value;
  525. *
  526. * @param mixed $offset The offset to set.
  527. * @param mixed $value The value to set.
  528. * @return void
  529. */
  530. public function offsetSet($offset, $value)
  531. {
  532. $this->set($offset, $value);
  533. }
  534. /**
  535. * Implements unset($result[$offset]);
  536. *
  537. * @param mixed $offset The offset to remove.
  538. * @return void
  539. */
  540. public function offsetUnset($offset)
  541. {
  542. $this->unsetProperty($offset);
  543. }
  544. /**
  545. * Fetch accessor method name
  546. * Accessor methods (available or not) are cached in $_accessors
  547. *
  548. * @param string $property the field name to derive getter name from
  549. * @param string $type the accessor type ('get' or 'set')
  550. * @return string method name or empty string (no method available)
  551. */
  552. protected static function _accessor($property, $type)
  553. {
  554. $class = static::class;
  555. if (isset(static::$_accessors[$class][$type][$property])) {
  556. return static::$_accessors[$class][$type][$property];
  557. }
  558. if (!empty(static::$_accessors[$class])) {
  559. return static::$_accessors[$class][$type][$property] = '';
  560. }
  561. if ($class === 'Cake\ORM\Entity') {
  562. return '';
  563. }
  564. foreach (get_class_methods($class) as $method) {
  565. $prefix = substr($method, 1, 3);
  566. if ($method[0] !== '_' || ($prefix !== 'get' && $prefix !== 'set')) {
  567. continue;
  568. }
  569. $field = lcfirst(substr($method, 4));
  570. $snakeField = Inflector::underscore($field);
  571. $titleField = ucfirst($field);
  572. static::$_accessors[$class][$prefix][$snakeField] = $method;
  573. static::$_accessors[$class][$prefix][$field] = $method;
  574. static::$_accessors[$class][$prefix][$titleField] = $method;
  575. }
  576. if (!isset(static::$_accessors[$class][$type][$property])) {
  577. static::$_accessors[$class][$type][$property] = '';
  578. }
  579. return static::$_accessors[$class][$type][$property];
  580. }
  581. /**
  582. * Returns an array with the requested properties
  583. * stored in this entity, indexed by property name
  584. *
  585. * @param array $properties list of properties to be returned
  586. * @param bool $onlyDirty Return the requested property only if it is dirty
  587. * @return array
  588. */
  589. public function extract(array $properties, $onlyDirty = false)
  590. {
  591. $result = [];
  592. foreach ($properties as $property) {
  593. if (!$onlyDirty || $this->isDirty($property)) {
  594. $result[$property] = $this->get($property);
  595. }
  596. }
  597. return $result;
  598. }
  599. /**
  600. * Returns an array with the requested original properties
  601. * stored in this entity, indexed by property name.
  602. *
  603. * Properties that are unchanged from their original value will be included in the
  604. * return of this method.
  605. *
  606. * @param array $properties List of properties to be returned
  607. * @return array
  608. */
  609. public function extractOriginal(array $properties)
  610. {
  611. $result = [];
  612. foreach ($properties as $property) {
  613. $result[$property] = $this->getOriginal($property);
  614. }
  615. return $result;
  616. }
  617. /**
  618. * Returns an array with only the original properties
  619. * stored in this entity, indexed by property name.
  620. *
  621. * This method will only return properties that have been modified since
  622. * the entity was built. Unchanged properties will be omitted.
  623. *
  624. * @param array $properties List of properties to be returned
  625. * @return array
  626. */
  627. public function extractOriginalChanged(array $properties)
  628. {
  629. $result = [];
  630. foreach ($properties as $property) {
  631. $original = $this->getOriginal($property);
  632. if ($original !== $this->get($property)) {
  633. $result[$property] = $original;
  634. }
  635. }
  636. return $result;
  637. }
  638. /**
  639. * Sets the dirty status of a single property.
  640. *
  641. * @param string $property the field to set or check status for
  642. * @param bool $isDirty true means the property was changed, false means
  643. * it was not changed. Defaults to true.
  644. * @return $this
  645. */
  646. public function setDirty($property, $isDirty = true)
  647. {
  648. if ($isDirty === false) {
  649. unset($this->_dirty[$property]);
  650. return $this;
  651. }
  652. $this->_dirty[$property] = true;
  653. unset($this->_errors[$property], $this->_invalid[$property]);
  654. return $this;
  655. }
  656. /**
  657. * Checks if the entity is dirty or if a single property of it is dirty.
  658. *
  659. * @param string|null $property The field to check the status for. Null for the whole entity.
  660. * @return bool Whether the property was changed or not
  661. */
  662. public function isDirty($property = null)
  663. {
  664. if ($property === null) {
  665. return !empty($this->_dirty);
  666. }
  667. return isset($this->_dirty[$property]);
  668. }
  669. /**
  670. * Gets the dirty properties.
  671. *
  672. * @return array
  673. */
  674. public function getDirty()
  675. {
  676. return array_keys($this->_dirty);
  677. }
  678. /**
  679. * Sets the entire entity as clean, which means that it will appear as
  680. * no properties being modified or added at all. This is an useful call
  681. * for an initial object hydration
  682. *
  683. * @return void
  684. */
  685. public function clean()
  686. {
  687. $this->_dirty = [];
  688. $this->_errors = [];
  689. $this->_invalid = [];
  690. $this->_original = [];
  691. }
  692. /**
  693. * Returns whether or not this entity has already been persisted.
  694. * This method can return null in the case there is no prior information on
  695. * the status of this entity.
  696. *
  697. * If called with a boolean it will set the known status of this instance,
  698. * true means that the instance is not yet persisted in the database, false
  699. * that it already is.
  700. *
  701. * @param bool|null $new true if it is known this instance was not yet persisted
  702. * @return bool Whether or not the entity has been persisted.
  703. */
  704. public function isNew($new = null)
  705. {
  706. if ($new === null) {
  707. return $this->_new;
  708. }
  709. $new = (bool)$new;
  710. if ($new) {
  711. foreach ($this->_properties as $k => $p) {
  712. $this->_dirty[$k] = true;
  713. }
  714. }
  715. return $this->_new = $new;
  716. }
  717. /**
  718. * Returns all validation errors.
  719. *
  720. * @return array
  721. */
  722. public function getErrors()
  723. {
  724. $diff = array_diff_key($this->_properties, $this->_errors);
  725. return $this->_errors + (new Collection($diff))
  726. ->filter(function ($value) {
  727. return is_array($value) || $value instanceof EntityInterface;
  728. })
  729. ->map(function ($value) {
  730. return $this->_readError($value);
  731. })
  732. ->filter()
  733. ->toArray();
  734. }
  735. /**
  736. * Returns validation errors of a field
  737. *
  738. * @param string $field Field name to get the errors from
  739. * @return array
  740. */
  741. public function getError($field)
  742. {
  743. $errors = isset($this->_errors[$field]) ? $this->_errors[$field] : [];
  744. if ($errors) {
  745. return $errors;
  746. }
  747. return $this->_nestedErrors($field);
  748. }
  749. /**
  750. * Sets error messages to the entity
  751. *
  752. * ## Example
  753. *
  754. * ```
  755. * // Sets the error messages for multiple fields at once
  756. * $entity->setErrors(['salary' => ['message'], 'name' => ['another message']]);
  757. * ```
  758. *
  759. * @param array $fields The array of errors to set.
  760. * @param bool $overwrite Whether or not to overwrite pre-existing errors for $fields
  761. * @return $this
  762. */
  763. public function setErrors(array $fields, $overwrite = false)
  764. {
  765. if ($overwrite) {
  766. foreach ($fields as $f => $error) {
  767. $this->_errors[$f] = (array)$error;
  768. }
  769. return $this;
  770. }
  771. foreach ($fields as $f => $error) {
  772. $this->_errors += [$f => []];
  773. // String messages are appended to the list,
  774. // while more complex error structures need their
  775. // keys perserved for nested validator.
  776. if (is_string($error)) {
  777. $this->_errors[$f][] = $error;
  778. } else {
  779. foreach ($error as $k => $v) {
  780. $this->_errors[$f][$k] = $v;
  781. }
  782. }
  783. }
  784. return $this;
  785. }
  786. /**
  787. * Sets errors for a single field
  788. *
  789. * ### Example
  790. *
  791. * ```
  792. * // Sets the error messages for a single field
  793. * $entity->setError('salary', ['must be numeric', 'must be a positive number']);
  794. * ```
  795. *
  796. * @param string $field The field to get errors for, or the array of errors to set.
  797. * @param string|array $errors The errors to be set for $field
  798. * @param bool $overwrite Whether or not to overwrite pre-existing errors for $field
  799. * @return $this
  800. */
  801. public function setError($field, $errors, $overwrite = false)
  802. {
  803. if (is_string($errors)) {
  804. $errors = [$errors];
  805. }
  806. return $this->setErrors([$field => $errors], $overwrite);
  807. }
  808. /**
  809. * Auxiliary method for getting errors in nested entities
  810. *
  811. * @param string $field the field in this entity to check for errors
  812. * @return array errors in nested entity if any
  813. */
  814. protected function _nestedErrors($field)
  815. {
  816. $path = explode('.', $field);
  817. // Only one path element, check for nested entity with error.
  818. if (count($path) === 1) {
  819. return $this->_readError($this->get($path[0]));
  820. }
  821. $entity = $this;
  822. $len = count($path);
  823. while ($len) {
  824. $part = array_shift($path);
  825. $len = count($path);
  826. $val = null;
  827. if ($entity instanceof EntityInterface) {
  828. $val = $entity->get($part);
  829. } elseif (is_array($entity)) {
  830. $val = isset($entity[$part]) ? $entity[$part] : false;
  831. }
  832. if (is_array($val) ||
  833. $val instanceof Traversable ||
  834. $val instanceof EntityInterface
  835. ) {
  836. $entity = $val;
  837. } else {
  838. $path[] = $part;
  839. break;
  840. }
  841. }
  842. if (count($path) <= 1) {
  843. return $this->_readError($entity, array_pop($path));
  844. }
  845. return [];
  846. }
  847. /**
  848. * Read the error(s) from one or many objects.
  849. *
  850. * @param array|\Cake\Datasource\EntityTrait $object The object to read errors from.
  851. * @param string|null $path The field name for errors.
  852. * @return array
  853. */
  854. protected function _readError($object, $path = null)
  855. {
  856. if ($path !== null && $object instanceof EntityInterface) {
  857. return $object->getError($path);
  858. }
  859. if ($object instanceof EntityInterface) {
  860. return $object->getErrors();
  861. }
  862. if (is_array($object)) {
  863. $array = array_map(function ($val) {
  864. if ($val instanceof EntityInterface) {
  865. return $val->getErrors();
  866. }
  867. }, $object);
  868. return array_filter($array);
  869. }
  870. return [];
  871. }
  872. /**
  873. * Get a list of invalid fields and their data for errors upon validation/patching
  874. *
  875. * @return array
  876. */
  877. public function getInvalid()
  878. {
  879. return $this->_invalid;
  880. }
  881. /**
  882. * Get a single value of an invalid field. Returns null if not set.
  883. *
  884. * @param string $field The name of the field.
  885. * @return mixed
  886. */
  887. public function getInvalidField($field)
  888. {
  889. $value = isset($this->_invalid[$field]) ? $this->_invalid[$field] : null;
  890. return $value;
  891. }
  892. /**
  893. * Set fields as invalid and not patchable into the entity.
  894. *
  895. * This is useful for batch operations when one needs to get the original value for an error message after patching.
  896. * This value could not be patched into the entity and is simply copied into the _invalid property for debugging purposes
  897. * or to be able to log it away.
  898. *
  899. * @param array $fields The values to set.
  900. * @param bool $overwrite Whether or not to overwrite pre-existing values for $field.
  901. * @return $this
  902. */
  903. public function setInvalid(array $fields, $overwrite = false)
  904. {
  905. foreach ($fields as $field => $value) {
  906. if ($overwrite === true) {
  907. $this->_invalid[$field] = $value;
  908. continue;
  909. }
  910. $this->_invalid += [$field => $value];
  911. }
  912. return $this;
  913. }
  914. /**
  915. * Sets a field as invalid and not patchable into the entity.
  916. *
  917. * @param string $field The value to set.
  918. * @param mixed $value The invalid value to be set for $field.
  919. * @return $this
  920. */
  921. public function setInvalidField($field, $value)
  922. {
  923. $this->_invalid[$field] = $value;
  924. return $this;
  925. }
  926. /**
  927. * Stores whether or not a property value can be changed or set in this entity.
  928. * The special property `*` can also be marked as accessible or protected, meaning
  929. * that any other property specified before will take its value. For example
  930. * `$entity->setAccess('*', true)` means that any property not specified already
  931. * will be accessible by default.
  932. *
  933. * You can also call this method with an array of properties, in which case they
  934. * will each take the accessibility value specified in the second argument.
  935. *
  936. * ### Example:
  937. *
  938. * ```
  939. * $entity->setAccess('id', true); // Mark id as not protected
  940. * $entity->setAccess('author_id', false); // Mark author_id as protected
  941. * $entity->setAccess(['id', 'user_id'], true); // Mark both properties as accessible
  942. * $entity->setAccess('*', false); // Mark all properties as protected
  943. * ```
  944. *
  945. * @param string|array $property single or list of properties to change its accessibility
  946. * @param bool $set true marks the property as accessible, false will
  947. * mark it as protected.
  948. * @return $this
  949. */
  950. public function setAccess($property, $set)
  951. {
  952. if ($property === '*') {
  953. $this->_accessible = array_map(function ($p) use ($set) {
  954. return (bool)$set;
  955. }, $this->_accessible);
  956. $this->_accessible['*'] = (bool)$set;
  957. return $this;
  958. }
  959. foreach ((array)$property as $prop) {
  960. $this->_accessible[$prop] = (bool)$set;
  961. }
  962. return $this;
  963. }
  964. /**
  965. * Checks if a property is accessible
  966. *
  967. * ### Example:
  968. *
  969. * ```
  970. * $entity->isAccessible('id'); // Returns whether it can be set or not
  971. * ```
  972. *
  973. * @param string $property Property name to check
  974. * @return bool
  975. */
  976. public function isAccessible($property)
  977. {
  978. $value = isset($this->_accessible[$property]) ?
  979. $this->_accessible[$property] :
  980. null;
  981. return ($value === null && !empty($this->_accessible['*'])) || $value;
  982. }
  983. /**
  984. * Returns the alias of the repository from which this entity came from.
  985. *
  986. * @return string
  987. */
  988. public function getSource()
  989. {
  990. return $this->_registryAlias;
  991. }
  992. /**
  993. * Sets the source alias
  994. *
  995. * @param string $alias the alias of the repository
  996. * @return $this
  997. */
  998. public function setSource($alias)
  999. {
  1000. $this->_registryAlias = $alias;
  1001. return $this;
  1002. }
  1003. /**
  1004. * Returns a string representation of this object in a human readable format.
  1005. *
  1006. * @return string
  1007. */
  1008. public function __toString()
  1009. {
  1010. return json_encode($this, JSON_PRETTY_PRINT);
  1011. }
  1012. /**
  1013. * Returns an array that can be used to describe the internal state of this
  1014. * object.
  1015. *
  1016. * @return array
  1017. */
  1018. public function __debugInfo()
  1019. {
  1020. $properties = $this->_properties;
  1021. foreach ($this->_virtual as $field) {
  1022. $properties[$field] = $this->$field;
  1023. }
  1024. return $properties + [
  1025. '[new]' => $this->isNew(),
  1026. '[accessible]' => $this->_accessible,
  1027. '[dirty]' => $this->_dirty,
  1028. '[original]' => $this->_original,
  1029. '[virtual]' => $this->_virtual,
  1030. '[errors]' => $this->_errors,
  1031. '[invalid]' => $this->_invalid,
  1032. '[repository]' => $this->_registryAlias
  1033. ];
  1034. }
  1035. }