cjsaylor / libdomain
PHP classes and traits to facilitate domain driven design.
Requires
- php: >=7.1
Requires (Dev)
- phpunit/phpunit: ^6
README
Libdomain is a php library to facilitate domain driven development.
Core concepts
This library contains the following abstract classes:
Entity
- An entity is an identifiable object.ValueObject
- An object who's values are immutable.Collection
- A collection ofEntity
objects.CollectionEntity
- An entity object that also acts as a collection (an identifiable collection).
In cases where the abstract classes can't be extended, all functionality of the abstract classes are provided
with traits. For example, if you wanted an existing entity to have the properties of a ValueObject
, then you would
use the ReadAccessable
trait:
use Cjsaylor\Domain\ValueObject\ValueObjectInterface; use Cjsaylor\Domain\Behavior\ReadAccessable; class ConcreteEntity implements ValueObjectInterface { use ReadAccessable; }
The ConcreteEntity
would now be an immutable value object.
Examples
Entity Example
The first example illustrates a user object that accepts an email
attribute that must be an immutable email value object.
use \Cjsaylor\Domain\Entity; use \Cjsaylor\Domain\ValueObject; class Email extends ValueObject { public function __construct(string $value) { // Validate an email address here $this['value'] = $value; } public function __toString() { return $this['value']; } } class User extends Entity { public function offsetSet($offset, $value) : void { if ($offset === 'email' && !$value instanceof Email) { throw new \LogicException('Email must be an email value object!'); } parent::offsetSet($offset, $value); } } $user = new User([ 'email' => new Email('user@somedomain.com') ]);
CollectionEntity example
This next example will illustrate a group of users where that group also has an identity.
Here we will make use of the CollectionEntity
which is both a Collection
and an Entity
.
It will make use of the User
entity defined in the first example.
use \Cjsaylor\Domain\CollectionEntity; class UserGroup extends CollectionEntity { // Here, we set the expectation that this collection can take only users public function __construct(array $data = [], User ...$users) { parent::__construct($data, ...$users); } // Here's a method to add additional users post-construction public function add(User $user) { $this->getItems()[] = $user; } } $users = [ new User([ 'id' => 1, 'email' => new Email('user@somedomain.com') ]), new User([ 'id' => 2, 'email' => new Email('user2@somedomain.com') ]) ]; $userGroup = new UserGroup([ 'id' => 1, ...$users ]);
Setter callback example
Let's modify the User
object with some custom setter callbacks (available in 1.0.1
).
This allows us to typehint (and do other custom set logic for the Email
value object).
class User extends Entity { public function setEmail(Email $email) { $this->data['email'] = $email; } } // Would produce an error as `setEmail` would be called and would not match the type. $user = new User(['email' => 'user@somedomain.com']); // Valid $user = new User(['email' => new Email('user@somedomain.com')]);
Concrete entities example
In some instances, we don't want extra properties to be set on our entities. To limit the properties
that can be set on an entity, the PropertyLimitable
interface/trait can be implemented:
use Cjsaylor\Domain\Behavior\PropertyLimitable; use Cjsaylor\Domain\Behavior\PropertyLimitTrait; class User extends Entity implements PropertyLimitable { use PropertyLimitTrait; public function concreteAttributes() { return ['id', 'email']; } } $user = new User(); $user['id'] = 1; // OK! $user['first_name'] = 'Chris'; // Has no affect and is not set.