C H A P T E R  15

image

Refactoring with Patterns

In this chapter we will see how some big refactoring techniques can improve the design of a procedural PHP application by refactoring with design patterns. All of the techniques attempt to solve common design problems with many common models, which helps us make the design of our application better and easier to maintain. Big refactoring techniques, in contrast to refactoring techniques, require a lot of time and care, because we are changing the software at the architectural level. We explain the mechanisms of big refactoring techniques using the legacy PHP application used in previous chapters.

If you are completely refactoring a procedural PHP application, follow the techniques in the same order suggested; otherwise you can also apply the techniques one by one as needed.

Design Patterns

The big refactoring techniques we use in this chapter use some object-oriented design patterns and architectural patterns often used for developing PHP applications to solve design problems. What are the design patterns, why do you need to use them, and when can you use them?

What Are Design Patterns?

Each pattern describes a problem which occurs over and over again in our environment, and then describes the core of the solution to that problem, in such a way that you can use this solution a million times over, without ever doing it the same way twice.

—Christopher Alexander [AIS+77]

In PHP, as in many other object-oriented languages, you can apply design patterns, because they are not related to languages but to models of common solutions.

The patterns are divided into object-oriented patterns, which typically show relationships and interactions between classes and objects, and architectural patterns that, at the highest level, describe how the system components communicate with each other. One famous architectural pattern is Model View Control.

The difference between an algorithm and a design pattern is that the first solves computational problems, while the second is related to the design aspects of the software.

The most famous design patterns have been cataloged by the Gang of Four [GOF] and are divided into these groups:

  • Creational: Solve problems relating to the creation of dynamic objects on the fly.
  • Structural: Allow reuse of existing objects by providing users with a more suitable interface.
  • Behavior: Resolve all common problems associated with the interaction between objects.

Each category has a collection of patterns suitable for solving a certain set of common design problems.

There are also other types of design patterns:

  • Architectural: Solve problems related to communication between macro components in large systems.
  • Methodology: Solve problems through the use of methodologies.
  • Competition: Solve problems related to concurrent programming and transmission of objects in concurrent environments.

Why Do I Need to Use Design Patterns?

As engineers, our daily task is to find design solutions to known problems. In my experience as a PHP developer I've read a lot of code written by different developers that, while solving similar problems, presented very different design, making it difficult to understand the solution. If a problem is known, why doesn't everyone apply a similar solution from a model that is equally well known?

Using design patterns to solve common problems greatly simplifies the understanding of the software, because we do not have to reinvent the wheel every time. When you recognize the pattern in the code, you instantly understand that component design.

Communication within a team and solving design problems with design patterns become much clearer when you can explain the solution in minutes.

When Do I Need to Use Them?

Design patterns are used as much as possible when programming with objects. If you can identify the solution to a problem in a pattern, it should be used. There are no contraindications for using design patterns. As for refactoring, the important thing is to follow the right model—you can't create hybrid models that don't conform to the pattern. Sometimes we can't immediately recognize a problem in a certain pattern, but we can always solve it in the simplest way and then change the design to design patterns with refactoring.

Refactoring with Patterns

In some of the techniques we will see later, we will try to apply refactoring techniques to solve the design problems of a procedural application through design patterns. Specifically we will apply the Façade pattern to transform the procedural code into object code, the Template View pattern and the Decorator Design pattern to separate the business logic from the representational logic, and, finally, the MVC pattern to further improve the design of our application and decouple its components.

Transform Procedural Code into Object-Oriented Code

Problem: “A PHP application is written in procedural code.”

Solution: “Using the Façade pattern, we transform the code from procedural to object-oriented.”

Motivation

PHP is characterized by its procedural nature and simplicity of use, due to a very permissive language. PHP 4 has very basic object support, and most of the applications using that version are written in a procedural manner. The presence of procedural code is a bad smell if we plan to code with objects, because procedural software is very difficult to maintain. For these reasons, there are important applications for some companies that cannot longer grow because they are written with PHP 4 and are now difficult to change.

With this technique we will learn how to begin the transformation from procedural code to object-oriented code. Obviously it is first necessary to bring the software to PHP 5 before starting this process. The migration from PHP4 to PHP5 is not covered in this book, but I recommend reading the official manual section on the php.net portal1, and the book php|architect's Guide to PHP 5 Migration [PRI08].

Design patterns help us in solving this long-standing problem, and the specific pattern that we will use to solve this design problem is the structural pattern Façade (shown in Figure 15-1).

Façade provides a unified interface to a set of interfaces in a subsystem. Façade defines a higher-level interface that makes the subsystem easier to use.

—Erich Gamma [GOF]

The typical architecture of procedural software written in PHP consists of a set of scripts, which can be accessed through a certain URL. Each script typically performs a series of common operations that can be driven by parameters passing via POST or GET. If we see each of these scripts as a complex interface to query, the Façade pattern can solve the problem of simplifying these interfaces.

__________

image

Figure 15-1. The Façade structural pattern

Mechanism

  1. Identify which methods you need to create in the Façade class. The methods coincide with the PHP scripts you can call from an URL.
  2. Create a new test case for the Façade class also called the Main class.
  3. Prepare the fixtures and configure the test cases to load them.

For each class method Main identified, carry out the following steps:

  1. Create functional tests verifying that the method has the correct behavior even if the request doesn't pass through a web server.
  2. Extract the PHP code from the script and create a new method in the Main class with script code.
  3. Adjust the code where necessary.
  4. Run unit tests and verify that the behavior has not changed.
  5. Replace in script procedural code with a new instance of Main class and call the method just created.
  6. Run the functional tests previously prepared and verify that the behavior is unchanged.

Example

In the following example we will use the PHP legacy application “Contacts Book,” already shown in previous chapters. The application has been caged in functional tests in Chapter 14, and these tests will ensure consistency of behavior in this process of big refactoring.

The first thing we need to do is identify what scripts we can call from an URL. Looking through the code we see that the scripts we can call are the following:

  • index.php
  • new.php
  • edit.php
  • remove.php

Then we will create four methods in the Main class that will replace procedural PHP code in our scripts. The methods are

  • index()
  • add()
  • edit()
  • remove()
Working with Fixtures

In Chapter 5 we saw that PHPUnit supports loading fixtures in the database from XML and CSV files. We use this feature to load data in our database. It's important that data is reloaded for each test to ensure independence between unit tests. With our favorite tools, export the database in .csv format, adjusted according to the PHPUnit specifications, and save it in the folder tests/fixtures/contacts.csv.

Now we can start writing a unit test for the Main class that we call ContactsBookMain, so we call the test case class ContactsBookMainTest.

--- tests/unit/ContactsBookMainTest.php ---

require_once 'PHPUnit/Extensions/Database/TestCase.php';
require_once 'PHPUnit/Extensions/Database/DataSet/CsvDataSet.php';
require_once dirname(__FILE__).'/../../lib/ContactsBookMain.php';

class ContactsBookMainTest extends PHPUnit_Extensions_Database_TestCase
{

  protected function getConnection()
  {
    $pdo = new PDO('mysql:host=localhost;dbname=contacts', 'root', ''),
    return $this->createDefaultDBConnection($pdo, 'contacts'),
  }

  protected function getDataSet()
  {
    $dataSet = new PHPUnit_Extensions_Database_DataSet_CsvDataSet();
    $dataSet->addTable('contacts', dirname(__FILE__).'/../fixtures/contacts.csv'),

    return $dataSet;
  }
}

The class extends the class of the PHPUnit framework PHPUnit_Extensions_Database_TestCase, which implements the interface to load the fixtures database from CSV files.

Next we will see the implementation of each method of the Façade class ContactsBookMain that simplifies the method we will use to simplify calls within the script application.

Index Action

The first method we implement is the index method that coincides with the call to the index.php script. We write the unit test and we verify that the output of the method is a valid DOM document. We do this check because the output of a call to index.php is a valid DOM document. To test the output we use the PHP output control functions.

--- tests/unit/ContactsBookMainTest.php ---

class ContactsBookMainTest extends PHPUnit_Extensions_Database_TestCase
{
  …
  public function testIndex()
  {
    ob_start();
    $contacts_book_main = new ContactsBookMain();
    $contacts_book_main->index();
    $output = ob_get_contents();
    ob_end_clean();
    ob_end_flush();

    error_reporting(E_ALL | E_STRICT);

    try
    {
      $dom = new DOMDocument();
      $dom->loadHTML($output);
    }
    catch (Exception $e)
    {
      $this->fail('Not valid dom document - '.$e->getMessage());
    }
  }
}

It is recommended, almost required, to write unit tests so that all PHP errors occur. Since the application sets errors such as E_ALL & ~ E_NOTICE, it could probably hide some surprises. To prevent this kind of surprise and to ensure that we identify all errors, we again set error reporting to E_ALL | E_STRICT after the call to the index() method.

Now we can run the unit test. First it fails because the class ContactsBookMain doesn't exist. We create it, and then the unit test fails because the index() method doesn't exist. We create it, too, extracting the code from the index.php script in the method. We adjust the code where needed. Pay attention to PHP open and close tags, and fix the path of the file included.

--- lib/ContactsBookMain.php ---

