Symfony/BDD example 03: mountains

31 December 2014

This is the third example in Symfony/BDD series. It demonstrates how to use fixtures.

1. Background

I assume that you have already read:

The task at hand can be specified as follows:

Here is the excerpt from the file:

- { name: "Mount Everest", height: 8880 }
- { name: "Mount Blanc", height: 4440 }
- { name: "Kilimanjaro", height: 5550 }

The application requirements:

Name Height
Mount Everest 8850
Mount Blanc 4810
Kilimanjaro 5895

2. Introductory step

2.1. Start the project

Create a new directory and clone my Symfony Standard repository:

# Host OS
$ mkdir symfony-bdd-example-03-fixtures
$ cd symfony-bdd-example-03-fixtures
$ git clone git@github.com:by-examples/symfony-standard.git .

2.2. Create a starting point for the project

Create a new orphan branch that starts at the tag Symfony Standard v2.6.1:

# Host OS
$ git checkout --orphan 2.6.1/bdd-example-03-fixtures v2.6.1

Commit your change:

# Host OS
$ git add -A
$ git commit -m "Symfony Standard 2.6.1"

2.3. Customize the project

Introduce the following changes in your project:

# Host OS
$ git cherry-pick origin/2.6.1/Gitignore
$ git cherry-pick origin/2.6.1/Cleanup
$ git cherry-pick origin/2.6.1/Vagrant
$ git cherry-pick origin/2.6.1/Speedup
$ git cherry-pick origin/2.6.1/Behat
$ git cherry-pick origin/2.6.1/BehatInitialization
$ git cherry-pick origin/2.6.1/Db
$ git cherry-pick origin/2.6.1/DoctrineFixturesBundle

2.4. Install the dependencies

Boot the VM with:

# Host OS
$ vagrant up
$ vagrant ssh

and run:

# Guest OS
$ composer install -o

When this is done, commit you changes with:

# Host OS
$ git add -A
$ git commit -m "Updated dependencies"

3. Run Behat - project is GREEN

Now, try to run:

# Guest OS
$ bin/behat

You will get the output similar to:

No scenarios
No steps
0m0.03s (9.00Mb)

The project is GREEN.

GREEN

4. Create YML file

Create the file: data/mountains.yml:

- { name: "Mount Everest", height: 8880 }
- { name: "Mount Blanc", height: 4440 }
- { name: "Kilimanjaro", height: 5550 }

Commit you changes with:

# Host OS
$ git add -A
$ git commit -m "Fixtures data file: mountains.yml"

5. Tests - the project is RED

Create the file features/fixtures.feature with the following contents:

Feature: We want one page with all the mountains...

  Scenario: List mountains
    Given I am on homepage
     Then I should see "Mount Everest"
      And I should see "8880"
     Then I should see "Mount Blanc"
      And I should see "4440"
     Then I should see "Kilimanjaro"
      And I should see "5550"

  Scenario: I want to check the number of records
    When I am on homepage
    Then I should see 3 ".records tbody tr" elements

Run Behat:

# Guest OS
$ bin/behat

The project is RED.

RED

Commit the test:

# Host OS
$ git add -A
$ git commit -m "[BDD:RED] tests for fixtures"

6. Generate Mountain entity

Run the command:

$ php app/console doctrine:generate:entity

The name of the entity consists of two parts: the bundle’s name and the name for the class.

In this example it is:

AppBundle:Mountain

Leave default settings for format: annotation.

Add two columns:

name     -   string 255
height   -   integer

When you are finished press enter (as many times as necessary).

The command generates a class stored in:

src/AppBundle/Entity/Mountain.php

For this entity ORM will create in the database a table named Mountain.

When you are done commit your changes with:

$ git commit -m "[BDD:RED] Create Mountain entity"

6. Write the code to load fixtures

Create the file src/AppBundle/DataFixtures/ORM/LoadMountains.php with the following contents:

<?php

namespace AppBundle\DataFixtures\ORM;

use Doctrine\Common\DataFixtures\FixtureInterface;
use Doctrine\Common\Persistence\ObjectManager;

use Symfony\Component\Yaml\Yaml;

use AppBundle\Entity\Mountain;

class LoadMountains implements FixtureInterface
{
    /**
     * {@inheritDoc}
     */
    public function load(ObjectManager $manager)
    {

        $filename = __DIR__ . '/../../../../data/mountains.yml';
        $yml = Yaml::parse(file_get_contents($filename));
        foreach ($yml as $item) {
            $mountain = new Mountain();
            $mountain->setName($item['name']);
            $mountain->setHeight($item['height']);
            $manager->persist($mountain);
        }

        $manager->flush();
    }
}

Commit your changes with:

$ git commit -m "[BDD:RED] LoadMountains.php fixtures"

7. Load the fixtures into your database

Run the command:

# Guest OS
$ php app/console doctrine:fixture -n

8. Create the homepage

This task consist of:

8.1. Action

Change the file src/AppBundle/Controller/DefaultController.php:

class DefaultController extends Controller
{
    /**
     * @Route("/", name="homepage")
     */
    public function indexAction()
    {
        $em = $this->getDoctrine()->getManager();

        $entities = $em->getRepository('AppBundle:Mountain')->findAll();

        return $this->render(
            'default/index.html.twig',
            array(
                'entities' => $entities,
            )
        );
    }
}

8.2. View

Change the file app/Resources/views/default/index.html.twig:

{% extends 'base.html.twig' %}

{% block body %}
    <h1>Mountains</h1>
    <table class="records">
        <thead>
            <tr>
                <th>#</th>
                <th>Name</th>
                <th>Height</th>
            </tr>
        </thead>
        <tbody>
        {% for entity in entities %}
            <tr>
                <td>{{ loop.index }}</td>
                <td>{{ entity.name }}</td>
                <td>{{ entity.height }}</td>
            </tr>
        {% endfor %}
        </tbody>
    </table>
{% endblock %}

9. Run the tests

Clear the cache:

# Guest OS
$ php app/console cache:clear --env=prod

and run the tests:

# Guest OS
$ bin/behat

The project is GREEN.

GREEN

Commit your changes with:

# Host OS
$ git add -A
$ git commit -m "[BDD:GREEN] code for test: fixtures"

10. Visit app with your browser

Run web browser and visit:

http://localhost:8880/
http://localhost:8880/app_dev.php/

You can also view your application in guest using lynx:

# Guest OS
$ lynx http://localhost/
$ lynx http://localhost/app_dev.php/

11. Test the efficiency

You can test the efficiency of the application with:

# Guest OS
$ ab -n 100 http://localhost/

Here are the results I get on my OS X machine (i7/8MB RAM/ssd):

Server Software:        Apache/2.4.10
Server Hostname:        localhost
Server Port:            80

Document Path:          /
Document Length:        909 bytes

Concurrency Level:      1
Time taken for tests:   2.034 seconds
Complete requests:      100
Failed requests:        0
Total transferred:      117200 bytes
HTML transferred:       90900 bytes
Requests per second:    49.17 [#/sec] (mean)
Time per request:       20.339 [ms] (mean)
Time per request:       20.339 [ms] (mean, across all concurrent requests)
Transfer rate:          56.27 [Kbytes/sec] received

My results from Windows machine (i5/4MB RAM/not ssd):

...to be done...

12. Remove unnecessary commits

The history of your project now contains 15 commits. However, if you run:

$ git log --oneline --all

you should see more than 1000 commits.

They are mostly the original commits from Symfony Standard. To remove them, use the following commands:

$ git remote rm origin
$ git branch -D 2.7
$ git tag | xargs git tag -d
$ git reflog expire --all --expire=now
$ git prune
$ git gc

Now your repository contains only 15 commits that you have authored working on this example.

13. The Example

You will find the source code of the example on GitHub.

For the instruction how to run the example refer to README.md file.

Fork me on GitHub