Home arrow PHP arrow Page 2 - Singletons in PHP
PHP

Singletons in PHP


In this first part of a two-part tutorial, I will show you how the use of Singletons can generate a lot of coupling, testing and global-state related issues, which in most cases remain invisible to the eyes of the average developer.

Author Info:
By: Alejandro Gervasio
Rating: 4 stars4 stars4 stars4 stars4 stars / 7
December 05, 2011
TABLE OF CONTENTS:
  1. · Singletons in PHP
  2. · Building a Simple Domain Model

print this article
SEARCH DEVARTICLES

Singletons in PHP - Building a Simple Domain Model
(Page 2 of 2 )

As I noted previously, in this section I will set up a simple domain layer, composed of a few user entities, which will be massaged by the mapping layer created before.

The driving force of this model will be an abstract class, tasked with modeling generic entities along with an array collection, which will handle collections of domain objects as well. The definitions of the latter and its segregated interface are as follows:

(Singletons/Model/Collection/CollectionInterface.php)

<?php

namespace SingletonsModelCollection;
use SingletonsModel;

interface CollectionInterface extends Countable, IteratorAggregate, ArrayAccess
{
    public function toArray();

    public function clear();

    public function reset();

    public function add($key, ModelAbstractEntity $entity);
   
    public function get($key);

    public function remove($key);

    public function exists($key);
}

 

(Singletons/Model/Collection/EntityCollection.php)

<?php

namespace SingletonsModelCollection;
use SingletonsModel;

class EntityCollection implements CollectionInterface
{
    protected $_entities = array();

    /**
     * Constructor
     */
    public function  __construct(array $entities = array())
    {
        $this->_entities = $entities;
        $this->reset();
    }

    /**
     * Get the entities stored in the collection
     */
    public function toArray()
    {
        return $this->_entities;
    }

    /**
     * Clear the collection
     */
    public function clear()
    {
        $this->_entities = array();
    }
    
    /**
     * Rewind the collection
     */    
    public function reset()
    {
        reset($this->_entities);
    }

    /**
     * Add an entity to the collection
     */
    public function add($key, ModelAbstractEntity $entity) {
        return $this->offsetSet($key, $entity);
    }

    /**
     * Get from the collection the entity with the specified key
     */
    public function get($key)
    {
        return $this->offsetGet($key);
    }

    /**
     * Remove from the collection the entity with the specified key
     */
    public function remove($key)
    {
        return $this->offsetUnset($key);
    }

    /**
     * Check if the entity with the specified key exists in the collection
     */
    public function exists($key)
    {
        return $this->offsetExists($key);
    }

    /**
     * Count the number of entities in the collection
     */
    public function count()
    {
        return count($this->_entities);
    }

    /**
     * Get the external array iterator
     */
    public function getIterator()
    {
        return new ArrayIterator($this->toArray());
    }

    /**
     * Add an entity to the collection
     */
    public function offsetSet($key, $entity)
    {
        if (!$entity instanceof ModelAbstractEntity) {
            throw new InvalidArgumentException('The entity to be added to the collection must be an instance of EntityAbstract.');
        }
        if (!isset($key)) {
            $this->_entities[] = $entity;
        }
        else {
            $this->_entities[$key] = $entity;
        }
        return true;
    }
   
    /**
     * Remove an entity from the collection
     */
    public function offsetUnset($key)
    {
        if ($key instanceof ModelAbstractEntity) {
            $this->_entities = array_filter($this->_entities, function ($v) use ($key) {
                return $v !== $key;
            });
            return true;
        }
        if (isset($this->_entities[$key])) {
            unset($this->_entities[$key]);
            return true;
        }
        return false;
    }
   
    /**
     * Get the specified entity in the collection
     */
    public function offsetGet($key)
    {
        return isset($this->_entities[$key])
            ? $this->_entities[$key] : null;
    } 
   
    /**
     * Check if the specified entity exists in the collection
     */    
    public function offsetExists($key)
    {
        return isset($this->_entities[$key]);
    }
}

As shown in the above code fragment, the "EntityCollection" class is a simple wrapper for a PHP array, which allows it to manipulate collections of entities in a pretty straightforward fashion, thanks to the implementation of the "Countable," "IteratorAggregate" and "ArrayAccess" SPL interfaces. Since the underlying logic of this collection is quite simple to follow, please move on and check the classes you'll find below. The first one is responsible for defining the structure and metadata of generic entities, while the last one does the same for user objects:

(Singletons/Model/AbstractEntity.php)

<?php

namespace SingletonsModel;

abstract class AbstractEntity
{
    protected $_values = array();
    protected $_allowedFields = array();
   
    /**
     * Constructor
     */
    public function __construct(array $fields)
    {
        foreach ($fields as $name => $value) {
            $this->$name = $value;
        }
    }
   
    /**
     * Assign a value to the specified field via the corresponding mutator (if it exists);
     * otherwise, assign the value directly to the '$_values' array
     */
    public function __set($name, $value)
    {
        if (!in_array($name, $this->_allowedFields)) {
             throw new InvalidArgumentException("Setting the field '$name' is not allowed for this entity."); 
        }
        $mutator = 'set' . ucfirst($name);
        if (method_exists($this, $mutator) && is_callable(array($this, $mutator))) {
            $this->$mutator($value);          
        }
        else {
            $this->_values[$name] = $value;
        }       
    }
   
    /**
     * Get the value of the specified field (via the getter if it exists or by getting it from the $_values array)
     */
    public function __get($name)
    {
        if (!in_array($name, $this->_allowedFields)) {
            throw new InvalidArgumentException("Getting the field '$name' is not allowed for this entity.");
        }
        $accessor = 'get' . ucfirst($name);
        if (method_exists($this, $accessor) && is_callable(array($this, $accessor))) {
            return $this->$accessor;   
        }
        else if (isset($this->_values[$name])) {
            return $this->_values[$name];  
        }
        else {
            throw new InvalidArgumentException("The field '$name' has not been set for this entity yet.");   
        }
    }
         
    /**
     * Check if the specified field has been assigned to the entity
     */
    public function __isset($name)
    {
        if (!in_array($name, $this->_allowedFields)) {
            throw new InvalidArgumentException("The field '$name' is not allowed for this entity.");
        }
        return isset($this->_values[$name]);
    }

    /**
     * Unset the specified field from the entity
     */
    public function __unset($name)
    {
        if (!in_array($name, $this->_allowedFields)) {
            throw new InvalidArgumentException("Unsetting the field '$name' is not allowed for this entity.");
        }
        if (isset($this->_values[$name])) {
            unset($this->_values[$name]);
            return true;
        }
        throw new InvalidArgumentException("The field '$name' has not been set for this entity yet.");
    }

    /**
     * Get an associative array with the values assigned to the fields of the entity
     */
    public function toArray()
    {
        return $this->_values;
    }   

 

(Singletons/Model/User.php)

<?php

namespace SingletonsModel;

class User extends AbstractEntity
{  
    protected $_allowedFields = array('id', 'name', 'email');
   
    /**
     * Set the user's ID
     */
    public function setId($id)
    {
        if(!filter_var($id, FILTER_VALIDATE_INT, array('options' => array('min_range' => 1, 'max_range' => 999999)))) {
            throw new InvalidArgumentException('The ID of the user is invalid.');
        }
        $this->_values['id'] = $id;
    }
   
    /**
     * Set the user's full name
     */ 
    public function setName($name)
    {
        if (strlen($name) < 2 || strlen($name) > 32) {
            throw new InvalidArgumentException('The name of the user is invalid.');
        }
        $this->_values['name'] = $name;
    }
   
    /**
     * Set the user's email address
     */
    public function setEmail($email)
    {
        if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
            throw new InvalidArgumentException('The email address of the user is invalid.');
        }
        $this->_values['email'] = $email;
    }                   
}

At the risk of sounding redundant, again I'd like to stress that I used these classes in a few other PHP tutorials. Therefore, if you've read them, the classes should be pretty familiar to you. In short, they're simple data holders for the fields assigned to generic domain objects, and more specifically to user entities. Do you understand how they do their business already? Good.

