Jump to content

PHP/Domain models

From Wikiversity
< PHP
Type classification: this is an article resource.

A while ago, the Python chaps wrote a "complete guide to piano-playing" that went something like:[1]

  1. Sit down at the piano;
  2. Select the right key;
  3. Put it in the piano, and open the lid;
  4. Once the piano is fully open, put your fingers on top of the notes
  5. Move your fingers about, making sure they hit the right notes in the correct order (like a pianist)
  6. Watch your friends be amazed

This article attempts to similarly enumerate the steps that one should go through when designing and writing the domain model portion of a computer program written in PHP (using Composer and PHPUnit). Hopefully your friends will be amazed.

The domain model is the first part of the program that is worked on, after the first requirements have been decided upon. It must be constructed with no regard to anything in the world beyond its borders (no user interface, no database, no files, nothing). The only interaction that the model has with the outside world is when it uses objects passed in to it (and these are all either primitive, i.e. basic or built-in) or are implementations of interfaces that the domain model itself defines.

The domain model is the most important part of a program. Its design is more important than the designs of either the user interface or the database schema (if either exist). A valid and verified domain model will have longer-lasting benefits than these other aspects of a program, because it deals with the real-world details of the domain in question.

Sit down at the computer

[edit | edit source]

Or stand, if a standing desk is more comfortable for you.

Obtain requirements

[edit | edit source]

Obtain (or write from scratch) a list of requirements, or problem statement.

For the purposes of explanation, this article assumes a need to maintain a list of Widgets and some information about them.

Set up test framework

[edit | edit source]

The test framework should be the only non-domain thing in existance, to start with.

To get started with this, make a composer.json file in a new empty directory and in it require PHPunit. Also set up autoloading for the classes' directory.[2]

composer.json

{
    "autoload": {
        "psr-0": { "": "classes/" }
    },
    "require-dev": {
        "phpunit/phpunit": "*"
    }
}

Then install it:

$ composer install


Also create phpunit.xml in the same directory:

phpunit.xml

<?xml version="1.0" encoding="utf-8" ?>
<phpunit bootstrap="vendor/autoload.php">
    <testsuites>
        <testsuite name="Tests">
            <directory>tests</directory>
        </testsuite>
    </testsuites>
</phpunit>

Create a first test in tests/FirstTest.php (a temporary name, and it'll be changed soon enough):

tests/FirstTest.php

<?php
class FirstTest extends PHPUnit_Framework_TestCase {
    /**
     * @test
     */
    public function can_test() {
        $this->assertTrue(TRUE);
    }
}

Now test that everything is set up by running:

$ ./vendor/bin/phpunit


At this point, development proper can begin. This is also the point to which we return in each development cycle when it is time to begin again (apart from sitting down beforehand).

Write a test

[edit | edit source]

Assume the following simple requirements of the system that is being developed:

  1. A list of Widgets must be maintained.
  2. A Widget has a name and a type, and the name must be unique for the given type.

This is enough to demonstrate various constraints and the idea of a data repository.

The first task is to draw a class diagram of the desired interface (shown at right). This must know nothing about the outside world. Create it with the words and structures used in the domain in question — this is not really, in one sense, a programming API but rather an expression of the ways in which one can interact with the domain.

Then start testing it. The first test will create a new Widget and give it a name:

tests/FirstTest.php

/**
 * @test
 */
public function create() {
    $widget = new Widget();
    $widget->setName('Test Widget');
    $this->assertEquals('Test Widget', $widget->getName());
}

Running this will result in a failure, because the Widget class doesn't exist. So, create it (in a new classes directory) and write the obvious getter-setter code (this could, if one were being a bit more 'pure' about TDD, be done in multiple steps, but it's quicker to just get the basics out of the way).

classes/Widget.php

<?php
class Widget {
    protected $name;
    public function setName($name) {
        $this->name = $name;
    }
    public function getName() {
        return $this->name;
    }
}

The test should now pass:

$ ./vendor/bin/phpunit
PHPUnit 4.0.12 by Sebastian Bergmann.
Configuration read from /home/user/code/wikiversity-domain-model/phpunit.xml

.

Time: 33 ms, Memory: 2.25Mb
OK (1 test, 1 assertion)


So far, so good, and nothing really has been achieved. The system is set up now, however, for actual development.

This is usually the point at which a nacent project can be put under version control. Put the domain model (and tests etc.) in their own separate repository, to help maintain the discipline of not developing them in any direction that is tied to any particular storage or interface system. This separate repository can then be added as a dependency in any project's composer.json.

Write more tests

[edit | edit source]

References

[edit | edit source]
  1. Monty Python (2005). The fairly incomplete & rather badly illustrated Monty Python song book. With complete instructions on how to play the piano.. ISBN 9780413775290. http://www.methuen.co.uk/the-fairly-incomplete-badly-illustrated-songbook/b/82. 
  2. "PHPUnit, Composer, Autoload, Bootstrap". Shane's blog. 22 May 2013. Retrieved 24 March 2014.