Hot File

How to Implementing a Unit of Work – Handling Domain Objects through a Transactional Model with PHP

View: 2643    Dowload: 0   Comment: 0   Post by: hanhga  
Author: none   Category: Php&mySql   Fields: Other

10 point/2 review File has been tested

Admittedly the question is rhetorical. In fact, it’s feasible to wrap collections of domain objects inside a fairly flexible business transactional model and just perform several database writes/deletes in one go, therefore avoiding having to break down the process into more atomic and expensive database calls, which always lead to the session-per-operation antipattern.

Introduction

Admittedly the question is rhetorical. In fact, it’s feasible to wrap collections of domain objects inside a fairly flexible business transactional model and just perform several database writes/deletes in one go, therefore avoiding having to break down the process into more atomic and expensive database calls, which always lead to the session-per-operation antipattern. Moreover, this transaction-based mechanism rests on the academic formalities of a design pattern commonly known as Unit of Work (UOW), and its implementation in several popular enterprise-level packages, such as Hibernate, is quite prolific and prosperous.

On the flip side, PHP is, for obvious reasons, still elusive at having a variety of UOWs running in production, excepting in a few well-trusted libraries like Doctrine and RedBeanPHP, which use the pattern’s forces at disparate levels in order to process and coordinate operations on entities. Despite this, it would be certainly pretty educational to take a closer look at the benefits a UOW provides, that way you can see if they are something that may meet your requirements.

Registering Domain Objects with a Unit of Work

In his book Patterns of Enterprise Application Architecture, Martin Fowler discusses two mainstream approaches that can be followed when it comes to implementing a UOW: the first makes the UOW directly responsible for registering or queuing domain objects for insertion, update, or deletion, and the second shifts this responsibility over to the domain objects themselves.

In this case, since I’d like to have the domain model only encapsulating my business logic and remain agnostic about any form of persistence that may exist further down in other layers, I’m going to just stick to the commandments of the first option. In either case, you’re free to pick the approach you feel will fit the bill the best.

A lightweight implementation of a UOW might look like this:

<?php
namespace ModelRepository;
use ModelEntityInterface;

interface UnitOfWorkInterface
{
    public function fetchById($id);
    public function registerNew(EntityInterface $entity);
    public function registerClean(EntityInterface $entity);
    public function registerDirty(EntityInterface $entity);
    public function registerDeleted(EntityInterface $entity);
    public function commit();
    public function rollback();
    public function clear();
}
<?php
namespace ModelRepository;
use MapperDataMapperInterface,
    LibraryStorageObjectStorageInterface,
    ModelEntityInterface;

class UnitOfWork implements UnitOfWorkInterface
{
    const STATE_NEW     = "NEW";
    const STATE_CLEAN   = "CLEAN";
    const STATE_DIRTY   = "DIRTY";
    const STATE_REMOVED = "REMOVED";
    
    protected $dataMapper;
    protected $storage;

    public function __construct(DataMapperInterface $dataMapper, ObjectStorageInterface $storage) {
        $this->dataMapper = $dataMapper;
        $this->storage = $storage;
    }
    
    public function getDataMapper() {
        return $this->dataMapper;
    }
    
    public function getObjectStorage() {
        return $this->storage;
    }
    
    public function fetchById($id) {
        $entity = $this->dataMapper->fetchById($id);
        $this->registerClean($entity);
        return $entity;
    }
    
    public function registerNew(EntityInterface $entity) {
        $this->registerEntity($entity, self::STATE_NEW);
        return $this;
    }
    
    public function registerClean(EntityInterface $entity) {
        $this->registerEntity($entity, self::STATE_CLEAN);
        return $this;
    }
    
    public function registerDirty(EntityInterface $entity) {
        $this->registerEntity($entity, self::STATE_DIRTY);
        return $this;
    }
    
    public function registerDeleted(EntityInterface $entity) {
        $this->registerEntity($entity, self::STATE_REMOVED);
        return $this;
    }
    
    protected function registerEntity($entity, $state = self::STATE_CLEAN) {
        $this->storage->attach($entity, $state);
    }
    
    public function commit() {
        foreach ($this->storage as $entity) {
            switch ($this->storage[$entity]) {
                case self::STATE_NEW:
                case self::STATE_DIRTY: 
                    $this->dataMapper->save($entity);
                    break;
                case self::STATE_REMOVED:
                    $this->dataMapper->delete($entity);
            }
        }
        $this->clear();
    }
    
