diff --git a/src/Illuminate/Contracts/Database/TypeCaster/Factory.php b/src/Illuminate/Contracts/Database/TypeCaster/Factory.php new file mode 100644 index 000000000000..6122ae1ada09 --- /dev/null +++ b/src/Illuminate/Contracts/Database/TypeCaster/Factory.php @@ -0,0 +1,16 @@ +registerQueueableEntityResolver(); + $this->registerTypeCasterFactory(); + // The connection factory is used to create the actual connection instances on // the database. We will inject the factory into the manager so that it may // make the connections while they are actually needed and not of before. @@ -85,4 +88,29 @@ protected function registerQueueableEntityResolver() return new QueueEntityResolver; }); } + + /** + * Register the Type Caster factory. + * + * @return void + */ + protected function registerTypeCasterFactory() + { + $this->app->singleton('typecaster', function ($app) { + return new TypeCasterFactory($app); + }); + } + + /** + * Get the services provided by the provider. + * + * @return array + */ + public function provides() + { + return [ + 'typecaster', + ]; + } } + diff --git a/src/Illuminate/Database/Eloquent/Model.php b/src/Illuminate/Database/Eloquent/Model.php index da31505ce7cf..31e6e1a45779 100755 --- a/src/Illuminate/Database/Eloquent/Model.php +++ b/src/Illuminate/Database/Eloquent/Model.php @@ -2812,9 +2812,13 @@ protected function castAttribute($key, $value) return $this->asDateTime($value); case 'timestamp': return $this->asTimeStamp($value); - default: - return $value; } + + if ($this->castExists($key)) { + return $this->asCustom($key, $value); + } + + return $value; } /** @@ -2944,6 +2948,76 @@ protected function asTimeStamp($value) return $this->asDateTime($value)->getTimestamp(); } + /** + * Return the custom cast object. + * + * @param mixed $value + * @return mixed + */ + protected function asCustom($key, $value) + { + $cast = $this->castClass($key); + + if ($value instanceof $cast) { + return $value; + } + + $reflectionClass = new \ReflectionClass($cast); + + return $reflectionClass->newInstanceArgs([$value] + $this->castParameters($key)); + } + + /** + * Has a custom cast type been defined for a model attribute and does it exist. + * + * @param string $key + * @return bool + */ + protected function castExists($key) + { + if (! $this->castClass($key)) { + return false; + } + + return true; + } + + /** + * Return the fully qualified custom class. + * + * @param string $key + * @return string + */ + protected function castClass($key) + { + if (strpos($cast = $this->getCasts()[$key], ',') !== false) { + $parameters = explode(',', $cast); + + $cast = $parameters[0]; + } + + if (class_exists($cast)) { + return $cast; + } + } + + /** + * Return an array of custom cast parameters. + * + * @param string $key + * @return array + */ + protected function castParameters($key) + { + if (strpos($cast = $this->getCasts()[$key], ',') !== false) { + $parameters = explode(',', $cast); + + return $parameters; + } + + return []; + } + /** * Prepare a date for array / JSON serialization. * diff --git a/src/Illuminate/Database/Eloquent/TypeCaster/Factory.php b/src/Illuminate/Database/Eloquent/TypeCaster/Factory.php new file mode 100644 index 000000000000..91134ac38293 --- /dev/null +++ b/src/Illuminate/Database/Eloquent/TypeCaster/Factory.php @@ -0,0 +1,74 @@ +container = $container; + } + + /** + * Create a new Type Caster instance. + * + * @param \Illuminate\Database\Eloquent\Model $model + * @return \Illuminate\Database\Eloquent\TypeCaster\TypeCaster + */ + public function make(Model $model) + { + $typecaster = new TypeCaster($model); + + // Next we'll set the IoC container instance of the type caster, which is used + // to resolve out class based type casting extensions. If it is not set then + // these extension types wont be possible on these type caster instances. + if (! is_null($this->container)) { + $typecaster->setContainer($this->container); + } + + $typecaster->addExtensions($this->extensions); + + return $typecaster; + } + + /** + * Register a custom Type Caster extension. + * + * @param string $rule + * @param \Closure|string $fromDatabase + * @param \Closure|string|null $toDatabase + * @return void + */ + public function extend($rule, $fromDatabase, $toDatabase = null) + { + $this->extensions[$rule] = [ + 'from' => $fromDatabase, + 'to' => $toDatabase, + ]; + } +} diff --git a/src/Illuminate/Database/Eloquent/TypeCaster/TypeCaster.php b/src/Illuminate/Database/Eloquent/TypeCaster/TypeCaster.php new file mode 100644 index 000000000000..dc6b48a35dbc --- /dev/null +++ b/src/Illuminate/Database/Eloquent/TypeCaster/TypeCaster.php @@ -0,0 +1,178 @@ +model = $model; + } + + /** + * Run the Type Caster's rule against an attribute. + * + * @return void + */ + public function cast() + { + + } + + /** + * Determine if an cast has been set on an attribute. + * + * @param string $attribute + * @return bool + */ + public function hasCast($attribute) + { + return ! is_null($this->getCast($attribute)); + } + + /** + * Get a cast type and its parameters for a given attribute. + * + * @param string $attribute + * @return array|null + */ + protected function getCast($attribute) + { + + } + + /** + * Get the array of the custom Type Caster extensions. + * + * @return array + */ + public function getExtensions() + { + return $this->extensions; + } + + /** + * Register an array of custom Type Caster extensions. + * + * @param array $extensions + * @return void + */ + public function addExtensions(array $extensions) + { + if ($extensions) { + $keys = array_map('\Illuminate\Support\Str::snake', array_keys($extensions)); + + $extensions = array_combine($keys, array_values($extensions)); + } + + $this->extensions = array_merge($this->extensions, $extensions); + } + + /** + * Register a custom Type Caster extension. + * + * @param string $rule + * @param \Closure|string $extension + * @return void + */ + public function addExtension($rule, $extension) + { + $this->extensions[Str::snake($rule)] = $extension; + } + + /** + * Set the IoC container instance. + * + * @param \Illuminate\Contracts\Container\Container $container + * @return void + */ + public function setContainer(Container $container) + { + $this->container = $container; + } + + /** + * Call a custom validator extension. + * + * @param string $rule + * @param array $parameters + * @return bool|null + */ + protected function callExtension($rule, $parameters) + { + $callback = $this->extensions[$rule]; + + if ($callback instanceof Closure) { + return call_user_func_array($callback, $parameters); + } elseif (is_string($callback)) { + return $this->callClassBasedExtension($callback, $parameters); + } + } + + /** + * Call a class based validator extension. + * + * @param string $callback + * @param array $parameters + * @return bool + */ + protected function callClassBasedExtension($callback, $parameters) + { + list($class, $method) = explode('@', $callback); + + return call_user_func_array([$this->container->make($class), $method], $parameters); + } + + /** + * Handle dynamic calls to class methods. + * + * @param string $method + * @param array $parameters + * @return mixed + * + * @throws \BadMethodCallException + */ + public function __call($method, $parameters) + { + $rule = Str::snake(substr($method, 8)); + + if (isset($this->extensions[$rule])) { + return $this->callExtension($rule, $parameters); + } + + throw new BadMethodCallException("Method [$method] does not exist."); + } +} diff --git a/src/Illuminate/Support/Facades/TypeCaster.php b/src/Illuminate/Support/Facades/TypeCaster.php new file mode 100644 index 000000000000..a2dd2ab212db --- /dev/null +++ b/src/Illuminate/Support/Facades/TypeCaster.php @@ -0,0 +1,19 @@ +