HasAttributes.php 33KB

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