    public function rollback() {
        // your custom rollback implementation goes here
    }
    
    public function clear() {
        $this->storage->clear();
        return $this;
    }  
}

It should be clear to see that a UOW is nothing but plain, in-memory object storage which keeps track of which domain objects should be scheduled for insertion, update, and removal. In short, the convention could be boiled down to something along these lines: domain objects that need to be added to the storage will be registered “NEW”; those being updated will be marked “DIRTY”; the ones flagged “REMOVED” will be… yep, dropped from the database. In addition, any object registered “CLEAN” will be kept frozen and safe in memory until the client code explicitly requests to modify its associated state.

Of course, the method that performs these persistence-related operations in just one single transaction iscommit(), which exploits the functionality of an still undefined data mapper to get access to the persistence layer. It would be even easier for you to understand the UOW’s inner workings if I show you the implementation of the collaborators injected in its constructor, so here’s the components that compose the object storage module:

<?php
namespace LibraryStorage;

interface ObjectStorageInterface extends Countable, Iterator, ArrayAccess
{
    public function attach($object, $data = null);
    public function detach($object);
    public function clear();
}
<?php
namespace LibraryStorage;

class ObjectStorage extends SplObjectStorage implements ObjectStorageInterface
{
    public function clear() {
        $tempStorage = clone $this;
        $this->addAll($tempStorage);
        $this->removeAll($tempStorage);
        $tempStorage = null;
    } 
}

In this case in particular, I decided to use a slightly-customized implementation of the SplObjectStorageclass for registering domain objects without much fuss along with their related states with the UOW, even though pretty much the same can be also achieved using plain arrays. Again, it’s up to you to have the domain objects registered by using the method that best accommodates your needs.

With the custom ObjectStorage class in place, let’s take a look at the implementation of the aforementioned data mapper:

<?php
namespace Mapper;
use ModelEntityInterface;

interface DataMapperInterface
{
    public function fetchById($id);
    public function fetchAll(array $conditions = array());
    public function insert(EntityInterface $entity);
    public function update(EntityInterface $entity);
    public function save(EntityInterface $entity);
    public function delete(EntityInterface $entity);
}
<?php
namespace Mapper;
use LibraryDatabaseDatabaseAdapterInterface,
    ModelCollectionEntityCollectionInterface,   
    ModelEntityInterface;

abstract class AbstractDataMapper implements DataMapperInterface
{
    protected $adapter;
    protected $collection;
    protected $entityTable;
    
    public function __construct(DatabaseAdapterInterface $adapter, EntityCollectionInterface $collection, $entityTable = null) {
        $this->adapter = $adapter;
        $this->collection = $collection;
        if ($entityTable !== null) {
            $this->setEntityTable($entityTable);
        }
    }
        
    public function setEntityTable($entityTable) {
        if (!is_string($table) || empty($entityTable)) {
            throw new InvalidArgumentException(
                "The entity table is invalid.");
        }
        $this->entityTable = $entityTable;
        return $this;
    }
    
    public function fetchById($id) {
        $this->adapter->select($this->entityTable, 
            array("id" => $id));
        if (!$row = $this->adapter->fetch()) {
            return null; 
        }
        return $this->loadEntity($row);
    }
    
    public function fetchAll(array $conditions = array()) {
        $this->adapter->select($this->entityTable, $conditions);
        $rows = $this->adapter->fetchAll();
        return $this->loadEntityCollection($rows);
    }
    
    public function insert(EntityInterface $entity) {
        return $this->adapter->insert($this->entityTable,
            $entity->toArray());
    }
    
    public function update(EntityInterface $entity) {
        return $this->adapter->update($this->entityTable,
            $entity->toArray(), "id = $entity->id");
    }
    
    public function save(EntityInterface $entity) {
        return !isset($entity->id) 
            ? $this->adapter->insert($this->entityTable,
                $entity->toArray()) 
            : $this->adapter->update($this->entityTable,
                $entity->toArray(), "id = $entity->id");   
    }
    
    public function delete(EntityInterface $entity) {
        return $this->adapter->delete($this->entityTable,
            "id = $entity->id");
    }
    
    protected function loadEntityCollection(array $rows) {
        $this->collection->clear();
        foreach ($rows as $row) {
            $this->collection[] = $this->loadEntity($row);
        }
        return $this->collection;
    }
    
    abstract protected function loadEntity(array $row);
}

