Notes on PHP Design Patterns, Part 1: Generating Objects and Flexible Object Programming

Design patterns are streamlined solutions to common problems. They provide guidelines that can be translated into code. This entry will focus on patterns used for Generating Objects and Flexible Object Programming; presenting the problem, solution and basic examples. The structure relies on Matt Zandstra's second edition of 'PHP Objects, Patterns, and Practice', the 'ZEND PHP 5 Certification STUDY GUIDE', Jack D Herrington's IBM Developer Works 'Five common PHP design patterns' as well as various other sources referenced at the bottom of this page.  

Generating Objects

  • The Singleton Pattern:

Problem: Certain resources (i.e.: database connections) can not be duplicated.

Solution: Create a class that can only be instantiated from within itself, by adding a private constructor to prevent object creation and a static method used for instantiation.

Example:

 class Database {
       // Stores a single Database instance.
       private static $_singleton;

       // Private constructor, meaning this class can not be instantiated 
       // from the external scope.
       private function __construct() {}

       // Method used for creating a single instance of this class.
       public static function getConnection() {
              if ( is_null( self::$_singleton ) ) {
                   self::$_singleton = new Database();
              }
              return self::$_singleton;
       }
 }
  • Registry Pattern:

Problem: Any object can be used as Singleton, without it being written that way.

Solution: Create a class that caches instantiated objects of the same type.

Example:

 class Registry {
       // Stores single instances of the same class.
       private static $_register;

       // Method used for fetching an instance of the requested class.
       public static function get( $className ) {
              // Check if the requested object exists in the $_register
              // array. If it does, return the object, otherwise throw 
              // an exception.
       }

       // Method used for adding an object to the registry.
       public static function add( $item, $className = null ) {
             // Add $item object to the self::$_register array,
             // having the $className value as key. If key is null,
             // use the $item class name.
       }

       // Method used for checking if an object exists in the $_register
       // array.
       public static function exists( $className ) {
            // Check if the array key with value $className exists,
            // and return true. If it doesn't exist, return false.
       }
 }
  • Factory Method Pattern:

Problem: To avoid tight coupling, we require a class that creates objects.

Solution: Create a class that creates new instances of requested objects.

Example:

class User {
      public function __construct( $id ) {
             // Code
      }
}
class UserFactory {
      // Return a new instance of the 'User' class.
      public static function create( $id ) { 
             return new User( $id );
      }
}
  • Abstract Factory Pattern:

Problem: Similarly to the Factory Pattern, to avoid tight coupling, we require classes that create related sets of objects.

Solution: Create classes that create new instances of one or more specialised classes, that handle the same task in different ways (i.e.: configuration store, or user objects).

Example:

class ConfigurationStoreFactory {
      // Returns an instance of a class that can handle the requested
      // type of configuration store.
      public static function getStore( $type ) {
              switch ( strtolower( $type ) ) {
                     case "xml":
                          return new Configuration_XML();
                          break;
                     case "ini":
                          return new Configuration_INI();
                          break;
                     default:
                          throw new Exception( "Unknown store type." );
                          break;
              }
      }
}

// NOTE: Configuration_XML and Configuration_INI are functionally parallel,
// thus each should extend a common abstract Configuration class. Similary,
// the UserAdministrator and UserRegular below, should extend a common 
// abstract User class.

class UserFactory {
      // Return an instance of a class that can handle a user of the 
      // requested type.
      public static function create( $type ) { 
             switch ( strtolower( $type ) ) {
                    case "administrator":
                         return new UserAdministrator();
                         break;
                    case "regular":
                         return new UserRegular();
                         break;
                    default:
                         throw new Exception( "Unknown user type." );
                         break;
             }
      }
}

Flexible Object Programming

  • The Composite Pattern:

Problem: Parsing Tree-structure data requires discrimination between leaf-nodes (individual objects) and branches (compositions of objects). A composite object is an object consisting of one or more similar objects (i.e. a menu and it's sub-menu items).

Solution: Create a class with functionality to Add/Remove/Retrieve child objects (composite), inheriting an abstract component class. Create a class that represents the components within the composite class (leafs), inheriting the same abstract component class. The component class is an abstraction of all components, providing functionality for performing an Operation.

Example:

// Component class
class Item {
      public function getName() {}
}

// Leaf class
class MenuItem extends Item {
      public function addMenuItem() {}
      public function getMenuItems() {}
      public function removeMenuItem() {}
}

// Composite class
class Menu extends MenuItem {
}
  • The Decorator Pattern:

Problem: The Composite Pattern allows adding objects of the same type. The Decorator Pattern helps solve the problem of adding objects of different types. For example, a Window object may require ScrollBar and Border child objects. The composite action 'resize()' requires that both components have their 'resize()' function called.

Solution: Create a component object that defines the 'operation()' method (i.e. resize() ). Create a composite object that provides functionality for Adding/Removing/Retrieving child objects. Create separate component objects that extend the parent component object.

Example:

      class WindowItemObject {
            public function resize() {}
      }

      class Window extends WindowItemObject {
            public function addItem() {}
            public function getItems() {}
            public function removeItem() {}
            public function resize() {
                   // Iterates all child objects, and calls the resize() function.
            }
      }

      class ScrollBar extends WindowItemObject {

      }
      class Border extends WindowItemObject {

      }

  • The Facade Pattern:

Problem: Parts of the system may become more and more difficult to maintain and use, thus making it more difficult for client coders to access methods that achieve clear ends.

Solution: Create a class that provides a single entry point for a subsystem.

Example:

      // Old functional code
      function getProductName( $id ) {}
      function getProductTitle( $id ) {}
      
      // Entry point for the old code
      class ProductFacade {
            private $id = null;
            public function __construct( $id ) {
                   $this->id = $id;
            }
            public function getName() {
                   return getProductName( $this->id );
            }
            public function getTitle() {
                   return getProductTitle( $this->id );
            }
      }

Extra references:

Composite Pattern

Decorator Pattern

Leave a Reply

Your email address will not be published. Required fields are marked *