Home arrow PHP arrow Page 2 - PHP Liskov Substitution Principle

Implementing a Composite View Rendering Layer - PHP

This PHP programming tutorial focuses on how to utilize the Liskov Substitution Principle (LSP) to develop better OOP applications.

TABLE OF CONTENTS:
  1. PHP Liskov Substitution Principle
  2. Implementing a Composite View Rendering Layer
  3. Violating the Liskov Substitution Principle
By: Alejandro Gervasio
Rating: starstarstarstarstar / 1
June 29, 2011

print this article
SEARCH DEV SHED

TOOLS YOU CAN USE

advertisement
 

One of the best ways to understand the relevance of the Liskov Substitution Principle is by showcasing a case where its unintentional violation makes an application crash noisily. Recently I went through a situation that made me learn this lesson the hard way. Simply put, all that I needed to do was add a small view rendering layer to a client's existing framework, which in turn allowed them to create master web page layouts and partials (views placed inside of other views) in a straightforward fashion.

I normally use Zend Framework for my work, but given the simplicity of this particular project I thought a good approach to tackle the problem would be to appeal to the functionality of the Composite View pattern. Given that, I started looking into some libraries written in the past and came up with a simple hierarchy of composite classes, whose abstract parent looked like this (edited for the sake of brevity):    

(App/View/AbstractView.php)<?phpnamespace App\View;abstract class AbstractView
{
    protected $_values = array();
    protected $_template;

    /**
     * Constructor
     */
    public function  __construct($template = null)
    {
        if ($template !== null) {
            $this->setTemplate($template);
        }
    }    /**
     * Set the view template
     */
    public function setTemplate($template)
    {
        $template = 'templates' . DIRECTORY_SEPARATOR . $template;
        if (!file_exists($template) || !is_readable($template)) {
            throw new \InvalidArgumentException('The specified template is invalid.');
        }
        $this->_template = $template;
    }    /**
     * Get the view template
     */
    public function getTemplate()
    {
        return $this->_template;
    }    /**
     * Assign a value to the specified field of the view via the corresponding mutator (if it exists);
     * otherwise, assign the value directly to the '$_values' protected array
     */
    public function __set($name, $value)
    {  
        $mutator = 'set' . ucfirst(strtolower($name));
        if (method_exists($this, $mutator) && is_callable(array($this, $mutator))) {
            $this->$mutator($value);
        }
        else {
            $this->_values[$name] = $value;
        }
    }    /**
     * Get the value assigned to the specified field of the view via the corresponding getter (if it exists);
     * otherwise, get the value directly from the '$_values' protected array
     */
    public function __get($name)
    {
        $accessor = 'get' . ucfirst(strtolower($name));
        if (method_exists($this, $accessor) && is_callable(array($this, $accessor))) {
            return $this->$accessor;
        }
        if (isset($this->_values[$name])) {
            return $this->_values[$name];
        }
        throw new \InvalidArgumentException('The field ' . $name . ' has not been set for this view yet.');
    }    /**
     * Check if the specified field has been assigned to the view
     */
    public function __isset($name)
    {
        return isset($this->_values[$name]);
    }

    /**
     * Unset the specified field from the view
     */
    public function __unset($name)
    {
        if (isset($this->_values[$name])) {
            unset($this->_values[$name]);
            return true;
        }
        return false;
    }    /**
     * Render the template
     */
    protected function _doRender()
    {
        if ($this->_template !== null) {
            extract($this->_values);
            ob_start();
            include $this->_template;
            return ob_get_clean();
        }
        return '';
    }    /**
     * Get an associative array with the values assigned to the fields of the view
     */
    public function toArray()
    {
        return $this->_values;
    }    abstract public function addView(AbstractView $view);    abstract public function addViews(array $views);
   
    abstract public function removeView(AbstractView $view);
   
    abstract public function render();
}

Even though the implementation of this base class seems to be rather complex, its functionality is simply limited to associating a template file (usually an entire web page or a section of it) to a view object, which can be assigned/removed dynamically a bunch of properties, thanks to the magic of the “__set()”, “__isset()”, “__unset()” and “__get()” PHP methods. Finally, the template can be internally rendered via the protected “_doRender()” method, which uses some output buffering to get the job done with minor hassles.

With this abstraction living comfortably on top of the hierarchy, the next step that I needed to take was to spawn a couple of concrete subclasses, capable of dealing with composite views and partials respectively (notice that set of abstract methods declared by the parent for this purpose). Again, I remembered that the task was pretty easy to accomplish and ended up creating a pair of derivatives, where the implementation of the first one was as follows: 

(App/View/CompositeView.php)<?phpnamespace App\View;class CompositeView extends AbstractView
{
    protected $_views = array();    /**
     * Add a view
     */
    public function addView(AbstractView $view)
    {
        if (!in_array($view, $this->_views, true)) {
            $this->_views[] = $view;
            return true;
        }
        return false;
    }     /**
     * Add multiple views
     */
    public function addViews(array $views)
    {
        foreach ($views as $view) {
            $this->addView($view);
        }
    }    /**
     * Remove a view
     */
    public function removeView(AbstractView $view)
    {
        $this->_views = array_filter($this->_views, function ($v) use ($view) {
            return $v !== $view;
        });
        return true;
    }    /**
     * Render both the injected views and the current one
     */
    public function render()
    {
        $output = '';
        // render the injected views
        if (!empty($this->_views)) {
            foreach ($this->_views as $_view) {
                $output .= $_view->render();
            }
        }
        // render the current view
        $output .= $this->_doRender();
        return $output;
    }
}