The AbstractDataMapper puts behind a pretty standard API the bulk of logic required for pulling domain objects in and out of the database. To make things even easier, it’d be also nice to derivate a refined implementation of it, that way we could easily test the UOW with a few sample user objects. Here’s how this extra mapping subclass looks:

<?php
namespace Mapper;
use ModelUser;

class UserMapper extends AbstractDataMapper
{
    protected $entityTable = "users";
    
    protected function loadEntity(array $row) {
        return new User(array(
            "id"    => $row["id"], 
            "name"  => $row["name"], 
            "email" => $row["email"],
            "role"  => $row["role"]));
    }
}

At this point we just could put our hands on the UOW and see if its transactional schema delivers what it promises. But before we do, first off we really should drop at least a few domain objects in memory. That way, we can get them neatly registered with the UOW. So let’s now define a primitive Domain Model which will be charged with supplying the objects in question.

Defining a basic Domain Model

Frankly speaking, there are several ways to implement a functional Domain Model (most likely there exists one per developer living and breathing out there). Since in this case I want the process to be both painless and short, the model I’ll be using for testing the UOW will be composed just of a prototypical entity class, along with a derivative, which will be charged with spawning basic users objects:

<?php
namespace Model;

interface EntityInterface
{
    public function setField($name, $value);
    public function getField($name);
    public function fieldExists($name);
    public function removeField($name);
    public function toArray();      
}
<?php
namespace Model;

abstract class AbstractEntity implements EntityInterface
{
    protected $fields = array(); 
    protected $allowedFields = array(); 

    public function __construct(array $fields = array()) {
        if (!empty($fields)) {
            foreach ($fields as $name => $value) {
                $this->$name = $value;
            } 
        }
    }
    
    public function setField($name, $value) {
        return $this->__set($name, $value);
    }
    
    public function getField($name) {
        return $this->__get($name);
    }
    
    public function fieldExists($name) {
        return $this->__isset($name);
    }
    
    public function removeField($name) {
        return $this->__unset($name);
    }
    
    public function toArray() {
        return $this->fields;
    }
             
    public function __set($name, $value) {
        $this->checkAllowedFields($name);
        $mutator = "set" . ucfirst(strtolower($name));
        if (method_exists($this, $mutator) && 
            is_callable(array($this, $mutator))) {
            $this->$mutator($value);
        }
        else {
            $this->fields[$name] = $value;
        }
        return $this;                 
    }
    
    public function __get($name) {
        $this->checkAllowedFields($name);
        $accessor = "get" . ucfirst($name);
        if (method_exists($this, $accessor) &&
            is_callable(array($this, $accessor))) {
            return $this->$accessor();
        }
        if (!$this->__isset($name)) {
            throw new InvalidArgumentException(
                "The field '$name' has not been set for this entity yet.");
        }
        return $this->fields[$name];
    }
    
    public function __isset($name) {
        $this->checkAllowedFields($name);
        return isset($this->fields[$name]);
    }
    
    public function __unset($name) {
        $this->checkAllowedFields($name);
        if (!$this->__isset($name)) {
            throw new InvalidArgumentException(
                "The field "$name" has not been set for this entity yet.");
        }
        unset($this->fields[$name]);
        return $this;
    }
    
    protected function checkAllowedFields($field) {
        if (!in_array($field, $this->allowedFields)) {
            throw new InvalidArgumentException(
                "The requested operation on the field '$field' is not allowed for this entity.");
        }
    }
}
<?php
namespace Model;

class User extends AbstractEntity
{
    const ADMINISTRATOR_ROLE = "Administrator";
    const GUEST_ROLE         = "Guest";
    
    protected $allowedFields = array("id", "name", "email", "role");
    
    public function setId($id) {
        if (isset($this->fields["id"])) {
            throw new BadMethodCallException(
                "The ID for this user has been set already.");
        }
        if (!is_int($id) || $id < 1) {
            throw new InvalidArgumentException(
                "The user ID is invalid.");
        }
        $this->fields["id"] = $id;
        return $this;
    }
    
    public function setName($name) {
        if (strlen($name) < 2 || strlen($name) > 30) {
            throw new InvalidArgumentException(
                "The user name is invalid.");
        }
        $this->fields["name"] = htmlspecialchars(trim($name),
            ENT_QUOTES);
        return $this;
    }
    
