We will develop a clone of Threes in Symfony. If you ever played 2048, they are very similar. Threes is its predecessor.

Gameplay:

The player slides numbered tiles on a four-by-four grid to combine addends and multiples of three.For example, ones and twos merge to become a single “three” tile, two threes merge into “six”, and two sixes merge into “12”. Swiping the screen up, down, left, or right moves all of the tiles on the grid in that direction and adds a new tile to the grid in the same direction

Before you start, I suggest you give the game a try, you can find a web version on the official website. Just a quick try to get a feeling about the gameplay then come back here.

You’re back, great! Let’s start by creating a standard Symfony application.

composer create-project symfony/framework-standard-edition threes

Hit enter to set default parameters when prompted.

We will develop this game using TDD style, if you are not familiar with TDD, this should be a good exercise to get you started. I recommend that you follow this tutorial as a workshop that you develop in a dev environment. It is not possible to feel the TDD click just by reading.

By default, Symfony standard doesn’t come with phpunit, so let’s require it.

composer require phpunit/phpunit --dev

The game is played on a 4×4 grid board, which gives us a good starting point. So a new board should have 16 empty cells, right? Let’s rewrite this assumption in PHP.

tests/Threes/BoardTest.php

<?php

namespace Tests\Threes;

use PHPUnit\Framework\TestCase;
use Threes\Board;

class BoardTest extends TestCase
{

    public function test_new()
    {
        $board = new Board();

        $this->assertEquals(
            [
                [0, 0, 0, 0],
                [0, 0, 0, 0],
                [0, 0, 0, 0],
                [0, 0, 0, 0],
            ],
            $board->getGrid()
        );
    }
}

The first version of the Board class in src/Threes/Board.php must be straightforward

<?php

namespace Threes;

class Board
{
    private $grid = [
        [0, 0, 0, 0],
        [0, 0, 0, 0],
        [0, 0, 0, 0],
        [0, 0, 0, 0],
    ];

    public function getGrid()
    {
        return $this->grid;
    }
}

Running the test suite, all good

./vendor/bin/phpunit
PHPUnit 4.8.35 by Sebastian Bergmann and contributors.

.

Time: 72 ms, Memory: 6.00MB

OK (1 test, 1 assertion)

Next, when we slide up, all cells should move up, this will be the second test case.

public function test_up()
{
    $board = new Board();

    $board->setGrid([
        [0, 0, 0, 0],
        [1, 0, 0, 0],
        [0, 0, 0, 0],
        [0, 0, 0, 0],
    ]);

    $board->up();

    $this->assertEquals(
        [
            [1, 0, 0, 0],
            [0, 0, 0, 0],
            [0, 0, 0, 0],
            [0, 0, 0, 0],
        ],
        $board->getGrid()
    );
}

First, we need to add a grid setter to the Board

public function setGrid(array $grid)
{
    $this->grid = $grid;
}

Then the up method

public function up()
{
    for ($l = 1; $l < 4; $l++) {
        for ($c = 0; $c < 4; $c++) {
            $currentValue = $this->grid[$l][$c];
            $this->grid[$l-1][$c] = $currentValue;
            $this->grid[$l][$c] = 0;
        }
    }
}

Running the tests confirms this algorithm works. Let’s update the test_up to cover the case where a value is stuck to the top border.

public function test_up()
{
    $board = new Board();

    $board->setGrid([
        [0, 1, 0, 0],
        [1, 0, 0, 0],
        [0, 0, 0, 0],
        [0, 0, 0, 0],
    ]);

    $board->up();

    $this->assertEquals(
        [
            [1, 1, 0, 0],
            [0, 0, 0, 0],
            [0, 0, 0, 0],
            [0, 0, 0, 0],
        ],
        $board->getGrid()
    );
}
./vendor/bin/phpunit
PHPUnit 4.8.35 by Sebastian Bergmann and contributors.

.F

Time: 80 ms, Memory: 6.00MB

There was 1 failure:

1) Tests\Threes\BoardTest::test_up
Failed asserting that two arrays are equal.
--- Expected
+++ Actual
@@ @@
 Array (
     0 => Array (
         0 => 1
-        1 => 1
+        1 => 0
         2 => 0
         3 => 0
     )
     1 => Array (...)
     2 => Array (...)
     3 => Array (...)
 )

/home/ilyes/projects/threes/tests/Threes/BoardTest.php:46

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

So our initial algorithm did overwrite the [0,1] cell. So if the current value is 0, we can simply exit the loop.

public function up()
{
    for ($l = 1; $l < 4; $l++) {
        for ($c = 0; $c < 4; $c++) {
            $currentValue = $this->grid[$l][$c];
            if ($currentValue === 0) {
                continue;
            }
            $this->grid[$l - 1][$c] = $currentValue;
            $this->grid[$l][$c] = 0;
        }
    }
}
$ ./vendor/bin/phpunit
PHPUnit 4.8.35 by Sebastian Bergmann and contributors.

..

Time: 74 ms, Memory: 6.00MB

OK (2 tests, 2 assertions)

Next case is when all the vertical column is filled, nothing should move. We can add this situation to the third column of our test.

public function test_up()
{
    $board = new Board();

    $board->setGrid([
        [0, 1, 1, 0],
        [1, 0, 1, 0],
        [0, 0, 1, 0],
        [0, 0, 1, 0],
    ]);

    $board->up();

    $this->assertEquals(
        [
            [1, 1, 1, 0],
            [0, 0, 1, 0],
            [0, 0, 1, 0],
            [0, 0, 1, 0],
        ],
        $board->getGrid()
    );
}

The test fails with

Failed asserting that two arrays are equal.
--- Expected
+++ Actual
@@ @@
         1 => 0
-        2 => 1
+        2 => 0
         3 => 0
     )
 )

Apparently our algorithm set the cell [2,3] to 0. So we need to check if a cell is empty before overwriting it’s value. The new up method looks like

public function up()
{
    for ($l = 1; $l < 4; $l++) {
        for ($c = 0; $c < 4; $c++) {
            $currentValue = $this->grid[$l][$c];
            if ($currentValue === 0) {
                continue;
            }
            if ($this->grid[$l - 1][$c] !== 0) {
                continue;
            }
            $this->grid[$l - 1][$c] = $currentValue;
            $this->grid[$l][$c] = 0;
        }
    }
}

Next case is when values are separated by an empty cell. We can use the fourfth column for this.

public function test_up()
{
    $board = new Board();

    $board->setGrid([
        [0, 1, 1, 0],
        [1, 0, 1, 1],
        [0, 0, 1, 0],
        [0, 0, 1, 1],
    ]);

    $board->up();

    $this->assertEquals(
        [
            [1, 1, 1, 1],
            [0, 0, 1, 0],
            [0, 0, 1, 1],
            [0, 0, 1, 0],
        ],
        $board->getGrid()
    );
}

Tests pass, this was covered out of the box.

Next game rule is that two cells with values 1 and 2 should merge into 3.

public function test_up_add()
{
    $board = new Board();

    $board->setGrid([
        [1, 0, 0, 0],
        [2, 0, 0, 0],
        [0, 0, 0, 0],
        [0, 0, 0, 0],
    ]);

    $board->up();

    $this->assertEquals(
        [
            [3, 0, 0, 0],
            [0, 0, 0, 0],
            [0, 0, 0, 0],
            [0, 0, 0, 0],
        ],
        $board->getGrid()
    );
}

Currently, our algorithm will skip processing if the cell below the current one is not empty if ($this->grid[$l - 1][$c] !== 0) {continue;}. So before continue we need to check if the current value and the next one are 1 and 3 and add them. The if block becomes

if ($this->grid[$l - 1][$c] !== 0) {
    if ($currentValue === 2 && $this->grid[$l - 1][$c] === 1) {
        $this->grid[$l - 1][$c] = $currentValue + $this->grid[$l - 1][$c];
        $this->grid[$l][$c] = 0;
    }
    continue;
}

Test are green, let’s use the second column of the test to include the reverse case, means 3 and 1.

public function test_up_add()
{
    $board = new Board();

    $board->setGrid([
        [1, 2, 0, 0],
        [2, 1, 0, 0],
        [0, 0, 0, 0],
        [0, 0, 0, 0],
    ]);

    $board->up();

    $this->assertEquals(
        [
            [3, 3, 0, 0],
            [0, 0, 0, 0],
            [0, 0, 0, 0],
            [0, 0, 0, 0],
        ],
        $board->getGrid()
    );
}

The tests fail again, we need to extend the condition to cover this case as well, the extended version looks like

if (
    $currentValue === 2 && $this->grid[$l - 1][$c] === 1
    ||
    $currentValue === 1 && $this->grid[$l - 1][$c] === 2
)

Tests are green, but the up method started to look unreadable.

public function up()
{
    for ($l = 1; $l < 4; $l++) {
        for ($c = 0; $c < 4; $c++) {
            $currentValue = $this->grid[$l][$c];
            if ($currentValue === 0) {
                continue;
            }
            if ($this->grid[$l - 1][$c] !== 0) {
                if (
                    $currentValue === 2 && $this->grid[$l - 1][$c] === 1
                    ||
                    $currentValue === 1 && $this->grid[$l - 1][$c] === 2
                ) {
                    $this->grid[$l - 1][$c] = $currentValue + $this->grid[$l - 1][$c];
                    $this->grid[$l][$c] = 0;
                }
                continue;
            }

            $this->grid[$l - 1][$c] = $currentValue;
            $this->grid[$l][$c] = 0;
        }
    }
}

Let’s perform some small refactorings to understand it better. We can start by extracting $this->grid[$l - 1][$c] into a local variable $nextValue.

Result:

public function up()
{
    for ($l = 1; $l < 4; $l++) {
        for ($c = 0; $c < 4; $c++) {
            $currentValue = $this->grid[$l][$c];
            if ($currentValue === 0) {
                continue;
            }
            $nextValue = $this->grid[$l - 1][$c];

            if ($nextValue !== 0) {
                if (
                    $currentValue === 2 && $nextValue === 1
                    ||
                    $currentValue === 1 && $nextValue === 2
                ) {
                    $this->grid[$l - 1][$c] = $currentValue + $nextValue;
                    $this->grid[$l][$c] = 0;
                }
            }

            $this->grid[$l - 1][$c] = $currentValue;
            $this->grid[$l][$c] = 0;
        }
    }
}

Next, let’s replace the second continue with an explicit second if.
Result:

public function up()
{
    for ($l = 1; $l < 4; $l++) {
        for ($c = 0; $c < 4; $c++) {
            $currentValue = $this->grid[$l][$c];
            if ($currentValue === 0) {
                continue;
            }
            $nextValue = $this->grid[$l - 1][$c];

            if ($nextValue !== 0) {
                if (
                    $currentValue === 2 && $nextValue === 1
                    ||
                    $currentValue === 1 && $nextValue === 2
                ) {
                    $this->grid[$l - 1][$c] = $currentValue + $nextValue;
                    $this->grid[$l][$c] = 0;
                }
            }
            if ($nextValue === 0) {
                $this->grid[$l - 1][$c] = $currentValue;
                $this->grid[$l][$c] = 0;
            }

        }
    }
}

Let’s merge the two embedded ifs into single one, so instead of

if ($nextValue !== 0) {
    if (
        $currentValue === 2 && $nextValue === 1
        ||
        $currentValue === 1 && $nextValue === 2
    ) {
        $this->grid[$l - 1][$c] = $currentValue + $nextValue;
        $this->grid[$l][$c] = 0;
    }
}

we have

if (
    $nextValue !== 0 && (
        $currentValue === 2 && $nextValue === 1
        ||
        $currentValue === 1 && $nextValue === 2
    )
) {
    $this->grid[$l - 1][$c] = $currentValue + $nextValue;
    $this->grid[$l][$c] = 0;
}

Now we can merge the two ifs into a single one with a large condition

Result:

public function up()
{
    for ($l = 1; $l < 4; $l++) {
        for ($c = 0; $c < 4; $c++) {
            $currentValue = $this->grid[$l][$c];
            if ($currentValue === 0) {
                continue;
            }
            $nextValue = $this->grid[$l - 1][$c];

            if (
                $nextValue === 0 ||
                $nextValue !== 0 && (
                    $currentValue === 2 && $nextValue === 1
                    ||
                    $currentValue === 1 && $nextValue === 2
                )
            ) {
                $this->grid[$l - 1][$c] = $currentValue + $nextValue;
                $this->grid[$l][$c] = 0;
            }
        }
    }
}

Is clear now that the block

if ($currentValue === 0) {
    continue;
}

is not relevant anymore, so we can get rid of it.

Let’s extract the if condition into a private method areMergable

Result:

public function up()
{
    for ($l = 1; $l < 4; $l++) {
        for ($c = 0; $c < 4; $c++) {
            $currentValue = $this->grid[$l][$c];
            $nextValue = $this->grid[$l - 1][$c];
            if ($this->areMergeable($nextValue, $currentValue)) {
                $this->grid[$l - 1][$c] = $currentValue + $nextValue;
                $this->grid[$l][$c] = 0;
            }
        }
    }
}

private function areMergeable($nextValue, $currentValue)
{
    return $nextValue === 0 ||
    $nextValue !== 0 && (
        $currentValue === 2 && $nextValue === 1
        ||
        $currentValue === 1 && $nextValue === 2
    );
}

I am tempted to extract a merge method as well

Result:

public function up()
{
    for ($l = 1; $l < 4; $l++) {
        for ($c = 0; $c < 4; $c++) {
            $this->mergeUp($l, $c);
        }
    }
}

private function mergeUp($line, $column)
{
    $currentValue = $this->grid[$line][$column];
    $nextValue = $this->grid[$line - 1][$column];
    if ($this->areMergeable($nextValue, $currentValue)) {
        $this->grid[$line - 1][$column] = $currentValue + $nextValue;
        $this->grid[$line][$column] = 0;
    }
}

Now with simple boolean substitution, we can make the areMergeable method more readable.

private function areMergeable($nextValue, $currentValue)
{
    return
        $nextValue === 0
        ||
        in_array([$currentValue, $nextValue], [[1, 2], [2, 1]]);
}

Don’t forget to confirm the correctness of each refactoring step by running the test suite.

Back to our test_up_add now, the next rule says that starting from 3 equal values add together. We use the third column for this case

public function test_up_add()
{
    $board = new Board();

    $board->setGrid([
        [1, 2, 3, 0],
        [2, 1, 3, 0],
        [0, 0, 0, 0],
        [0, 0, 0, 0],
    ]);

    $board->up();

    $this->assertEquals(
        [
            [3, 3, 6, 0],
            [0, 0, 0, 0],
            [0, 0, 0, 0],
            [0, 0, 0, 0],
        ],
        $board->getGrid()
    );
}

Now it becomes trivial to cover this case, we need an extra simple check in areMergeable method to see if the current and next values are equal.

private function areMergeable($nextValue, $currentValue)
{
    return
        $nextValue === 0
        ||
        in_array([$currentValue, $nextValue], [[1, 2], [2, 1]])
        || $currentValue === $nextValue && $nextValue > 2;
}

Next rule is when there are 3,3,1,2 on a column, only the first 3+3 should merge, we can add this case using the fourfth column in test_up_add

public function test_up_add()
{
    $board = new Board();

    $board->setGrid([
        [1, 2, 3, 3],
        [2, 1, 3, 3],
        [0, 0, 0, 1],
        [0, 0, 0, 2],
    ]);

    $board->up();

    $this->assertEquals(
        [
            [3, 3, 6, 6],
            [0, 0, 0, 1],
            [0, 0, 0, 2],
            [0, 0, 0, 0],
        ],
        $board->getGrid()
    );
}

Again, this was already covered. Now we can try the down feature. The test case will be very similar with test_up with the cells moving down.

public function test_down()
{
    $board = new Board();

    $board->setGrid([
        [0, 0, 1, 1],
        [1, 0, 1, 0],
        [0, 0, 1, 1],
        [0, 1, 1, 0],
    ]);

    $board->down();

    $this->assertEquals(
        [
            [0, 0, 1, 0],
            [0, 0, 1, 1],
            [1, 0, 1, 0],
            [0, 1, 1, 1],
        ],
        $board->getGrid()
    );
}

Let’s have a first try by duplicating up and mergeUp method and change the direction from up to down.

public function down()
{
    for ($l = 2; $l >= 0; $l--) {
        for ($c = 0; $c < 4; $c++) {
            $this->mergeDown($l, $c);
        }
    }
}

private function mergeDown($line, $column)
{
    $currentValue = $this->grid[$line][$column];
    $nextValue = $this->grid[$line + 1][$column];

    if ($this->areMergeable($nextValue, $currentValue)) {
        $this->grid[$line + 1][$column] = $currentValue + $nextValue;
        $this->grid[$line][$column] = 0;
    }
}

Running the tests.. Works! Looking at mergeUp and mergeDown method the only difference is $line - 1 in the first and $line + 1 in the second. It seems that we can pass it as $nextLine argument and merge the two methods into a single merge method. The new method becomes

private function merge($line, $column, $nextLine)
{
    $currentValue = $this->grid[$line][$column];
    $nextValue = $this->grid[$nextLine][$column];

    if ($this->areMergeable($nextValue, $currentValue)) {
        $this->grid[$nextLine][$column] = $currentValue + $nextValue;
        $this->grid[$line][$column] = 0;
    }
}

We get rid of mergeUp and mergeDown and we change the call to merge from up and down method.

public function up()
{
    for ($l = 1; $l < 4; $l++) {
        for ($c = 0; $c < 4; $c++) {
            $this->merge($l, $c, $l - 1);
        }
    }
}

public function down()
{
    for ($l = 2; $l >= 0; $l--) {
        for ($c = 0; $c < 4; $c++) {
            $this->merge($l, $c, $l + 1);
        }
    }
}

What we did so far is that we separated the 3 main concepts into separate methods
– Traversal boundaries and direction
– Merging algorithm
– Merging conditions

It is clear now that the test_down_add will work without any code change, let’s add the test

public function test_down_add()
{
    $board = new Board();

    $board->setGrid([
        [0, 0, 0, 1],
        [0, 0, 0, 2],
        [2, 2, 3, 3],
        [1, 1, 3, 3],
    ]);

    $board->down();

    $this->assertEquals(
        [
            [0, 0, 0, 0],
            [0, 0, 0, 1],
            [0, 0, 0, 2],
            [3, 3, 6, 6],
        ],
        $board->getGrid()
    );
}

Green tests confirms our assumption.

I bet you are now able to add right and left methods and get them right in the first shot. Let’s start from the right

public function test_right()
{
    $board = new Board();

    $board->setGrid([
        [1, 0, 0, 0],
        [0, 0, 0, 1],
        [1, 1, 1, 1],
        [1, 0, 1, 0],
    ]);

    $board->right();

    $this->assertEquals(
        [
            [0, 1, 0, 0],
            [0, 0, 0, 1],
            [1, 1, 1, 1],
            [0, 1, 0, 1],
        ],
        $board->getGrid()
    );
}
public function right()
{
    for ($l = 0; $l < 4; $l++) {
        for ($c = 2; $c >= 0; $c--) {
            $this->merge($l, $c, $l, $c + 1);
        }
    }

}

Like we added $nextLine argument to merge, it is obvious now that we need a $nextColumn one.

private function merge($line, $column, $nextLine, $nextColumn)
{
    $currentValue = $this->grid[$line][$column];
    $nextValue = $this->grid[$nextLine][$nextColumn];

    if ($this->areMergeable($nextValue, $currentValue)) {
        $this->grid[$nextLine][$nextColumn] = $currentValue + $nextValue;
        $this->grid[$line][$column] = 0;
    }
}

up and down methods will simply pass $c as third parameter.

I expect test_right_add to just work now, let’s see

public function test_right_add()
{
    $board = new Board();

    $board->setGrid([
        [0, 0, 2, 1],
        [0, 0, 1, 2],
        [0, 0, 3, 3],
        [1, 2, 3, 3],
    ]);

    $board->right();

    $this->assertEquals(
        [
            [0, 0, 0, 3],
            [0, 0, 0, 3],
            [0, 0, 0, 6],
            [0, 1, 2, 6],
        ],
        $board->getGrid()
    );
}

The left tests and method should be extremely trivial now

public function test_left()
{
    $board = new Board();

    $board->setGrid([
        [0, 1, 0, 0],
        [1, 0, 0, 0],
        [1, 1, 1, 1],
        [0, 1, 0, 1],
    ]);

    $board->left();

    $this->assertEquals(
        [
            [1, 0, 0, 0],
            [1, 0, 0, 0],
            [1, 1, 1, 1],
            [1, 0, 1, 0],
        ],
        $board->getGrid()
    );
}

public function test_left_add()
{
    $board = new Board();

    $board->setGrid([
        [1, 2, 0, 0],
        [2, 1, 0, 0],
        [3, 3, 0, 0],
        [3, 3, 1, 2],
    ]);

    $board->left();

    $this->assertEquals(
        [
            [3, 0, 0, 0],
            [3, 0, 0, 0],
            [6, 0, 0, 0],
            [6, 1, 2, 0],
        ],
        $board->getGrid()
    );
}
public function left()
{
    for ($l = 0; $l < 4; $l++) {
        for ($c = 1; $c < 4; $c++) {
            $this->merge($l, $c, $l, $c - 1);
        }
    }
}

In the next part, we will integrate the Board class into Symfony to be able to play it in a web browser.

We have been working on PerformanceMeterBundle that logs HTTP requests. Another important metric for a web application is database query execution time. Doctrine DBAL is currently part of the Symfony standard edition (required by Doctrine ORM). Doctrine is the most commonly used ORM in Symfony applications that requires a database layer. We will add a new feature that logs queries execution time, if the application is using DoctrineBundle.

PerformanceMeterBundle V0.0.2

Before thinking about this Doctrine stuff, we need to add support for multiple loggers first. Currently, configuring performance_meter: is enough to get it to work. Since we are planning to measure database queries as well, users will need to configure which metrics they are interested in. To support multiple loggers, the bundle should be able to support a configuration like

performance_meter:
  loggers:
    logger1:
    logger2:

We should make sure that:
1. Configuration is loaded
2. Request loggers are registered
3. KernelEventSubscriber is registered
4. KernelEventSubscriber accepts and uses 2 request loggers

Multiple loggers

1. Configuration loading

Replace tests/DependencyInjection/Fixtures/config.yml with the previous configuration, now the tests fail with Unrecognized option "loggers" under "performance_meter". We just need to make the tests pass to ensure the configuration is accepted.
We update src/DependencyInjection/Configuration.php to support the new option, before the return statement, add an option loggers of type array of arrays

$loggers = $root->children()->arrayNode('loggers')->prototype('array');

2. Register request loggers

First, let’s rename Tests\DependencyInjection\PerformanceMeterExtensionTest::test_registers_request_logger() to test_registers_request_loggers since we now expect multiple loggers. Second, change the test case to reflect the new expectation, we just wrap the previous code in a foreach as following

public function test_registers_request_loggers()
{
    $container = $this->createContainer();
    $container->compile();

    foreach (array('logger1', 'logger2') as $logger) {
        $requestLoggerDefinition = $container->getDefinition('performance_meter.request.' . $logger);
        $this->assertEquals(RequestLogger::class, $requestLoggerDefinition->getClass());
        $this->assertEquals(
            array(
                new Reference('logger', ContainerInterface::NULL_ON_INVALID_REFERENCE)
            ),
            $requestLoggerDefinition->getArguments()
        );
    }
}

This change should fail the tests with You have requested a non-existent service "performance_meter.request.logger1".
Until now, we defined the request logger service in Resources/config/services.xml. This was a simple method to do it, however now we have a dynamic number of loggers, so the XML method is not a valid option anymore. We need to register those services manually.
The ContainerBuilder passed to the extension’s load method offers a flexible interface to manipulate definitions. Let’s add a method to src/DependencyInjection/PerformanceMeterExtension.php that will programatically register all the loggers

private function registerRequestLoggers(array $config, ContainerBuilder $container)
{
    $loggerReference = new Reference('logger', ContainerInterface::NULL_ON_INVALID_REFERENCE);
    foreach ($config['loggers'] as $name => $logger) {
        $id = 'performance_meter.request.' . $name;
        $container->register($id, RequestLogger::class)->addArgument($loggerReference);
    }
}

Add a call to it $this->registerRequestLoggers($config, $container) after $loader->load('services.xml').

3. KernelEventSubscriber

In Tests\DependencyInjection\PerformanceMeterExtensionTest::test_registers_kernel_event_subscriber() we expect the constructor to have one argument: a Reference to performance_meter.request_logger. Which is obviously wrong since we should expect 2 loggers with the sample configuration we have. Instead of changing the constructor argument to an array, we will use a method addLogger for an easier to use interface.
Let’s update the test case to reflect the new expectation. The class is not changing, we only need to change the second assert to:

$this->assertEquals(
    array(
        array(
            'addLogger',
            array(
                new Reference('performance_meter.request.logger1')
            )
        ),
        array(
            'addLogger',
            array(
                new Reference('performance_meter.request.logger2')
            )
        ),
    )
    ,
    $eventSubscriberDefinition->getMethodCalls()
);

We will update registerRequestLoggers and add a method call to addLogger for each logger, the final code should look like this (the 2 added lines are commented):

private function registerRequestLoggers(array $config, ContainerBuilder $container)
{
    $loggerReference = new Reference('logger', ContainerInterface::NULL_ON_INVALID_REFERENCE);
    //Get the subscriber definition
    $eventSubscriberDefinition = $container->getDefinition('performance_meter.kernel_events_subscriber');
    foreach ($config['loggers'] as $name => $logger) {
        $id = 'performance_meter.request.' . $name;
        $container->register($id, RequestLogger::class)->addArgument($loggerReference);
        //Add a method call for this logger
        $eventSubscriberDefinition->addMethodCall('addLogger', array(new Reference($id)));
    }
}

4. KernelEventSubscriber accepts multiple loggers

Next, is to make sure KernelEventSubscriber will call all loggers on kernel.response event.
In Tests\KernelEventsSubscriberTest::test_logs_request_on_kernel_response change
$mockRequestLogger->expects($this->once())
to
$mockRequestLogger->expects($this->exactly(2)).
Remove $mockRequestLogger argument when instanciating KernelEventsSubscriber. Add $eventSubscriber->addLogger($mockRequestLogger); twice right after after $eventSubscriber instantiation $eventSubscriber->addLogger($mockRequestLogger); (we add the same logger twice).

The tests should fail miserably, we need to update Skafandri\PerformanceMeterBundle\KernelEventsSubscriber.
1. Remove the request logger from the constructor
2. Rename the property requestLogger to requestLoggers and give it an initial value of array()
3. Add addLogger method

public function addLogger(RequestLogger $logger)
{
    $this->requestLoggers[] = $logger;
}
  1. In onKernelResponse method, replace $this->requestLogger->logRequest($event->getRequest(), $duration); with
foreach ($this->requestLoggers as $logger) {
    $logger->logRequest($event->getRequest(), $duration);
}

Tests are green, all good. Or not? When your tests are green, as they always should be, you are able to clean and refactor without fear. Forget about the did I possibly broke something? feeling. My favorite refactoring technique is deleting code. Nothing can make a code base cleaner than deleting unnecessary code. Let’s delete some code from Resources/config/services.xml
All the performance_meter.request_logger service definition can go away, green tests confirmed.
The EventSubscriber argument can go away, green tests confirmed.

Wait a minute, we just refactored some configuration, that’s not code. I like to answer with a question. Consider the following function:

public function getPort()
{
    $line = explode("\n", file_get_contents('config.cfg'))[1];
    return explode(":", $line)[1];
}

and the following config.cfg

host: hostname
port: 300
memory_limit: 600

A call to getPort should return 300.
If you change 1 to 2 in the first line of the function, getPort will return 600.
If you swap port and memory_limit in the config, getPort will return 600.
Now getPort started to return a different value than before, was it a code change or a configuration change?
My answer: It was a code change.

Multiple logger types

So far we support multiple loggers but we assume they are all RequestLoggers since is the only supported logger. We are about to add an SQLLogger, so the user must configure the metric for each logger. Update the configuration fixture to

performance_meter:
  loggers:
    logger1:
      metric: request
    logger2:
      metric: request
    logger3:
      metric: sql

Running our test suite, it fails with Unrecognized option "metric". To add this option, in src/DependencyInjection/Configuration.php before the return statement, for each item in loggers, add an option metric of type scalar.

$loggers = $root->children()->arrayNode('loggers')->prototype('array');
$loggers->children()->scalarNode('metric');

return $tree;

Now PerformanceMeterExtensionTest::test_registers_kernel_event_subscriber fails because the bundle registered a logger3. To fix it, in src/DependencyInjection/PerformanceMeterExtension.php registerRequestLoggers should only register loggers of metric request. So at the beginning of the foreach, we add a simple check.

if ($logger['metric'] !== 'request') {
    continue;
}

So all we did is: support a metric configuration option for each logger and load RequestLoggers only when metric=request. Now we are ready to start working on an SQLLogger, or any new logger.

SQL logger

When we worked on RequestLogger, we plugged our Logger to the application using an event subscriber. This technique works for most Symfony components and bundles. Most of them dispatch useful events (Requests, Console, Security, Twig..). Doctrine has a lot of useful events as well, except for start/finish query which is what we need now. So a quick tour in Doctrine code might be fruiteful.
We can start by requiring doctrine bundle do get the source code composer require doctrine/doctrine-bundle --dev.
We know that every interaction with the database happens through a connection. So we can start by inspecting Doctrine\DBAL\Connection. The query method’s description says Executes an SQL statement. Seems exactly what we are looking for. Method source at the time of writing:

public function query()
{
    $this->connect();
    $args = func_get_args();

    $logger = $this->_config->getSQLLogger();
    if ($logger) {
        $logger->startQuery($args[0]);
    }
    try {
        switch (func_num_args()) {
            case 1:
                $statement = $this->_conn->query($args[0]);
                break;
            case 2:
                $statement = $this->_conn->query($args[0], $args[1]);
                break;
            default:
                $statement = call_user_func_array(array($this->_conn, 'query'), $args);
                break;
        }
    } catch (\Exception $ex) {
        throw DBALException::driverExceptionDuringQuery($this->_driver, $ex, $args[0]);
    }
    $statement->setFetchMode($this->defaultFetchMode);
    if ($logger) {
        $logger->stopQuery();
    }
    return $statement;
}

The interesting lines are:

$logger = $this->_config->getSQLLogger();
$logger->startQuery($args[0]);
$logger->stopQuery();

So if a logger is configured, it will be called before and after a query. $_config is an instance of Doctrine\DBAL\Configuration which has a nice setSQLLogger method, we need to call that method with our logger and it must implement Doctrine\DBAL\Logging\SQLLogger interface. We need to find where this class is being instantiated. A search with the FQCN in the doctrine source leads to vendor/doctrine/doctrine-bundle/Resources/config/dbal.xml. The relevant part is:

<parameter key="doctrine.dbal.configuration.class">Doctrine\DBAL\Configuration</parameter>
<service id="doctrine.dbal.connection.configuration"
  class="%doctrine.dbal.configuration.class%" public="false" abstract="true" />

So DoctrineBundle registers a connection configuration as service, so all we need to do is to find the configuration definition and add the correct method call to it. But there is a little problem: that service is private and abstract. A little bit of more digging, in DoctrineBundle\DependencyInjection\DoctrineExtension::loadDbalConnection the first line looks like

$configuration = $container->setDefinition(sprintf('doctrine.dbal.%s_connection.configuration', $name), 
new DefinitionDecorator('doctrine.dbal.connection.configuration'));`

So it will create a configuration service for each connection, we should consider this finding when writing a test case. Later in the Doctrine extension, it also registers a parameter with all connections named doctrine.connections.
The Connection class accepts a single logger. Since we need multiple loggers, we will have to pass a class that implements SQLLogger but supports multiple loggers. Luckily there is Doctrine\DBAL\Logging\LoggerChain which like it’s name suggests, chains multiple loggers.

So we know what we have to do:
1. Our SQLLogger should implement Doctrine\DBAL\Logging\SQLLogger interface.
2. Register a LoggerChain with references to all loggers.
3. Add a call configuration->setSQLLogger with a reference to our LoggerChain.

1. SQLLogger interface implementation

The interface is very clear, having only 2 methods startQuery and stopQuery.On stopQuery, we expect our logger to log an info with a context matching the arguments passed to startQuery. And it shouldn’t break in case there is no logger.

tests/SQLLoggerTest.php

<?php

namespace Skafandri\PerformanceMeterBundle\Tests;

use PHPUnit\Framework\TestCase;
use Psr\Log\LoggerInterface;
use Skafandri\PerformanceMeterBundle\SQLLogger;

class SQLLoggerTest extends TestCase
{
    public function test_logs_request()
    {
        $mockLogger = $this->getMockBuilder(LoggerInterface::class)->getMock();
        $mockLogger->expects($this->once())
            ->method('info')
            ->with(
                'performance_meter.sql_query',
                $this->callback(function ($context) {
                    return
                        'sql' === $context['sql'] &&
                        array('params') === $context['params'] &&
                        is_numeric($context['duration']);
                })
            );

        $sqlLogger = new SQLLogger($mockLogger);
        $sqlLogger->startQuery('sql', array('params'));
        $sqlLogger->stopQuery();
    }

    public function test_doesnt_break_without_logger()
    {
        $sqlLogger = new SQLLogger();
        $sqlLogger->startQuery('sql', array('params'));
        $sqlLogger->stopQuery();
    }
}

The implementation is only slightly different from RequestLogger

src/SQLLogger.php

<?php

namespace Skafandri\PerformanceMeterBundle;

use Psr\Log\LoggerInterface;
use Symfony\Component\Stopwatch\Stopwatch;

class SQLLogger implements \Doctrine\DBAL\Logging\SQLLogger
{

    /** @var LoggerInterface */
    private $logger;

    /** @var Stopwatch */
    private $stopwatch;

    private $sql;
    private $params;

    /**
     * SQLLogger constructor.
     * @param LoggerInterface|null $logger
     */
    public function __construct(LoggerInterface $logger = null)
    {
        $this->logger = $logger;
        $this->stopwatch = new Stopwatch();
    }

    /**
     * @inheritdoc
     */
    public function startQuery($sql, array $params = null, array $types = null)
    {
        $this->sql = $sql;
        $this->params = $params;
        $this->stopwatch->start('query');
    }

    /**
     * @inheritdoc
     */
    public function stopQuery()
    {
        if (!$this->logger) {
            return;
        }
        $duration = $this->stopwatch->stop('query')->getDuration();
        $context = array(
            'sql' => $this->sql,
            'params' => $this->params,
            'duration' => $duration
        );
        $this->logger->info('performance_meter.sql_query', $context);
    }
}

2. Register a LoggerChain

It must be of class LoggerChain and have a method call to addLogger with logger3. We add the test case to tests/DependencyInjection/PerformanceMeterExtensionTest.php

public function test_registers_logger_chain()
{
    $container = $this->createContainer();
    $container->compile();

    $loggerChainDefinition = $container->getDefinition('performance_meter.logger_chain');

    $this->assertEquals(LoggerChain::class, $loggerChainDefinition->getClass());
    $this->assertEquals(
        array(
            array(
                'addLogger',
                array(
                    new Reference('performance_meter.sql.logger3')
                )
            ),
        )
        ,
        $loggerChainDefinition->getMethodCalls()
    );
}

The first test fails with You have requested a non-existent service "performance_meter.logger_chain", we just need to register the service in Resources/config/services.xml <service id="performance_meter.logger_chain" class="Doctrine\DBAL\Logging\LoggerChain"/>. Now it fails to assert that getMethodCalls returns the expected calls. We will add registerSQLLoggers method in src/DependencyInjection/PerformanceMeterExtension.php

private function registerSQLLoggers(array $config, ContainerBuilder $container)
{
    $loggerReference = new Reference('logger', ContainerInterface::NULL_ON_INVALID_REFERENCE);
    $loggerChainDefinition = $container->getDefinition('performance_meter.logger_chain');
    foreach ($config['loggers'] as $name => $logger) {
        if ($logger['metric'] !== 'sql') {
            continue;
        }
        $id = 'performance_meter.sql.' . $name;
        $container->register($id, SQLLogger::class)->addArgument($loggerReference);
        $loggerChainDefinition->addMethodCall('addLogger', array(new Reference($id)));
    }
}

and add a call to it right after $this->registerRequestLoggers.

3. Inject LoggerChain in connection configurations

Remember that DoctrineBundle will register a configuration service for each connection. Let’s first append a doctrine configuration to our fixture config.yml

doctrine:
  dbal:
    connections:
      conn1:
      conn2:

If we run the test suite after this change, it would fail with There is no extension able to load the configuration for "doctrine". That’s simply because the Doctrine extension is not loaded. To load it, add $container->registerExtension(new DoctrineExtension()); after $container->registerExtension(new PerformanceMeterExtension()); in PerformanceMeterExtensionTest::createContainer. Now the tests pass again.

Remember that DoctrineBundle registers configuration services with sprintf('doctrine.dbal.%s_connection.configuration', $name).
The expected result is straight forward, 'doctrine.dbal.conn1_connection.configuration' and 'doctrine.dbal.conn2_connection.configuration' should both have performance_meter.logger_chain as SQLLogger. We add another test case to PerformanceMeterExtensionTest

public function test_calls_doctrine_connection_configuration_setSQLLogger_with_logger_chain()
{
    $container = $this->createContainer();
    $container->compile();

    foreach (array('conn1', 'conn2') as $name) {
        $configurationDefinition = $container->getDefinition('doctrine.dbal.' . $name . '_connection.configuration');
        $this->assertEquals(
            array(
                array(
                    'setSQLLogger',
                    array(
                        new Reference('performance_meter.logger_chain')
                    )
                ),
            )
            ,
            $configurationDefinition->getMethodCalls()
        );
    }
}

The assert obviously fails. Let’s start with a first attempt, we know how to manipulate definitions and how to add method calls. Also DoctrineBundle registers a parameter with all connections named doctrine.connections. So in PerformanceMeterExtension::load, after $this->registerSQLLoggers($config, $container); we can add

foreach ($container->getParameter('doctrine.connections') as $connection) {
  $container->getDefinition($connection . '.configuration')
      ->addMethodCall('setSQLLogger', array(new Reference('performance_meter.logger_chain')));
}

The tests fail with You have requested a non-existent parameter "doctrine.connections". Why is that? To understand what happened let’s take a look at Symfony\Component\DependencyInjection\Compiler\MergeExtensionConfigurationPass which has a single method. But it has a lot of knowledge about Symfony bundle development.

public function process(ContainerBuilder $container)
{
    $parameters = $container->getParameterBag()->all();
    $definitions = $container->getDefinitions();
    $aliases = $container->getAliases();
    $exprLangProviders = $container->getExpressionLanguageProviders();

    foreach ($container->getExtensions() as $extension) {
        if ($extension instanceof PrependExtensionInterface) {
            $extension->prepend($container);
        }
    }

    foreach ($container->getExtensions() as $name => $extension) {

        if (!$config = $container->getExtensionConfig($name)) {
            // this extension was not called
            continue;
        }
        $config = $container->getParameterBag()->resolveValue($config);

        $tmpContainer = new ContainerBuilder($container->getParameterBag());
        $tmpContainer->setResourceTracking($container->isTrackingResources());
        $tmpContainer->addObjectResource($extension);

        foreach ($exprLangProviders as $provider) {
            $tmpContainer->addExpressionLanguageProvider($provider);
        }

        $extension->load($config, $tmpContainer);

        $container->merge($tmpContainer);
        $container->getParameterBag()->add($parameters);
    }

    $container->addDefinitions($definitions);
    $container->addAliases($aliases);
}

I recommend that you navigate (aka Ctrl-click) everything is inspect what it is doing. We can already learn couple of things from the method body.

  1. If an extension (read: bundle) doesn’t have a config (entry in config.yml), it won’t be loaded.
  2. $config = $container->getExtensionConfig($name)
    Each extension is loaded only with it’s own configuration, means it won’t see other bundles configs.
  3. $tmpContainer = new ContainerBuilder($container->getParameterBag()); then $container->merge($tmpContainer);
    Each extension will receive a copy of ContainerBuilder that will be merged later to the main ContainerBuilder. This means that extensions won’t be able to see each other definitions. This is why our test failed then!
  4. This class implements CompilerPassInterface, so if we do the same, we can have access to a full ContainerBuilder, right?

Let’s try. We will simply cut the code snippet we added to the extension and didn’t work, and add it to the new class.

src/DependencyInjection/Compiler/SetSQLLoggerPass.php

<?php
namespace Skafandri\PerformanceMeterBundle\DependencyInjection\Compiler;

use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;

class SetSQLLoggerPass implements CompilerPassInterface
{

    public function process(ContainerBuilder $container)
    {
      foreach ($container->getParameter('doctrine.connections') as $connection) {
          $container->getDefinition($connection . '.configuration')
              ->addMethodCall('setSQLLogger', array(new Reference('performance_meter.logger_chain')));
      }
    }
}

To load this compiler we need to override the Bundle build method in src/PerformanceMeterBundle.php

public function build(ContainerBuilder $container)
{
    parent::build($container);
    $container->addCompilerPass(new SetSQLLoggerPass());
}

An to simulate how the kernel build a container, we need to add to PerformanceMeterExtensionTest:createContainer, just before the return statement

$performanceMeterBundle = new PerformanceMeterBundle();
$performanceMeterBundle->build($container);

Previous: Part 2

In How to create a Symfony bundle part 1 we developed a minimal bundle that logs every HTTP request. In this iteration we will add some more functionality then release V0.0.1
We will add the feature to enable/disable the bundle through configuration.

PerformanceMeterBundle V0.0.1

When we implemented the request logging, we required the bundle in a test application to test it. The more we will add functionalities, the more the manual testing method becomes impractical. If you work within a team, automated testing becomes even vital to the success of any non trivial project.

First, we need to prepare the required pieces for a testing environment. We will store the tests in tests directory, so create the directory. Add an autoload block to the root of composer.json

"autoload-dev": {
    "psr-4": {
      "Skafandri\\PerformanceMeterBundle\\Tests\\": "tests/"
    }
  },

Require phpunit

$ composer require --dev phpunit/phpunit

Configure phpunit

phpunit.xml.dist

<?xml version="1.0" encoding="UTF-8"?>
<phpunit bootstrap="vendor/autoload.php">
    <testsuites>
        <testsuite name="PerformanceMeterBundle Test Suite">
            <directory>tests</directory>
        </testsuite>
    </testsuites>

    <filter>
        <whitelist>
            <directory>.</directory>
            <exclude>
                <directory>Resources</directory>
                <directory>tests</directory>
                <directory>vendor</directory>
            </exclude>
        </whitelist>
    </filter>
</phpunit>

The whitelist filter is required to generate a code coverage report, we will use it later.

Check if everything is OK

$ ./vendor/bin/phpunit
PHPUnit 5.7.6 by Sebastian Bergmann and contributors.

Time: 24 ms, Memory: 2.00MB

No tests executed!

No tests executed! means all is good, we can start writing the first test case.

We will simply write the test cases in the same order we produced the bundle files. The first test for RequestLogger should be trivial.

tests/RequestLoggerTest.php

<?php
namespace Skafandri\PerformanceMeterBundle\Tests;
use PHPUnit\Framework\TestCase;
use Psr\Log\LoggerInterface;
use Skafandri\PerformanceMeterBundle\RequestLogger;
use Symfony\Component\HttpFoundation\Request;

class RequestLoggerTest extends TestCase
{
    public function test_logs_request()
    {
        $mockLogger = $this->getMockBuilder(LoggerInterface::class)->getMock();
        $mockLogger->expects($this->once())
            ->method('info')
            ->with(
                'performance_meter.request',
                array('uri' => 'http://:/', 'duration' => 10)
            );

        $requestLogger = new RequestLogger($mockLogger);
        $requestLogger->logRequest(new Request(), 10);
    }

    public function test_doesnt_break_without_logger()
    {
        $requestLogger = new RequestLogger();
        $requestLogger->logRequest(new Request(), 10);
    }
}

The second test has no assertions, isn’t this a bad practice? This test is simply a safeguard against a possible optimization to the block

if (!$this->logger) {
    return;
}

in RequestLogger. PHPUnit doesn’t have an assertion similar to assertEverythingIsFine() and I wouldn’t use it anyway. The function name should be self explanatory.
Needless to say that every time you change the code, you run ./vendor/bin/phpunit to execute the test suite.

Second test case will be to test KernelEventsSubscriber, the scenario is:

-1- create an event subscriber with a mocked request logger
-2- call eventSubscriber->onKernelRequest with a mocked event
-3- call eventSubscriber->onKernelResponse with a mocked event
-*- make sure request logger was called properly

tests/KernelEventsSubscriberTest.php

<?php
namespace Skafandri\PerformanceMeterBundle\Tests;
use PHPUnit\Framework\TestCase;
use Skafandri\PerformanceMeterBundle\KernelEventsSubscriber;
use Skafandri\PerformanceMeterBundle\RequestLogger;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpKernel\HttpKernelInterface;

class KernelEventsSubscriberTest extends TestCase
{
    public function test_logs_request_on_kernel_response()
    {
        $request = new Request();

        $mockGetResponseEvent = $this->getGetResponseEventMock();
        $mockGetResponseEvent->expects($this->any())
            ->method('getRequestType')
            ->willReturn(HttpKernelInterface::MASTER_REQUEST);

        $mockFilterResponseEvent = $this->getFilterResponseEventMock();
        $mockFilterResponseEvent->expects($this->any())
            ->method('getRequest')
            ->willReturn($request);

        $mockRequestLogger = $this->getMockBuilder(RequestLogger::class)->getMock();
        $mockRequestLogger->expects($this->once())
            ->method('logRequest')
            ->with($this->callback(function ($requestArgument) use ($request) {
                return $requestArgument === $request;
            }));

        $eventSubscriber = new  KernelEventsSubscriber($mockRequestLogger);
        $eventSubscriber->onKernelRequest($mockGetResponseEvent);
        $eventSubscriber->onKernelResponse($mockFilterResponseEvent);
    }

    private function getGetResponseEventMock()
    {
        return $this
            ->getMockBuilder(GetResponseEvent::class)
            ->disableOriginalConstructor()
            ->getMock();
    }

    private function getFilterResponseEventMock()
    {
        return $this
            ->getMockBuilder(FilterResponseEvent::class)
            ->disableOriginalConstructor()
            ->getMock();
    }
}

The test code seems bigger than the tested code, isn’t it? Yes, and it may get even bigger. A ratio of 2:1 or more is not uncommon. That’s the price you pay to buy this safety net.
In fact, you can estimate the worthiness of automated testing in a particular project:

if (time_to_write_automated_tests + time_to_run_automated_tests*N < time_to_run_manual_tests*N)
    you should write automated tests
else    
    you should do manual testing
endif

Where N is the number of times tests will run, automatically or manually.

Notice that the more N goes up, the less time_to_write_automated_tests is affecting the balance. When N is too big, which is the case in most projects, you can simplify it by just comparing time_to_run_automated_tests to time_to_run_manual_tests.

After the event subscriber, we created services.xml and the extension class. The extension just loads the configuration file into a container, the test scenario is:

-1- Create a container
-2- Register the extension
-*- Make sure request logger is registered with the correct class and arguments
-*- Make sure events subscriber is registered with the correct class, arguments and tags

tests/DependencyInjection/Fixtures/config.yml

performance_meter:

tests/DependencyInjection/PerformanceMeterExtensionTest.php

<?php

namespace Skafandri\PerformanceMeterBundle\Tests\DependencyInjection;

use PHPUnit\Framework\TestCase;
use Skafandri\PerformanceMeterBundle\DependencyInjection\PerformanceMeterExtension;
use Skafandri\PerformanceMeterBundle\KernelEventsSubscriber;
use Skafandri\PerformanceMeterBundle\RequestLogger;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\DependencyInjection\Reference;

class PerformanceMeterExtensionTest extends TestCase
{
    public function test_registers_request_logger()
    {
        $container = $this->createContainer();
        $container->compile();

        $requestLoggerDefinition = $container->getDefinition('performance_meter.request_logger');

        $this->assertEquals(RequestLogger::class, $requestLoggerDefinition->getClass());
        $this->assertEquals(
            array(
                new Reference('logger', ContainerInterface::NULL_ON_INVALID_REFERENCE)
            ),
            $requestLoggerDefinition->getArguments()
        );
    }

    public function test_registers_kernel_event_subscriber()
    {
        $container = $this->createContainer();
        $container->compile();

        $eventSubscriberDefinition = $container->getDefinition('performance_meter.kernel_events_subscriber');

        $this->assertEquals(KernelEventsSubscriber::class, $eventSubscriberDefinition->getClass());
        $this->assertEquals(
            array(
                new Reference('performance_meter.request_logger')
            ),
            $eventSubscriberDefinition->getArguments()
        );
        $this->assertEquals(
            array('kernel.event_subscriber' => array(array())),
            $eventSubscriberDefinition->getTags()
        );
    }

    private function createContainer()
    {
        $container = new ContainerBuilder();

        $locator = new FileLocator(__DIR__.'/Fixtures');
        $loader = new YamlFileLoader($container, $locator);
        $loader->load('config.yml');

        $container->registerExtension(new PerformanceMeterExtension());

        $container->getCompilerPassConfig()->setOptimizationPasses(array());

        return $container;
    }
}

I think the previous example beats all the quests to find the golden number that defines a good test coverage percentage, and must be between 0 and 100.
PHPUnit will think the previous test is testing PerformanceMeterExtension, which has 3 lines of code. But in reality, we are testing services.xml.

We are done testing our previous code, we can proceed to implement the next feature. The user should be able to activate/deactivate the bundle using a configuration toggle.

performance_meter:
    enabled: true|false defaults to true

Before thinking about how to implement this feature, let’s think how to test it first. We can create a dummy configuration file with enabled: false and make sure the bundle is disabled. The scenario looks like:

-1- Create a container
-2- load disabled.yml
-*- check that no services are loaded

tests/DependencyInjection/Fixtures/disabled.yml

performance_meter:
    enabled: false

in tests/DependencyInjection/PerformanceMeterExtensionTest.php we add another test case

public function test_registers_nothing_when_disabled()
{
    $container = $this->createContainer();

    $locator = new FileLocator(__DIR__ . '/Fixtures');
    $loader = new YamlFileLoader($container, $locator);
    $loader->load('disabled.yml');

    $container->compile();

    $this->assertFalse($container->has('performance_meter.request_logger'));
    $this->assertFalse($container->has('performance_meter.kernel_events_subscriber'));
}

Running the test suite again, it fails obviously

$ ./vendor/bin/phpunit
PHPUnit 5.7.6 by Sebastian Bergmann and contributors.

..F...                                                              6 / 6 (100%)

Time: 207 ms, Memory: 6.00MB

There was 1 failure:

1) Skafandri\PerformanceMeterBundle\Tests\DependencyInjection\PerformanceMeterExtensionTest::test_registers_nothing_when_disabled
Failed asserting that true is false.

/home/ilyes/projects/PerformanceMeterBundle/tests/DependencyInjection/PerformanceMeterExtensionTest.php:63

FAILURES!
Tests: 6, Assertions: 8, Failures: 1.

Once we manage to pass the tests, we will be done with this feature.

PerformanceMeterExtension seems a good candidate, all we need is to check for the enabled configuration and call $loader->load('services.xml') only when it has the value true.
The first argument $configs for the load method is a raw array of configs defined by users and appended by other bundles. This array needs to be processed using a Configuration class that defines how configuration should be.

So let’s create the configuration class.
src/DependencyInjection/Configuration.php

<?php
namespace Skafandri\PerformanceMeterBundle\DependencyInjection;
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
use Symfony\Component\Config\Definition\ConfigurationInterface;

class Configuration implements ConfigurationInterface
{

    /**
     * Generates the configuration tree builder.
     *
     * @return \Symfony\Component\Config\Definition\Builder\TreeBuilder The tree builder
     */
    public function getConfigTreeBuilder()
    {
        $tree = new TreeBuilder();
        $tree->root('performance_meter');

        return $tree;
    }
}

We will use it from PerformanceMeterExtension to process the $configs array. Before $loader->load('services.xml'), insert $config = $this->processConfiguration(new Configuration(), $configs);

Run the tests again, now we get an exception Symfony\Component\Config\Definition\Exception\InvalidConfigurationException: Unrecognized option "enabled" under "performance_meter". We need to add to the TreeBuilder returned from the Configuration class expect a key of type scalar and a default value true. Edit the configuration class, assign the root node creation to a variable $root = $tree->root('performance_meter'); and add the enabled node $root->children()->scalarNode('enabled')->defaultTrue();.

Run the tests again, we get back to Failed asserting that true is false. Since now we have the new config recognized and parsed, we update PerformanceMeterExtension to load services.xml only when enabled=true, we just wrap it in an if clause

if ($this->isConfigEnabled($container, $config)) {
    $loader->load('services.xml');
}

Run the tests again.. all good.

If you haven’t done it before, congratulations, this was your first TDD session.

In the previous tutorial we had to add "minimum-stability": "dev" to the test application in order to be able to require performance-meter-bundle. The minimum stability defaults to stable, which from composer’s perspective, it just means that the project has some specifically named git tags. The tags represent Semantic Versioning.
To release the first stable version of this bundle, we need to add a version tag v0.0.1

$ git add .; git commit -m "V0.0.1"; git tag v0.0.1.

Before pushing the changes to Github, you can go to the project settings on Github, Integrations & services and add Packagist from the list. Now changes pushed to Github will automatically update the project on Packagist through a webhook.

$ git push origin HEAD --tags

Next: How to create a Symfony bundle part 3
Previous: How to create a Symfony bundle part 1

I wanted to buy a tablet with the following carectersitics:
– around 10″ display
– 4 Gb+ of ram
– preferably dockable, or connect a blutooth keyboard to it
– Android OS or whatever I can (replace with)/(run on top of it) a Linux distribution
– HDMI port (to connect to a datashow)
– USB port (for a mouse, clicker and possibly other accessories)
– 4h+ autonomy on battery

My main motivations were mobility and flexibility with a descent computation power.
I needed a computer for web browsing, ssh connections, casual development activities, presentations and videoconferences.

The best option I found at my local provider was the Lenovo Tab Yoga Book YB1-X90F for $620 at the moment of writing.
Pros: The stilet looks promissing, and if it works well, it may replace my notebook and pen, tow less things to carry.
Cons: Touch keyboard, I am really fun of mechanical keyboards.

I also went to check the laptops category, maybe I could find a small enough laptop that feets my needs, I was willing to accept up to 13″ display if I find a good device. I came accross the HP Chromebook 11 G4 11.6″ display, 4Gb of ram, 32Gb of SSD and descent dual core CPU at 2.16GHz. The price was $300. The device looked pretty promosing, also less than half the price of the tablet option. Looked interesting, I had only heard about the ChromeOs and chromebooks at that tome, so I did some more googling about them. Once I found out that I can easily install a Linux on it, I ordered the thing. This laptop was cheaper than my low budget phone!

Last night my order arrived, I unpacked it and put it to charge. On first sight it looked damn owesome and very light. In the meanwhile I had some reading about the ChromeOs.
Today, at 10AM, I opened my new Chromebook. First thing I noticed, it booted in less than 5 seconds, wake up time was about one second. After signing up with my google account, some updates and stuff, I started looking around. The thing is blazing fast, I love this. The touchpad and the keyboard seemed a bit awkward, there is no easy right click or scrolling, everything need some special Chromebookish way of handling the touchpad. It is really annoying that some keys are missing, the ones I miss the most are home end and del. The only way is some weird key combinations. I understand the motivation behind a web oriented device but I still need to do some programming and scripting even when I am mobile.
I checked the battery indicator and is showed 99%, 10h 21min remaining. I got really skeptical when I saw the 10h of autonomy but I left my judgement for later.
The first thing I wanted to check was a terminal. To get a shell, you need to activate the developer mode, which was the first thing I did after I signed in.

$ uname -a
Linux chrome 3.10.18 #1 SMP Fri Jan 6 23:49:50 PST 2017 x86_64 Intel(R) Celeron(R) CPU N2840 @ 2.16GHz GenuineIntel GNU/Linux

I would have preferred a 4.x kernel, but for a device manufactured in 2014, this was a good deal.
I kept the devide on almost all the day, I watched HD videos on youtube for 4 hours.
After playing around with ChromeOs for a while, I installed crouton and booted an ubuntu OS.
I felt almost home, I still missed some important keys from the keyboard, but I managed to install my IDEs and tools, mounted my remote servers and have a fully working environment. I even started getting used with the touchpad gestures and I realized that scrolling and right clicking in particular are more practical than on a regular touchpad (scroll area and right button).

I am actually writing this post on my new Chromebook right now, it’s almost midnight, and the battery shows 1h30min of remaining time. I find this really impressing. I was almost continuously using it since 14 hours by now.

So far I am very happy with this toy. If I find something interesting in the future, I will come back to write about it.

Today a friend asked me: Can you recommend a good tutorial on how to create a Symfony bundle?
When I googled (yes, I use it as a verb) how to create a symfony bundle
I was preparing to evaluate some tutorials and recommend one of them.
Surprisingly, I didn’t found any material! I came across The bundle system and Generating a New Bundle Skeleton from the Symfony documentation. But they didn’t even scratch the surface of a bundle creation process. The Doctrine section of the documentation, like most of the topics, is well detailed, with links to more advanced subjects. At the time of writing,
I think that the documentation is of very high quality, but there are still some corners that are not addressed well.

So, here we are. This is step by step tutorial to create a Symfony bundle.

After a while of writing, my scrollbar started to look small on my 30cm hight screen.
The tutorial got too long already and I didn’t cover even a tiny part of the topics I am planning to work with.
So I will split it in smaller tutorials. We will develop a PerformanceMeter Bundle that measures Symfony application’s performance (HTTP requests, queries..).
Here is the breakdown:

  • V0.0.0 (this one)
    • Develop a bare minimum Bundle to log HTTP requests duration
    • Share the bundle on Github and Packagist.org
  • V0.0.1
    • Automated testing
    • Configuration
    • Versioning
  • V0.0.2
    • Integrate with Doctrine
    • Compiler passes
  • V0.0.3
    • Travis-CI, service container
  • V0.0.4
    • Integrate with Twig
    • Console component
  • V0.0.5
    • Monolog Bundle
    • EventDispatcher component

If you want to work on the tutorial as a workshop, which I urge you to do, you can clone the previous branch and start from it. If you want to start with V0.0.1, for instance, you can checkout the V0.0.0 branch and start from there.

PerformanceMeterBundle V0.0.0

For the first iteration, we will just log all HTTP requests duration and URI.

I assume you already have Composer installed, if not, please install it. The initial functionality consists on logging every request.
Thus, we will need 3 packages: The HttpFoundation Component, Stopwatch component and PSR log.
The choice of PSR-log over a concrete logger enables us to be independent of users choice of their logger as long as it implements Psr\Log\LoggerInterface. Let’s create a project directory and start working.

$ mkdir PerformanceMeterBundle

PerformanceMeterBundle/composer.json

{
  "name": "skafandri/performance-meter-bundle",
  "autoload": {
    "psr-4": {
      "Skafandri\\PerformanceMeterBundle\\": "src/"
    }
  },
  "require": {
    "symfony/http-foundation": "^3.2",
    "symfony/stopwatch": "^3.2",
    "psr/log": "^1.0"
  }
}

Run composer install to get all the necessary packages.

$ composer install
Loading composer repositories with package information
Updating dependencies (including require-dev)
- Installing symfony/polyfill-mbstring (v1.3.0)
  Loading from cache

- Installing symfony/http-foundation (v3.2.2)
  Loading from cache

- Installing symfony/stopwatch (v3.2.2)
  Loading from cache

- Installing psr/log (1.0.2)
  Loading from cache

Writing lock file
Generating autoload files

Now we can create the first version of a RequestLogger.

PerformanceMeterBundle/src/RequestLogger.php

<?php
namespace Skafandri\PerformanceMeterBundle;
use Psr\Log\LoggerInterface;
use Symfony\Component\HttpFoundation\Request;

class RequestLogger
{
    /** @var  LoggerInterface */
    private $logger;

    /**
     * RequestLogger constructor.
     * @param LoggerInterface $logger
     */
    public function __construct(LoggerInterface $logger = null)
    {
        $this->logger = $logger;
    }

    public function logRequest(Request $request, $duration)
    {
        if (!$this->logger) {
            return;
        }
        $context = array(
            'uri' => $request->getUri(),
            'duration' => $duration
        );
        $this->logger->info('performance_meter.request', $context);
    }
}

In order to run the previous code, we would need to write something like:

//Somewhere before a request is processed
$stopWatch = new Stopwatch(); //http://symfony.com/doc/current/components/stopwatch.html
$stopWatch->start('request');
$requestLogger = new RequestLogger($logger);
...
//After a request is processed
$duration = $stopWatch->stop('request')->getDuration();
$requestLogger->logRequest($request, $duration);

Checking the events documentation, it seems that kernel.request and kernel.response are good candidates where we can plug the previous pseudo code.
We need to require http-kernel and event-dispatcher

$ composer require symfony/http-kernel symfony/event-dispatcher
Using version ^3.2 for symfony/http-kernel
Using version ^3.2 for symfony/event-dispatcher
./composer.json has been updated
Loading composer repositories with package information
Updating dependencies (including require-dev)
  - Installing symfony/debug (v3.2.2)
    Downloading: 100%         
  - Installing symfony/event-dispatcher (v3.2.2)
    Downloading: 100%         
  - Installing symfony/http-kernel (v3.2.2)
    Downloading: 100%         
Writing lock file
Generating autoload files

We’ve got the kernel and an event dispatcher, we can now write an event subscriber.

PerformanceMeterBundle/src/KernelEventsSubscriber.php

<?php
namespace Skafandri\PerformanceMeterBundle;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpKernel\HttpKernelInterface;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\Stopwatch\Stopwatch;

class KernelEventsSubscriber implements EventSubscriberInterface
{
    /** @var  RequestLogger */
    private $requestLogger;
    /** @var  Stopwatch */
    private $stopwatch;

    /**
     * KernelEventsSubscriber constructor.
     * @param RequestLogger $requestLogger
     */
    public function __construct(RequestLogger $requestLogger)
    {
        $this->requestLogger = $requestLogger;
        $this->stopwatch = new Stopwatch();
    }

    public function onKernelRequest(GetResponseEvent $event)
    {
        if ($event->getRequestType() === HttpKernelInterface::MASTER_REQUEST) {
            $this->stopwatch->start('request');
        }
    }

    public function onKernelResponse(FilterResponseEvent $event)
    {
        $duration = $this->stopwatch->stop('request')->getDuration();
        $this->requestLogger->logRequest($event->getRequest(), $duration);
    }

    public static function getSubscribedEvents()
    {
        return array(
            KernelEvents::REQUEST => 'onKernelRequest',
            KernelEvents::RESPONSE => 'onKernelResponse'
        );
    }
}

So we simply start a stopwatch timer on kernel.request, and we log the request duration onkernel.response.

Now we just need to register this subscriber as a service, which leads us to require the dependency injection component.

$ composer require symfony/dependency-injection symfony/config
Using version ^3.2 for symfony/dependency-injection
Using version ^3.2 for symfony/config
./composer.json has been updated
Loading composer repositories with package information
Updating dependencies (including require-dev)
  - Installing symfony/dependency-injection (v3.2.2)
    Downloading: 100%         
  - Installing symfony/filesystem (v3.2.2)
    Downloading: 100%
  - Installing symfony/config (v3.2.2)
    Loading from cache  

Writing lock file
Generating autoload files

PerformanceMeterBundle/Resources/config/service.xml

<?xml version="1.0" encoding="UTF-8" ?>
<container xmlns="http://symfony.com/schema/dic/services"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://symfony.com/schema/dic/services
        http://symfony.com/schema/dic/services/services-1.0.xsd">

    <services>
        <service id="performance_meter.request_logger" class="Skafandri\PerformanceMeterBundle\RequestLogger">
            <argument type="service" id="logger" on-invalid="null"/>
        </service>
        <service id="performance_meter.kernel_events_subscriber"
                 class="Skafandri\PerformanceMeterBundle\KernelEventsSubscriber">
            <argument type="service" id="performance_meter.request_logger"/>
            <tag name="kernel.event_subscriber"/>
        </service>
    </services>
</container>

on-invalid="null" tells Symfony to pass null to RequestLogger constructor if there is no logger service. In this case, RequestLogger will silently do nothing on kernel response. Check Dependency Injection documentation.
<tag name="kernel.event_subscriber"/> registers a kernel event subsriber. Check Event Dispatcher documentation.
This is a valid dependency injection configuration file. In order to load it, we need to create an extension where we will have access to a ContainerBuilder object. Among many other things, an extension gives access to a ContainerBuilder during configuration loading phase.

PerformanceMeterBundle/src/DependencyInjection/PerformanceMeterExtension.php

<?php
namespace Skafandri\PerformanceMeterBundle\DependencyInjection;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
use Symfony\Component\HttpKernel\DependencyInjection\Extension;

class PerformanceMeterExtension extends Extension
{

    public function load(array $configs, ContainerBuilder $container)
    {
        $locator = new FileLocator(array(__DIR__ . '/../../Resources/config'));
        $loader = new XmlFileLoader($container, $locator);
        $loader->load('services.xml');
    }
}

We must also create a Bundle class, even if we don’t need it for the moment.

PerformanceMeterBundle/src/PerformanceMeterBundle.php

<?php

namespace Skafandri\PerformanceMeterBundle;
use Symfony\Component\HttpKernel\Bundle\Bundle;

class PerformanceMeterBundle extends Bundle
{
}

This will be the class that users will instantiate in their AppKernel (or WhataverKernel.php).

We are almost done with this iteration’s code, we need a final .gitignore touch because we don’t want to distribute the vendor directory or composer.lock

PerformanceMeterBundle/.gitignore

composer.lock
vendor

Next, we can commit and push the new bundle to github. I created a PerformanceMeterBundle under my github username.

$ git init; git add .gitignore Resources/ composer.json src/
$ git remote add origin git@github.com:skafandri/PerformanceMeterBundle.git
$ git push origin HEAD
Counting objects: 13, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (10/10), done.
Writing objects: 100% (13/13), 2.44 KiB | 0 bytes/s, done.
Total 13 (delta 0), reused 0 (delta 0)
To git@github.com:skafandri/PerformanceMeterBundle.git

The newborn bundle is now available on github. But not yet available to simply work as composer require skafandri/performance-meter-bundle.
To do so, we need to add it to packagist. After you login or create an account (github connect is supported), you just click submit and paste in the bundle’s git URL (git@github.com:skafandri/PerformanceMeterBundle.git in my case). Voila, the bundle is up now at https://packagist.org/packages/skafandri/performance-meter-bundle.

Next, we will create a standard Symfony application and try to require the new bundle. Check the symfony documentation on how to setup Symfony locally. You can use your preferred method, but make sure you have MonologBundle enabled. I recommend to simply use the standard edition for this turorial.

$ symfony new performance-meter-test

 Downloading Symfony...

    5.3 MiB/5.3 MiB ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓  100%

 Preparing project...

 ✔  Symfony 3.2.1 was successfully installed. Now you can:

    * Change your current directory to /home/ilyes/projects/performance-meter-test

    * Configure your application in app/config/parameters.yml file.

    * Run your application:
        1. Execute the php bin/console server:start command.
        2. Browse to the http://localhost:8000 URL.

    * Read the documentation at http://symfony.com/doc

Require performance-meter-bundle

$ composer require skafandri/performance-meter-bundle
 [InvalidArgumentException]                                                                                        
 Could not find package skafandri/performance-meter-bundle at any version for your minimum-stability (stable). Check the package spelling or your minimum-stability

By default, composer sets the minimum-stability to stable (which is good). We will come later to this, for the moment, to be able to require the bundle, we need to add "minimum-stability": "dev" to the root of composer.json in the newly created application and rerun the previous command.

Enable the bundle as usual by adding new Skafandri\PerformanceMeterBundle\PerformanceMeterBundle() to the $bundles array in performance-meter-test/app/AppKernel.php

Voila! Run the PHP built in web server bin/console server:run then visit http://127.0.0.1:8000/ in your browser.
In var/log/app_dev.log you should see an entry similar to

[2017-01-23 05:47:05] app.INFO: performance_meter.request {"uri":"http://127.0.0.1:8000/_wdt/078b04","duration":19} []

We are done with V0.0.0

After we moved to a new address, 3 things needed to happen:

  • change my wife’s ID
  • change my staying permit
  • change the car ID

Since I don’t have much knowledge about government administration, I decided to model the process as software and code-review it to see how efficient it is.

The process is the following:

  • When a citizen’s address changes, the new address and a proof of address are required to update his ID.
  • When the ID is created, the citizen is notified (I invented this feature to make the process easier to design, the reality resembles more to a sleep/retry loop).
  • When a citizen’s address changes, the new address and a proof of address are required to update his resident partner’s staying permit.
  • When a citizen’s address changes, the new address and a proof of address are required to update his car registration document.
  • When the car registration document is created, the citizen is notified (invented).
  • When a resident’s address changes, the new address and a proof of address are required to update his staying permit.
  • When staying permit is created, resident is notified about staying permit ready (invented, see above).

I can’t emphasize enough how much I simplified the process. The real version is a bit more complicated.

Next, I will try to model this process as a flow chart.

 

I will suppose we are building a software, and a junior developer sent me this initial attempt for code review.

At first glance, it seems that Citizen’s address changed event is requiring 3 actions that are performed at 3 different targets. This is clearly redundant. We can solve this issue in two ways:

Solution 1: Hide all the involved targets behind a single unified interface, something like Citizens affairs Administration. This new administration will be the single contact point with citizens and will handle distributing the event to the interested administration.

Solution 2: Pick one of the existing administrations, the most powerful, the most secure or however you play your politics, and make it act as the unified interface to the citizens.

From software infrastructure point of view, Solution 1 is not the best. It will require new machines to provision, install, develop and run software on. In a governmental institution context, this would mean a whole bunch of construction projects, boring news, and endless nonsense debates on TV. Let’s proceed with Solution 2.

The refactored process :

 

This variation reduces the required actions for a Citizen from 3 to 1. We will suppose we only reduced it from 2 to 1, because not everybody has a car neither everybody has a foreign partner. Although some other people would need other documents to change if they change their addresses, the 2 to 1 average seems a fair compromise. What does that mean in human terms?

I will put a lot of over simplifications on the following estimations:

– Today, there are about 20 million people in Romania.
– On average, a citizen will change his address once in 50 years (read as: on average, a citizen is most likely to never change his address, maybe change it once, but rarely more than once)
– This means in the next 50 years we should expect an average of 400000 address changes/year
– If an optimization reduces 1 hour for each citizen, the population would save 400000 hours/year

That’s 45 years, or the equivalent of a team of 200 people working a full year without taking vacations.
How hard it is, in theory, to shave only 10 hours/year from a citizen’s time spent in administrations in your country?

commodore 64

    commodore 64

Sometime around 1994, my father bought me a Commodore 64. I quickly got bored of the few games I had, and God! It took ages to load a game from a tape. And if I miss-typed the game address, I had to rewind the tape and start over again. There was a small local computer club in my neighborhood. There were kids playing games like I did, and some older people just typing text into a blue screen. Quite boring, but they seemed to enjoy it. Sometimes they even got serious and argued about it. Driven by a toddler’s instinct to imitate older people, I decided to do what the old men were doing. Those guys had a book, “Basic programming for Commodore 64”. I am not very sure about the title, it was plain white with the title in blue. I somehow managed to make a hard copy of the book.

The book contained 50 or so small programs in Basic, and I think an explanation of the code. I am not sure again, because back then I didn’t knew English. My whole vocabulary consisted on (on, off, start, pause, level, game over), the English words you could encounter while dealing with consoles and video games. It was actually 4 years later when I started learning elementary English at school. All I could do was to type the programs and see how it runs. Those kind of programs that draw vertical bars on the screen, a moving disc, a blinking rectangle.. I remember the most “advanced” program had to play from and record to a tape.

As simple the programs were, I was impatiently rushing to type all the code to see the results after execution. I was following the instructions perfectly, and it was amusing. After couple of programs, I started to notice some things. Some of those lines were identical among some programs. Some were different but very similar. I started wondering if I could change the programs a little bit. The first program was obviously a 2 lines Hello world in Basic. I introduced my name instead of Hello, and cautiously run the program. Holy shit! The computer actually displayed my name! That was awesome! I tried to change couple of other places just to get an error in my face. But I noticed something very important, if I change the text between quotes I don’t get an error most of the time. That drew my first separation line between changeable and non changeable code. I ventured more with my programs (changing every quoted text unless I get an error) and some other patterns started to pop up. There were quite few possibilities for the first word in a line (INPUT, PRINT, FOR..), however the rest of the line was often different. Some words showed up only if the line started with a particular word. I kept writing those programs.

I started changing numbers to draw 10 lines instead of 5. Changed some other numbers and noticed sizes and positions of shapes changing on the screen. I think I quickly managed to see every error the Basic interpreter could output, they are not so many anyway. The errors become less and less frequent, and some of them were expected. I started to understand what I was meddling. I was somehow telling the machine what to do. But I was still copying text from the book. So I decided to put the computer solve a personal problem that I had. And I put down the book.

Well, how complicated a 9 years old boy’s problem could be? The problem was: A farmer has a farm of sqm, he buys seeds for bp a kg, and kg of seeds are necessary to plant 1 sqm of land. His crop yields kg/sqm and he sells his harvest for sp a kg. How much mony will the farmer make after selling his crops?

I got to work, I think I spent about 1 week to get a first running version. At the moment I didn’t knew about recording to the tape. My persistent storage was a piece of paper where I type my last advancements. At the end, the program runned and gave correct results! It even could print “The farmer will loose 120″ if revenue-expenses was -120. That was amazing. I think that was the moment I became a programmer. But I didn’t realize it back then.