class ContactsBookMain
{
public static function index()
    {

    include(dirname(__FILE__).'/../config.php'),

    $db = @mysql_connect($database['host'], $database['username'], $database['password'])
  or die('Can't connect do database'),

          @mysql_select_db($database['name'])
          or die('The database selected does not exists'),

    $query = 'SELECT * FROM contacts ORDER BY lastname';
    $rs = mysql_query($query);

    if (!$rs)
    {
      die_with_error(mysql_error(), $query);
    }

    $num = mysql_num_rows($rs);

    ?>

    <?php include(dirname(__FILE__).'/../header.php') ?>

    <div class="actions">
      <a href="new.php">New contact</a>
     </div>

    <?php if ($num) : ?>
      <table border="1" cellspacing="0" cellpadding="5">
      <tr>
        <th>Last Name</th>
        <th>First Name</th>
        <th>Phone</th>
        <th>Mobile</th>
        <th>&nbsp;</th>
      </tr>
      <?php while($row = mysql_fetch_assoc($rs)) :?>
        <tr>
          <td><a href="edit.php?id=<?php echo $row['id']?>" title="Edit"><?php echo image
$row['lastname']?></a></td>
          <td><?php echo $row['firstname']?></a></td>
          <td><a href="callto://<?php echo $row['phone']?>"><?php echo $row['phone']?></a></td>
          <td><a href="callto://<?php echo $row['mobile']?>"><?php echo $row['mobile']?></a></td>
          <td>[<a href="remove.php?id=<?php echo $row['id']?>" title="Delete" onclick="if image
(confirm('Are you sure?')) {return true;} return false;">X</a>]</td>
        </tr>
      <?php endwhile;?>
      </table>

     <?php else: ?>
      Database is empty
    <?php endif ?>

    <?php include(dirname(__FILE__).'/../footer.php') ?>
<?php
      mysql_free_result($rs);
      mysql_close($db);
    }
}

We run the test and there is another failure.

$ phpunit tests/unit/ContactsBookMainTest.php
PHPUnit 3.4.1 by Sebastian Bergmann.

F

Time: 0 seconds

There was 1 failure:

1) ContactsBookMainTest::testIndex
Not valid dom document - DOMDocument::loadHTML(): Unexpected end tag : a in Entity, line: 65

/Users/cphp/Dropbox/Progetti/Libri/Apress/ProPHPRefactoring/chapters/bundled_code/image
bigrefactoring/tests/unit/ContactsBookMainTest.php:43

FAILURES!
Tests: 1, Assertions: 0, Failures: 1.

Oops—this is a bug. The HTML created by our application is not a valid DOM document. While doing refactoring we can find bugs. We fix the bug, deleting the unexpected tag, and run the test again.

--- lib/ContactsBookMain.php ---

public static function index()
{
  …
    <td><?php echo $row['firstname']?></td>
  …
}

After running the test we find another unexpected error with the “&” character not admitted in the footer. We change “&” with the HTML entity “&amp;” and run the test again.

--- footer.php ---

All &copy; Francesco Trucchia & Jacopo Romei - Pro PHP Refactoring

Now the tests are OK.

$ phpunit tests/unit/ContactsBookMainTest.php
PHPUnit 3.4.1 by Sebastian Bergmann.

.

Time: 0 seconds

OK (1 test, 0 assertions)

Now we integrate the unit test to verify that the HTML output is correct. We use the functional test that we created for Selenium as a starting point. Not using the extension for Selenium, we don't have the same methods to query the DOM. However, using the XPath object is useful to make queries in the DOM document, and we can easily do the same checks.

--- tests/unit/ContactsBookMainTest.php ---

class ContactsBookMainTest extends PHPUnit_Extensions_Database_TestCase
{
  …
  public function testIndex()
  {
    …
    $xpath = new DOMXPath($dom);

    $this->assertEquals('Contacts Book', $this->getText($xpath, '//head/title'));
    $this->assertEquals('Contacts Book', $this->getText($xpath, '//div[@id="header"]/h1'));
    $this->assertEquals('New contact', $this->getText($xpath, '//div[@class="actions"]/a'));

    $this->assertEquals('Last Name', $this->getText($xpath, '//table/tr/th[1]'));
    $this->assertEquals('First Name', $this->getText($xpath, '//table/tr/th[2]'));
    $this->assertEquals('Phone', $this->getText($xpath, '//table/tr/th[3]'));
    $this->assertEquals('Mobile', $this->getText($xpath, '//table/tr/th[4]'));

    $this->assertEquals('Romei', $this->getText($xpath, '//table/tr[2]/td[1]'));
    $this->assertEquals('Romei', $this->getText($xpath, '//table/tr[2]/td[1]/a'));
    $this->assertEquals('Jacopo', $this->getText($xpath, '//table/tr[2]/td[2]'));
    $this->assertEquals('0543123543', $this->getText($xpath, '//table/tr[2]/td[3]'));
    $this->assertEquals('0543123543', $this->getText($xpath, '//table/tr[2]/td[3]/a'));
    $this->assertEquals('34012345', $this->getText($xpath, '//table/tr[2]/td[4]'));
    $this->assertEquals('34012345', $this->getText($xpath, '//table/tr[2]/td[4]/a'));
    $this->assertEquals('[X]', $this->getText($xpath, '//table/tr[2]/td[5]'));
    $this->assertEquals('X', $this->getText($xpath, '//table/tr[2]/td[5]/a'));

    $this->assertEquals('Trucchia', $this->getText($xpath, '//table/tr[3]/td[1]'));
    $this->assertEquals('Trucchia', $this->getText($xpath, '//table/tr[3]/td[1]/a'));
    $this->assertEquals('Francesco', $this->getText($xpath, '//table/tr[3]/td[2]'));
    $this->assertEquals('12345', $this->getText($xpath, '//table/tr[3]/td[3]'));
    $this->assertEquals('12345', $this->getText($xpath, '//table/tr[3]/td[3]/a'));
    $this->assertEquals('234 12345', $this->getText($xpath, '//table/tr[3]/td[4]'));
    $this->assertEquals('234 12345', $this->getText($xpath, '//table/tr[3]/td[4]/a'));
    $this->assertEquals('[X]', $this->getText($xpath, '//table/tr[3]/td[5]'));
    $this->assertEquals('X', $this->getText($xpath, '//table/tr[3]/td[5]/a'));
  }

  private function getText(DOMXPath $xpath, $query)
  {
    if ($xpath->query($query)->length == 0)
    {
      throw new Exception('Text not found in query ' . $query);
    }

    return $xpath->query($query)->item(0)->nodeValue;
  }
}

We run the test again and verify that all assertions are correct. Once the correctness of the test has been verified, we must use the method index() of the class ContactsBookMain in our application, replacing the code in index.php with it.

--- index.php ---
require_once('lib/ContactsBookMain.php'),

$contacts_book_main = new ContactsBookMain();
$contacts_book_main->index();

To be sure that our first step in refactoring is going to succeed, we run all tests, both functional and unit.

PHPUnit 3.4.1 by Sebastian Bergmann.

……

Time: 44 seconds

OK (6 tests, 101 assertions)
Add Action

After the index action, we turn to the second action, adding a new contact record. As we have seen, first of all we create a new unit test that verifies the creation of a new record. As for the other tests, we use the same strategy and verify that the output the method add() returns is a valid DOM document.

--- tests/unit/ContactsBookMainTest.php ---

class ContactsBookMainTest extends PHPUnit_Extensions_Database_TestCase
{
  …
  public function testAdd()
  {
    ob_start();
    $contacts_book_main = new ContactsBookMain();
    $contacts_book_main->add();
    $output = ob_get_contents();
    ob_end_clean();

    error_reporting(E_ALL | E_STRICT);

    try
    {
      $dom = new DOMDocument();
      $dom->loadHTML($output);
    }
    catch (Exception $e)
    {
      $this->fail('Not valid dom document - '.$e->getMessage());
    }

    $xpath = new DOMXPath($dom);
  }
}

We create the add() method in the class ContactsBookMain and we copy inside the code of a new.php script, adjusting the paths of included files and PHP tags.

--- lib/ContactsBookMain.php ---

class ContactsBookMain
{
  …
  public static function add()
  {

    include(dirname(__FILE__).'/../config.php'),

    if($_SERVER['REQUEST_METHOD'] == 'POST')
    {
      $errors = validate(array('firstname', 'lastname', 'phone'), $_POST);

      if(count($errors) == 0)
      {
        $db = @mysql_connect($database['host'], $database['username'], $database['password']) image
or die('Can't connect do database'),
        @mysql_select_db($database['name']) or die('The database selected does not exists'),

        $query = sprintf("INSERT INTO contacts (firstname, lastname, phone, mobile) VALUES image
('%s', '%s', '%s', '%s')",
                           mysql_real_escape_string($_POST['firstname']),
                           mysql_real_escape_string($_POST['lastname']),
                           mysql_real_escape_string($_POST['phone']),
                           mysql_real_escape_string($_POST['mobile'])
                          );

        $rs = mysql_query($query);

        if (!$rs)
        {
          die_with_error(mysql_error(), $query);
        }

        mysql_close($db);

        header('Location: index.php'),

      }
    }
    ?>

    <?php include(dirname(__FILE__).'/../header.php') ?>
    <?php include(dirname(__FILE__).'/../_form.php') ?>
    <?php include(dirname(__FILE__).'/../footer.php'),
  }
}

We run the test, and it fails.

PHPUnit 3.4.1 by Sebastian Bergmann.

.E

Time: 0 seconds

There was 1 error:

1) ContactsBookMainTest::testNew
Undefined index:  REQUEST_METHOD

bigrefactoring/lib/ContactsBookMain.php:72
bigrefactoring/tests/unit/ContactsBookMainTest.php:92

FAILURES!
Tests: 2, Assertions: 25, Errors: 1.

After the test we notice that the index REQUEST_METHOD of the super global variable $_SERVER is not set. That's right, because we are in the CLI environment and not in the web environment. PHP CLI doesn't populate the super global variable $_SERVER as does PHP on the web. Therefore we introduce the method setUp() in tests that will create the PHP environment like we're on the web.

--- tests/unit/ContactsBookMainTest.php ---

class ContactsBookMainTest extends PHPUnit_Extensions_Database_TestCase
{
  …
  public function setUp()
  {
    parent::setUp();
    $_SERVER['REQUEST_METHOD'] = 'GET';
  }
}

We perform the test ContactsBookMainTest again and it fails again.

PHPUnit 3.4.1 by Sebastian Bergmann.

.

    <form method="post">


<input type="hidden" name="id" value="E

Time: 0 seconds

There was 1 error:

1) ContactsBookMainTest::testAdd
Undefined index:  id

/Users/cphp/Dropbox/Progetti/Libri/Apress/ProPHPRefactoring/chapters/bundled_code/image
bigrefactoring/_form.php:11
/Users/cphp/Dropbox/Progetti/Libri/Apress/ProPHPRefactoring/chapters/bundled_code/image
bigrefactoring/lib/ContactsBookMain.php:105
/Users/cphp/Dropbox/Progetti/Libri/Apress/ProPHPRefactoring/chapters/bundled_code/image
bigrefactoring/tests/unit/ContactsBookMainTest.php:97

FAILURES!
Tests: 2, Assertions: 25, Errors: 1.

The test fails again due to a bug that was already in our application but that we find only now, as we have ability to E_STRICT errors. The test fails because the index “ID” in the super global variable $_POST is not set. This is correct when the form is empty; it was our code that didn't check the condition. Finding bugs while you do refactoring always makes you feel good. Fix the bug by checking if the index is set, then print it, otherwise print an empty string. Add the same condition in all other form fields.

--- form.php ---

<input type="hidden" name="id" value="<?php echo isset($_POST['id'])?$_POST['id']:''?>" />