At this point, the scenario was looking even better, as I had managed to set up a composite view class capable of rendering itself along with other views attached via the “addView() and “addViews()” methods. To get things up and running though, I needed to derive yet another subclass, tasked with rendering partials. So, I put my hands back on the keyboard and built the following one:

(App/View/PartialView.php)<?phpnamespace App\View;class PartialView extends AbstractView
{
    /**
     * Add a view (throws an exception)
     */
    public function addView(AbstractView $view)
    {
        throw new \InvalidArgumentException('A partial view cannot aggregate another view.');
    }    /**
     * Add multiple views (throws an exception)
     */
    public function addViews(array $views)
    {
        throw new \InvalidArgumentException('A partial view cannot aggregate other views.');
    }    /**
     * Remove a view (throws an exception)
     */
    public function removeView(AbstractView $view)
    {
        throw new \InvalidArgumentException('A partial view cannot remove another view.');
    }    /**
     * Render the partial view
     */
    public function render()
    {
        return $this->_doRender();
    }
}

      
At first I felt a little uncomfortable making the “addView()”, “addViews()” and “removeView()” methods of the earlier subclass to throw some exceptions, as this was a clear sign that something wasn’t exactly right - but since the derivative was responsible for rendering partials, I quickly changed my mind and forgot about this potential issue.

With this hierarchy of composite view classes ready to go, the last thing that I needed to do was create a few PHP templates, and check if the classes in question were that functional so that they could fit my client’s requirements. Thus, I defined three trivial templates, which looked like this:

(App/Templates/header.php)<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <title>My sample web page</title>
</head>
<body>
<div id="header">
    <h2><?php echo $content;?></h2>
</div>
 
(App/Templates/body.php)<div id="content">
    <h2><?php echo $content;?></h2>
</div> (App/Templates/footer.php)<div id="footer">
    <h2><?php echo $content;?></h2>
</div>
</body>
</html>

Finally, after doing some unit testing, the composite view layer was ready for some real action! Since I was feeling a bit smug with the neat results achieved so far, I simply built up a script to put the composite classes to work. The script was similar to the one below (I decided to omit the autoloader’s implementation to keep the whole code uncluttered and easier to read):  

<?phpuse App\View\CompositeView as CompositeView,
    App\View\PartialView as PartialView;// include the autoloader
require_once 'Autoloader.php';
Autoloader::getInstance();// create a composite view
$masterLayout = new CompositeView;// create some partials
$header = new PartialView('header.php');
$header->content = 'This is the header section';$body = new PartialView('body.php');
$body->content = 'This is the body section';$footer = new PartialView('footer.php');
$footer->content ='This is the footer section';// add the partials to the composite view
$masterLayout->addViews(array(
    $header,
    $body,
    $footer
));// render the partials in one go
echo $masterLayout->render();

As seen above, my testing script firstly spawned a composite view object, which was fed in turn with three different partials, containing the “header.php”, “body.php” and “footer.php” templates defined before. After running the pertaining script in the server, it nicely rendered the following output:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <title>My sample web page</title>
</head>
<body>
<div id="header">
    <h2>This is the header section</h2>
</div>
<div id="content">
    <h2>This is the body section</h2>
</div>
<div id="footer">
    <h2>This is the footer section</h2>
</div>
</body>
</html>

That worked like a charm. Even when there was still a little voice in my head warning me that the implementation of the earlier “PartialView” class wasn’t entirely correct, in general I was pretty satisfied with the functionality of my composite view layer. Therefore, I contacted the client with the good news, sent him the source files and finally got my payment.

Everything was goin fine until my client called me back a few weeks later telling me that he also needed to implement a small application for building some web page layouts in an automated way, which would use my composite view layer.

He asked me, "Can this be done in a timely manner?" Of course I answered with a resounding: "yes!" Then I launched my code editor and started writing a simple layout builder class. To my surprise, the task would be a pain, due to the wrong hierarchy of classes built in the past.    



 
 
>>> More PHP Articles          >>> More By Alejandro Gervasio
 

blog comments powered by Disqus
escort Bursa Bursa escort Antalya eskort
   

PHP ARTICLES

- Hackers Compromise PHP Sites to Launch Attac...
- Red Hat, Zend Form OpenShift PaaS Alliance
- PHP IDE News
- BCD, Zend Extend PHP Partnership
- PHP FAQ Highlight
- PHP Creator Didn't Set Out to Create a Langu...
- PHP Trends Revealed in Zend Study
- PHP: Best Methods for Running Scheduled Jobs
- PHP Array Functions: array_change_key_case
- PHP array_combine Function
- PHP array_chunk Function
- PHP Closures as View Helpers: Lazy-Loading F...
- Using PHP Closures as View Helpers
- PHP File and Operating System Program Execut...
- PHP: Effects of Wrapping Code in Class Const...

Developer Shed Affiliates

 


Dev Shed Tutorial Topics: