Domain-Driven Design with Zend Framework
时间:2009-04-06 来源:cobrawgl
Some of the Domain-driven design concepts explained in my previous posts are applied in this sample application. I’m also going to use the Zend Framework infrastructure to speed up some development tasks.
Directory Structure
app/ -> Application and UI Layers domain/ -> Domain Layer lib/ -> Infrastructure Layer
Application and UI Layers
app/ controllers/ UserController.php views/ layouts/ scripts/
Domain Layer
This layer should be well separated from the other layers and it should have few dependencies on the framework(s) you are using. I’m going to group all the classes and interfaces under the namespace “Project” and add it to my include path:
domain/ Project/ Dao/ Db/ IUser.php IUserProfile.php User.php UserProfile.php Model/ User/ IRepository.php Repository.php User.php UserProfile.php Users.php
Infrastructure Layer
This layer acts as a supporting library for all the other layers:
lib/ Zend/ ... Zf/ Persistence/ Db/ Adapter.php Exception.php Replicated.php Domain/ Collection.php Entity.php Exception.php Repository.php
User Entity
The User and UserProfile objects have a one-to-one relationship and form an Aggregate. An Aggregate is as a collection of related objects that have references between each other. Within an Aggregate there’s always an Aggregate Root (parent Entity), in this case User:
class Project_Model_User extends Zf_Domain_Entity { /* SELECT id, name FROM user WHERE id = ? */ private $properties = array( 'id' => null, 'name' => null ); /* @var Project_Model_UserProfile */ private $profile; public function __construct($array) { $this->populate($array); } } abstract class Zf_Domain_Entity { private $properties = array(); public function __call($method, $args) {} public function __set($key, $value) {} public function __get($key) {} public function __isset($key) {} public function __unset($key) {} public function populate($values) {} public function hasDependency($property) {} public function setDependency($property, $object) {} public function getDependency($property) {} }
Usage:
$array = array('id'=>1, 'name'=>'Federico'); $user = new Project_Model_User($array); $user->setProfile($profile); echo $user->getId(); // Outputs 1 echo $user->getName(); // Outputs Federico
Users Collection
A collection is simply an object that groups multiple elements into a single unit.
class Project_Model_Users extends Zf_Domain_Collection { public function __construct($users) { foreach ($users as $user) { if (!($user instanceof Project_Model_User)) { throw new Zf_Domain_Exception(...); } $this->append($user); } } } abstract class Zf_Domain_Collection implements Countable, Iterator { ... }
User DAO
The UserDAO class allows data access mechanisms to change independently of the code that uses the data:
class Project_Dao_Db_User extends Zf_Persistence_Db_Adapter { public function find($id) { $db = $this->getAdapter(); $query = $db->select(); $query->from('user'); $query->where('id = ?', $id); $result = $db->fetchRow($query); return $result; } public function findAll() { ... return $resultSet; } }
User Repository
A Repository is basically a collection of Aggregate Roots. Collections are used to store, retrieve and manipulate Entities and Value objects, however, object management is beyond the scope of this post.
The UserRepository object injects dependencies on demand, making the instantiation process inexpensive. A caller method is responsible for dynamically creating the setter and getter methods. You can easily mock objects by passing a custom config array via the constructor.
class Project_Model_User_Repository extends Zf_Domain_Repository { /* @var Project_Dao_Db_User */ private $userDao; /* @var Project_Dao_Db_UserProfile */ private $userProfileDao; /* @var array IoC Spec */ private $inject = array( 'userDao' => 'Project_Dao_Db_User', 'userProfileDao' => 'Project_Dao_Db_UserProfile' ); public function getUserById($id) { $row = $this->getUserDao()->find($id); $user = new Project_Model_User($row); $row = $this->getUserProfileDao()->findByUserId($id); $profile = new Project_Model_Profile($row); $user->setProfile($profile); return $user; } public function getUsers() { $users = array(); $rows = $this->getUserDao()->findAll(); foreach ($rows as $row) { $users[] = new Project_Model_User($row); } return new Project_Model_Users($users); } } abstract class Zf_Domain_Repository { ... public function __call($method, $arguments) { $property = lcfirst(substr($method, 3)); if (!property_exists($property)) { throw new Zf_Domain_Exception(...); } if (null === $this->$property && array_key_exists($property, $this->inject)) { $this->$property = new $this->inject[$property]; } return $this->$property; } ... }
Usage:
$repo = new Project_Model_User_Repository(); $user = $repo->getUserById(1); $profile = $user->getProfile(); $users = $repo->getUsers();
Links
If you’re interested in learning more about Domain-driven design, I recommend the following articles: