HasAttributes.php 32 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141
  1. <?php
  2. namespace Illuminate\Database\Eloquent\Concerns;
  3. use LogicException;
  4. use DateTimeInterface;
  5. use Illuminate\Support\Arr;
  6. use Illuminate\Support\Str;
  7. use Illuminate\Support\Carbon;
  8. use Illuminate\Contracts\Support\Arrayable;
  9. use Illuminate\Database\Eloquent\Relations\Relation;
  10. use Illuminate\Support\Collection as BaseCollection;
  11. use Illuminate\Database\Eloquent\JsonEncodingException;
  12. trait HasAttributes
  13. {
  14. /**
  15. * The model's attributes.
  16. *
  17. * @var array
  18. */
  19. protected $attributes = [];
  20. /**
  21. * The model attribute's original state.
  22. *
  23. * @var array
  24. */
  25. protected $original = [];
  26. /**
  27. * The changed model attributes.
  28. *
  29. * @var array
  30. */
  31. protected $changes = [];
  32. /**
  33. * The attributes that should be cast to native types.
  34. *
  35. * @var array
  36. */
  37. protected $casts = [];
  38. /**
  39. * The attributes that should be mutated to dates.
  40. *
  41. * @var array
  42. */
  43. protected $dates = [];
  44. /**
  45. * The storage format of the model's date columns.
  46. *
  47. * @var string
  48. */
  49. protected $dateFormat;
  50. /**
  51. * The accessors to append to the model's array form.
  52. *
  53. * @var array
  54. */
  55. protected $appends = [];
  56. /**
  57. * Indicates whether attributes are snake cased on arrays.
  58. *
  59. * @var bool
  60. */
  61. public static $snakeAttributes = true;
  62. /**
  63. * The cache of the mutated attributes for each class.
  64. *
  65. * @var array
  66. */
  67. protected static $mutatorCache = [];
  68. /**
  69. * Convert the model's attributes to an array.
  70. *
  71. * @return array
  72. */
  73. public function attributesToArray()
  74. {
  75. // If an attribute is a date, we will cast it to a string after converting it
  76. // to a DateTime / Carbon instance. This is so we will get some consistent
  77. // formatting while accessing attributes vs. arraying / JSONing a model.
  78. $attributes = $this->addDateAttributesToArray(
  79. $attributes = $this->getArrayableAttributes()
  80. );
  81. $attributes = $this->addMutatedAttributesToArray(
  82. $attributes, $mutatedAttributes = $this->getMutatedAttributes()
  83. );
  84. // Next we will handle any casts that have been setup for this model and cast
  85. // the values to their appropriate type. If the attribute has a mutator we
  86. // will not perform the cast on those attributes to avoid any confusion.
  87. $attributes = $this->addCastAttributesToArray(
  88. $attributes, $mutatedAttributes
  89. );
  90. // Here we will grab all of the appended, calculated attributes to this model
  91. // as these attributes are not really in the attributes array, but are run
  92. // when we need to array or JSON the model for convenience to the coder.
  93. foreach ($this->getArrayableAppends() as $key) {
  94. $attributes[$key] = $this->mutateAttributeForArray($key, null);
  95. }
  96. return $attributes;
  97. }
  98. /**
  99. * Add the date attributes to the attributes array.
  100. *
  101. * @param array $attributes
  102. * @return array
  103. */
  104. protected function addDateAttributesToArray(array $attributes)
  105. {
  106. foreach ($this->getDates() as $key) {
  107. if (! isset($attributes[$key])) {
  108. continue;
  109. }
  110. $attributes[$key] = $this->serializeDate(
  111. $this->asDateTime($attributes[$key])
  112. );
  113. }
  114. return $attributes;
  115. }
  116. /**
  117. * Add the mutated attributes to the attributes array.
  118. *
  119. * @param array $attributes
  120. * @param array $mutatedAttributes
  121. * @return array
  122. */
  123. protected function addMutatedAttributesToArray(array $attributes, array $mutatedAttributes)
  124. {
  125. foreach ($mutatedAttributes as $key) {
  126. // We want to spin through all the mutated attributes for this model and call
  127. // the mutator for the attribute. We cache off every mutated attributes so
  128. // we don't have to constantly check on attributes that actually change.
  129. if (! array_key_exists($key, $attributes)) {
  130. continue;
  131. }
  132. // Next, we will call the mutator for this attribute so that we can get these
  133. // mutated attribute's actual values. After we finish mutating each of the
  134. // attributes we will return this final array of the mutated attributes.
  135. $attributes[$key] = $this->mutateAttributeForArray(
  136. $key, $attributes[$key]
  137. );
  138. }
  139. return $attributes;
  140. }
  141. /**
  142. * Add the casted attributes to the attributes array.
  143. *
  144. * @param array $attributes
  145. * @param array $mutatedAttributes
  146. * @return array
  147. */
  148. protected function addCastAttributesToArray(array $attributes, array $mutatedAttributes)
  149. {
  150. foreach ($this->getCasts() as $key => $value) {
  151. if (! array_key_exists($key, $attributes) || in_array($key, $mutatedAttributes)) {
  152. continue;
  153. }
  154. // Here we will cast the attribute. Then, if the cast is a date or datetime cast
  155. // then we will serialize the date for the array. This will convert the dates
  156. // to strings based on the date format specified for these Eloquent models.
  157. $attributes[$key] = $this->castAttribute(
  158. $key, $attributes[$key]
  159. );
  160. // If the attribute cast was a date or a datetime, we will serialize the date as
  161. // a string. This allows the developers to customize how dates are serialized
  162. // into an array without affecting how they are persisted into the storage.
  163. if ($attributes[$key] &&
  164. ($value === 'date' || $value === 'datetime')) {
  165. $attributes[$key] = $this->serializeDate($attributes[$key]);
  166. }
  167. }
  168. return $attributes;
  169. }
  170. /**
  171. * Get an attribute array of all arrayable attributes.
  172. *
  173. * @return array
  174. */
  175. protected function getArrayableAttributes()
  176. {
  177. return $this->getArrayableItems($this->attributes);
  178. }
  179. /**
  180. * Get all of the appendable values that are arrayable.
  181. *
  182. * @return array
  183. */
  184. protected function getArrayableAppends()
  185. {
  186. if (! count($this->appends)) {
  187. return [];
  188. }
  189. return $this->getArrayableItems(
  190. array_combine($this->appends, $this->appends)
  191. );
  192. }
  193. /**
  194. * Get the model's relationships in array form.
  195. *
  196. * @return array
  197. */
  198. public function relationsToArray()
  199. {
  200. $attributes = [];
  201. foreach ($this->getArrayableRelations() as $key => $value) {
  202. // If the values implements the Arrayable interface we can just call this
  203. // toArray method on the instances which will convert both models and
  204. // collections to their proper array form and we'll set the values.
  205. if ($value instanceof Arrayable) {
  206. $relation = $value->toArray();
  207. }
  208. // If the value is null, we'll still go ahead and set it in this list of
  209. // attributes since null is used to represent empty relationships if
  210. // if it a has one or belongs to type relationships on the models.
  211. elseif (is_null($value)) {
  212. $relation = $value;
  213. }
  214. // If the relationships snake-casing is enabled, we will snake case this
  215. // key so that the relation attribute is snake cased in this returned
  216. // array to the developers, making this consistent with attributes.
  217. if (static::$snakeAttributes) {
  218. $key = Str::snake($key);
  219. }
  220. // If the relation value has been set, we will set it on this attributes
  221. // list for returning. If it was not arrayable or null, we'll not set
  222. // the value on the array because it is some type of invalid value.
  223. if (isset($relation) || is_null($value)) {
  224. $attributes[$key] = $relation;
  225. }
  226. unset($relation);
  227. }
  228. return $attributes;
  229. }
  230. /**
  231. * Get an attribute array of all arrayable relations.
  232. *
  233. * @return array
  234. */
  235. protected function getArrayableRelations()
  236. {
  237. return $this->getArrayableItems($this->relations);
  238. }
  239. /**
  240. * Get an attribute array of all arrayable values.
  241. *
  242. * @param array $values
  243. * @return array
  244. */
  245. protected function getArrayableItems(array $values)
  246. {
  247. if (count($this->getVisible()) > 0) {
  248. $values = array_intersect_key($values, array_flip($this->getVisible()));
  249. }
  250. if (count($this->getHidden()) > 0) {
  251. $values = array_diff_key($values, array_flip($this->getHidden()));
  252. }
  253. return $values;
  254. }
  255. /**
  256. * Get an attribute from the model.
  257. *
  258. * @param string $key
  259. * @return mixed
  260. */
  261. public function getAttribute($key)
  262. {
  263. if (! $key) {
  264. return;
  265. }
  266. // If the attribute exists in the attribute array or has a "get" mutator we will
  267. // get the attribute's value. Otherwise, we will proceed as if the developers
  268. // are asking for a relationship's value. This covers both types of values.
  269. if (array_key_exists($key, $this->attributes) ||
  270. $this->hasGetMutator($key)) {
  271. return $this->getAttributeValue($key);
  272. }
  273. // Here we will determine if the model base class itself contains this given key
  274. // since we don't want to treat any of those methods as relationships because
  275. // they are all intended as helper methods and none of these are relations.
  276. if (method_exists(self::class, $key)) {
  277. return;
  278. }
  279. return $this->getRelationValue($key);
  280. }
  281. /**
  282. * Get a plain attribute (not a relationship).
  283. *
  284. * @param string $key
  285. * @return mixed
  286. */
  287. public function getAttributeValue($key)
  288. {
  289. $value = $this->getAttributeFromArray($key);
  290. // If the attribute has a get mutator, we will call that then return what
  291. // it returns as the value, which is useful for transforming values on
  292. // retrieval from the model to a form that is more useful for usage.
  293. if ($this->hasGetMutator($key)) {
  294. return $this->mutateAttribute($key, $value);
  295. }
  296. // If the attribute exists within the cast array, we will convert it to
  297. // an appropriate native PHP type dependant upon the associated value
  298. // given with the key in the pair. Dayle made this comment line up.
  299. if ($this->hasCast($key)) {
  300. return $this->castAttribute($key, $value);
  301. }
  302. // If the attribute is listed as a date, we will convert it to a DateTime
  303. // instance on retrieval, which makes it quite convenient to work with
  304. // date fields without having to create a mutator for each property.
  305. if (in_array($key, $this->getDates()) &&
  306. ! is_null($value)) {
  307. return $this->asDateTime($value);
  308. }
  309. return $value;
  310. }
  311. /**
  312. * Get an attribute from the $attributes array.
  313. *
  314. * @param string $key
  315. * @return mixed
  316. */
  317. protected function getAttributeFromArray($key)
  318. {
  319. if (isset($this->attributes[$key])) {
  320. return $this->attributes[$key];
  321. }
  322. }
  323. /**
  324. * Get a relationship.
  325. *
  326. * @param string $key
  327. * @return mixed
  328. */
  329. public function getRelationValue($key)
  330. {
  331. // If the key already exists in the relationships array, it just means the
  332. // relationship has already been loaded, so we'll just return it out of
  333. // here because there is no need to query within the relations twice.
  334. if ($this->relationLoaded($key)) {
  335. return $this->relations[$key];
  336. }
  337. // If the "attribute" exists as a method on the model, we will just assume
  338. // it is a relationship and will load and return results from the query
  339. // and hydrate the relationship's value on the "relationships" array.
  340. if (method_exists($this, $key)) {
  341. return $this->getRelationshipFromMethod($key);
  342. }
  343. }
  344. /**
  345. * Get a relationship value from a method.
  346. *
  347. * @param string $method
  348. * @return mixed
  349. *
  350. * @throws \LogicException
  351. */
  352. protected function getRelationshipFromMethod($method)
  353. {
  354. $relation = $this->$method();
  355. if (! $relation instanceof Relation) {
  356. throw new LogicException(get_class($this).'::'.$method.' must return a relationship instance.');
  357. }
  358. return tap($relation->getResults(), function ($results) use ($method) {
  359. $this->setRelation($method, $results);
  360. });
  361. }
  362. /**
  363. * Determine if a get mutator exists for an attribute.
  364. *
  365. * @param string $key
  366. * @return bool
  367. */
  368. public function hasGetMutator($key)
  369. {
  370. return method_exists($this, 'get'.Str::studly($key).'Attribute');
  371. }
  372. /**
  373. * Get the value of an attribute using its mutator.
  374. *
  375. * @param string $key
  376. * @param mixed $value
  377. * @return mixed
  378. */
  379. protected function mutateAttribute($key, $value)
  380. {
  381. return $this->{'get'.Str::studly($key).'Attribute'}($value);
  382. }
  383. /**
  384. * Get the value of an attribute using its mutator for array conversion.
  385. *
  386. * @param string $key
  387. * @param mixed $value
  388. * @return mixed
  389. */
  390. protected function mutateAttributeForArray($key, $value)
  391. {
  392. $value = $this->mutateAttribute($key, $value);
  393. return $value instanceof Arrayable ? $value->toArray() : $value;
  394. }
  395. /**
  396. * Cast an attribute to a native PHP type.
  397. *
  398. * @param string $key
  399. * @param mixed $value
  400. * @return mixed
  401. */
  402. protected function castAttribute($key, $value)
  403. {
  404. if (is_null($value)) {
  405. return $value;
  406. }
  407. switch ($this->getCastType($key)) {
  408. case 'int':
  409. case 'integer':
  410. return (int) $value;
  411. case 'real':
  412. case 'float':
  413. case 'double':
  414. return (float) $value;
  415. case 'string':
  416. return (string) $value;
  417. case 'bool':
  418. case 'boolean':
  419. return (bool) $value;
  420. case 'object':
  421. return $this->fromJson($value, true);
  422. case 'array':
  423. case 'json':
  424. return $this->fromJson($value);
  425. case 'collection':
  426. return new BaseCollection($this->fromJson($value));
  427. case 'date':
  428. return $this->asDate($value);
  429. case 'datetime':
  430. return $this->asDateTime($value);
  431. case 'timestamp':
  432. return $this->asTimestamp($value);
  433. default:
  434. return $value;
  435. }
  436. }
  437. /**
  438. * Get the type of cast for a model attribute.
  439. *
  440. * @param string $key
  441. * @return string
  442. */
  443. protected function getCastType($key)
  444. {
  445. return trim(strtolower($this->getCasts()[$key]));
  446. }
  447. /**
  448. * Set a given attribute on the model.
  449. *
  450. * @param string $key
  451. * @param mixed $value
  452. * @return $this
  453. */
  454. public function setAttribute($key, $value)
  455. {
  456. // First we will check for the presence of a mutator for the set operation
  457. // which simply lets the developers tweak the attribute as it is set on
  458. // the model, such as "json_encoding" an listing of data for storage.
  459. if ($this->hasSetMutator($key)) {
  460. $method = 'set'.Str::studly($key).'Attribute';
  461. return $this->{$method}($value);
  462. }
  463. // If an attribute is listed as a "date", we'll convert it from a DateTime
  464. // instance into a form proper for storage on the database tables using
  465. // the connection grammar's date format. We will auto set the values.
  466. elseif ($value && $this->isDateAttribute($key)) {
  467. $value = $this->fromDateTime($value);
  468. }
  469. if ($this->isJsonCastable($key) && ! is_null($value)) {
  470. $value = $this->castAttributeAsJson($key, $value);
  471. }
  472. // If this attribute contains a JSON ->, we'll set the proper value in the
  473. // attribute's underlying array. This takes care of properly nesting an
  474. // attribute in the array's value in the case of deeply nested items.
  475. if (Str::contains($key, '->')) {
  476. return $this->fillJsonAttribute($key, $value);
  477. }
  478. $this->attributes[$key] = $value;
  479. return $this;
  480. }
  481. /**
  482. * Determine if a set mutator exists for an attribute.
  483. *
  484. * @param string $key
  485. * @return bool
  486. */
  487. public function hasSetMutator($key)
  488. {
  489. return method_exists($this, 'set'.Str::studly($key).'Attribute');
  490. }
  491. /**
  492. * Determine if the given attribute is a date or date castable.
  493. *
  494. * @param string $key
  495. * @return bool
  496. */
  497. protected function isDateAttribute($key)
  498. {
  499. return in_array($key, $this->getDates()) ||
  500. $this->isDateCastable($key);
  501. }
  502. /**
  503. * Set a given JSON attribute on the model.
  504. *
  505. * @param string $key
  506. * @param mixed $value
  507. * @return $this
  508. */
  509. public function fillJsonAttribute($key, $value)
  510. {
  511. list($key, $path) = explode('->', $key, 2);
  512. $this->attributes[$key] = $this->asJson($this->getArrayAttributeWithValue(
  513. $path, $key, $value
  514. ));
  515. return $this;
  516. }
  517. /**
  518. * Get an array attribute with the given key and value set.
  519. *
  520. * @param string $path
  521. * @param string $key
  522. * @param mixed $value
  523. * @return $this
  524. */
  525. protected function getArrayAttributeWithValue($path, $key, $value)
  526. {
  527. return tap($this->getArrayAttributeByKey($key), function (&$array) use ($path, $value) {
  528. Arr::set($array, str_replace('->', '.', $path), $value);
  529. });
  530. }
  531. /**
  532. * Get an array attribute or return an empty array if it is not set.
  533. *
  534. * @param string $key
  535. * @return array
  536. */
  537. protected function getArrayAttributeByKey($key)
  538. {
  539. return isset($this->attributes[$key]) ?
  540. $this->fromJson($this->attributes[$key]) : [];
  541. }
  542. /**
  543. * Cast the given attribute to JSON.
  544. *
  545. * @param string $key
  546. * @param mixed $value
  547. * @return string
  548. */
  549. protected function castAttributeAsJson($key, $value)
  550. {
  551. $value = $this->asJson($value);
  552. if ($value === false) {
  553. throw JsonEncodingException::forAttribute(
  554. $this, $key, json_last_error_msg()
  555. );
  556. }
  557. return $value;
  558. }
  559. /**
  560. * Encode the given value as JSON.
  561. *
  562. * @param mixed $value
  563. * @return string
  564. */
  565. protected function asJson($value)
  566. {
  567. return json_encode($value);
  568. }
  569. /**
  570. * Decode the given JSON back into an array or object.
  571. *
  572. * @param string $value
  573. * @param bool $asObject
  574. * @return mixed
  575. */
  576. public function fromJson($value, $asObject = false)
  577. {
  578. return json_decode($value, ! $asObject);
  579. }
  580. /**
  581. * Return a timestamp as DateTime object with time set to 00:00:00.
  582. *
  583. * @param mixed $value
  584. * @return \Illuminate\Support\Carbon
  585. */
  586. protected function asDate($value)
  587. {
  588. return $this->asDateTime($value)->startOfDay();
  589. }
  590. /**
  591. * Return a timestamp as DateTime object.
  592. *
  593. * @param mixed $value
  594. * @return \Illuminate\Support\Carbon
  595. */
  596. protected function asDateTime($value)
  597. {
  598. // If this value is already a Carbon instance, we shall just return it as is.
  599. // This prevents us having to re-instantiate a Carbon instance when we know
  600. // it already is one, which wouldn't be fulfilled by the DateTime check.
  601. if ($value instanceof Carbon) {
  602. return $value;
  603. }
  604. // If the value is already a DateTime instance, we will just skip the rest of
  605. // these checks since they will be a waste of time, and hinder performance
  606. // when checking the field. We will just return the DateTime right away.
  607. if ($value instanceof DateTimeInterface) {
  608. return new Carbon(
  609. $value->format('Y-m-d H:i:s.u'), $value->getTimezone()
  610. );
  611. }
  612. // If this value is an integer, we will assume it is a UNIX timestamp's value
  613. // and format a Carbon object from this timestamp. This allows flexibility
  614. // when defining your date fields as they might be UNIX timestamps here.
  615. if (is_numeric($value)) {
  616. return Carbon::createFromTimestamp($value);
  617. }
  618. // If the value is in simply year, month, day format, we will instantiate the
  619. // Carbon instances from that format. Again, this provides for simple date
  620. // fields on the database, while still supporting Carbonized conversion.
  621. if ($this->isStandardDateFormat($value)) {
  622. return Carbon::createFromFormat('Y-m-d', $value)->startOfDay();
  623. }
  624. // Finally, we will just assume this date is in the format used by default on
  625. // the database connection and use that format to create the Carbon object
  626. // that is returned back out to the developers after we convert it here.
  627. return Carbon::createFromFormat(
  628. str_replace('.v', '.u', $this->getDateFormat()), $value
  629. );
  630. }
  631. /**
  632. * Determine if the given value is a standard date format.
  633. *
  634. * @param string $value
  635. * @return bool
  636. */
  637. protected function isStandardDateFormat($value)
  638. {
  639. return preg_match('/^(\d{4})-(\d{1,2})-(\d{1,2})$/', $value);
  640. }
  641. /**
  642. * Convert a DateTime to a storable string.
  643. *
  644. * @param \DateTime|int $value
  645. * @return string
  646. */
  647. public function fromDateTime($value)
  648. {
  649. return empty($value) ? $value : $this->asDateTime($value)->format(
  650. $this->getDateFormat()
  651. );
  652. }
  653. /**
  654. * Return a timestamp as unix timestamp.
  655. *
  656. * @param mixed $value
  657. * @return int
  658. */
  659. protected function asTimestamp($value)
  660. {
  661. return $this->asDateTime($value)->getTimestamp();
  662. }
  663. /**
  664. * Prepare a date for array / JSON serialization.
  665. *
  666. * @param \DateTimeInterface $date
  667. * @return string
  668. */
  669. protected function serializeDate(DateTimeInterface $date)
  670. {
  671. return $date->format($this->getDateFormat());
  672. }
  673. /**
  674. * Get the attributes that should be converted to dates.
  675. *
  676. * @return array
  677. */
  678. public function getDates()
  679. {
  680. $defaults = [static::CREATED_AT, static::UPDATED_AT];
  681. return $this->usesTimestamps()
  682. ? array_unique(array_merge($this->dates, $defaults))
  683. : $this->dates;
  684. }
  685. /**
  686. * Get the format for database stored dates.
  687. *
  688. * @return string
  689. */
  690. protected function getDateFormat()
  691. {
  692. return $this->dateFormat ?: $this->getConnection()->getQueryGrammar()->getDateFormat();
  693. }
  694. /**
  695. * Set the date format used by the model.
  696. *
  697. * @param string $format
  698. * @return $this
  699. */
  700. public function setDateFormat($format)
  701. {
  702. $this->dateFormat = $format;
  703. return $this;
  704. }
  705. /**
  706. * Determine whether an attribute should be cast to a native type.
  707. *
  708. * @param string $key
  709. * @param array|string|null $types
  710. * @return bool
  711. */
  712. public function hasCast($key, $types = null)
  713. {
  714. if (array_key_exists($key, $this->getCasts())) {
  715. return $types ? in_array($this->getCastType($key), (array) $types, true) : true;
  716. }
  717. return false;
  718. }
  719. /**
  720. * Get the casts array.
  721. *
  722. * @return array
  723. */
  724. public function getCasts()
  725. {
  726. if ($this->getIncrementing()) {
  727. return array_merge([$this->getKeyName() => $this->getKeyType()], $this->casts);
  728. }
  729. return $this->casts;
  730. }
  731. /**
  732. * Determine whether a value is Date / DateTime castable for inbound manipulation.
  733. *
  734. * @param string $key
  735. * @return bool
  736. */
  737. protected function isDateCastable($key)
  738. {
  739. return $this->hasCast($key, ['date', 'datetime']);
  740. }
  741. /**
  742. * Determine whether a value is JSON castable for inbound manipulation.
  743. *
  744. * @param string $key
  745. * @return bool
  746. */
  747. protected function isJsonCastable($key)
  748. {
  749. return $this->hasCast($key, ['array', 'json', 'object', 'collection']);
  750. }
  751. /**
  752. * Get all of the current attributes on the model.
  753. *
  754. * @return array
  755. */
  756. public function getAttributes()
  757. {
  758. return $this->attributes;
  759. }
  760. /**
  761. * Set the array of model attributes. No checking is done.
  762. *
  763. * @param array $attributes
  764. * @param bool $sync
  765. * @return $this
  766. */
  767. public function setRawAttributes(array $attributes, $sync = false)
  768. {
  769. $this->attributes = $attributes;
  770. if ($sync) {
  771. $this->syncOriginal();
  772. }
  773. return $this;
  774. }
  775. /**
  776. * Get the model's original attribute values.
  777. *
  778. * @param string|null $key
  779. * @param mixed $default
  780. * @return mixed|array
  781. */
  782. public function getOriginal($key = null, $default = null)
  783. {
  784. return Arr::get($this->original, $key, $default);
  785. }
  786. /**
  787. * Get a subset of the model's attributes.
  788. *
  789. * @param array|mixed $attributes
  790. * @return array
  791. */
  792. public function only($attributes)
  793. {
  794. $results = [];
  795. foreach (is_array($attributes) ? $attributes : func_get_args() as $attribute) {
  796. $results[$attribute] = $this->getAttribute($attribute);
  797. }
  798. return $results;
  799. }
  800. /**
  801. * Sync the original attributes with the current.
  802. *
  803. * @return $this
  804. */
  805. public function syncOriginal()
  806. {
  807. $this->original = $this->attributes;
  808. return $this;
  809. }
  810. /**
  811. * Sync a single original attribute with its current value.
  812. *
  813. * @param string $attribute
  814. * @return $this
  815. */
  816. public function syncOriginalAttribute($attribute)
  817. {
  818. $this->original[$attribute] = $this->attributes[$attribute];
  819. return $this;
  820. }
  821. /**
  822. * Sync the changed attributes.
  823. *
  824. * @return $this
  825. */
  826. public function syncChanges()
  827. {
  828. $this->changes = $this->getDirty();
  829. return $this;
  830. }
  831. /**
  832. * Determine if the model or given attribute(s) have been modified.
  833. *
  834. * @param array|string|null $attributes
  835. * @return bool
  836. */
  837. public function isDirty($attributes = null)
  838. {
  839. return $this->hasChanges(
  840. $this->getDirty(), is_array($attributes) ? $attributes : func_get_args()
  841. );
  842. }
  843. /**
  844. * Determine if the model or given attribute(s) have remained the same.
  845. *
  846. * @param array|string|null $attributes
  847. * @return bool
  848. */
  849. public function isClean($attributes = null)
  850. {
  851. return ! $this->isDirty(...func_get_args());
  852. }
  853. /**
  854. * Determine if the model or given attribute(s) have been modified.
  855. *
  856. * @param array|string|null $attributes
  857. * @return bool
  858. */
  859. public function wasChanged($attributes = null)
  860. {
  861. return $this->hasChanges(
  862. $this->getChanges(), is_array($attributes) ? $attributes : func_get_args()
  863. );
  864. }
  865. /**
  866. * Determine if the given attributes were changed.
  867. *
  868. * @param array $changes
  869. * @param array|string|null $attributes
  870. * @return bool
  871. */
  872. protected function hasChanges($changes, $attributes = null)
  873. {
  874. // If no specific attributes were provided, we will just see if the dirty array
  875. // already contains any attributes. If it does we will just return that this
  876. // count is greater than zero. Else, we need to check specific attributes.
  877. if (empty($attributes)) {
  878. return count($changes) > 0;
  879. }
  880. // Here we will spin through every attribute and see if this is in the array of
  881. // dirty attributes. If it is, we will return true and if we make it through
  882. // all of the attributes for the entire array we will return false at end.
  883. foreach (Arr::wrap($attributes) as $attribute) {
  884. if (array_key_exists($attribute, $changes)) {
  885. return true;
  886. }
  887. }
  888. return false;
  889. }
  890. /**
  891. * Get the attributes that have been changed since last sync.
  892. *
  893. * @return array
  894. */
  895. public function getDirty()
  896. {
  897. $dirty = [];
  898. foreach ($this->getAttributes() as $key => $value) {
  899. if (! $this->originalIsEquivalent($key, $value)) {
  900. $dirty[$key] = $value;
  901. }
  902. }
  903. return $dirty;
  904. }
  905. /**
  906. * Get the attributes that were changed.
  907. *
  908. * @return array
  909. */
  910. public function getChanges()
  911. {
  912. return $this->changes;
  913. }
  914. /**
  915. * Determine if the new and old values for a given key are equivalent.
  916. *
  917. * @param string $key
  918. * @param mixed $current
  919. * @return bool
  920. */
  921. protected function originalIsEquivalent($key, $current)
  922. {
  923. if (! array_key_exists($key, $this->original)) {
  924. return false;
  925. }
  926. $original = $this->getOriginal($key);
  927. if ($current === $original) {
  928. return true;
  929. } elseif (is_null($current)) {
  930. return false;
  931. } elseif ($this->isDateAttribute($key)) {
  932. return $this->fromDateTime($current) ===
  933. $this->fromDateTime($original);
  934. } elseif ($this->hasCast($key)) {
  935. return $this->castAttribute($key, $current) ===
  936. $this->castAttribute($key, $original);
  937. }
  938. return is_numeric($current) && is_numeric($original)
  939. && strcmp((string) $current, (string) $original) === 0;
  940. }
  941. /**
  942. * Append attributes to query when building a query.
  943. *
  944. * @param array|string $attributes
  945. * @return $this
  946. */
  947. public function append($attributes)
  948. {
  949. $this->appends = array_unique(
  950. array_merge($this->appends, is_string($attributes) ? func_get_args() : $attributes)
  951. );
  952. return $this;
  953. }
  954. /**
  955. * Set the accessors to append to model arrays.
  956. *
  957. * @param array $appends
  958. * @return $this
  959. */
  960. public function setAppends(array $appends)
  961. {
  962. $this->appends = $appends;
  963. return $this;
  964. }
  965. /**
  966. * Get the mutated attributes for a given instance.
  967. *
  968. * @return array
  969. */
  970. public function getMutatedAttributes()
  971. {
  972. $class = static::class;
  973. if (! isset(static::$mutatorCache[$class])) {
  974. static::cacheMutatedAttributes($class);
  975. }
  976. return static::$mutatorCache[$class];
  977. }
  978. /**
  979. * Extract and cache all the mutated attributes of a class.
  980. *
  981. * @param string $class
  982. * @return void
  983. */
  984. public static function cacheMutatedAttributes($class)
  985. {
  986. static::$mutatorCache[$class] = collect(static::getMutatorMethods($class))->map(function ($match) {
  987. return lcfirst(static::$snakeAttributes ? Str::snake($match) : $match);
  988. })->all();
  989. }
  990. /**
  991. * Get all of the attribute mutator methods.
  992. *
  993. * @param mixed $class
  994. * @return array
  995. */
  996. protected static function getMutatorMethods($class)
  997. {
  998. preg_match_all('/(?<=^|;)get([^;]+?)Attribute(;|$)/', implode(';', get_class_methods($class)), $matches);
  999. return $matches[1];
  1000. }
  1001. }