1<?php 2 3namespace Illuminate\Database\Eloquent\Concerns; 4 5use Carbon\CarbonInterface; 6use DateTimeInterface; 7use Illuminate\Contracts\Database\Eloquent\Castable; 8use Illuminate\Contracts\Database\Eloquent\CastsInboundAttributes; 9use Illuminate\Contracts\Support\Arrayable; 10use Illuminate\Database\Eloquent\InvalidCastException; 11use Illuminate\Database\Eloquent\JsonEncodingException; 12use Illuminate\Database\Eloquent\Relations\Relation; 13use Illuminate\Support\Arr; 14use Illuminate\Support\Carbon; 15use Illuminate\Support\Collection as BaseCollection; 16use Illuminate\Support\Facades\Crypt; 17use Illuminate\Support\Facades\Date; 18use Illuminate\Support\Str; 19use InvalidArgumentException; 20use LogicException; 21 22trait HasAttributes 23{ 24 /** 25 * The model's attributes. 26 * 27 * @var array 28 */ 29 protected $attributes = []; 30 31 /** 32 * The model attribute's original state. 33 * 34 * @var array 35 */ 36 protected $original = []; 37 38 /** 39 * The changed model attributes. 40 * 41 * @var array 42 */ 43 protected $changes = []; 44 45 /** 46 * The attributes that should be cast. 47 * 48 * @var array 49 */ 50 protected $casts = []; 51 52 /** 53 * The attributes that have been cast using custom classes. 54 * 55 * @var array 56 */ 57 protected $classCastCache = []; 58 59 /** 60 * The built-in, primitive cast types supported by Eloquent. 61 * 62 * @var string[] 63 */ 64 protected static $primitiveCastTypes = [ 65 'array', 66 'bool', 67 'boolean', 68 'collection', 69 'custom_datetime', 70 'date', 71 'datetime', 72 'decimal', 73 'double', 74 'encrypted', 75 'encrypted:array', 76 'encrypted:collection', 77 'encrypted:json', 78 'encrypted:object', 79 'float', 80 'int', 81 'integer', 82 'json', 83 'object', 84 'real', 85 'string', 86 'timestamp', 87 ]; 88 89 /** 90 * The attributes that should be mutated to dates. 91 * 92 * @deprecated Use the "casts" property 93 * 94 * @var array 95 */ 96 protected $dates = []; 97 98 /** 99 * The storage format of the model's date columns. 100 * 101 * @var string 102 */ 103 protected $dateFormat; 104 105 /** 106 * The accessors to append to the model's array form. 107 * 108 * @var array 109 */ 110 protected $appends = []; 111 112 /** 113 * Indicates whether attributes are snake cased on arrays. 114 * 115 * @var bool 116 */ 117 public static $snakeAttributes = true; 118 119 /** 120 * The cache of the mutated attributes for each class. 121 * 122 * @var array 123 */ 124 protected static $mutatorCache = []; 125 126 /** 127 * The encrypter instance that is used to encrypt attributes. 128 * 129 * @var \Illuminate\Contracts\Encryption\Encrypter 130 */ 131 public static $encrypter; 132 133 /** 134 * Convert the model's attributes to an array. 135 * 136 * @return array 137 */ 138 public function attributesToArray() 139 { 140 // If an attribute is a date, we will cast it to a string after converting it 141 // to a DateTime / Carbon instance. This is so we will get some consistent 142 // formatting while accessing attributes vs. arraying / JSONing a model. 143 $attributes = $this->addDateAttributesToArray( 144 $attributes = $this->getArrayableAttributes() 145 ); 146 147 $attributes = $this->addMutatedAttributesToArray( 148 $attributes, $mutatedAttributes = $this->getMutatedAttributes() 149 ); 150 151 // Next we will handle any casts that have been setup for this model and cast 152 // the values to their appropriate type. If the attribute has a mutator we 153 // will not perform the cast on those attributes to avoid any confusion. 154 $attributes = $this->addCastAttributesToArray( 155 $attributes, $mutatedAttributes 156 ); 157 158 // Here we will grab all of the appended, calculated attributes to this model 159 // as these attributes are not really in the attributes array, but are run 160 // when we need to array or JSON the model for convenience to the coder. 161 foreach ($this->getArrayableAppends() as $key) { 162 $attributes[$key] = $this->mutateAttributeForArray($key, null); 163 } 164 165 return $attributes; 166 } 167 168 /** 169 * Add the date attributes to the attributes array. 170 * 171 * @param array $attributes 172 * @return array 173 */ 174 protected function addDateAttributesToArray(array $attributes) 175 { 176 foreach ($this->getDates() as $key) { 177 if (! isset($attributes[$key])) { 178 continue; 179 } 180 181 $attributes[$key] = $this->serializeDate( 182 $this->asDateTime($attributes[$key]) 183 ); 184 } 185 186 return $attributes; 187 } 188 189 /** 190 * Add the mutated attributes to the attributes array. 191 * 192 * @param array $attributes 193 * @param array $mutatedAttributes 194 * @return array 195 */ 196 protected function addMutatedAttributesToArray(array $attributes, array $mutatedAttributes) 197 { 198 foreach ($mutatedAttributes as $key) { 199 // We want to spin through all the mutated attributes for this model and call 200 // the mutator for the attribute. We cache off every mutated attributes so 201 // we don't have to constantly check on attributes that actually change. 202 if (! array_key_exists($key, $attributes)) { 203 continue; 204 } 205 206 // Next, we will call the mutator for this attribute so that we can get these 207 // mutated attribute's actual values. After we finish mutating each of the 208 // attributes we will return this final array of the mutated attributes. 209 $attributes[$key] = $this->mutateAttributeForArray( 210 $key, $attributes[$key] 211 ); 212 } 213 214 return $attributes; 215 } 216 217 /** 218 * Add the casted attributes to the attributes array. 219 * 220 * @param array $attributes 221 * @param array $mutatedAttributes 222 * @return array 223 */ 224 protected function addCastAttributesToArray(array $attributes, array $mutatedAttributes) 225 { 226 foreach ($this->getCasts() as $key => $value) { 227 if (! array_key_exists($key, $attributes) || 228 in_array($key, $mutatedAttributes)) { 229 continue; 230 } 231 232 // Here we will cast the attribute. Then, if the cast is a date or datetime cast 233 // then we will serialize the date for the array. This will convert the dates 234 // to strings based on the date format specified for these Eloquent models. 235 $attributes[$key] = $this->castAttribute( 236 $key, $attributes[$key] 237 ); 238 239 // If the attribute cast was a date or a datetime, we will serialize the date as 240 // a string. This allows the developers to customize how dates are serialized 241 // into an array without affecting how they are persisted into the storage. 242 if ($attributes[$key] && 243 ($value === 'date' || $value === 'datetime')) { 244 $attributes[$key] = $this->serializeDate($attributes[$key]); 245 } 246 247 if ($attributes[$key] && $this->isCustomDateTimeCast($value)) { 248 $attributes[$key] = $attributes[$key]->format(explode(':', $value, 2)[1]); 249 } 250 251 if ($attributes[$key] && $attributes[$key] instanceof DateTimeInterface && 252 $this->isClassCastable($key)) { 253 $attributes[$key] = $this->serializeDate($attributes[$key]); 254 } 255 256 if ($attributes[$key] && $this->isClassSerializable($key)) { 257 $attributes[$key] = $this->serializeClassCastableAttribute($key, $attributes[$key]); 258 } 259 260 if ($attributes[$key] instanceof Arrayable) { 261 $attributes[$key] = $attributes[$key]->toArray(); 262 } 263 } 264 265 return $attributes; 266 } 267 268 /** 269 * Get an attribute array of all arrayable attributes. 270 * 271 * @return array 272 */ 273 protected function getArrayableAttributes() 274 { 275 return $this->getArrayableItems($this->getAttributes()); 276 } 277 278 /** 279 * Get all of the appendable values that are arrayable. 280 * 281 * @return array 282 */ 283 protected function getArrayableAppends() 284 { 285 if (! count($this->appends)) { 286 return []; 287 } 288 289 return $this->getArrayableItems( 290 array_combine($this->appends, $this->appends) 291 ); 292 } 293 294 /** 295 * Get the model's relationships in array form. 296 * 297 * @return array 298 */ 299 public function relationsToArray() 300 { 301 $attributes = []; 302 303 foreach ($this->getArrayableRelations() as $key => $value) { 304 // If the values implements the Arrayable interface we can just call this 305 // toArray method on the instances which will convert both models and 306 // collections to their proper array form and we'll set the values. 307 if ($value instanceof Arrayable) { 308 $relation = $value->toArray(); 309 } 310 311 // If the value is null, we'll still go ahead and set it in this list of 312 // attributes since null is used to represent empty relationships if 313 // if it a has one or belongs to type relationships on the models. 314 elseif (is_null($value)) { 315 $relation = $value; 316 } 317 318 // If the relationships snake-casing is enabled, we will snake case this 319 // key so that the relation attribute is snake cased in this returned 320 // array to the developers, making this consistent with attributes. 321 if (static::$snakeAttributes) { 322 $key = Str::snake($key); 323 } 324 325 // If the relation value has been set, we will set it on this attributes 326 // list for returning. If it was not arrayable or null, we'll not set 327 // the value on the array because it is some type of invalid value. 328 if (isset($relation) || is_null($value)) { 329 $attributes[$key] = $relation; 330 } 331 332 unset($relation); 333 } 334 335 return $attributes; 336 } 337 338 /** 339 * Get an attribute array of all arrayable relations. 340 * 341 * @return array 342 */ 343 protected function getArrayableRelations() 344 { 345 return $this->getArrayableItems($this->relations); 346 } 347 348 /** 349 * Get an attribute array of all arrayable values. 350 * 351 * @param array $values 352 * @return array 353 */ 354 protected function getArrayableItems(array $values) 355 { 356 if (count($this->getVisible()) > 0) { 357 $values = array_intersect_key($values, array_flip($this->getVisible())); 358 } 359 360 if (count($this->getHidden()) > 0) { 361 $values = array_diff_key($values, array_flip($this->getHidden())); 362 } 363 364 return $values; 365 } 366 367 /** 368 * Get an attribute from the model. 369 * 370 * @param string $key 371 * @return mixed 372 */ 373 public function getAttribute($key) 374 { 375 if (! $key) { 376 return; 377 } 378 379 // If the attribute exists in the attribute array or has a "get" mutator we will 380 // get the attribute's value. Otherwise, we will proceed as if the developers 381 // are asking for a relationship's value. This covers both types of values. 382 if (array_key_exists($key, $this->attributes) || 383 array_key_exists($key, $this->casts) || 384 $this->hasGetMutator($key) || 385 $this->isClassCastable($key)) { 386 return $this->getAttributeValue($key); 387 } 388 389 // Here we will determine if the model base class itself contains this given key 390 // since we don't want to treat any of those methods as relationships because 391 // they are all intended as helper methods and none of these are relations. 392 if (method_exists(self::class, $key)) { 393 return; 394 } 395 396 return $this->getRelationValue($key); 397 } 398 399 /** 400 * Get a plain attribute (not a relationship). 401 * 402 * @param string $key 403 * @return mixed 404 */ 405 public function getAttributeValue($key) 406 { 407 return $this->transformModelValue($key, $this->getAttributeFromArray($key)); 408 } 409 410 /** 411 * Get an attribute from the $attributes array. 412 * 413 * @param string $key 414 * @return mixed 415 */ 416 protected function getAttributeFromArray($key) 417 { 418 return $this->getAttributes()[$key] ?? null; 419 } 420 421 /** 422 * Get a relationship. 423 * 424 * @param string $key 425 * @return mixed 426 */ 427 public function getRelationValue($key) 428 { 429 // If the key already exists in the relationships array, it just means the 430 // relationship has already been loaded, so we'll just return it out of 431 // here because there is no need to query within the relations twice. 432 if ($this->relationLoaded($key)) { 433 return $this->relations[$key]; 434 } 435 436 // If the "attribute" exists as a method on the model, we will just assume 437 // it is a relationship and will load and return results from the query 438 // and hydrate the relationship's value on the "relationships" array. 439 if (method_exists($this, $key) || 440 (static::$relationResolvers[get_class($this)][$key] ?? null)) { 441 return $this->getRelationshipFromMethod($key); 442 } 443 } 444 445 /** 446 * Get a relationship value from a method. 447 * 448 * @param string $method 449 * @return mixed 450 * 451 * @throws \LogicException 452 */ 453 protected function getRelationshipFromMethod($method) 454 { 455 $relation = $this->$method(); 456 457 if (! $relation instanceof Relation) { 458 if (is_null($relation)) { 459 throw new LogicException(sprintf( 460 '%s::%s must return a relationship instance, but "null" was returned. Was the "return" keyword used?', static::class, $method 461 )); 462 } 463 464 throw new LogicException(sprintf( 465 '%s::%s must return a relationship instance.', static::class, $method 466 )); 467 } 468 469 return tap($relation->getResults(), function ($results) use ($method) { 470 $this->setRelation($method, $results); 471 }); 472 } 473 474 /** 475 * Determine if a get mutator exists for an attribute. 476 * 477 * @param string $key 478 * @return bool 479 */ 480 public function hasGetMutator($key) 481 { 482 return method_exists($this, 'get'.Str::studly($key).'Attribute'); 483 } 484 485 /** 486 * Get the value of an attribute using its mutator. 487 * 488 * @param string $key 489 * @param mixed $value 490 * @return mixed 491 */ 492 protected function mutateAttribute($key, $value) 493 { 494 return $this->{'get'.Str::studly($key).'Attribute'}($value); 495 } 496 497 /** 498 * Get the value of an attribute using its mutator for array conversion. 499 * 500 * @param string $key 501 * @param mixed $value 502 * @return mixed 503 */ 504 protected function mutateAttributeForArray($key, $value) 505 { 506 $value = $this->isClassCastable($key) 507 ? $this->getClassCastableAttributeValue($key, $value) 508 : $this->mutateAttribute($key, $value); 509 510 return $value instanceof Arrayable ? $value->toArray() : $value; 511 } 512 513 /** 514 * Merge new casts with existing casts on the model. 515 * 516 * @param array $casts 517 * @return $this 518 */ 519 public function mergeCasts($casts) 520 { 521 $this->casts = array_merge($this->casts, $casts); 522 523 return $this; 524 } 525 526 /** 527 * Cast an attribute to a native PHP type. 528 * 529 * @param string $key 530 * @param mixed $value 531 * @return mixed 532 */ 533 protected function castAttribute($key, $value) 534 { 535 $castType = $this->getCastType($key); 536 537 if (is_null($value) && in_array($castType, static::$primitiveCastTypes)) { 538 return $value; 539 } 540 541 // If the key is one of the encrypted castable types, we'll first decrypt 542 // the value and update the cast type so we may leverage the following 543 // logic for casting this value to any additionally specified types. 544 if ($this->isEncryptedCastable($key)) { 545 $value = $this->fromEncryptedString($value); 546 547 $castType = Str::after($castType, 'encrypted:'); 548 } 549 550 switch ($castType) { 551 case 'int': 552 case 'integer': 553 return (int) $value; 554 case 'real': 555 case 'float': 556 case 'double': 557 return $this->fromFloat($value); 558 case 'decimal': 559 return $this->asDecimal($value, explode(':', $this->getCasts()[$key], 2)[1]); 560 case 'string': 561 return (string) $value; 562 case 'bool': 563 case 'boolean': 564 return (bool) $value; 565 case 'object': 566 return $this->fromJson($value, true); 567 case 'array': 568 case 'json': 569 return $this->fromJson($value); 570 case 'collection': 571 return new BaseCollection($this->fromJson($value)); 572 case 'date': 573 return $this->asDate($value); 574 case 'datetime': 575 case 'custom_datetime': 576 return $this->asDateTime($value); 577 case 'timestamp': 578 return $this->asTimestamp($value); 579 } 580 581 if ($this->isClassCastable($key)) { 582 return $this->getClassCastableAttributeValue($key, $value); 583 } 584 585 return $value; 586 } 587 588 /** 589 * Cast the given attribute using a custom cast class. 590 * 591 * @param string $key 592 * @param mixed $value 593 * @return mixed 594 */ 595 protected function getClassCastableAttributeValue($key, $value) 596 { 597 if (isset($this->classCastCache[$key])) { 598 return $this->classCastCache[$key]; 599 } else { 600 $caster = $this->resolveCasterClass($key); 601 602 $value = $caster instanceof CastsInboundAttributes 603 ? $value 604 : $caster->get($this, $key, $value, $this->attributes); 605 606 if ($caster instanceof CastsInboundAttributes || ! is_object($value)) { 607 unset($this->classCastCache[$key]); 608 } else { 609 $this->classCastCache[$key] = $value; 610 } 611 612 return $value; 613 } 614 } 615 616 /** 617 * Get the type of cast for a model attribute. 618 * 619 * @param string $key 620 * @return string 621 */ 622 protected function getCastType($key) 623 { 624 if ($this->isCustomDateTimeCast($this->getCasts()[$key])) { 625 return 'custom_datetime'; 626 } 627 628 if ($this->isDecimalCast($this->getCasts()[$key])) { 629 return 'decimal'; 630 } 631 632 return trim(strtolower($this->getCasts()[$key])); 633 } 634 635 /** 636 * Increment or decrement the given attribute using the custom cast class. 637 * 638 * @param string $method 639 * @param string $key 640 * @param mixed $value 641 * @return mixed 642 */ 643 protected function deviateClassCastableAttribute($method, $key, $value) 644 { 645 return $this->resolveCasterClass($key)->{$method}( 646 $this, $key, $value, $this->attributes 647 ); 648 } 649 650 /** 651 * Serialize the given attribute using the custom cast class. 652 * 653 * @param string $key 654 * @param mixed $value 655 * @return mixed 656 */ 657 protected function serializeClassCastableAttribute($key, $value) 658 { 659 return $this->resolveCasterClass($key)->serialize( 660 $this, $key, $value, $this->attributes 661 ); 662 } 663 664 /** 665 * Determine if the cast type is a custom date time cast. 666 * 667 * @param string $cast 668 * @return bool 669 */ 670 protected function isCustomDateTimeCast($cast) 671 { 672 return strncmp($cast, 'date:', 5) === 0 || 673 strncmp($cast, 'datetime:', 9) === 0; 674 } 675 676 /** 677 * Determine if the cast type is a decimal cast. 678 * 679 * @param string $cast 680 * @return bool 681 */ 682 protected function isDecimalCast($cast) 683 { 684 return strncmp($cast, 'decimal:', 8) === 0; 685 } 686 687 /** 688 * Set a given attribute on the model. 689 * 690 * @param string $key 691 * @param mixed $value 692 * @return mixed 693 */ 694 public function setAttribute($key, $value) 695 { 696 // First we will check for the presence of a mutator for the set operation 697 // which simply lets the developers tweak the attribute as it is set on 698 // this model, such as "json_encoding" a listing of data for storage. 699 if ($this->hasSetMutator($key)) { 700 return $this->setMutatedAttributeValue($key, $value); 701 } 702 703 // If an attribute is listed as a "date", we'll convert it from a DateTime 704 // instance into a form proper for storage on the database tables using 705 // the connection grammar's date format. We will auto set the values. 706 elseif ($value && $this->isDateAttribute($key)) { 707 $value = $this->fromDateTime($value); 708 } 709 710 if ($this->isClassCastable($key)) { 711 $this->setClassCastableAttribute($key, $value); 712 713 return $this; 714 } 715 716 if (! is_null($value) && $this->isJsonCastable($key)) { 717 $value = $this->castAttributeAsJson($key, $value); 718 } 719 720 // If this attribute contains a JSON ->, we'll set the proper value in the 721 // attribute's underlying array. This takes care of properly nesting an 722 // attribute in the array's value in the case of deeply nested items. 723 if (Str::contains($key, '->')) { 724 return $this->fillJsonAttribute($key, $value); 725 } 726 727 if (! is_null($value) && $this->isEncryptedCastable($key)) { 728 $value = $this->castAttributeAsEncryptedString($key, $value); 729 } 730 731 $this->attributes[$key] = $value; 732 733 return $this; 734 } 735 736 /** 737 * Determine if a set mutator exists for an attribute. 738 * 739 * @param string $key 740 * @return bool 741 */ 742 public function hasSetMutator($key) 743 { 744 return method_exists($this, 'set'.Str::studly($key).'Attribute'); 745 } 746 747 /** 748 * Set the value of an attribute using its mutator. 749 * 750 * @param string $key 751 * @param mixed $value 752 * @return mixed 753 */ 754 protected function setMutatedAttributeValue($key, $value) 755 { 756 return $this->{'set'.Str::studly($key).'Attribute'}($value); 757 } 758 759 /** 760 * Determine if the given attribute is a date or date castable. 761 * 762 * @param string $key 763 * @return bool 764 */ 765 protected function isDateAttribute($key) 766 { 767 return in_array($key, $this->getDates(), true) || 768 $this->isDateCastable($key); 769 } 770 771 /** 772 * Set a given JSON attribute on the model. 773 * 774 * @param string $key 775 * @param mixed $value 776 * @return $this 777 */ 778 public function fillJsonAttribute($key, $value) 779 { 780 [$key, $path] = explode('->', $key, 2); 781 782 $value = $this->asJson($this->getArrayAttributeWithValue( 783 $path, $key, $value 784 )); 785 786 $this->attributes[$key] = $this->isEncryptedCastable($key) 787 ? $this->castAttributeAsEncryptedString($key, $value) 788 : $value; 789 790 return $this; 791 } 792 793 /** 794 * Set the value of a class castable attribute. 795 * 796 * @param string $key 797 * @param mixed $value 798 * @return void 799 */ 800 protected function setClassCastableAttribute($key, $value) 801 { 802 $caster = $this->resolveCasterClass($key); 803 804 if (is_null($value)) { 805 $this->attributes = array_merge($this->attributes, array_map( 806 function () { 807 }, 808 $this->normalizeCastClassResponse($key, $caster->set( 809 $this, $key, $this->{$key}, $this->attributes 810 )) 811 )); 812 } else { 813 $this->attributes = array_merge( 814 $this->attributes, 815 $this->normalizeCastClassResponse($key, $caster->set( 816 $this, $key, $value, $this->attributes 817 )) 818 ); 819 } 820 821 if ($caster instanceof CastsInboundAttributes || ! is_object($value)) { 822 unset($this->classCastCache[$key]); 823 } else { 824 $this->classCastCache[$key] = $value; 825 } 826 } 827 828 /** 829 * Get an array attribute with the given key and value set. 830 * 831 * @param string $path 832 * @param string $key 833 * @param mixed $value 834 * @return $this 835 */ 836 protected function getArrayAttributeWithValue($path, $key, $value) 837 { 838 return tap($this->getArrayAttributeByKey($key), function (&$array) use ($path, $value) { 839 Arr::set($array, str_replace('->', '.', $path), $value); 840 }); 841 } 842 843 /** 844 * Get an array attribute or return an empty array if it is not set. 845 * 846 * @param string $key 847 * @return array 848 */ 849 protected function getArrayAttributeByKey($key) 850 { 851 if (! isset($this->attributes[$key])) { 852 return []; 853 } 854 855 return $this->fromJson( 856 $this->isEncryptedCastable($key) 857 ? $this->fromEncryptedString($this->attributes[$key]) 858 : $this->attributes[$key] 859 ); 860 } 861 862 /** 863 * Cast the given attribute to JSON. 864 * 865 * @param string $key 866 * @param mixed $value 867 * @return string 868 */ 869 protected function castAttributeAsJson($key, $value) 870 { 871 $value = $this->asJson($value); 872 873 if ($value === false) { 874 throw JsonEncodingException::forAttribute( 875 $this, $key, json_last_error_msg() 876 ); 877 } 878 879 return $value; 880 } 881 882 /** 883 * Encode the given value as JSON. 884 * 885 * @param mixed $value 886 * @return string 887 */ 888 protected function asJson($value) 889 { 890 return json_encode($value); 891 } 892 893 /** 894 * Decode the given JSON back into an array or object. 895 * 896 * @param string $value 897 * @param bool $asObject 898 * @return mixed 899 */ 900 public function fromJson($value, $asObject = false) 901 { 902 return json_decode($value, ! $asObject); 903 } 904 905 /** 906 * Decrypt the given encrypted string. 907 * 908 * @param string $value 909 * @return mixed 910 */ 911 public function fromEncryptedString($value) 912 { 913 return (static::$encrypter ?? Crypt::getFacadeRoot())->decrypt($value, false); 914 } 915 916 /** 917 * Cast the given attribute to an encrypted string. 918 * 919 * @param string $key 920 * @param mixed $value 921 * @return string 922 */ 923 protected function castAttributeAsEncryptedString($key, $value) 924 { 925 return (static::$encrypter ?? Crypt::getFacadeRoot())->encrypt($value, false); 926 } 927 928 /** 929 * Set the encrypter instance that will be used to encrypt attributes. 930 * 931 * @param \Illuminate\Contracts\Encryption\Encrypter $encrypter 932 * @return void 933 */ 934 public static function encryptUsing($encrypter) 935 { 936 static::$encrypter = $encrypter; 937 } 938 939 /** 940 * Decode the given float. 941 * 942 * @param mixed $value 943 * @return mixed 944 */ 945 public function fromFloat($value) 946 { 947 switch ((string) $value) { 948 case 'Infinity': 949 return INF; 950 case '-Infinity': 951 return -INF; 952 case 'NaN': 953 return NAN; 954 default: 955 return (float) $value; 956 } 957 } 958 959 /** 960 * Return a decimal as string. 961 * 962 * @param float $value 963 * @param int $decimals 964 * @return string 965 */ 966 protected function asDecimal($value, $decimals) 967 { 968 return number_format($value, $decimals, '.', ''); 969 } 970 971 /** 972 * Return a timestamp as DateTime object with time set to 00:00:00. 973 * 974 * @param mixed $value 975 * @return \Illuminate\Support\Carbon 976 */ 977 protected function asDate($value) 978 { 979 return $this->asDateTime($value)->startOfDay(); 980 } 981 982 /** 983 * Return a timestamp as DateTime object. 984 * 985 * @param mixed $value 986 * @return \Illuminate\Support\Carbon 987 */ 988 protected function asDateTime($value) 989 { 990 // If this value is already a Carbon instance, we shall just return it as is. 991 // This prevents us having to re-instantiate a Carbon instance when we know 992 // it already is one, which wouldn't be fulfilled by the DateTime check. 993 if ($value instanceof CarbonInterface) { 994 return Date::instance($value); 995 } 996 997 // If the value is already a DateTime instance, we will just skip the rest of 998 // these checks since they will be a waste of time, and hinder performance 999 // when checking the field. We will just return the DateTime right away. 1000 if ($value instanceof DateTimeInterface) { 1001 return Date::parse( 1002 $value->format('Y-m-d H:i:s.u'), $value->getTimezone() 1003 ); 1004 } 1005 1006 // If this value is an integer, we will assume it is a UNIX timestamp's value 1007 // and format a Carbon object from this timestamp. This allows flexibility 1008 // when defining your date fields as they might be UNIX timestamps here. 1009 if (is_numeric($value)) { 1010 return Date::createFromTimestamp($value); 1011 } 1012 1013 // If the value is in simply year, month, day format, we will instantiate the 1014 // Carbon instances from that format. Again, this provides for simple date 1015 // fields on the database, while still supporting Carbonized conversion. 1016 if ($this->isStandardDateFormat($value)) { 1017 return Date::instance(Carbon::createFromFormat('Y-m-d', $value)->startOfDay()); 1018 } 1019 1020 $format = $this->getDateFormat(); 1021 1022 // Finally, we will just assume this date is in the format used by default on 1023 // the database connection and use that format to create the Carbon object 1024 // that is returned back out to the developers after we convert it here. 1025 try { 1026 $date = Date::createFromFormat($format, $value); 1027 } catch (InvalidArgumentException $e) { 1028 $date = false; 1029 } 1030 1031 return $date ?: Date::parse($value); 1032 } 1033 1034 /** 1035 * Determine if the given value is a standard date format. 1036 * 1037 * @param string $value 1038 * @return bool 1039 */ 1040 protected function isStandardDateFormat($value) 1041 { 1042 return preg_match('/^(\d{4})-(\d{1,2})-(\d{1,2})$/', $value); 1043 } 1044 1045 /** 1046 * Convert a DateTime to a storable string. 1047 * 1048 * @param mixed $value 1049 * @return string|null 1050 */ 1051 public function fromDateTime($value) 1052 { 1053 return empty($value) ? $value : $this->asDateTime($value)->format( 1054 $this->getDateFormat() 1055 ); 1056 } 1057 1058 /** 1059 * Return a timestamp as unix timestamp. 1060 * 1061 * @param mixed $value 1062 * @return int 1063 */ 1064 protected function asTimestamp($value) 1065 { 1066 return $this->asDateTime($value)->getTimestamp(); 1067 } 1068 1069 /** 1070 * Prepare a date for array / JSON serialization. 1071 * 1072 * @param \DateTimeInterface $date 1073 * @return string 1074 */ 1075 protected function serializeDate(DateTimeInterface $date) 1076 { 1077 return Carbon::instance($date)->toJSON(); 1078 } 1079 1080 /** 1081 * Get the attributes that should be converted to dates. 1082 * 1083 * @return array 1084 */ 1085 public function getDates() 1086 { 1087 if (! $this->usesTimestamps()) { 1088 return $this->dates; 1089 } 1090 1091 $defaults = [ 1092 $this->getCreatedAtColumn(), 1093 $this->getUpdatedAtColumn(), 1094 ]; 1095 1096 return array_unique(array_merge($this->dates, $defaults)); 1097 } 1098 1099 /** 1100 * Get the format for database stored dates. 1101 * 1102 * @return string 1103 */ 1104 public function getDateFormat() 1105 { 1106 return $this->dateFormat ?: $this->getConnection()->getQueryGrammar()->getDateFormat(); 1107 } 1108 1109 /** 1110 * Set the date format used by the model. 1111 * 1112 * @param string $format 1113 * @return $this 1114 */ 1115 public function setDateFormat($format) 1116 { 1117 $this->dateFormat = $format; 1118 1119 return $this; 1120 } 1121 1122 /** 1123 * Determine whether an attribute should be cast to a native type. 1124 * 1125 * @param string $key 1126 * @param array|string|null $types 1127 * @return bool 1128 */ 1129 public function hasCast($key, $types = null) 1130 { 1131 if (array_key_exists($key, $this->getCasts())) { 1132 return $types ? in_array($this->getCastType($key), (array) $types, true) : true; 1133 } 1134 1135 return false; 1136 } 1137 1138 /** 1139 * Get the casts array. 1140 * 1141 * @return array 1142 */ 1143 public function getCasts() 1144 { 1145 if ($this->getIncrementing()) { 1146 return array_merge([$this->getKeyName() => $this->getKeyType()], $this->casts); 1147 } 1148 1149 return $this->casts; 1150 } 1151 1152 /** 1153 * Determine whether a value is Date / DateTime castable for inbound manipulation. 1154 * 1155 * @param string $key 1156 * @return bool 1157 */ 1158 protected function isDateCastable($key) 1159 { 1160 return $this->hasCast($key, ['date', 'datetime']); 1161 } 1162 1163 /** 1164 * Determine whether a value is JSON castable for inbound manipulation. 1165 * 1166 * @param string $key 1167 * @return bool 1168 */ 1169 protected function isJsonCastable($key) 1170 { 1171 return $this->hasCast($key, ['array', 'json', 'object', 'collection', 'encrypted:array', 'encrypted:collection', 'encrypted:json', 'encrypted:object']); 1172 } 1173 1174 /** 1175 * Determine whether a value is an encrypted castable for inbound manipulation. 1176 * 1177 * @param string $key 1178 * @return bool 1179 */ 1180 protected function isEncryptedCastable($key) 1181 { 1182 return $this->hasCast($key, ['encrypted', 'encrypted:array', 'encrypted:collection', 'encrypted:json', 'encrypted:object']); 1183 } 1184 1185 /** 1186 * Determine if the given key is cast using a custom class. 1187 * 1188 * @param string $key 1189 * @return bool 1190 */ 1191 protected function isClassCastable($key) 1192 { 1193 if (! array_key_exists($key, $this->getCasts())) { 1194 return false; 1195 } 1196 1197 $castType = $this->parseCasterClass($this->getCasts()[$key]); 1198 1199 if (in_array($castType, static::$primitiveCastTypes)) { 1200 return false; 1201 } 1202 1203 if (class_exists($castType)) { 1204 return true; 1205 } 1206 1207 throw new InvalidCastException($this->getModel(), $key, $castType); 1208 } 1209 1210 /** 1211 * Determine if the key is deviable using a custom class. 1212 * 1213 * @param string $key 1214 * @return bool 1215 * 1216 * @throws \Illuminate\Database\Eloquent\InvalidCastException 1217 */ 1218 protected function isClassDeviable($key) 1219 { 1220 return $this->isClassCastable($key) && 1221 method_exists($castType = $this->parseCasterClass($this->getCasts()[$key]), 'increment') && 1222 method_exists($castType, 'decrement'); 1223 } 1224 1225 /** 1226 * Determine if the key is serializable using a custom class. 1227 * 1228 * @param string $key 1229 * @return bool 1230 * 1231 * @throws \Illuminate\Database\Eloquent\InvalidCastException 1232 */ 1233 protected function isClassSerializable($key) 1234 { 1235 return $this->isClassCastable($key) && 1236 method_exists($this->parseCasterClass($this->getCasts()[$key]), 'serialize'); 1237 } 1238 1239 /** 1240 * Resolve the custom caster class for a given key. 1241 * 1242 * @param string $key 1243 * @return mixed 1244 */ 1245 protected function resolveCasterClass($key) 1246 { 1247 $castType = $this->getCasts()[$key]; 1248 1249 $arguments = []; 1250 1251 if (is_string($castType) && strpos($castType, ':') !== false) { 1252 $segments = explode(':', $castType, 2); 1253 1254 $castType = $segments[0]; 1255 $arguments = explode(',', $segments[1]); 1256 } 1257 1258 if (is_subclass_of($castType, Castable::class)) { 1259 $castType = $castType::castUsing($arguments); 1260 } 1261 1262 if (is_object($castType)) { 1263 return $castType; 1264 } 1265 1266 return new $castType(...$arguments); 1267 } 1268 1269 /** 1270 * Parse the given caster class, removing any arguments. 1271 * 1272 * @param string $class 1273 * @return string 1274 */ 1275 protected function parseCasterClass($class) 1276 { 1277 return strpos($class, ':') === false 1278 ? $class 1279 : explode(':', $class, 2)[0]; 1280 } 1281 1282 /** 1283 * Merge the cast class attributes back into the model. 1284 * 1285 * @return void 1286 */ 1287 protected function mergeAttributesFromClassCasts() 1288 { 1289 foreach ($this->classCastCache as $key => $value) { 1290 $caster = $this->resolveCasterClass($key); 1291 1292 $this->attributes = array_merge( 1293 $this->attributes, 1294 $caster instanceof CastsInboundAttributes 1295 ? [$key => $value] 1296 : $this->normalizeCastClassResponse($key, $caster->set($this, $key, $value, $this->attributes)) 1297 ); 1298 } 1299 } 1300 1301 /** 1302 * Normalize the response from a custom class caster. 1303 * 1304 * @param string $key 1305 * @param mixed $value 1306 * @return array 1307 */ 1308 protected function normalizeCastClassResponse($key, $value) 1309 { 1310 return is_array($value) ? $value : [$key => $value]; 1311 } 1312 1313 /** 1314 * Get all of the current attributes on the model. 1315 * 1316 * @return array 1317 */ 1318 public function getAttributes() 1319 { 1320 $this->mergeAttributesFromClassCasts(); 1321 1322 return $this->attributes; 1323 } 1324 1325 /** 1326 * Get all of the current attributes on the model for an insert operation. 1327 * 1328 * @return array 1329 */ 1330 protected function getAttributesForInsert() 1331 { 1332 return $this->getAttributes(); 1333 } 1334 1335 /** 1336 * Set the array of model attributes. No checking is done. 1337 * 1338 * @param array $attributes 1339 * @param bool $sync 1340 * @return $this 1341 */ 1342 public function setRawAttributes(array $attributes, $sync = false) 1343 { 1344 $this->attributes = $attributes; 1345 1346 if ($sync) { 1347 $this->syncOriginal(); 1348 } 1349 1350 $this->classCastCache = []; 1351 1352 return $this; 1353 } 1354 1355 /** 1356 * Get the model's original attribute values. 1357 * 1358 * @param string|null $key 1359 * @param mixed $default 1360 * @return mixed|array 1361 */ 1362 public function getOriginal($key = null, $default = null) 1363 { 1364 return (new static)->setRawAttributes( 1365 $this->original, $sync = true 1366 )->getOriginalWithoutRewindingModel($key, $default); 1367 } 1368 1369 /** 1370 * Get the model's original attribute values. 1371 * 1372 * @param string|null $key 1373 * @param mixed $default 1374 * @return mixed|array 1375 */ 1376 protected function getOriginalWithoutRewindingModel($key = null, $default = null) 1377 { 1378 if ($key) { 1379 return $this->transformModelValue( 1380 $key, Arr::get($this->original, $key, $default) 1381 ); 1382 } 1383 1384 return collect($this->original)->mapWithKeys(function ($value, $key) { 1385 return [$key => $this->transformModelValue($key, $value)]; 1386 })->all(); 1387 } 1388 1389 /** 1390 * Get the model's raw original attribute values. 1391 * 1392 * @param string|null $key 1393 * @param mixed $default 1394 * @return mixed|array 1395 */ 1396 public function getRawOriginal($key = null, $default = null) 1397 { 1398 return Arr::get($this->original, $key, $default); 1399 } 1400 1401 /** 1402 * Get a subset of the model's attributes. 1403 * 1404 * @param array|mixed $attributes 1405 * @return array 1406 */ 1407 public function only($attributes) 1408 { 1409 $results = []; 1410 1411 foreach (is_array($attributes) ? $attributes : func_get_args() as $attribute) { 1412 $results[$attribute] = $this->getAttribute($attribute); 1413 } 1414 1415 return $results; 1416 } 1417 1418 /** 1419 * Sync the original attributes with the current. 1420 * 1421 * @return $this 1422 */ 1423 public function syncOriginal() 1424 { 1425 $this->original = $this->getAttributes(); 1426 1427 return $this; 1428 } 1429 1430 /** 1431 * Sync a single original attribute with its current value. 1432 * 1433 * @param string $attribute 1434 * @return $this 1435 */ 1436 public function syncOriginalAttribute($attribute) 1437 { 1438 return $this->syncOriginalAttributes($attribute); 1439 } 1440 1441 /** 1442 * Sync multiple original attribute with their current values. 1443 * 1444 * @param array|string $attributes 1445 * @return $this 1446 */ 1447 public function syncOriginalAttributes($attributes) 1448 { 1449 $attributes = is_array($attributes) ? $attributes : func_get_args(); 1450 1451 $modelAttributes = $this->getAttributes(); 1452 1453 foreach ($attributes as $attribute) { 1454 $this->original[$attribute] = $modelAttributes[$attribute]; 1455 } 1456 1457 return $this; 1458 } 1459 1460 /** 1461 * Sync the changed attributes. 1462 * 1463 * @return $this 1464 */ 1465 public function syncChanges() 1466 { 1467 $this->changes = $this->getDirty(); 1468 1469 return $this; 1470 } 1471 1472 /** 1473 * Determine if the model or any of the given attribute(s) have been modified. 1474 * 1475 * @param array|string|null $attributes 1476 * @return bool 1477 */ 1478 public function isDirty($attributes = null) 1479 { 1480 return $this->hasChanges( 1481 $this->getDirty(), is_array($attributes) ? $attributes : func_get_args() 1482 ); 1483 } 1484 1485 /** 1486 * Determine if the model and all the given attribute(s) have remained the same. 1487 * 1488 * @param array|string|null $attributes 1489 * @return bool 1490 */ 1491 public function isClean($attributes = null) 1492 { 1493 return ! $this->isDirty(...func_get_args()); 1494 } 1495 1496 /** 1497 * Determine if the model or any of the given attribute(s) have been modified. 1498 * 1499 * @param array|string|null $attributes 1500 * @return bool 1501 */ 1502 public function wasChanged($attributes = null) 1503 { 1504 return $this->hasChanges( 1505 $this->getChanges(), is_array($attributes) ? $attributes : func_get_args() 1506 ); 1507 } 1508 1509 /** 1510 * Determine if any of the given attributes were changed. 1511 * 1512 * @param array $changes 1513 * @param array|string|null $attributes 1514 * @return bool 1515 */ 1516 protected function hasChanges($changes, $attributes = null) 1517 { 1518 // If no specific attributes were provided, we will just see if the dirty array 1519 // already contains any attributes. If it does we will just return that this 1520 // count is greater than zero. Else, we need to check specific attributes. 1521 if (empty($attributes)) { 1522 return count($changes) > 0; 1523 } 1524 1525 // Here we will spin through every attribute and see if this is in the array of 1526 // dirty attributes. If it is, we will return true and if we make it through 1527 // all of the attributes for the entire array we will return false at end. 1528 foreach (Arr::wrap($attributes) as $attribute) { 1529 if (array_key_exists($attribute, $changes)) { 1530 return true; 1531 } 1532 } 1533 1534 return false; 1535 } 1536 1537 /** 1538 * Get the attributes that have been changed since the last sync. 1539 * 1540 * @return array 1541 */ 1542 public function getDirty() 1543 { 1544 $dirty = []; 1545 1546 foreach ($this->getAttributes() as $key => $value) { 1547 if (! $this->originalIsEquivalent($key)) { 1548 $dirty[$key] = $value; 1549 } 1550 } 1551 1552 return $dirty; 1553 } 1554 1555 /** 1556 * Get the attributes that were changed. 1557 * 1558 * @return array 1559 */ 1560 public function getChanges() 1561 { 1562 return $this->changes; 1563 } 1564 1565 /** 1566 * Determine if the new and old values for a given key are equivalent. 1567 * 1568 * @param string $key 1569 * @return bool 1570 */ 1571 public function originalIsEquivalent($key) 1572 { 1573 if (! array_key_exists($key, $this->original)) { 1574 return false; 1575 } 1576 1577 $attribute = Arr::get($this->attributes, $key); 1578 $original = Arr::get($this->original, $key); 1579 1580 if ($attribute === $original) { 1581 return true; 1582 } elseif (is_null($attribute)) { 1583 return false; 1584 } elseif ($this->isDateAttribute($key)) { 1585 return $this->fromDateTime($attribute) === 1586 $this->fromDateTime($original); 1587 } elseif ($this->hasCast($key, ['object', 'collection'])) { 1588 return $this->castAttribute($key, $attribute) == 1589 $this->castAttribute($key, $original); 1590 } elseif ($this->hasCast($key, ['real', 'float', 'double'])) { 1591 if (($attribute === null && $original !== null) || ($attribute !== null && $original === null)) { 1592 return false; 1593 } 1594 1595 return abs($this->castAttribute($key, $attribute) - $this->castAttribute($key, $original)) < PHP_FLOAT_EPSILON * 4; 1596 } elseif ($this->hasCast($key, static::$primitiveCastTypes)) { 1597 return $this->castAttribute($key, $attribute) === 1598 $this->castAttribute($key, $original); 1599 } 1600 1601 return is_numeric($attribute) && is_numeric($original) 1602 && strcmp((string) $attribute, (string) $original) === 0; 1603 } 1604 1605 /** 1606 * Transform a raw model value using mutators, casts, etc. 1607 * 1608 * @param string $key 1609 * @param mixed $value 1610 * @return mixed 1611 */ 1612 protected function transformModelValue($key, $value) 1613 { 1614 // If the attribute has a get mutator, we will call that then return what 1615 // it returns as the value, which is useful for transforming values on 1616 // retrieval from the model to a form that is more useful for usage. 1617 if ($this->hasGetMutator($key)) { 1618 return $this->mutateAttribute($key, $value); 1619 } 1620 1621 // If the attribute exists within the cast array, we will convert it to 1622 // an appropriate native PHP type dependent upon the associated value 1623 // given with the key in the pair. Dayle made this comment line up. 1624 if ($this->hasCast($key)) { 1625 return $this->castAttribute($key, $value); 1626 } 1627 1628 // If the attribute is listed as a date, we will convert it to a DateTime 1629 // instance on retrieval, which makes it quite convenient to work with 1630 // date fields without having to create a mutator for each property. 1631 if ($value !== null 1632 && \in_array($key, $this->getDates(), false)) { 1633 return $this->asDateTime($value); 1634 } 1635 1636 return $value; 1637 } 1638 1639 /** 1640 * Append attributes to query when building a query. 1641 * 1642 * @param array|string $attributes 1643 * @return $this 1644 */ 1645 public function append($attributes) 1646 { 1647 $this->appends = array_unique( 1648 array_merge($this->appends, is_string($attributes) ? func_get_args() : $attributes) 1649 ); 1650 1651 return $this; 1652 } 1653 1654 /** 1655 * Set the accessors to append to model arrays. 1656 * 1657 * @param array $appends 1658 * @return $this 1659 */ 1660 public function setAppends(array $appends) 1661 { 1662 $this->appends = $appends; 1663 1664 return $this; 1665 } 1666 1667 /** 1668 * Return whether the accessor attribute has been appended. 1669 * 1670 * @param string $attribute 1671 * @return bool 1672 */ 1673 public function hasAppended($attribute) 1674 { 1675 return in_array($attribute, $this->appends); 1676 } 1677 1678 /** 1679 * Get the mutated attributes for a given instance. 1680 * 1681 * @return array 1682 */ 1683 public function getMutatedAttributes() 1684 { 1685 $class = static::class; 1686 1687 if (! isset(static::$mutatorCache[$class])) { 1688 static::cacheMutatedAttributes($class); 1689 } 1690 1691 return static::$mutatorCache[$class]; 1692 } 1693 1694 /** 1695 * Extract and cache all the mutated attributes of a class. 1696 * 1697 * @param string $class 1698 * @return void 1699 */ 1700 public static function cacheMutatedAttributes($class) 1701 { 1702 static::$mutatorCache[$class] = collect(static::getMutatorMethods($class))->map(function ($match) { 1703 return lcfirst(static::$snakeAttributes ? Str::snake($match) : $match); 1704 })->all(); 1705 } 1706 1707 /** 1708 * Get all of the attribute mutator methods. 1709 * 1710 * @param mixed $class 1711 * @return array 1712 */ 1713 protected static function getMutatorMethods($class) 1714 { 1715 preg_match_all('/(?<=^|;)get([^;]+?)Attribute(;|$)/', implode(';', get_class_methods($class)), $matches); 1716 1717 return $matches[1]; 1718 } 1719} 1720