EntityTrait.php 38 KB

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