diff --git a/src/Illuminate/Database/Eloquent/Casters/AbstractCaster.php b/src/Illuminate/Database/Eloquent/Casters/AbstractCaster.php new file mode 100644 index 000000000000..bf4f8d96b5d2 --- /dev/null +++ b/src/Illuminate/Database/Eloquent/Casters/AbstractCaster.php @@ -0,0 +1,45 @@ +options = $options; + + return $this; + } + + /** + * Prepare a value to be stored. + * + * @param mixed $value + * + * @return mixed + */ + abstract public function as($value); + + /** + * Prepare a value to be retrieved. + * + * @param mixed $value + * + * @return mixed + */ + abstract public function from($value); +} diff --git a/src/Illuminate/Database/Eloquent/Casters/ArrayCaster.php b/src/Illuminate/Database/Eloquent/Casters/ArrayCaster.php new file mode 100644 index 000000000000..e03b88cb1ebc --- /dev/null +++ b/src/Illuminate/Database/Eloquent/Casters/ArrayCaster.php @@ -0,0 +1,30 @@ +options->asObject); + } +} diff --git a/src/Illuminate/Database/Eloquent/Casters/BooleanCaster.php b/src/Illuminate/Database/Eloquent/Casters/BooleanCaster.php new file mode 100644 index 000000000000..1e522ece2db5 --- /dev/null +++ b/src/Illuminate/Database/Eloquent/Casters/BooleanCaster.php @@ -0,0 +1,22 @@ +format('Y-m-d H:i:s.u'), $value->getTimeZone() + ); + } + + // If this value is an integer, we will assume it is a UNIX timestamp's value + // and format a Carbon object from this timestamp. This allows flexibility + // when defining your date fields as they might be UNIX timestamps here. + if (is_numeric($value)) { + return Carbon::createFromTimestamp($value); + } + + // If the value is in simply year, month, day format, we will instantiate the + // Carbon instances from that format. Again, this provides for simple date + // fields on the database, while still supporting Carbonized conversion. + if (preg_match('/^(\d{4})-(\d{1,2})-(\d{1,2})$/', $value)) { + return Carbon::createFromFormat('Y-m-d', $value)->startOfDay(); + } + + // Finally, we will just assume this date is in the format used by default on + // the database connection and use that format to create the Carbon object + // that is returned back out to the developers after we convert it here. + return Carbon::createFromFormat($this->getDateFormat(), $value); + } + + /** + * {@inheritdoc} + */ + public function from($value) + { + return $this->as($value)->format($this->options['format']); + } +} diff --git a/src/Illuminate/Database/Eloquent/Casters/EncryptedCaster.php b/src/Illuminate/Database/Eloquent/Casters/EncryptedCaster.php new file mode 100644 index 000000000000..284994d80dbe --- /dev/null +++ b/src/Illuminate/Database/Eloquent/Casters/EncryptedCaster.php @@ -0,0 +1,22 @@ +options($this->options) + ->as($value) + ->getTimestamp(); + } +} diff --git a/src/Illuminate/Database/Eloquent/Model.php b/src/Illuminate/Database/Eloquent/Model.php index 2008ce7570ba..48a7ba9809e5 100644 --- a/src/Illuminate/Database/Eloquent/Model.php +++ b/src/Illuminate/Database/Eloquent/Model.php @@ -260,6 +260,36 @@ abstract class Model implements ArrayAccess, Arrayable, Jsonable, JsonSerializab */ public static $manyMethods = ['belongsToMany', 'morphToMany', 'morphedByMany']; + /** + * The attribute casters. + * + * @var array + */ + protected static $casters = [ + 'int' => Casters\IntegerCaster::class, + 'integer' => Casters\IntegerCaster::class, + 'real' => Casters\FloatCaster::class, + 'float' => Casters\FloatCaster::class, + 'double' => Casters\FloatCaster::class, + 'string' => Casters\StringCaster::class, + 'bool' => Casters\BooleanCaster::class, + 'boolean' => Casters\BooleanCaster::class, + 'object' => Casters\ObjectCaster::class, + 'array' => Casters\ArrayCaster::class, + 'json' => Casters\ArrayCaster::class, + 'collection' => Casters\CollectionCaster::class, + 'date' => Casters\DateTimeCaster::class, + 'datetime' => Casters\DateTimeCaster::class, + 'encrypted' => Casters\EncryptedCaster::class, + 'timestamp' => Casters\TimestampCaster::class, + ]; + /** + * The user defined attribute casters. + * + * @var array + */ + protected static $customCasters = []; + /** * The name of the "created at" column. * @@ -2855,6 +2885,17 @@ protected function isDateCastable($key) return $this->hasCast($key, ['date', 'datetime']); } + /** + * Determine whether a value should be encrypted. + * + * @param string $key + * @return bool + */ + protected function isEncryptCastable($key) + { + return $this->hasCast($key, ['encrypted']); + } + /** * Determine whether a value is JSON castable for inbound manipulation. * @@ -2874,6 +2915,10 @@ protected function isJsonCastable($key) */ protected function getCastType($key) { + if(!array_key_exists($key, $this->getCasts())) { + return; + } + return trim(strtolower($this->getCasts()[$key])); } @@ -2890,34 +2935,30 @@ protected function castAttribute($key, $value) return $value; } - switch ($this->getCastType($key)) { - case 'int': - case 'integer': - return (int) $value; - case 'real': - case 'float': - case 'double': - return (float) $value; - case 'string': - return (string) $value; - case 'bool': - case 'boolean': - return (bool) $value; - case 'object': - return $this->fromJson($value, true); - case 'array': - case 'json': - return $this->fromJson($value); - case 'collection': - return new BaseCollection($this->fromJson($value)); - case 'date': - case 'datetime': - return $this->asDateTime($value); - case 'timestamp': - return $this->asTimeStamp($value); - default: - return $value; + return $this->getCaster($key)->as($value); + } + + /** + * Get a new caster instance. + * + * @param string $key + * @param string|null $castType + * + * @return \Illuminate\Database\Eloquent\Casters\AbstractCaster + */ + protected function getCaster($key, $castType = null) + { + $casters = array_merge(static::$casters, static::$customCasters); + + if (!$castType) { + $castType = $this->getCastType($key); + } + + if (!array_key_exists($castType, $casters)) { + throw new InvalidArgumentException($castType); } + + return new $casters[$castType]; } /** @@ -2945,6 +2986,10 @@ public function setAttribute($key, $value) $value = $this->fromDateTime($value); } + if ($this->isEncryptCastable($key) && ! is_null($value)) { + $value = $this->asEncrypted($value); + } + if ($this->isJsonCastable($key) && ! is_null($value)) { $value = $this->asJson($value); } @@ -3012,11 +3057,9 @@ public function getDates() */ public function fromDateTime($value) { - $format = $this->getDateFormat(); - - $value = $this->asDateTime($value); - - return $value->format($format); + return $this->getCaster(null, 'datetime')->options([ + 'format' => $this->getDateFormat() + ])->from($value); } /** @@ -3027,40 +3070,9 @@ public function fromDateTime($value) */ protected function asDateTime($value) { - // If this value is already a Carbon instance, we shall just return it as is. - // This prevents us having to re-instantiate a Carbon instance when we know - // it already is one, which wouldn't be fulfilled by the DateTime check. - if ($value instanceof Carbon) { - return $value; - } - - // If the value is already a DateTime instance, we will just skip the rest of - // these checks since they will be a waste of time, and hinder performance - // when checking the field. We will just return the DateTime right away. - if ($value instanceof DateTimeInterface) { - return new Carbon( - $value->format('Y-m-d H:i:s.u'), $value->getTimeZone() - ); - } - - // If this value is an integer, we will assume it is a UNIX timestamp's value - // and format a Carbon object from this timestamp. This allows flexibility - // when defining your date fields as they might be UNIX timestamps here. - if (is_numeric($value)) { - return Carbon::createFromTimestamp($value); - } - - // If the value is in simply year, month, day format, we will instantiate the - // Carbon instances from that format. Again, this provides for simple date - // fields on the database, while still supporting Carbonized conversion. - if (preg_match('/^(\d{4})-(\d{1,2})-(\d{1,2})$/', $value)) { - return Carbon::createFromFormat('Y-m-d', $value)->startOfDay(); - } - - // Finally, we will just assume this date is in the format used by default on - // the database connection and use that format to create the Carbon object - // that is returned back out to the developers after we convert it here. - return Carbon::createFromFormat($this->getDateFormat(), $value); + return $this->getCaster(null, 'datetime')->options([ + 'format' => $this->getDateFormat() + ])->as($value); } /** @@ -3071,7 +3083,9 @@ protected function asDateTime($value) */ protected function asTimeStamp($value) { - return $this->asDateTime($value)->getTimestamp(); + return $this->getCaster(null, 'timestamp')->options([ + 'format' => $this->getDateFormat() + ])->as($value); } /** @@ -3108,6 +3122,17 @@ public function setDateFormat($format) return $this; } + /** + * Encrypt the given value. + * + * @param mixed $value + * @return string + */ + protected function asEncrypted($value) + { + return $this->getCaster(null, 'encrypted')->as($value); + } + /** * Encode the given value as JSON. * @@ -3116,7 +3141,7 @@ public function setDateFormat($format) */ protected function asJson($value) { - return json_encode($value); + return $this->getCaster(null, 'json')->as($value); } /** @@ -3128,7 +3153,9 @@ protected function asJson($value) */ public function fromJson($value, $asObject = false) { - return json_decode($value, ! $asObject); + return $this->getCaster(null, 'json')->options([ + 'asObject' => ! $asObject + ])->from($value); } /**