With this basic domain model up and running, the next step is to put all of the previous classes and interfaces to work together to fetch some user entities from the underlying persistence mechanism (remember that in this specific case, it's a MySQL database).

The following script does exactly that:

<?php

use SingletonsLibraryLoaderAutoloader as Autoloader,
    SingletonsLibraryDatabaseMysqlAdapter as MysqlAdapter,
    SingletonsModelMapperUserMapper as UserMapper;

// include the autoloader
require_once __DIR__ . '/Library/Loader/Autoloader.php';
$autoloader = new Autoloader;

// create an instance of the user mapper
$userMapper = new UserMapper;

// fetch all users from the storage
$users = $userMapper->find();
foreach ($users as $user) {
    echo ' ID: ' . $user->id
         . ' Name: ' . $user->name
         . ' Email: ' . $user->email . '<br />' . PHP_EOL;
}

Done. If you run the above script (and provided that a "users" MySQL table has been created and populated with data about some users), it should work like a charm. So, what's the biggest flaw with this approach?

Well, as you'll recall from the previous section, the database adapter is a Singleton which is statically grabbed within the user mapper's constructor. This introduces a strong coupling effect between these two objects, making it hard to test them in isolation. It also makes the whole API confusing, as it's not clear to see from the client code what dependencies are used by the pertinent mapper. Additionally, the global and mutable state exposed by the adapter makes it even trickier to predict what behavior it will have when consumed by different client classes. Now, do you hear the alarm bells ringing in your head? I hope so.

Naturally, the question that arises here is: can the Singleton (and therefore its associated artifacts) be removed while keeping the functionality of the layers of this sample PHP framework untouched? Fortunately, the answer is a resounding yes! And contrary to what you might think, the solution can be implemented in a few easy steps.

Keep in mind, however, that the fine details of this topic will be covered in the final part of this tutorial.    

Final Thoughts

In this first installment of this tutorial, I used a pretty realistic example to show you how the use of Singletons can generate a lot of coupling, testing and global-state related issues, which in most cases, remain invisible to the eyes of the average developer.

If you take a closer look at the implementation of the database adapter (in this case, the infamous sinner), you'll realize that it effectively breaks the Single Responsibility Principle. Even though on the surface its responsibility is only to provide an API to work with the "mysqli" PHP extension, it performs yet another task: it controls object instantiation, which certainly has nothing to do with MySQL. Yes, this is another issue that Singletons hide silently under the hood.

But don't feel concerned, as in the last chapter I'll be showing you how to get rid of these effects by appealing to the combined forces of Dependency Injection, separation of concerns and some simple yet effective conventions.

Don't miss the final part!


DISCLAIMER: The content provided in this article is not warranted or guaranteed by Developer Shed, Inc. The content provided is intended for entertainment and/or educational purposes in order to introduce to the reader key ideas, concepts, and/or product reviews. As such it is incumbent upon the reader to employ real-world tactics for security and implementation of best practices. We are not liable for any negative consequences that may result from implementing any information covered in our articles or tutorials. If this is a hardware review, it is not recommended to open and/or modify your hardware.

blog comments powered by Disqus
PHP ARTICLES

- Removing Singletons in PHP
- Singletons in PHP
- Implement Facebook Javascript SDK with PHP
- Making Usage Statistics in PHP
- Installing PHP under Windows: Further Config...
- File Version Management in PHP
- Statistical View of Data in a Clustered Bar ...
- Creating a Multi-File Upload Script in PHP
- Executing Microsoft SQL Server Stored Proced...
- Code 10x More Efficiently Using Data Access ...
- A Few Tips for Speeding Up PHP Code
- The Modular Web Page
- Quick E-Commerce with PHP and PayPal
- Regression Testing With JMeter
- Building an Iterator with PHP

Watch our Tech Videos 
Dev Articles Forums 
 RSS  Articles
 RSS  Forums
 RSS  All Feeds
Write For Us 
Weekly Newsletter
 
Developer Updates  
Free Website Content 
Contact Us 
Site Map 
Privacy Policy 
Support 

Developer Shed Affiliates

 




© 2003-2017 by Developer Shed. All rights reserved. DS Cluster - Follow our Sitemap
Popular Web Development Topics
All Web Development Tutorials