We run the unit test again and everything is running correctly. Having fixed a bug, before going forward, we run all the tests and verify that all functional tests still resolve properly. Only with green text can we continue our work. At this point we increase the unit test checking the output of the form and the correct creation of a record. Like the previous test, we use the functional test AddTest as a starting point for the unit test code and fix it a little bit because we don't use a Selenium extension here.

--- tests/unit/ContactsBookMainTest.php ---

class ContactsBookMainTest extends PHPUnit_Extensions_Database_TestCase
{
  …
  private function isElementPresent(DOMXPath $xpath, $query)
  {
    if ($xpath->query($query)->length == 0)
    {
      throw new Exception('Element with name '.$name.' not found'),
    }

    return true;
  }

  private function type($name, $value)
  {
    $_POST[$name] = $value;
  }

  public function testAdd()
  {
    …
    $xpath = new DOMXPath($dom);

    $this->assertEquals('Contacts Book', $this->getText($xpath, '//head/title'));
    $this->assertEquals('Contacts Book', $this->getText($xpath, '//div[@id="header"]/h1'));

    $this->assertEquals("First Name*", $this->getText($xpath, image
"//div[@id='content']/form/label[1]"));
    $this->assertEquals("Last Name*", $this->getText($xpath, image
"//div[@id='content']/form/label[2]"));
$this->assertEquals("Phone*", $this->getText($xpath, "//div[@id='content']/form/label[3]"));
    $this->assertEquals("Mobile", $this->getText($xpath, "//div[@id='content']/form/label[4]"));

    $this->assertTrue($this->isElementPresent($xpath, '//input[@name="firstname"]'));
    $this->assertTrue($this->isElementPresent($xpath, '//input[@name="lastname"]'));
    $this->assertTrue($this->isElementPresent($xpath, '//input[@name="phone"]'));
    $this->assertTrue($this->isElementPresent($xpath, '//input[@name="mobile"]'));
    $this->assertEquals("Cancel", $this->getText($xpath, "//a"));

    $this->assertEquals("(* Mandatory fields)", $this->getText($xpath, image
"//div[@id='content']/em"));
    $this->assertRegExp("/All © Francesco Trucchia &grave; Jacopo Romei - Pro PHP Refactoring/",
$this->getText($xpath, '//div[@id="footer"]'));

    $this->type("firstname", "Girolamo");
    $this->type("lastname", "Pompei");
    $this->type("phone", "098245678");
    $this->type("mobile", "3402343879");

    $_SERVER['REQUEST_METHOD'] = 'POST';

    $contacts_book_main->add();
  }
}

To comply with the interfaces used in our functional tests, we have created the new methods isElementPresent(), which checks for an element within the document, and type(), which populates the super global variable $_POST. At the end of the test we simulate a POST call to verify that the record is inserted. We launch the test and the test fails.

PHPUnit 3.4.1 by Sebastian Bergmann.

.E

Time: 1 second

There was 1 error:

1) ContactsBookMainTest::testAdd
Cannot modify header information - headers already sent by (output started at /Applications/MAMP/bin/php5/lib/php/PHPUnit/Util/Printer.php:173)

bigrefactoring/lib/ContactsBookMain.php:97
bigrefactoring/tests/unit/ContactsBookMainTest.php:163

FAILURES!
Tests: 2, Assertions: 38, Errors: 1.

The test fails because our code calls the header() function, which cannot be called here, because the headers are already sent by the test environment. We can use the error to test that the behavior is correct, because this error ensures that the function header() is called. To capture the error we use the PHP try catch construct.

--- tests/unit/ContactsBookMainTest.php ---


class ContactsBookMainTest extends PHPUnit_Extensions_Database_TestCase
{
  …
  public function testAdd()
  {
    …
    try
    {
      ContactsBookMain::add();
      $this->fail();
    }
    catch(Exception $e)
    {
      $this->assertRegExp('/Cannot modify header information/', $e->getMessage());
    }
  }
}

We run the test and now it passes.

PHPUnit 3.4.1 by Sebastian Bergmann.



Time: 0 seconds

OK (3 tests, 39 assertions)

Now we can replace the procedural code of the new.php script with a call to the add() method of the ContactsBookMain class.

--- new.php ---
require_once('lib/ContactsBookMain.php'),

$contacts_book_main = new ContactsBookMain();
$contacts_book_main->add();

Finally we run all tests and verify that the behavior of our application has not changed.

Edit Action

As already seen in the refactoring of the two previous actions, first of all we prepare the unit test for the new method edit() of the ContactsBookMain class and check that the output of the methods is a valid DOM document passing the ID of the record in GET parameters.

--- tests/unit/ContactsBookMainTest.php ---

class ContactsBookMainTest extends PHPUnit_Extensions_Database_TestCase
{
  …
  public function testEdit()
  {
$_GET['id'] = 1;


    ob_start();
    $contacts_book_main = new ContactsBookMain();
    $contacts_book_main->edit();
    $output = ob_get_contents();
    ob_end_clean();

    error_reporting(E_ALL | E_STRICT);

    try
    {
      $dom = new DOMDocument();
      $dom->loadHTML($output);
    }
    catch (Exception $e)
    {
      $this->fail('Not valid dom document - '.$e->getMessage());
    }

    $xpath = new DOMXPath($dom);
  }
  …
}

We implement the edit() method and copy the code in the edit.php script to the new method. As we did previously, we fix the code where needed and modify the path of included files.

--- lib/ContactsBookMain.php ---