    public function setEmail($email) {
        if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
            throw new InvalidArgumentException(
                "The user email is invalid.");
        }
        $this->fields["email"] = $email;
        return $this;
    }
    
    public function setRole($role) {
        if ($role !== self::ADMINISTRATOR_ROLE &&
            $role !== self::GUEST_ROLE) {
            throw new InvalidArgumentException(
                "The user role is invalid.");
        }
        $this->fields["role"] = $role;
        return $this;
    }
}

While the implementations of the AbstractEntity and User classes might look complex at first glance, I assure this is just a fuzzy impression. In fact, the former is a skeletal wrapper for some typical PHP magic methods, while the latter encapsulates some straightforward mutators, in order to assign the appropriate values to the fields of generic user objects.

With these domain classes already doing their business in relaxed insulation, let’s now do the last building block of the model. In reality, this is an optional class which can be skipped over if the situation warrants, and its responsibility is just to wrap collections of entities. Its implementation is as following:

<?php
namespace ModelCollection;
use ModelEntityInterface;

interface EntityCollectionInterface extends Countable, ArrayAccess, IteratorAggregate 
{
    public function add(EntityInterface $entity);
    public function remove(EntityInterface $entity);
    public function get($key);
    public function exists($key);
    public function clear();
    public function toArray();
}
<?php
namespace ModelCollection;
use ModelEntityInterface;
    
class EntityCollection implements EntityCollectionInterface
{
    protected $entities = array();
    
    public function __construct(array $entities = array()) 
    {
        if (!empty($entities)) {
            $this->entities = $entities;
        }
    }
    
    public function add(EntityInterface $entity) {
        $this->offsetSet($entity);
    }
    
    public function remove(EntityInterface $entity) {
        $this->offsetUnset($entity);
    }
    
    public function get($key) {
        $this->offsetGet($key);
        
    }
    
    public function exists($key) {
        return $this->offsetExists($key);
    }
    
    public function clear() {
        $this->entities = array();
    }
    
    public function toArray() {
        return $this->entities;
    }
    
    public function count() {
        return count($this->entities);
    }
    
    public function offsetSet($key, $entity)
    {
        if (!$entity instanceof EntityInterface) {
            throw new InvalidArgumentException(
                "Could not add the entity to the collection.");
        }
        if (!isset($key)) {
            $this->entities[] = $entity;
        }
        else {
            $this->entities[$key] = $entity;
        }
    }
    
    public function offsetUnset($key) {
        if ($key instanceof EntityInterface) {
            $this->entities = array_filter($this->entities, 
                function ($v) use ($key) {
                    return $v !== $key;
                });
        }
        else if (isset($this->entities[$key])) {
            unset($this->entities[$key]);
        }
    }
    
    public function offsetGet($key) {
        if (isset($this->entities[$key])) {
            return $this->entities[$key];
        }
    }
    
    public function offsetExists($key) {
        return $key instanceof EntityInterface 
            ? array_search($key, $this->entities) 
            : isset($this->entities[$key]);
    }
    
    public function getIterator() {
        return new ArrayIterator($this->entities);
    }
}

At this point we’ve managed to create a primitive domain model, which certainly we can use for engendering user objects without a major hassle. In doing do, we have a real chance to see if the UOW is actually the functional component it seems to be when it comes to persisting multiple entities in the database as one single transaction.

Putting the UOW Under Test

If you’ve reached this point of the article, you probably feel like you’re being pulled in opposite directions, wondering if all of the hard up front work required in writing a bunch of interfaces and classes was really worth it. In fact, it was. Moreover, if you’re still skeptical, make sure check the following code snippet, which shows how to put the UOW to work in sweet synchrony with some naïve user objects:

<?php    
require_once __DIR__ . "/Library/Loader/Autoloader.php";
$autoloader = new Autoloader;
$autoloader->register();

$adapter = new PdoAdapter("mysql:dbname=test", "myfancyusername",
    "myhardtoguesspassword");

$unitOfWork = new UnitOfWork(new UserMapper($adapter,
    new EntityCollection), new ObjectStorage);

$user1 = new User(array("name" => "John Doe", 
    "email" => "john@example.com"));
$unitOfWork->registerNew($user1);

$user2 = $unitOfWork->fetchById(1);
$user2->name = "Joe";
$unitOfWork->registerDirty($user2);

$user3 = $unitOfWork->fetchById(2);
$unitOfWork->registerDeleted($user3);

$user4 = $unitOfWork->fetchById(3);
$user4->name = "Julie";

$unitOfWork->commit();

Leaving aside some irrelevant details, such as assuming there’s effectively a PDO adapter living somewhere, the driving logic of the earlier script should be fairly easy to assimilate. Simply put, it shows off how to get things rolling with the UOW, which drags in some user objects from the database and queues them for insertion, update, and deletion by using the corresponding registering methods. At the end of the process, commit() just loops internally over the registered objects and performs the proper operations all in one go.

While in a standard implementation a UOW does expose the typical set of registering methods that we’d expect to see, its formal definition doesn’t provide any kind of finder. In this case, however, I decided intentionally to implement a generic one so you can see more clearly how to pull in objects from storage and in turn register them with the UOW without struggling with the oddities of a standalone, closer-to-the domain structure, such as a Repository or even an overkill Service.

How to Implementing a Unit of Work – Handling Domain Objects through a Transactional Model with PHP

How to Implementing a Unit of Work – Handling Domain Objects through a Transactional Model with PHP Posted on 19-04-2016  Admittedly the question is rhetorical. In fact, it’s feasible to wrap collections of domain objects inside a fairly flexible business transactional model and just perform several database writes/deletes in one go, therefore avoiding having to break down the process into more atomic and expensive database calls, which always lead to the session-per-operation antipattern. 5/10 2643

Comment:

To comment you must be logged in members.

Files with category

  • How to Picking the Brains of Your Customers with Microsoft’s Text Analytics

    View: 1735    Download: 0   Comment: 0   Author: none  

    How to Picking the Brains of Your Customers with Microsoft’s Text Analytics

    Category: Php&mySql
    Fields: Other

    5/1 review
    With the explosion of machine learning services in recent years, it has become easier than ever for developers to create “smart apps”. In this article, I’ll introduce you to Microsoft’s offering for providing machine-learning capabilities to apps.

  • How to MySqli Tutorial PHP MySqli Extension

    View: 206    Download: 0   Comment: 0   Author: none  

    How to MySqli Tutorial PHP MySqli Extension

    Category: Php&mySql
    Fields: Other

    0/0 review
    PHP provides three api to connect mysql Database.

  • Make Laravel Artisan Commands

    View: 186    Download: 0   Comment: 0   Author: none  

    Make Laravel Artisan Commands

    Category: Php&mySql
    Fields: Other

    0/0 review
    Artisan is the command line tool used in Laravel framework. It offers a bunch of useful command that can help you develop application quickly. Apart from Artisan available commands, you can create your own custom commands to improve your workflow.

  • Check if a Number is a Power of 2

    View: 165    Download: 0   Comment: 0   Author: none  

    Check if a Number is a Power of 2

    Category: Php&mySql
    Fields: Other

    2.25/2 review
    How to check if a number is a power of 2. To understand this question, let’s take some example.

  • Concatenate columns in MySql

    View: 225    Download: 0   Comment: 0   Author: none  

    Concatenate columns in MySql

    Category: Php&mySql
    Fields: Other

    0/1 review
    Artisan is the command line tool used in Laravel framework. It offers a bunch of useful command that can help you develop application quickly. Apart from Artisan available commands, you can create your own custom commands to improve your workflow

  • How to Query NULL Value in MySql

    View: 159    Download: 0   Comment: 0   Author: none  

    How to Query NULL Value in MySql

    Category: Php&mySql
    Fields: Other

    5/1 review
    Misunderstanding NULL is common mistake beginners do while writing MySql query. While quering in MySql they compare column name with NULL. In MySql NULL is nothing or in simple word it isUnknown Value so if you use comparison operator for NULL values...

  • How to Abstract Class in PHP

    View: 194    Download: 0   Comment: 0   Author: none  

    How to Abstract Class in PHP

    Category: Php&mySql
    Fields: Other

    0/0 review
    What is an abstract class in PHP and when to use an abstract class in your application. In this tutorial, we’ll learn about abstract class and their implementation.

  • Use Enums in Rails for Mapped Values

    View: 188    Download: 0   Comment: 0   Author: none  

    Use Enums in Rails for Mapped Values

    Category: Php&mySql
    Fields: Other

    5/1 review
    When I worked in a call center, we used to mark cases with different statuses. This allowed upper management to get a handle on where cases stood, what the bottlenecks were and flow of calls. Thankfully it has been a long time since I worked in a...

 
Newsletter Email

File suggestion for you

File top downloads

logo codetitle
Codetitle.com - library source code to share, download the file to the community
Copyright © 2015. All rights reserved. codetitle.com Develope by Vinagon .Ltd