fede.carg / wiki

Refactoring the Front Controller of the Zend Framework

From Federico Cargnelutti

Jump to: navigation, search

Contents

Introduction

One of the most fundamental decision in object design is deciding where to put responsibilities. No one, and I mean no one, gets it right the first time. That's why refactoring is so important. As Kent Beck puts it, refactoring is the process of taking a system and adding to its value, not by changing its behaviour but by giving it more of these qualities that enable us to continue developing at speed.

Extract Class Refactoring

You've probably heard that a class should handle a few clear responsibilities. In practice, classes grow. You add some operations here, a bit of data there. You add a responsibility to a class feeling that it's not worth a separate class, but as that responsibility grows and breeds, the class becomes too complicated. Extract Class is a common technique for moving features between objects.

Extract Class:

"You have one class doing work that should be done by two."

Zend Framework

Zend Framework organizes code in a project structure and puts the project files into different directory structures:

  1. MVC directory structure
  2. Modular directory structure

The framework allows users to choose between one or the other. My main goal is to move this responsibility away from the Zend_Controller_Front class. To do this I need to use the Extract Class to alter the internal structure of the class without changing its external behaviour. This refactoring will reduce the complexity of the Front Controller and increase its flexibility. Also, it will allow users to define custom directory names and paths.

Mechanics

First, I need to identify the methods I want to extract from the Zend_Controller_Front class:

addControllerDirectory()
setControllerDirectory()
getControllerDirectory()
removeControllerDirectory()
addModuleDirectory()
setModuleControllerDirectoryName()
getModuleControllerDirectoryName()

Then, I need to create a set of classes to express the split-off responsibilities:

Zend_Controller_Directory_Abstract
Zend_Controller_Directory_Exception
Zend_Controller_Directory_Standard

And finally, I need to use Move Field and Move Method to move fields and methods over from Zend_Controller_Front to the new classes.

abstract class Zend_Controller_Directory_Abstract
{
    public function setControllerDirectoryName()
    public function getControllerDirectoryName()
    public function setControllerDirectory()
    public function getControllerDirectory()
    public function addControllerDirectory()
    public function removeControllerDirectory()
}
 
class Zend_Controller_Directory_Standard
  extends Zend_Controller_Directory_Abstract
{
    public function addApplicationDirectory()
    public function addModuleDirectory()
}

Front Controller

All I need to do now is add a setter and getter method to the Zend_Controller_Front class and inject the object. For the sake of this example, I'll assume that I can add additional methods to the Zend_Controller_Front class.

class Zend_Controller_Front
{
    /**
     * Instance of Zend_Controller_Directory_Abstract
     * @var Zend_Controller_Directory_Abstract
     */
    protected $_directory = null;
 
    /**
     * Resets all object properties of the singleton instance
     *
     * Primarily used for testing; could be used to chain front controllers.
     *
     * @return void
     */
    public function resetInstance()
    {
        $reflection = new ReflectionObject($this);
        foreach ($reflection->getProperties() as $property) {
            $name = $property->getName();
            switch ($name) {
                case '_instance':
                    break;
                case '_invokeParams':
                    $this->{$name} = array();
                    break;
                case '_plugins':
                    $this->{$name} = new Zend_Controller_Plugin_Broker();
                    break;
                case '_throwExceptions':
                case '_returnResponse':
                    $this->{$name} = false;
                    break;
                case '_directory':
                    $this->{$name} = null;
                    break;
                default:
                    $this->{$name} = null;
                    break;
            }
        }
    }
 
    /**
     * Convenience feature, calls setControllerDirectory()->setRouter()->dispatch()
     *
     * In PHP 5.1.x, a call to a static method never populates $this -- so run()
     * may actually be called after setting up your front controller.
     *
     * @param string|array $controllerDirectory Path to Zend_Controller_Action
     * controller classes or array of such paths
     * @return void
     * @throws Zend_Controller_Exception if called from an object instance
     */
    public static function run($controllerDirectory)
    {
        $frontController = self::getInstance();
        $frontController->getDirectory()->setControllerDirectory($controllerDirectory);
        $frontController->dispatch();
    }
 
    /**
     * Return the directory object
     *
     * @return Zend_Controller_Directory_Abstract
     */
    public function getDirectory()
    {
        if (!$this->_directory instanceof Zend_Controller_Directory_Abstract) {
            require_once 'Zend/Controller/Directory/Standard.php';
            $this->_directory = new Zend_Controller_Directory_Standard();
        }
        return $this->_directory; 
    }
 
    /**
     * Set the directory object
     *
     * @param Zend_Controller_Directory_Abstract $directory
     * @return Zend_Controller_Front
     */
    public function setDirectory(Zend_Controller_Directory_Abstract $directory)
    {
        $this->_directory = $directory;
        return $this;
    }
}

Bootstrapper

Before:

require_once 'Zend/Controller/Front.php'; 
$frontController = Zend_Controller_Front::getInstance();
$frontController->throwExceptions(true);
$frontController->setModuleControllerDirectoryName('controllers');
$frontController->setDefaultModule('default'); 
$frontController->addModuleDirectory('../application/modules');

After:

require_once 'Zend/Controller/Directory/Standard.php'; 
$directory = new Zend_Controller_Directory_Standard(); 
$directory->setControllerDirectoryName('controllers'); 
$directory->setDefaultModule('default'); 
$directory->addModuleDirectory('../application/modules'); 
 
require_once 'Zend/Controller/Front.php'; 
$frontController = Zend_Controller_Front::getInstance();  
$frontController->throwExceptions(true);  
$frontController->setDirectory($directory);

That's it. I've demonstrated how to refactor the Front Controller of the Zend Framework using a refactoring known as “Extract Class”. Martin Fowler discusses this technique in "Refactoring: Improving the Design of Existing Code".

Contact
Personal tools