class ContactsBookMain
{
  …
  public static function edit()
  {
    include(dirname(__FILE__).'/../config.php'),

    if(!$_GET['id'])
    {
     die('Some error occured!!'),
    }

    $db = @mysql_connect($database['host'], $database['username'], $database['password']) image
or die('Can't connect do database'),
    @mysql_select_db($database['name']) or die('The database selected does not exists'),

    if($_SERVER['REQUEST_METHOD'] == 'POST')
    {
      $errors = validate(array('id', 'firstname', 'lastname', 'phone'), $_POST);

      if(count($errors) == 0)
      {
        $query = sprintf("UPDATE contacts set firstname = '%s',
                                                                     lastname = '%s',
                                                                     phone = '%s',
                                                                     mobile = '%s' WHERE id = %s",
mysql_real_escape_string($_POST['firstname']),
                           mysql_real_escape_string($_POST['lastname']),
                           mysql_real_escape_string($_POST['phone']),
                           mysql_real_escape_string($_POST['mobile']),
                           mysql_real_escape_string($_POST['id'])
                          );

        $rs = mysql_query($query);

        if (!$rs)
        {
          die_with_error(mysql_error(), $query);
        }

        header('Location: index.php'),
      }
    }
    else
    {
      $query = sprintf('SELECT * FROM contacts WHERE id = %s', image
mysql_real_escape_string($_GET['id']));

      $rs = mysql_query($query);

      if (!$rs)
      {
        die_with_error(mysql_error(), $query);
      }

      $row = mysql_fetch_assoc($rs);

      $_POST['id'] = $row['id'];
      $_POST['firstname'] = $row['firstname'];
      $_POST['lastname'] = $row['lastname'];
      $_POST['phone'] = $row['phone'];
      $_POST['mobile'] = $row['mobile'];
    }

    mysql_close($db);

    ?>

    <?php include(dirname(__FILE__).'/../header.php') ?>

    <?php include(dirname(__FILE__).'/../_form.php') ?>

    <?php include(dirname(__FILE__).'/../footer.php'),
  }
}

We run the test and make sure everything is correct.

PHPUnit 3.4.1 by Sebastian Bergmann.



Time: 0 seconds

OK (3 tests, 39 assertions)

Looking at our test class, we note that in the methods testIndex(), testAdd(), and testEdit() there is a piece of duplicated code that calls a different method of ContactsBookMain but always gets output and verifies the validity of the DOM document. For the rule of three times, this portion of code should be refactored. Through the “Extract Method” technique we create a new assertDomReturnXPath() method, moving the block of code and replacing all of the duplicate blocks of code or other methods with the method we just created.


class ContactsBookMainTest extends PHPUnit_Extensions_Database_TestCase
{
  …
  private function assertDomReturnXPath($action)
  {
    ob_start();
    $contacts_book_main = new ContactsBookMain();
    $contacts_book_main->$action();
    $output = ob_get_contents();
    ob_end_clean();

    error_reporting(E_ALL | E_STRICT);

    try
    {
      $dom = new DOMDocument();
      $dom->loadHTML($output);
    }
    catch (Exception $e)
    {
      $this->fail('Not valid dom document - '.$e->getMessage());
    }

    return new DOMXPath($dom);
  }
  …
  public function testEdit()
  {
    $_GET['id'] = 1;

    $xpath = $this->assertDomReturnXPath('edit'),
  }
}

After performing this step of refactoring, we go on integrating our method testEdit() with more assertions. As with other actions we use the functional test EditTest as a starting point and we fix our calls where needed.


class ContactsBookMainTest extends PHPUnit_Extensions_Database_TestCase
{
  …
  public function testEdit()
  {
    $_GET['id'] = 1;

    $xpath = $this->assertDomReturnXPath('edit'),

    $this->assertEquals('Contacts Book', $this->getText($xpath, '//head/title'));
    $this->assertEquals('Contacts Book', $this->getText($xpath, '//div[@id="header"]/h1'));

    $this->assertEquals("First Name*", $this->getText($xpath, image
"//div[@id='content']/form/label[1]"));
    $this->assertEquals("Last Name*", $this->getText($xpath, image
"//div[@id='content']/form/label[2]"));
    $this->assertEquals("Phone*", $this->getText($xpath, "//div[@id='content']/form/label[3]"));
    $this->assertEquals("Mobile", $this->getText($xpath, "//div[@id='content']/form/label[4]"));

    $this->assertTrue($this->isElementPresent($xpath, '//input[@name="firstname"]'));
    $this->assertTrue($this->isElementPresent($xpath, '//input[@name="lastname"]'));
    $this->assertTrue($this->isElementPresent($xpath, '//input[@name="phone"]'));
    $this->assertTrue($this->isElementPresent($xpath, '//input[@name="mobile"]'));
    $this->assertEquals("Cancel", $this->getText($xpath, "//a"));

    $this->assertEquals("Jacopo", $this->getValue($xpath, '//input[@name="firstname"]'));
    $this->assertEquals("Romei", $this->getValue($xpath, '//input[@name="lastname"]'));
    $this->assertEquals("0543123543", $this->getValue($xpath, '//input[@name="phone"]'));
    $this->assertEquals("34012345", $this->getValue($xpath, '//input[@name="mobile"]'));

    $this->assertEquals("(* Mandatory fields)", $this->getText($xpath, image
"//div[@id='content']/em"));
    $this->assertRegExp("/All © Francesco Trucchia &grave; Jacopo Romei - Pro PHP image
Refactoring/", $this->getText($xpath, '//div[@id="footer"]'));

    $this->type("firstname", "Jack");
    $this->type("lastname", "Brown");
    $this->type("phone", "1234");
    $this->type("mobile", "4321");
    $this->type("id", "1");

    $_SERVER['REQUEST_METHOD'] = 'POST';

    try
    {
      $contacts_book_main = new ContactsBookMain();
      $contacts_book_main->edit();
      $this->fail();
    }
    catch(Exception $e)
    {
      $this->assertRegExp('/Cannot modify header information/', $e->getMessage());
    }

    $xpath = $this->assertDomReturnXPath('index'),
$this->assertEquals('Brown', $this->getText($xpath, '//table/tr[2]/td[1]'));

    $this->assertEquals('Jack', $this->getText($xpath, '//table/tr[2]/td[2]'));
    $this->assertEquals('1234', $this->getText($xpath, '//table/tr[2]/td[3]'));
    $this->assertEquals('4321', $this->getText($xpath, '//table/tr[2]/td[4]'));
  }
}

We run the test case and verify that all tests are OK.

PHPUnit 3.4.1 by Sebastian Bergmann.



Time: 0 seconds

OK (3 tests, 61 assertions)

Now we replace the code in the edit.php script with the call to the method edit() of the class ContactsBookMain and run all unit and functional tests again.

--- edit.php ---
require_once('lib/ContactsBookMain.php'),

$contacts_book_main = new ContactsBookMain();
$contacts_book_main->edit();
Remove Action

Finally we prepare the unit test for the last action, that of removal. We verify that when calling the method a record is deleted. To verify the removal, we call the method index() at the beginning, and we count how many rows are in the list table. Then we call to the remove() method, and at the end we call index() again and check that the number of row has decreased.

--- tests/unit/ContactsBookMainTest.php ---

class ContactsBookMainTest extends PHPUnit_Extensions_Database_TestCase
{
  …
  public function testRemove()
  {
    $xpath = $this->assertDomReturnXPath('index'),
    $this->assertEquals(3, $this->getXPathCount($xpath, '//table/tr'));

    $_GET['id'] =  1;
    try
    {
      $contacts_book_main = new ContactsBookMain();
      $contacts_book_main->remove();
      $this->fail();
    }
    catch(Exception $e)
    {
      $this->assertRegExp('/Cannot modify header information/', $e->getMessage());
    }

    $xpath = $this->assertDomReturnXPath('index'),
$this->assertEquals(2, $this->getXPathCount($xpath, '//table/tr'));

  }
}

We create the remove() method in the class ContactsBookMain and copy the code of the remove.php script code inside the method, replacing include_once() with include() and adjusting the path of the included files.

<code>
--- lib/ContactsBookMain.php ---
<?php
class ContactsBookMain
{
  …
  public static function remove()
  {
    include(dirname(__FILE__).'/../config.php'),

    if(!$_GET['id'])
    {
     die('Some error occured!!'),
    }

    $db = @mysql_connect($database['host'], $database['username'], $database['password']) image
or die('Can't connect do database'),
    @mysql_select_db($database['name']) or die('The database selected does not exists'),

    $query = sprintf('DELETE FROM contacts where ID = %s',
                     mysql_real_escape_string($_GET['id']));

    if(!mysql_query($query))
    {
      die_with_error(mysql_error(), $query);
    }

    mysql_close($db);

    header('Location: index.php'),
  }
}

Run the test and verify that everything is OK.

PHPUnit 3.4.1 by Sebastian Bergmann.

….

Time: 0 seconds

OK (4 tests, 64 assertions)

Now replace the code in the script remove.php with the call to the remove() method of the class ContactsBookMain.

--- remove.php ---

require_once('lib/ContactsBookMain.php'),

ContactsBookMain::remove();

We run all functional tests and unit tests for the last time and verify that all tests are still correct.

PHPUnit 3.4.1 by Sebastian Bergmann.

………

Time: 45 seconds

OK (9 tests, 140 assertions)

With this refactoring, we transformed our procedural code into object code, and we were able to test it at the unit level. We have opened the way to further improving the design of our code, which still has a lot of bad smells. The next steps will be introducing an ORM, introducing a Template Engine, and, finally, implementing the MVC architecture.

Replace SQL with ORM

Problem: “We have an application that doesn't use data structure objects, but only scalar values extracted from SQL queries.”

Solution: “Introduce an Object Relationship Mapper to convert data between incompatible types.”

Motivation

Classical relational databases can handle only scalars and records, not objects. An object, compared to a record, can be easily reusable and have behaviors and properties. A record, however, is merely the expression of a set of primitive data. This approach is very limited when we work with objects and leads to a very impressive duplication of code, as well as a difficult management of SQL queries needed to query the database.

Object-relational mapping (ORM, O/RM, and O/R mapping) in computer software is a programming technique for converting data between incompatible type systems in relational databases and object-oriented programming languages. This creates, in effect, a “virtual object database” that can be used from within the programming language.

—Wikipedia

Introducing an ORM can transform our model from record-relational to object-relational and benefit from all the characteristics of the object-oriented paradigm.

In most cases, a disadvantage of ORM is the difficulty in being tested, because it is very coupled with the database adapter. Another disadvantage is performance. Many ORMs can not easily scale with increasing data, and sometimes it is needed to return to a procedural management of our data.

Mechanism

  1. Install a PHP ORM library.
  2. Configure the database connection and the bootstrapping.
  3. Replace calls to the primary database with the adapter library.
  4. Generate the classes that map the database tables.
  5. Replace the SQL relational method calls of the model.

Example

In the example we will see how to insert the open-source ORM library Doctrine, in our application “Contacts Book,” after having succeeded, in the first step of big refactoring, in transforming our code from procedural to object through the Façade pattern.

Saying that our code respects the object-oriented paradigm, at the point where we have come earlier in our Contacts Book application, it is still too strong a statement. The methods of the Main class are still too procedural, while our goal is to have an object-oriented design. Following this goal, to improve the design we can insert an Object Relationship Mapper, which helps us to map the structure of the database into objects.

In the PHP world there are many ORM libraries competing with each other. We decided to use Doctrine2 because we believe it is very easy to use and has a very active community behind it.

From the official site, www.doctrine-project.org, comes this brief description that highlights the strengths of the library:

Doctrine is an object relational mapper (ORM) for PHP 5.2.3+ that sits on top of a powerful database abstraction layer (DBAL). One of its key features is the option to write database queries in a proprietary object-oriented SQL dialect called Doctrine Query Language (DQL), inspired by Hibernates HQL. This provides developers with a powerful alternative to SQL that maintains flexibility without requiring unnecessary code duplication.

We install the Doctrine library in the folder lib/vendor/doctrine of our application. For installation refer to the official documentation on the web site: www.doctrine-project.org/documentation/manual/1_2/en/getting-started.

To use Doctrine we must configure a bootstrapping file. We create a new file in the lib folder named bootstrap.php and we copy the following code inside:

--- lib/bootstrap.php ---
require_once(dirname(__FILE__) . '/vendor/doctrine/Doctrine.php'),
spl_autoload_register(array('Doctrine', 'autoload'));
Doctrine_Manager::connection('mysql://root@localhost/contacts', 'ContactsBook'),

With this code we register the Doctrine autoloader and we configure the database connection. We include this file in the ContactsBookMain.php file and run all tests. The advantage to including an ORM as Doctrine is to be able to change the database transparently, simply by changing the connection string, and all of our code will still work.

_______________

$ phpunit lib/tests
PHPUnit 3.4.1 by Sebastian Bergmann.

………

Time: 45 seconds

OK (9 tests, 140 assertions)

Once you configure the library, the next step is to replace primitive calls used to query our MySql database with calls of the MySql adapter for Doctrine.

We begin first with the method index() in the class ContactsBookMain. To replace the primitive calls we must, first of all, create mock functions that help us to switch to Doctrine. We create an object called DatabaseMock, which will help us in this step, and we make each primitive call to MySql a static call to the DatabaseMock method. Any static method keeps the same name as its primary function.

For example, replace the call mysql_connect() with DatabaseMock::mysql_connect(), the call mysql_select_db() with DatabaseMock::mysql_select_db(), and so on. The final result of the index() method of the class ContactsBookMain will be the following:

--- lib/ContactsBookMain.php ---

class ContactsBookMain
{
  public static function index()
  {
    include(dirname(__FILE__).'/../config.php'),

          $db = DatabaseMock::mysql_connect($database['host'], $database['username'], image
$database['password']) or die('Can't connect do database'),
      DatabaseMock::mysql_select_db($database['name']) or die('The database selected does image
not exists'),

      $query = 'SELECT * FROM contacts ORDER BY lastname';
      $rs = DatabaseMock::mysql_query($query);

      if (!$rs)
      {
        die_with_error(DatabaseMock::mysql_error(), $query);
      }

      $num = DatabaseMock::mysql_num_rows($rs);

      include_once(dirname(__FILE__).'/../header.php') ?>

      <div class="actions">
        <a href="new.php">New contact</a>
       </div>

      <?php if ($num) : ?>
        <table border="1" cellspacing="0" cellpadding="5">
        <tr>
          <th>Last Name</th>
          <th>First Name</th>
          <th>Phone</th>
          <th>Mobile</th>
          <th>&nbsp;</th>
        </tr>
<?php while($row = DatabaseMock::mysql_fetch_assoc($rs)) :?>
          <tr>
            <td><a href="edit.php?id=<?php echo $row['id']?>" title="Edit"><?php echo image
$row['lastname']?></a></td>
            <td><?php echo $row['firstname']?></td>
            <td><a href="callto://<?php echo $row['phone']?>"><?php echo $row['phone']?></a></td>
            <td><a href="callto://<?php echo $row['mobile']?>"><?php echo image
$row['mobile']?></a></td>
            <td>[<a href="remove.php?id=<?php echo $row['id']?>" title="Delete" onclick="if image
(confirm('Are you sure?')) {return true;} return false;">X</a>]</td>
          </tr>
        <?php endwhile;?>
        </table>

       <?php else: ?>
        Database is empty
      <?php endif ?>

      <?php
        include_once(dirname(__FILE__).'/../footer.php'),
        DatabaseMock::mysql_free_result($rs);
        DatabaseMock::mysql_close($db);
  }
}

We run the test cases ContactsBookMainTest and let ourselves be guided by the test failures, implementing only the simplest code block so that each assertion passes. We implement the DatabaseMock class in the easy way, so that the text does not give errors. The first result is the following. Of course we need to include the class file in the ContactsBooksMain.php file.

--- test/lib/mock/DatabaseMock.php ---

class DatabaseMock
{
  public static function mysql_connect()
  {
    return true;
  }

  public static function mysql_select_db()
  {
    return true;
  }

  public static function mysql_query()
  {
    return true;
  }

  public static function mysql_fetch_assoc(){}

  public static function mysql_error(){}

  public static function mysql_num_rows(){}

  public static function mysql_free_result(){}
public static function mysql_close(){}
}

At this point, the test still does not pass, because the methods do not effectively query to the database, and they do not return any results.

PHPUnit 3.4.1 by Sebastian Bergmann.

E.EE

Time: 1 second

There were 3 errors:

1) ContactsBookMainTest::testIndex
Exception: Element not found with query //table/tr/th[1]

bigrefactoring/tests/unit/ContactsBookMainTest.php:33
bigrefactoring/tests/unit/ContactsBookMainTest.php:40
bigrefactoring/tests/unit/ContactsBookMainTest.php:100

2) ContactsBookMainTest::testEdit
Exception: Element not found with query //table/tr[2]/td[1]

bigrefactoring/tests/unit/ContactsBookMainTest.php:33
bigrefactoring/tests/unit/ContactsBookMainTest.php:40
bigrefactoring/tests/unit/ContactsBookMainTest.php:213

3) ContactsBookMainTest::testRemove
Exception: Element not found with query //table/tr

bigrefactoring/tests/unit/ContactsBookMainTest.php:33
bigrefactoring/tests/unit/ContactsBookMainTest.php:56
bigrefactoring/tests/unit/ContactsBookMainTest.php:222

FAILURES!
Tests: 4, Assertions: 35, Errors: 3.

Since our goal is to replace primitive calls with ORM calls, in the DatabaseMock class we implement only the methods needed to run the tests, keeping the others empty.

The connection to the database, the database selection, the connection closure, and the memory management are already initialized and managed by ORM in the bootstrapping, so we can leave the related mock calls empty. To run the tests we implement only the methods mysql_query(), mysql_fetch_assoc(), and mysql_num_rows(). PDO, that is, the driver behind the PHP Doctrine ORM, gives us all the interfaces to handle these calls. The DatabaseMock final class will be as follows:

--- test/lib/mock/DatabaseMock.php ---
class DatabaseMock
{
  public static function mysql_connect()
  {
  return true;
  }

  public static function mysql_select_db()
  {
return true;

  }

  public static function mysql_query($query)
  {

    $connection = Doctrine_Manager::connection()->getDbh();
    return $connection->query($query);
  }

  public static function mysql_fetch_assoc(PDOStatement $statement)
  {
    return $statement->fetch();
  }

  public static function mysql_error(){}

  public static function mysql_num_rows(PDOStatement $statement)
  {
    return $statement->rowCount();
  }

  public static function mysql_free_result(){}

  public static function mysql_close(){}

}

We run the test cases ContactsBookMainTest and everything still works properly. The next step is to remove the null database method from the index() method, so we remove the method calls to mysql_connect(), mysql_select_db(), mysql_error(), mysql_free_result(), and mysql_close(). We can also remove the condition over the result set and the subsequent death in case of error in the query, as we delegate responsibility to PDO, which is able to independently manage all these controls. We can also remove the inclusion of the config.php file, because of the database connection parameters we have moved to the bootstrap, moving the inclusion it made in the boostrap.php file. Less is better.

--- lib/ContactsBookMain.php ---
class ContactsBookMain
{
  public static function index()
  {
    $rs = DatabaseMock::mysql_query('SELECT * FROM contacts ORDER BY lastname'),
    $num = DatabaseMock::mysql_num_rows($rs);

    include_once(dirname(__FILE__).'/../header.php') ?>

    <div class="actions">
      <a href="new.php">New contact</a>
     </div>

    <?php if ($num) : ?>
      <table border="1" cellspacing="0" cellpadding="5">
        <tr>
        <th>Last Name</th>
        <th>First Name</th>
        <th>Phone</th>
        <th>Mobile</th>
        <th>&nbsp;</th>
</tr>
      <?php while($row = DatabaseMock::mysql_fetch_assoc($rs)) :?>
        <tr>
          <td><a href="edit.php?id=<?php echo $row['id']?>" title="Edit"><?php echo image
$row['lastname']?></a></td>
          <td><?php echo $row['firstname']?></td>
          <td><a href="callto://<?php echo $row['phone']?>"><?php echo $row['phone']?></a></td>
          <td><a href="callto://<?php echo $row['mobile']?>"><?php echo $row['mobile']?></a></td>
          <td>[<a href="remove.php?id=<?php echo $row['id']?>" title="Delete" onclick="if image
(confirm('Are you sure?')) {return true;} return false;">X</a>]</td>
        </tr>
      <?php endwhile;?>
      </table>

     <?php else: ?>
      Database is empty
    <?php endif ?>

    <?php
      include_once(dirname(__FILE__).'/../footer.php'),
  }
}

I would say that this is already a better result than that of our old code. Delegating responsibility for the management of the database to an ORM and removing it tfrom our code is a great way to beautify our code. The last step is to extract code from the DatabaseFake method and replace the static calls with this code. The DatabaseMock class is only a temporary support. We adjust the code, also removing the temp variable.

--- lib/ContactsBookMain.php ---
class ContactsBookMain
{
  public static function index()
    {
    $connection = Doctrine_Manager::connection()->getDbh();
    $rs = $connection->query('SELECT * FROM contacts ORDER BY lastname'),

    include_once(dirname(__FILE__).'/../header.php') ?>

    <div class="actions">
      <a href="new.php">New contact</a>
     </div>

    <?php if ($rs->rowCount()) : ?>
      <table border="1" cellspacing="0" cellpadding="5">
      <tr>
        <th>Last Name</th>
        <th>First Name</th>
        <th>Phone</th>
        <th>Mobile</th>
        <th>&nbsp;</th>
      </tr>
      <?php while($row = $rs->fetch()) :?>
        <tr>
          <td><a href="edit.php?id=<?php echo $row['id']?>" title="Edit"><?php echo image
$row['lastname']?></a></td>
          <td><?php echo $row['firstname']?></td>
          <td><a href="callto://<?php echo $row['phone']?>"><?php echo $row['phone']?></a></td>
<td><a href="callto://<?php echo $row['mobile']?>"><?php echo $row['mobile']?></a></td>
          <td>[<a href="remove.php?id=<?php echo $row['id']?>" title="Delete" onclick="if image
(confirm('Are you sure?')) {return true;} return false;">X</a>]</td>
        </tr>
      <?php endwhile;?>
      </table>

     <?php else: ?>
      Database is empty
    <?php endif ?>

    <?php
      include_once(dirname(__FILE__).'/../footer.php'),
  }
  …
}

After performing the same steps in all other class methods of ContactsBookMain, we can remove the class DatabaseMock. Run all tests and verify that everything is still correct.

The next step is to generate our model, mapping relational database entities to objects. In this way we could remove the SQL query and use a logical object.

Doctrine provides the tools to create model classes automatically by querying a database. To use this feature we create a PHP script that could run from the shell. Create a new folder called bin and add the file build_model.php file inside. We also need to create a folder called model inside the lib folder, where the script will put the model classes. Copy the following code inside the build_model.php file:

--- bin/build_model.php ---
require_once(dirname(__FILE__).'/../lib/bootstrap.php'),

Doctrine::generateModelsFromDb(dirname(__FILE__).'/../lib/model', array('ContactsBook'), array('generateTableClasses' => true));

Run the script, and it creates the following files inside the model folder:

$ php bin/build_model.php
$ ls -al lib/model
total 16
drwxr-xr-x  3 cphp  staff  170 22 Apr 22:10 .
drwxr-xr-x  5 cphp  staff  238 22 Apr 22:05 ..
-rw-r--r--  1 cphp  staff  307 22 Apr 22:10 Contacts.php
-rw-r--r--  1 cphp  staff  356 22 Apr 22:10 ContactsTable.php
drwxr-xr-x  2 cphp  staff  102 22 Apr 22:10 generated

The script generated all the classes of our model in the lib/model folder. To include them, add the following code in the script bootstrap.php.

--- lib/bootstrap.php ---

Doctrine::loadModels(dirname(__FILE__).'/model/generated'),
Doctrine::loadModels(dirname(__FILE__).'/model/'),

Now we can replace the SQL query in class methods of ContactsBookMain using the interfaces of the models just created. We begin with the index() method .

Here is the old code:

--- lib/ContactsBookMain.php ---
class ContactsBookMain
{
  …
  public function index()
  {
    …
    $connection = Doctrine_Manager::connection()->getDbh();
    $rs = $connection->query('SELECT * FROM contacts ORDER BY lastname'),
    …
   <?php while($row = $rs->fetch()) :?>
     <tr>
       <td><a href="edit.php?id=<?php echo $row['id']?>" title="Edit"><?php echo image
$row['lastname']?></a></td>
       <td><?php echo $row['firstname']?></td>
       <td><a href="callto://<?php echo $row['phone']?>"><?php echo $row['phone']?></a></td>
       <td><a href="callto://<?php echo $row['mobile']?>"><?php echo $row['mobile']?></a></td>
       <td>[<a href="remove.php?id=<?php echo $row['id']?>" title="Delete" onclick="if image
(confirm('Are you sure?')) {return true;} return false;">X</a>]</td>
     </tr>
<?php endwhile;?>
   …
  }
}

That becomes:

--- lib/ContactsBookMain.php ---

class ContactsBookMain
{
  …
  public function index()
  {
    …
      $contacts = Doctrine::getTable('Contacts')->
                            createQuery()->
                            orderBy('lastname ASC')->
                            execute();
    …
      if (count($contacts))
    …
      <?php foreach($contacts as $contact) :?>
        <tr>
          <td><a href="edit.php?id=<?php echo $contact->id?>" title="Edit"><?php echo image
$contact->lastname?></a></td>
          <td><?php echo $contact->firstname?></td>
          <td><a href="callto://<?php echo $contact->phone?>"><?php echo image
$contact->phone?></a></td>
          <td><a href="callto://<?php echo $contact->mobile?>"><?php echo image
contact->mobile?></a></td>
          <td>[<a href="remove.php?id=<?php echo $contact->id?>" title="Delete" onclick="if image
(confirm('Are you sure?')) {return true;} return false;">X</a>]</td>
        </tr>
<?php endforeach;?>

    …
  }
  …
}

Through the model class, the procedural SQL code is automatically converted into object code. Through the model we can query our database and see the results as objects rather than as an array, so we can use the model with this behavior in other parts of our software.

Run all tests and verify that all tests are correct.

If we want to insert a new record through the ORM, we need to replace the SQL code in the edit() method of the class ContactsBookMain with the following code:

--- lib/ContactsBookMain.php ---

class ContactsBookMain
{
  …
  public function edit()
  {
    …
    $connection = Doctrine_Manager::connection()->getDbh();
    $statement = $connection->prepare('INSERT INTO contacts (firstname, lastname, phone, image
mobile) VALUES (:firstname, :lastname, :phone, :mobile)'),
    $statement->bindValue(':firstname', $_POST['firstname'], PDO::PARAM_STR);
    $statement->bindValue(':lastname', $_POST['lastname'], PDO::PARAM_STR);
    $statement->bindValue(':phone', $_POST['phone'], PDO::PARAM_STR);
    $statement->bindValue(':mobile', $_POST['mobile'], PDO::PARAM_STR);
    $statement->execute();
    …
  }
  …
}

That becomes:

--- lib/ContactsBookMain.php ---

class ContactsBookMain
{
  …
  public function edit()
  {
    …
    $contact = new Contacts();
    $contact->firstname = $_POST['firstname'];
    $contact->lastname = $_POST['lastname'];
    $contact->phone = $_POST['phone'];
    $contact->mobile = $_POST['mobile'];
    $contact->save();
    …
  }
}

Run the tests for the last time and make sure everything is still correct.

Separate Business Logic from View

Problem: “The representation of the application (GUI) is coupled with the business logic.”

Solution: “Separate the business logic from its representation through the Decorator pattern and Template View pattern.”

Motivation

Many PHP applications are written in a procedural manner with business logic, that is, code delegated to the retrieval of data, such as from a database, fully coupled with the logic of representation, usually HTML. The common portions of the code related to view such as the header, footer, or sidebar are included as PHP script. This type of design is very complex to maintain. For example, if we want to add a new view for some of our data to the database, such as a view for mobile devices, we should duplicate all the business logic in a new script and then write the new logic of the representation. If, for example, we want to reuse the same representation with a different header, footer, and sidebar, we can do that only duplicating the code.

To solve this problem of software design inside this kind of PHP script, once again we are helped by design patterns, especially by the Decorator Design pattern and the Template View pattern.

The Decorator Design pattern allows you to draw a certain object in different ways by attaching different responsibilities dynamically. For example, we can have a basic layout that it is needed to render the content in the simplest way. To this layout we can dynamically attach a layout with a header, then a layout with a footer, and finally a layout with a sidebar. In practice, I can make my layout quite dynamic.

The second pattern that helps us represent our data in a totally flexible way is the Template View pattern. Creating the HTML code dynamically is much more difficult than it may seem, but a static HTML page does not fit to represent the data that can change dynamically based on demand. The best way to create dynamic web pages is to compose the pages as if they were static pages, but put markers that can be translated into calls providing dynamic information.

The Template View pattern is implemented with a template engine that can parse the HTML pages and replace the markers with the correct values. In PHP there are many template engines. Smarty is probably the most famous, although, lately, for the simple nature of PHP itself, there are some template engines using PHP itself as a marker in the template. Obviously if you use PHP as markers, you must make the logic simple, as the templates may also be modified by non-expert developers, such as graphics or UX experts.

The advantage of using a template engine is the ease of amending the code of representation and reusing the same code.

Mechanism

  • Extract the logic of view towards the Decorator pattern.
  • Implement a simple Decorator.
  • Install a template engine.
  • Extract the representational logic in templates.
  • Implement the rendering of views through the template engine.

Example

In the following example we will see how to implement the Decorator Design pattern and then the Template View pattern in our application “Contacts Book.” After some refactoring steps, we transformed our procedural code into object code, and we included an ORM to map our database related to the object model.

Decorator Design Pattern

The first step is to implement the Decorator pattern. We must change the logic of how we include HTML code around the content in the methods of the class ContactsBookMain. Instead of including the header and footer, we need to extract the included files and make only the content return to the method, delegating the rendering of the content to the class Decorator, which decorates content with the right layout.

In all methods we include header.php and footer.php after and before the rendering of content. The first step is delegating the rendering of the header and footer to a class called Decorator. With small steps, by refactoring we will extract the class Decorator.

First of all we extract two methods that include header.php and footer.php in the ContactsBookMain class.

--- lib/ContactsBookMain.php ---
class ContactsBookMain
{
  …
  public function header()
  {
    include(dirname(__FILE__).'/../header.php'),
  }

  public function footer()
  {
    include_once(dirname(__FILE__).'/../footer.php'),
  }
  …
}

Remove all headers and footers from the ContactsBookMainTest methods except from the header() and footer() methods, just extracted, and fix the test cases class ContactsBookMainTest, calling the two methods header() and footer() before the call to action.

--- tests/unit/ContactsBookMainTest.php ---
class ContactsBookMainTest extends PHPUnit_Extensions_Database_TestCase
{
  …

  public function setUp()
  {
    …
    $this->contacts_book_main = new ContactsBookMain();
    …
  }

  private function assertDomReturnXPath($action)
  {
    ob_start();
$this->contacts_book_main->header();
    $this->contacts_book_main->$action();
    $this->contacts_book_main->footer();
    $output = ob_get_contents();
    ob_end_clean();
    ….
  }
}

Run the unit test and verify that the tests are OK. At this point we can modify the index.php script so that it directly calls the methods header() and footer().

--- index.php ---
require_once('lib/ContactsBookMain.php'),

$contacts_book_main = new ContactsBookMain();
$contacts_book_main->header();
$contacts_book_main->index();
$contacts_book_main->footer();

And run functional test case tests/functional/contact/ListTest.php.

PHPUnit 3.4.1 by Sebastian Bergmann.

.

Time: 9 seconds

OK (1 test, 24 assertions)

With the “Extract Class” technique, we extract a new class called Decorator delegated to render the header and footer of the HTML page.

--- lib/Decorator.php ---

class Decorator
{
  public function header()
  {
    include(dirname(__FILE__).'/../header.php'),
  }

  public function footer()
  {
    include(dirname(__FILE__).'/../footer.php'),
  }
}

We modify the index.php script and the test cases ContactsBookMain so that they use the Decorator class instead of the methods header() and footer() of the ContactsBookMain class and remove these useless methods.

--- index.php ---
require_once('lib/ContactsBookMain.php'),

$decorator = new Decorator();
$contacts_book_main = new ContactsBookMain();
$decorator->header();
$contacts_book_main->index();
$decorator->footer();

--- test/unit/ContactsBookMainTest.php ---
class ContactsBookMainTest extends PHPUnit_Extensions_Database_TestCase
{
  …
  public function setUp()
  {
    …
    $this->contacts_book_main = new ContactsBookMain();
    $this->decorator = new Decorator();
    …
  }

  private function assertDomReturnXPath($action)
  {
    ob_start();
    $this->decorator->header();
    $this->contacts_book_main->$action();
    $this->decorator->footer();
    $output = ob_get_contents();
    ob_end_clean();
    ….
  }
}

We run the unit test and check that everything is still working.

PHPUnit 3.4.1 by Sebastian Bergmann.

….

Time: 1 second

OK (4 tests, 66 assertions)

A unit test should test only one class, so putting the class Decorator in ContactsBookMainTest unit test is wrong, because we're creating coupling in the test. To decouple, we need to create a new unit test called DecoratorTest and remove the code of the class Decorator from the test ContactsBookMainTest. Since we are delegating the class Decorator to rendering the header and footer, we move the tests for this output into the test case class DecoratorTest.

--- tests/unit/DecoratorTest.php ---

class DecoratorTest extends PHPUnit_Framework_TestCase
{
  protected $object;

  protected function setUp()
  {
    $this->decorator = new Decorator;
  }
public function testHeader()
  {
    ob_start();
    $this->decorator->header();
    $output = ob_get_contents();
    ob_end_clean();

    $this->assertRegExp('/<h1>Contacts Book</h1>/', $output);
    $this->assertRegExp('/<div id="content">/', $output);
  }

  public function testFooter()
  {
    ob_start();
    $this->decorator->footer();
    $output = ob_get_contents();
    ob_end_clean();

    $this->assertRegExp('/<div id="footer">/', $output);
    $this->assertRegExp('/All &copy; Francesco Trucchia & Jacopo Romei - Pro PHP image
Refactoring/', $output);
    $this->assertRegExp('/</div>/', $output);
  }
}

Run the test and verify that everything is OK. Then we modify the test ContactsBookMainTest, removing the instance of Decorator and removing the test of the header and footer output. Then, following the Decorator pattern, we can implement the render method that will decorate a content string with the right layout. We write the unit test first.

--- tests/unit/DecoratorTest.php ---
….
class DecoratorTest extends PHPUnit_Framework_TestCase
{
  …
  public function testRender()
  {
    ob_start();
    $this->decorator->render('Hello World'),
    $output = ob_get_contents();
    ob_end_clean();

    $this->assertRegExp('/<h1>Contacts Book</h1>/', $output);
    $this->assertRegExp('/<div id="content">/', $output);
    $this->assertRegExp('/Hello World/', $output);

    $this->assertRegExp('/<div id="footer">/', $output);
    $this->assertRegExp('/All &copy; Francesco Trucchia & Jacopo Romei - Pro PHP image
Refactoring/', $output);
    $this->assertRegExp('/</div>/', $output);
  }
}
?>
--- lib/DecoratorTest.php ---
<?php
class Decorator
{
  …
public function render($content)
  {
    $this->header();
    echo $content;
    $this->footer();
  }
}

The test checks that the content represented by the “Hello World” string is decorated with the right header and footer.

Last but not least, move the buffering output from the test to the render() method, so we can decide whether to print the output and simplify testing.

--- tests/unit/DecoratorTest.php ---
….
class DecoratorTest extends PHPUnit_Framework_TestCase
{
  …
  public function testRenderPrinting()
  {
    ob_start();
    $this->decorator->render('Hello World'),
    $output = ob_get_contents();
    ob_end_clean();

    $this->assertRegExp('/<h1>Contacts Book</h1>/', $output);
    $this->assertRegExp('/<div id="content">/', $output);
    $this->assertRegExp('/Hello World/', $output);

    $this->assertRegExp('/<div id="footer">/', $output);
    $this->assertRegExp('/All &copy; Francesco Trucchia & Jacopo Romei - Pro PHP image
Refactoring/', $output);
    $this->assertRegExp('/</div>/', $output);
  }

  public function testRender()
  {
    $output = $this->decorator->render('Hello World', false);

    $this->assertRegExp('/<h1>Contacts Book</h1>/', $output);
    $this->assertRegExp('/<div id="content">/', $output);
    $this->assertRegExp('/Hello World/', $output);

    $this->assertRegExp('/<div id="footer">/', $output);
    $this->assertRegExp('/All &copy; Francesco Trucchia & Jacopo Romei - Pro PHP image
Refactoring/', $output);
    $this->assertRegExp('/</div>/', $output);
  }
}

--- lib/DecoratorTest.php ---
class Decorator
{
  …
  public function render($content, $print = true)
  {
    ob_start();
    $this->header();
echo $content;
    $this->footer();
    $output = ob_get_contents();
    ob_end_clean();

    if ($print) echo $output;
    return $output;
  }
}

Run the DecoratorTest test and verify that everything is OK. Now we need to replace the calls header() and footer() in the index.php script with the render() call, finding a way to pass the output of the class ContactsBookMain to the render() method.

--- index.php ---
require_once('lib/ContactsBookMain.php'),
require_once('lib/Decorator.php'),

$decorator = new Decorator();
$contacts_book_main = new ContactsBookMain();

ob_start();
$contacts_book_main->index();
$content = ob_get_contents();
ob_end_clean();

$decorator->render($content);

To pass the output of the index() method to the render() method, we use the output buffer functions. Having to bufferize the output in all of the other scripts of our actions, to not duplicate the code, we decide to move the code into a new method called getContent() inside the class ContactsBookMain. The method takes as input the name of the string and returns the output of the content. We prepare the new test for this method and then we implement it.

--- tests/unit/ContactsBookMainTest.php ---
class ContactsBookMainTest extends PHPUnit_Extensions_Database_TestCase
{
  …

  public function actionExceptionProvider()
  {
    return array(array(), array(''), array('invalid'));
  }

  /**
   * @expectedException Exception
   * @dataProvider actionExceptionProvider
   */
  public function testGetContentException($action)
  {
    $this->contacts_book_main->getContent($action);
  }

  public function testGetContent()
  {
    $output = $this->contacts_book_main->getContent('index'),
$dom = new DOMDocument();
    $dom->loadHTML($output);

    $this->assertIndexOutput(new DOMXPath($dom));

  }
  …
}

--- lib/ContactsBookMain.php ---
class ContactsBookMain
{
  …
  public function getContent($action)
  {
    if (!method_exists($this, $action))
    {
      throw new Exception('Method '.$action.' does not exists'),
    }

    ob_start();
    $this->$action();
    $content = ob_get_contents();
    ob_end_clean();

    return $content;
  }
  …
}

--- index.php ---

$decorator = new Decorator();
$contacts_book_main = new ContactsBookMain();

$decorator->render($contacts_book_main->getContent('index'));

We run the listTest.php functional test and check that everything is correct. Then we can add in all the other PHP script logic of the Decorator pattern. Through this pattern, it becomes easy to decorate the output in any way, creating new classes that extend our Decorator.

Template View Pattern

The second major step is to separate business logic from the logic of representation within the class methods ContactsBookMain. The business logic is now coupled with the logic of representation. To solve this problem, we will use the Template View pattern, which will move the logic of representation in a template managed by a template engine.

We decide to use the template engine library Savant3 (http://phpsavant.com), a simple open-source template engine for PHP5 that uses PHP language as the template language. We install the library in the lib/vendor/savant folder. For installation details refer to the online document http://phpsavant.com/docs.

We need to include the class Savant3 in the bootstrap so that it is always available when needed.

--- lib/bootstrap.php ---

require_once(dirname(__FILE__) . '/vendor/savant/Savant3.php'),

To simplify the job of extracting the logical representation in the template, starting from the index() method of the class ContactsBookMain, we can extract the logic of representation in the method viewIndex() of the same class with the “Extract Method” refactoring technique.

--- lib/ContactsBookMain.php ---
class ContactsBookMain
{
  …
  public function viewIndex($contacts)
  {
    ?>
    <div class="actions">
      <a href="new.php">New contact</a>
    </div>

    <?php if (count($contacts)) : ?>
      <table border="1" cellspacing="0" cellpadding="5">
      <tr>
        <th>Last Name</th>
        <th>First Name</th>
        <th>Phone</th>
        <th>Mobile</th>
        <th>&nbsp;</th>
      </tr>
      <?php foreach($contacts as $contact) :?>
        <tr>
          <td><a href="edit.php?id=<?php echo $contact->id?>" title="Edit"><?php echo image
$contact->lastname?></a></td>
          <td><?php echo $contact->firstname?></td>
          <td><a href="callto://<?php echo $contact->phone?>"><?php echo image
$contact->phone?></a></td>
          <td><a href="callto://<?php echo $contact->mobile?>"><?php echo image
$contact->mobile?></a></td>
          <td>[<a href="remove.php?id=<?php echo $contact->id?>" title="Delete" onclick="if image
(confirm('Are you sure?')) {return true;} return false;">X</a>]</td>
        </tr>
      <?php endforeach;?>
      </table>

     <?php else: ?>
      Database is empty
    <?php endif;
  }

  public function index()
  {
    $contacts = Doctrine::getTable('Contacts')->
                          createQuery()->
                          orderBy('lastname ASC')->
                          execute();
$this->viewIndex($contacts);
  }
  …
}

Run the unit test ContactsBookMainTest and verify that everything is OK. Now create a new folder named templates in the root of the project and create a new file called index.tpl.php inside, where we'll move all the code inside the method viewIndex().

--- templates/index.tpl.php ---
<div class="actions">
  <a href="new.php">New contact</a>
</div>

<?php if (count($contacts)) : ?>
  <table border="1" cellspacing="0" cellpadding="5">
  <tr>
    <th>Last Name</th>
    <th>First Name</th>
    <th>Phone</th>
    <th>Mobile</th>
    <th>&nbsp;</th>
  </tr>
  <?php foreach($contacts as $contact) :?>
    <tr>
      <td><a href="edit.php?id=<?php echo $contact->id?>" title="Edit"><?php echo image
$contact->lastname?></a></td>
      <td><?php echo $contact->firstname?></td>
      <td><a href="callto://<?php echo $contact->phone?>"><?php echo $contact->phone?></a></td>
      <td><a href="callto://<?php echo $contact->mobile?>"><?php echo $contact->mobile?></a></td>
      <td>[<a href="remove.php?id=<?php echo $contact->id?>" title="Delete" onclick="if image
(confirm('Are you sure?')) {return true;} return false;">X</a>]</td>
    </tr>
  <?php endforeach;?>
  </table>

<?php else: ?>
  Database is empty
<?php endif; ?>

We implement in the method viewIndex() all the logic to use the template with the template engine Savant3.

class ContactsBookMain
{
  …
  public function viewIndex($contacts)
  {
    $tpl = new Savant3();
    $tpl->contacts = $contacts;
    $tpl->addPath('template', dirname(__FILE__).'/../templates/'),
    $tpl->display('index.tpl.php'),
  }
?>

If we need to pass some variable to the template we just can pass it as a Savant3 attribute. In the template we have to change the variable $contacts to $this->contacts, because we can access the variable passed to the template only as attributes of the class.

--- templates/index.tpl.php ---

<?php if (count($this->contacts)) : ?>
  …
  <?php foreach($this->contacts as $contact) :?>
  …

Since the method viewIndex() is not used by anybody but the method index() and because it is a very simple method, we decide to remove its code to the end of the index() method.

Now we can perform all the same steps for all other class methods of ContactsBookMain, where business logic is coupled with representational logic.

To improve our code, we make the instance of Savant3 an attribute of the Contacts BookMain class, so we can remove code duplication in the method.

class ContactsBookMain
{
  protected $tpl;

  public function  __construct()
  {
    $this->tpl = new Savant3();
    $this->tpl->addPath('template', dirname(__FILE__).'/../templates/'),
  }
  …
  public function index()
  {
    …
    $this->tpl->contacts = $contacts;
    $this->tpl->display('index.tpl.php'),

  }
}

We run all the tests again and if everything is correct, we have succeeded in this big refactoring, which has made our code more maintainable, easier to use, and more object-oriented.

PHPUnit 3.4.1 by Sebastian Bergmann.

……………

Time: 47 seconds

OK (15 tests, 170 assertions)

A useful exercise, which we leave to the reader, is to use the template engine in the Decorator class, making it more configurable. In this way we can make the layout of our application more dynamic, such as customizing the title or changing the CSS, depending on the action.

MVC Architecture

Problem: “The actions of the software are separated into many different scripts, in which the view and the model are coupled.”

Solution: “Implement the Model View Controller architecture to separate the model from view and have a controller that tries to create a response by invoking the right action.”

Motivation

The essential purpose of MVC is to bridge the gap between the human user's mental model and the digital model that exists in the computer.

—Trygve M. H. Reenskaug[REE78]

The coupling of models, which corresponds to the logic and data of our domain, with the view, which is how we can represent the logic of our domain (GUI), with the controller, delegated to receive input and send a response to the client, is a bad smell, because the application makes it very difficult to test and maintain.

The Model View Controller is an architectural pattern that solves problems about the right design of these components, decoupling and explaining how they communicate. MVC is often seen in web applications where the view is the HTML or XHTML generated by the application. The controller receives GET or POST input and decides what to do with it, handing it over to domain objects (i.e., the model) that contain the business rules and know how to carry out specific tasks such as processing a new subscription.

In PHP there are many MVC frameworks that can be used in our application, however, we recommend passing first through a simple implementation of an MVC architecture, as we shall see in the example, then maybe moving to a more mature open-source MVC framework, respecting the principle of small steps.

Mechanism

If we start from procedural code, first we need to implement a Façade class through the technique of “Transform Procedural Code into Object Code.”

Then if I have a relational database and I query it with simple SQL, I could introduce an ORM to map my relational database into objects through the technique “Replace With SQL ORM.”

Finally, if I have business logic coupled with view logic, I could introduce a template engine to the decoupling component through the technique of “Separate Business Logic from View.”

Once these preliminary steps are made, implementing an MVC architecture becomes very simple, and I can do it with the following steps:

Extract the Controller class from the Façade class.

  1. Delegate the execution of the Façade actions to the Controller class.
  2. Replace client calls to the class Façade with calls to the Controller class.
  3. Delegate the Controller class to render the whole response.
  4. Extract from the Façade class the Actions class that implements only the user actions.
  5. Remove the Façade class.

Example

We will use the “Contacts Book” application as an example for this refactoring, starting from the state we achieved in the previous section. We have a Façade class ContactsBookMain delegated to the execution of individual actions, an ORM delegated to manage our model, and a template engine delegated to represent the model.

The target of this last step of refactoring is to remove the Façade class, which serves only as a support to improve the procedural code initially, and delegate the execution of the right action called by the client rendered with the correct view to a Controller class.

The Façade class implements a method called getContent(), which is the controller of our application, since it takes as input an action name, executes it, and returns the output.

--- lib/ContactsBookMain.php ---
class ContactsBookMain
{
  …
  public function getContent($action)
  {
    if (!method_exists($this, $action))
    {
      throw new Exception('Method '.$action.' does not exists'),
    }

    ob_start();
    $this->$action();
    $content = ob_get_contents();
    ob_end_clean();

    return $content;
  }
  …
}

The method getContent() should not be in the Façade class, but in the Controller class. We extract the Controller class and the method getContent()from the class ContactsBookMain. Before doing so, we write a simple unit test for the Controller class.

--- tests/unit/ControllerTest.php ---

class ControllerTest extends PHPUnit_Extensions_Database_TestCase
{
  public function setUp()
  {
    $this->controller = new Controller();
  }

  public function testGetContent()
  {
    $output = $this->controller->getContent('index'),
    $this->asserTrue(is_string($output));
  }
}

Run the test, which will fail because the Controller class doesn't exist. Then we create it and include it in the test.

--- lib/Controller.php ---
class Controller
{ }

After running the unit test, it fails again because the method getContent() doesn't exist. We move the getContent() method from the ContactsBookMain class to the Controller class.

--- lib/Controller.php ---
class Controller
{
  public function getContent($action)
  {
    if (!method_exists($this, $action))
    {
      throw new Exception('Method '.$action.' does not exists'),
    }

    ob_start();
    $this->$action();
    $content = ob_get_contents();
    ob_end_clean();

    return $content;
  }
  …
}

Run the test again. It fails again, notifying that the method index() does not exist. The method index() doesn't exist because it is a method of the ContactsBookMain class. Creating a new instance of the class ContactsBookMain inside the method getContent() would couple the Controller class with the ContactsBookMain class, which is wrong because it makes the Controller test dependent on ContactsBookMain. So we decide to pass the class name to use as a parameter of the Controller constructor. We also check that if the class and action passed don't exist, the method will throw an exception.

--- tests/lib/mock/ModuleMock.php ---
class ModuleMock
{
  public function index()
  {
    echo 'Hello World';
  }
}

--- tests/unit/ControllerTest.php ---
class ControllerTest extends PHPUnit_Framework_TestCase
{
  …
  public function testGetContent()
  {
    $output = $this->controller->getContent('ModuleMock', 'index'),
    $this->assertTrue(is_string($output));
    $this->assertEquals('Hello World', $output);
  }

  /**
   * @expectedException Exception
*/
  public function testExceptionGetContentNotValidClassName()
  {
    $output = $this->controller->getContent('NotValidClassName', 'index'),
  }

  /**
   * @expectedException Exception
   */
  public function testExceptionGetContentNotValidModuleName()
  {
    $output = $this->controller->getContent('ModuleMock', 'invalidMethod'),
  }
  …
}

Edit the getContent() method in order to create a new instance of the class passed and a call to right action.

--- lib/Controller.php ---
class Controller
{
  private $module;

  public function getContent($module, $action)
  {
    if (!class_exists($module))
    {
      throw new Exception('Class '.$module.' does not exists'),
    }

    $this->module = new $module();

    if (!method_exists($this->module, $action))
    {
      throw new Exception('Method '.$action.' does not exists'),
    }

    ob_start();
    $this->module->$action();
    $content = ob_get_contents();
    ob_end_clean();

    return $content;
  }
}

We run the test and everything is correct.

PHPUnit 3.4.1 by Sebastian Bergmann.



Time: 0 seconds

OK (3 tests, 4 assertions)

Finally, we transform the method getContent() in the ContactsBookMain class in a proxy method to the getContent() method of the Controller class.

--- lib/ContactsBookMain.php ---
class ContactsBookMain
{
  …
  public function getContent($action)
  {
    $controller = new Controller();
    return $controller->getContent('ContactsBookMain', $action);
  }
  …
}

Run all tests and verify that the behavior of our application has not changed.

PHPUnit 3.4.1 by Sebastian Bergmann.

………………

Time: 46 seconds

OK (18 tests, 174 assertions)

Replace the instance of the ContactsBookMain class in all our scripts with the instance of the Controller class and remove the method getContent() from the class ContactsBookMain. Run the test and verify that everything is OK.

For example, in the index.php script the result at the end of the replacement will be the following:

--- index.php ---
require_once('lib/ContactsBookMain.php'),
require_once('lib/Decorator.php'),

$decorator = new Decorator();
$controller = new Controller();
$decorator->render($controller->getContent('ContactsBookMain', 'index'));

In MVC architecture, the controller is delegated to prepare the whole response to be sent to the client. For this reason, the class Decorator must be passed to the Controller class. Let's change the interface of the Controller construct passing a new instance of the Decorator class and delegating the method getContent() to call the render() method of the Decorator class. In the test we use a stub to verify that the render() method is called successfully.

--- tests/unit/ControllerTest.php ---

class ControllerTest extends PHPUnit_Framework_TestCase
{
  public function setUp()
  {
    $this->decorator = $this->getMock('Decorator', array('render'));
    $this->controller = new Controller($this->decorator);
  }

  public function testGetContent()
  {
    $this->decorator->
expects($this->once())->
           method('render')->
           with($this->equalTo('Hello World'));

    $output = $this->controller->getContent('ModuleMock', 'index'),
    $this->assertTrue(is_string($output));
    $this->assertEquals('Hello World', $output);
  }
  …
}

Modify the Controller class to accommodate the instance of the Decorator class and perform the call to render() method.

--- lib/Controller.php ---
class Controller
{
  private $module;
  private $decorator;

  public function __construct(Decorator $decorator)
  {
    $this->decorator = $decorator;
  }

  public function getContent($module, $action)
  {
    …
    $this->decorator->render($content);
    return $content;
  }
}

Replace client calls to the Controller class passing an instance of the Decorator class. For example, the index.php script becomes

--- index.php ---
require_once('lib/ContactsBookMain.php'),
require_once('lib/Decorator.php'),

$controller = new Controller(new Decorator());
$controller->getContent('ContactsBookMain', 'index'),

The method getContent() of the class Controller initially simply returns the content; now it also performs other activities. It is delegated to dispatch input and render the representation. Through the “Extract Method” technique, we want to separate the two actions, extracting a new method called dispatch() from the method getContent(). We prepare a unit test.

--- tests/unit/ControllerTest.php
class ControllerTest extends PHPUnit_Framework_TestCase
{
  …
  public function testDispatch()
  {
    $this->decorator->
           expects($this->once())->
           method('render')->
with($this->equalTo('Hello World'));

    $this->controller->dispatch('ModuleMock', 'index'),
  }
  …
}

Transform the getContent()method in a proxy method to dispatch().

--- lib/Controller.php ---
class Controller
{
  …
  public function dispatch($module, $action)
  {
    if (!class_exists($module))
    {
      throw new Exception('Class '.$module.' does not exists'),
    }

    $this->module = new $module();

    if (!method_exists($this->module, $action))
    {
      throw new Exception('Method '.$action.' does not exists'),
    }

    ob_start();
    $this->module->$action();
    $content = ob_get_contents();
    ob_end_clean();

    $this->decorator->render($content);

    return $content;
  }

  public function getContent($module, $action)
  {
    return $this->dispatch($module, $action);
  }
}

Run all tests and verify that the code is still correct. Finally, as the last step, we transform the variable $content in the dispatch() method, in the attribute $content of the Controller class. Then, we replace all the client calls to the method getContent() with calls to the dispatch() method, and then we make sure that the method getContent() is only a getter method of the $content attribute.

The end result of the Controller class is as follows:

--- lib/Controller.php ---

class Controller
{
  private $module;
  private $decorator;
  private $content;
public function __construct(Decorator $decorator)
  {
    $this->decorator = $decorator;
  }

  public function dispatch($module, $action)
  {
    if (!class_exists($module))
    {
      throw new Exception('Class '.$module.' does not exists'),
    }

    $this->module = new $module();

    if (!method_exists($this->module, $action))
    {
      throw new Exception('Method '.$action.' does not exists'),
    }

    ob_start();
    $this->module->$action();
    $this->content = ob_get_contents();
    ob_end_clean();

    $this->decorator->render($this->content);
  }

  public function getContent()
  {
    return $this->content;
  }
}

Changing client calls, the index.php script, for example, is as follows:

--- index.php ---
require_once('lib/ContactsBookMain.php'),
require_once('lib/Decorator.php'),

$controller = new Controller(new Decorator());
$controller->dispatch('ContactsBookMain', 'index'),

Finally, we have to extract from the ContactsBookMain class the ContactsActions class, which implements all the actions that the user can perform in our software with the Contacts entity. Through the techniques of the “Pull Up Field” and “Pull Up Method” seen in Chapter 12, move all methods and attributes from the class ContactsBookMain to the class ContactsActions. Finally, with the technique “Collapse Hierarchy” shown in Chapter 12, remove the class ContactsBookMain. Remove all of the reference in the code also, changing it with the ContactsActions reference.

--- lib/ContactsActions ---
class ContactsActions
{
  …
}

We also have to change in all PHP scripts the first parameters passed to the dispatch() method of the Controller class from “ContactsBookMain” to “ContactsActions.” For example, in index.php the code is the following:

--- index.php ---

$controller = new Controller(new Decorator());
$controller->dispatch('ContactsActions', 'index'),

Fix all unit tests and run all functional and unit tests again.

PHPUnit 3.4.1 by Sebastian Bergmann.

……………

Time: 49 seconds

OK (15 tests, 152 assertions)

All tests builds are green, and now our software has MVC architecture.

Summary

In this chapter we transformed our procedural application in an object-oriented application on MVC architecture. After the first essential step, using the Façade pattern, we succeeded with refactoring techniques presented in previous chapters and design patterns, moving in small steps, to achieve a better design of our application.

Surely there is still work to do to make our application better, but now we know the techniques of refactoring that at any time will allow us to improve a software's design. So now, adding new features or fixing bugs, through refactoring, we will constantly improve the quality of our application without losing any value.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
13.58.114.29