今天分享一些 DDD 抽象類別的實作。
這邊使用 __set 與 __get 模式方法來時做出一個只能 new 而不能改值得物件
不過實際的類別要是使用 public setters 那就另當別論了。
以 PHP 來說,這部分只能依靠團隊共識跟 Code review 來補足了。
use ArrayAccess;
abstract class ValueObject implements ArrayAccess
{
    protected $props = [];
    const SETTER_ACCESS_MESSAGE = 'The value of the ValueObject cannot be modified';
    public function __construct(array $props)
    {
        $this->props = $props;
    }
    public function __set($key, $value)
    {
        throw new Exception(static::SETTER_ACCESS_MESSAGE);
    }
    public function __get($key)
    {
        return $this->props[$key] ?? null;
    }
    public function offsetSet($offset, $value)
    {
        throw new Exception(static::SETTER_ACCESS_MESSAGE);
    }
    public function offsetExists($offset)
    {
        return isset($this->props[$offset]);
    }
    public function offsetUnset($offset)
    {
        throw new Exception(static::SETTER_ACCESS_MESSAGE);
    }
    public function offsetGet($offset)
    {
        return $this->props[$offset] ?? null;
    }
    public function toArray()
    {
        return $this->props;
    }
}
class Color extends ValueObject
{
    // ...
}
Entity 繼承了 ValueObject 並把 setters 打開
abstract class Entity extends ValueObject
{
    public function __set($key, $value)
    {
        $this->props[$key] = $value;
        
        return $value;
    }
    public function offsetSet($offset, $value)
    {
        $this->props[$offset] = $value;
    }
    public function offsetUnset($offset)
    {
        unset($this->props[$offset]);
    }
}
Entity 的子類別可以依照所需再次複寫這些方法,例如:
<?php
class User
{
    public function __construct($props)
    {
        $this->props = $props;
    }
    
    public function __set($key, $value)
    {
        if ($key === 'email') {
            $this->setEmail($value);
        } else {
            $this->props[$key] = $value;
        }
    
        return $value;
    }
    public function setEmail($value)
    {
        var_dump('Check email format ...');
        $this->props['email'] = $value;
        
        return $this;
    }
    public function toArray()
    {
        return $this->props;
    }
}
$user = new User([
    'name' => 'Petter'
]);
$user->email = 'example@mail.com';
var_dump($user->toArray());
// output:
// string(22) "Check email format ..."
// array(2) {
//   ["props"]=>
//   array(1) {
//     ["name"]=>
//     string(6) "Petter"
//   }
//   ["email"]=>
//   string(16) "example@mail.com"
// }
這時候的 setters 是否要設為 public,就看自己的開發團隊共識。
Aggregate 實際上是 Entity 的子類因此沒有抽象類,延續上一個案例的 User 類別
它的 Aggregate 可能像是這樣:
use User;
class UserAggregate extends User
{
    public function __set($key, $value)
    {
        if ($key === 'group') {
            $this->setGroup($value);
        } else {
            parent::__set($key, $value);
        }
    }
    
    public function setGroup($group)
    {
        var_dump('Check something ...');
        $this->props['group'] = $group;
        
        return $this;
    }
}
$user = new UserAggregate([
    'name' => 'Petter'
]);
$user->email = 'example@mail.com';
$user->group = 'MyGroup';
var_dump($user->toArray());
// output:
// string(22) "Check email format ..."
// string(19) "Check something ..."
// array(3) {
//   ["props"]=>
//   array(1) {
//     ["name"]=>
//     string(6) "Petter"
//   }
//   ["email"]=>
//   string(16) "example@mail.com"
//   ["group"]=>
//   string(7) "MyGroup"
// }
基本上就是變成丟進建構子的 $props 屬性變多了,然後對增加的那些屬性新增 getters/setters。