Skip to content

Instantly share code, notes, and snippets.

@cmaas
Last active March 21, 2020 12:56
Show Gist options
  • Save cmaas/26224ca294cee5e741d9 to your computer and use it in GitHub Desktop.
Save cmaas/26224ca294cee5e741d9 to your computer and use it in GitHub Desktop.
A Trait to automatically cast value objects in Laravel without needing a Mutator and an Accessor.

How to cast Value Objects in Laravel with $casts

This is just a gist, because you need to check how your value objects should look like. Use like this:

class User extends Model {
    use CastsValueObjects;
    protected $casts = [
        'email' => EmailAddress::class
    ];
}

See files attached to this gist.

<?php
trait CastsValueObjects
{
protected function castAttribute($key, $value)
{
$castToClass = $this->getValueObjectCastType($key);
// no Value Object? simply pass this up to the parent
if (!$castToClass) {
return parent::castAttribute($key, $value);
}
// otherwise create a Value Object
return $castToClass::fromNative($value);
}
public function setAttribute($key, $value)
{
$castToClass = $this->getValueObjectCastType($key);
if (!$castToClass) {
return parent::setAttribute($key, $value);
}
// Enforce type defined in $casts
if (! ($value instanceof $castToClass)) {
throw new InvalidArgumentException("Attribute '$key' must be an instance of " . $castToClass);
}
// now it has the type, store as native value
return parent::setAttribute($key, $value->getNativeValue());
}
private function getValueObjectCastType($key) {
$casts = $this->getCasts();
$castToClass = isset($casts[$key]) ? $casts[$key] : null;
if (class_exists($castToClass)) {
return $castToClass;
}
return null;
}
}
<?php
// concrete implementation of a Value Object – note that the constructor is `protected`
final class EmailAddress extends NativeType
{
/**
* Creates an EmailAddress object given a PHP native string as parameter.
*
* @param string $value
*/
protected function __construct($value)
{
$filteredValue = filter_var($value, FILTER_VALIDATE_EMAIL);
if ($filteredValue === false) {
throw new \InvalidArgumentException("Invalid argument $value: Not an email address.");
}
$this->value = $filteredValue;
}
}
<?php
// Want to use [PHP Enum](https://github.com/marc-mabe/php-enum)? Use this wrapper:
use MabeEnum\Enum as BaseEnum;
abstract class EnumValueObject extends BaseEnum implements ValueObject, JsonSerializable {
public static function fromNative($value)
{
return static::get($value);
}
public function sameValueAs(ValueObject $object)
{
return $this->is($object);
}
public function getNativeValue() {
return $this->getValue();
}
public function jsonSerialize() {
return $this->getName();
}
}
<?php
// example of a primitive Value Object
class NativeType implements ValueObject, \JsonSerializable
{
/**
* @var mixed
*/
protected $value;
protected function __construct($value)
{
$this->value = (string)$value;
}
/**
* Named constructor to instantiate a Value Object
*
* @param $value
* @return static
*/
public static function fromNative($value)
{
return new static($value);
}
/**
* Decides whether or not this Value Object is considered equal to another Value Object
*
* @param ValueObject $object
* @return bool
*/
public function sameValueAs(ValueObject $object) {
if (false === Util::classEquals($this, $object)) {
return false;
}
return $this->getNativeValue() === $object->getNativeValue();
}
/**
* Returns the raw $value
*
* @return mixed
*/
public function getNativeValue() {
return $this->value;
}
/**
* Returns the string representation of $value
*
* @return string
*/
public function __toString() {
return (string)$this->getNativeValue();
}
/**
* Converts the value to JSON
*
* @return mixed
*/
public function jsonSerialize() {
return $this->getNativeValue();
}
}
<?php
// could be made inline – for equality comparison), taken from [PHP Value Objects](https://github.com/nicolopignatelli/valueobjects
class Util
{
/**
* Tells whether two objects are of the same class
*
* @param object $object_a
* @param object $object_b
* @return bool
*/
public static function classEquals($object_a, $object_b)
{
return \get_class($object_a) === \get_class($object_b);
}
/**
* Returns full namespaced class as string
*
* @param $object
* @return string
*/
public static function getClassAsString($object)
{
return \get_class($object);
}
}
<?php
interface ValueObject {
/**
* Named constructor to make a Value Object from a native value.
*
* @param $value
* @return mixed
*/
public static function fromNative($value);
/**
* Compares two Value Objects and tells if they can be considered equal.
*
* @param ValueObject $object
* @return bool
*/
public function sameValueAs(ValueObject $object);
/**
* Returns the string representation of this Value Object.
*
* @return string
*/
public function __toString();
/**
* Returns the native value of this Value Object.
*
* @return mixed
*/
public function getNativeValue();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment