EntityTrait.php 40 KB

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