Modularity and Layered Data Model Attila Jenei 2014. www.attilajenei.com
Jun 28, 2015
Modularity and Layered Data Model
Attila Jenei 2014. !
www.attilajenei.com
Modularity
Modularity
Our problems:
• Long development time
• Generic functionality in all projects
• Near identical operation of same features
• Hard maintainability
Modularity
What we need?
• Compatible components
• High reusability
• Exchangeable parts
• Configurable system
• Improved maintainability
High-level Modularity
We can
• Remove hard-coded class calls
• Turn Singletons into services
• Use dynamic table names for data layer
• Define keys for soft referencing
Soft References
Keys to
• Class names - simple “new”
• Factories - complex initialization
• Aliases - reusability
• Initializers - additional initialization
Soft References
Managers
• Handling references
• Lazy loading for performance and low-memory profile
• Shared or own instances
• We can use cloning to prevent recurring initializations
PHP Example
$user = new User; !
// turns to !
$user = clone $services->get(‘Model\User’);
PHP Example
$path = Config::get(‘ViewPath’); !
// turns to !
$path = $services->get(‘MyConfig’)->get(‘ViewPath’);
Example Configarray( ‘invokables’ => array( ‘Model\User’ => ‘Project\Model\User’ ), ‘factories’ => array( ‘Model\User\Table’ => function ($sm) { return \Project\Model\User\Table( $sm->get(‘Model\User\TableGateway’)); }, ‘Model\User\TableGateway’ => function ($sm) { return \Project\Model\User\TableGateway( $sm->get(‘Zend\Db\Adapter\Adapter’), ‘user’, $sm->get(‘Model\User’)); }, ‘Zend\Db\Adapter\Adapter’ => ‘Zend\Db\Adapter\AdapterServiceFactory’, ),);
A Simple Service Managerpublic function get($name) { if (isset($this->instances[$name]) { return $this->instances[$name]; } if (isset($this->invokables[$name])) { $className = $this->invokables[$name]; if (class_exists($className)) { $instance = new $className; } else { throw new \Exception(‘Unknown class: ’ . $className); } }
A Simple Service Manager else if (isset($this->factories[$name]) { $factory = $this->factories[$name]; if ($factory instanceof FactoryInterface) { $factory = array($factory, ‘createService’); } if (is_callable($factory) { $instance = call_user_func($factory, $this, $name); } else { throw new \Exception(‘Wrong factory: ’ . $name); } }
A Simple Service Manager else { throw new \Exception(‘Unknown key: ’ . $name); } if ($instance) { $this->instances[$name] = $instance; return $instance; } throw new \Exception(‘No instance: ’ . $name); }
Data &
Responsibility
Higher-level OOP
• Database engine independent
• Unknown “outside world”
• Focus (only) on own tasks
• An entity class holds the information and has its related methods
Entity
• Represents the information
• ≠ database record
• Takes part in execution
Entity
An Object represents a Database Record.
A Database Record stores the information of an Object.
Example: CMS
• Page as entity
• Control flow
!
• Column as entity
• Display flow
Data Model
Layers
ENTITY Information
TABLE Entities
TABLE GATEWAY Database Interface
HYDRATOR Object - Information
Entity Classabstract class Entity{ protected $serviceLocator; protected $storedPrimaryKey; protected $table; final public function getServiceLocator() { return $this->serviceLocator; } final public function getStoredPrimaryKey() { return $this->storedPrimaryKey; } final public function getTable() { return $this->table; }
Entity Class final public function setServiceLocator(Ser…ace $serviceLocator) { $this->serviceLocator = $serviceLocator; return $this; } final public function setStoredPrimaryKey(array $storedPrimaryKey) { $this->storedPrimaryKey = $storedPrimaryKey; return $this; } final public function setTable(Table $table) { $this->table = $table; return $this; }
Entity Class public function delete() { if (!$this->storedPrimaryKey) { throw new \Exception(‘Entity is not stored’); } $this->table->delete($this->storedPrimaryKey); $this->storedPrimaryKey = array(); return $this; } abstract public function exchangeArray(array $data);
Entity Class public function save() { $this->table->save($this); $reloaded = $this->table->fetchAll($this->storedPrimaryKey)->current(); if ($reloaded) { $this->exchangeEntity($reloaded); } else { throw new \Exception(‘Error on reload’); } }}
Example: User Entityclass User extends Entity{ protected $name; protected $userID; public function getName() {…} public function getUserID() {…} public function setName($name) {…} public function setUserID($userID) {…} . . .
Example: User Entity public function exchangeArray(array $data) { $this->name = isset($data[‘name’]) ? $data[‘name’] : null; $this->userID = isset($data[‘userID’]) ? $data[‘userID’] : null; $this->storedPrimaryKey = array(‘userID’ => $this->userID); return $this; } public function exchangeEntity(User $entity) { $this->name = $entity->name; $this->userID = $entity->userID; $this->storedPrimaryKey = $entity->storedPrimaryKey; return $this; }
Example: User Entity public function exchangeArray(array $data) { $this->name = isset($data[‘name’]) ? $data[‘name’] : null; $this->userID = isset($data[‘userID’]) ? $data[‘userID’] : null; $this->storedPrimaryKey = array(‘userID’ => $this->userID); return $this; } public function exchangeEntity(User $entity) { $this->name = $entity->name; $this->userID = $entity->userID; $this->storedPrimaryKey = $entity->storedPrimaryKey; return $this; }
Hydrator
Table Classabstract class Table{ protected $serviceLocator; protected $tableGateway; public function __construct(TableGateway $tableGateway) { $this->tableGateway = $tableGateway; } final public function getServiceLocator() {…} final public function getTableGateway() {…} final public function setServiceLocator(…tor) {…} . . .
Table Class public function delete($where) { $this->tableGateway->delete($where); return $this; } final public function fetchAll($where = null) { return $this->tableGateway->select($where); }}
Example: User Tableclass User\Table extends Table{ public function save(User $entity) { $data = array(‘userID’ => $entity->getUserID(), ‘name’ => $entity->getName()); if ($entity->getStoredPrimaryKey()) { $this->tableGateway->update($data, $entity->getStoredPrimaryKey()); } else { $this->tableGateway->insert($data); $data[‘userID’] = $this->tableGateway->getLastInsertValue(); } $entity->setStoredPrimaryKey(array(‘userID’ => $data[‘userID’])); }
Example: User Tableclass User\Table extends Table{ public function save(User $entity) { $data = array(‘userID’ => $entity->getUserID(), ‘name’ => $entity->getName()); if ($entity->getStoredPrimaryKey()) { $this->tableGateway->update($data, $entity->getStoredPrimaryKey()); } else { $this->tableGateway->insert($data); $data[‘userID’] = $this->tableGateway->getLastInsertValue(); } $entity->setStoredPrimaryKey(array(‘userID’ => $data[‘userID’])); }
Hydrator
TableGateway Classabstract class TableGateway extends AbstractTableGateway{ protected $entityPrototype; protected $serviceLocator; public function __construct(Adapter $adapter, $table, $entityPrototype) { $this->adapter = $adapter; $this->table = $table; $this->entityPrototype = $entityPrototype; $this->resultSetPrototype = new ResultSet; $this->resultSetPrototype->setArrayObjectPrototype($entityPrototype); $this->sql = new Sql($adapter, $table); } . . .
TableGateway Class final public function getServiceLocator() {…} final public function setServiceLocator(…) {…} public function create() { return clone $this->entityPrototype; }}
AbstractTableGateway Class
Have to implement basic operations:
• select()
• insert()
• update()
• delete()
Example: User - Group Relation
class User extends Entity{ . . . protected $group; public function getGroup() { if (!is_object($this->group) && !empty($this->group)) { $this->group = $this->serviceLocator->get(‘Model\Group\Table’) ->fetchAll(array(‘groupID’ => $this->group))->current(); } return $this->group; }
Example: User - Group Relation
public function getGroupID() { return is_object($this->group) ? $this->group->getGroupID() : $this->group; }
public function setGroup($group) {…}}
Summary
• Modules - maintainability, reusability
• Network of simpler components - complex system
• Entity-based - focus on information
• Layered Data Model - thinnest database engine related layer, more portability
http://www.attilajenei.com [email protected]
Attila Jenei 2014.