Skip to content
Logo Theodo

How to manage fixtures in a PHP project

Benjamin Grandfond6 min read

Dealing with data developing a new application is a real challenge for the development team. You often need them to develop and then test your app. For example, for a login system, you would need different users with roles and permissions in the database in order to test that the configuration is well set. To build a list of books, you would need books with author, title, description, picture and so on. In the end developers will need to create many fixtures to be able to test what they are developing.

When you are working on a fresh new project, you will create fake data. However, if you are working on an existing application, you may use a recent snapshot taken from the database on the production server. This way you will have data on your pages when you launch the application locally. But as far as tests are concerned, this can be problematic. When you write tests, you want them to stay independent of each other to avoid weird behaviors. Thus, to be sure that you will not have any problems, most of the time, you would reload the database with a set of data used by the test. So, loading the whole snapshot on each test will slow your tests down and you won’t run them as often as you should do (not to say never). Therefore you should use data fixtures.

Furthermore, fixtures will be of great help when you want to provide your Product Owner with test data after you deployed a new feature on the test server. It is the best way for you to make him able to see the result of your hard work quickly and easily.

I will try to show you some of the tools we use at Theodo.

Doctrine Fixtures

Doctrine fixtures is a PHP library that loads a set of data fixtures fully written in PHP. Creating a fixtures file is simple, you only have to create a PHP class that implements the FixtureInterface and instantiate as many object as you want to store.

class LoadUserData implements FixtureInterface
{
    public function load(ObjectManager $manager)
    {
        $user = new User();
        $user->setUsername("benja-M-1");

        $manager->persist($user);
        $manager->flush();
    }
}

Moreover, you can share objects between fixtures that ease the creation of
relation between your entities. If you want to have more information about
doctrine fixtures you should read the documentation from the
github repository. In a Symfony2 project, you can install the Doctrine fixtures library adding the DoctrineFixturesBundle in your composer.json file and registering the bundle in your AppKernel class.

Generating fake content

Sometimes you have to write fake data which can be a boring task. For example you want to provide a content for a blog post, but it could be written in latin, chinese, brainfuck, you don’t care. Faker is the perfect tool for that. It is a fake data generator that allows you to create fake text content, username, emails, urls, locations, etc. Here is a small and simple example:

class LoadUserData implements FixtureInterface
{
    public function load(ObjectManager $manager)
    {
        $faker = Faker\Factory::create();

        $user = new User();
        $user->setFirstname($faker->firstname);
        $user->setLastname($faker->lastname);

        $manager->persist($user);
        $manager->flush();
    }
}

Alice without Bob

Writing your fixtures with PHP can quickly be a problem: you will have to write a huge amount of code, even many files. To reduce this pain you should use Alice.

Alice is a PHP fixtures generator that allows you to load fixtures from PHP or Yaml files easily. Here is a snippet of code that loads some data fixtures from a Doctrine Fixtures class:

class LoadUserData implements FixtureInterface
{
    public function load(ObjectManager $manager)
    {
        // load objects from a yaml file
        $loader = new \Nelmio\Alice\Loader\Yaml();
        $objects = $loader->load(__DIR__.'/users.yml');

        $persister = new \Nelmio\Alice\ORM\Doctrine($manager);
        $persister->persist($objects);
    }
}
# users.yml
MyProject\User:
    user_1:
        firstName: "Benjamin"
        lastName:  "Grandfond"
        username:  "benja-M-1"
        email:     "benjaming@theodo.fr"

As you can see, creating and loading data is easy and will save you a lot of time, but it can be even easier:

class LoadUserData implements FixtureInterface
{
    public function load(ObjectManager $manager)
    {
        Fixtures::load(__DIR__.'/users.yml', $manager);
    }
}

Furthermore, you can generate a range of data with a simple notation to avoid duplication in your yaml files. For example, to generate 50 users you can do this:

# users.yml
MyProject\User:
    user_{1..50}:
        firstName: "Benjamin"
        lastName:  "Grandfond"
        username:  "benja-M-1"
        email:     "benjaming@theodo.fr"

Last but not least Alice natively integrates Faker, so you can write bunch of fake data in few lines of Yaml:

# users.yml
MyProject\User:
    user_{1..50}:
        firstName: <firstName()>
        lastName:  <lastName()>
        username:  <username()>
        email:     <email()>

Use SQL in your fixtures

Generally, you use Doctrine fixtures to load entities mapped in your Doctrine2 application and with Alice and Faker, why would you use SQL to create fixtures? Working with a legacy project you may need to load data that are not mapped in your fresh new Symfony2 app. Don’t forget that fixtures classes are written in PHP code so you can do whatever you want! By the way, you can access the EntityManager (or DocumentManager if you work with the ODM), so you are able to execute any SQL statement:

class LoadUserData implements FixturesInterface
{
    public function load(ObjectManager $manager)
    {
        // ... lot of stuff done before

        $connection = $manager->getConnection();

        $userId = $connection->fetchColumn("SELECT id FROM sf_guard_user WHERE username like '%benjaming%'");
        $groupId = $connection->fetchColumn("SELECT id FROM sf_guard_group WHERE name like 'theodoer'");

        $connection->exec("INSERT INTO sf_guard_user_group (user_id, group_id) VALUES($userId, $groupId)");
    }
}

What about pictures?

In my recent project, users were managed by a symfony 1 application and
pictures were stored inside its /web/uploads/users/avatar folder. I wanted to have some fake pictures in the list of users written with Symfony2, but I wrote the fixtures inside Symfony2 and I could not add the fake fixtures inside the upload folder as it was not versioned (hopefully…). Then the only solution that I found was to copy the files once the fixtures were loaded, but how?

Once again, they are written in PHP files, thus I can find these files and copy them where I want! Furthermore, implementing the ContainerAwareInterface I can access the Symfony2 container.

class LoadUserData implements FixturesInterface, ContainerAwareInterface
{
    public function load(ObjectManager $manager)
    {
        // ... lot of stuff done before

        // Copy images into the legacy application
        $fs = $this->container->get('filesystem');
        $legacyPath = $this->container->getParameter('legacy_path').'/web/uploads';
        $basePath = __DIR__.'/../Files';

        $finder = \Symfony\Component\Finder\Finder::create()
            ->files()
            ->in($basePath)
            ->ignoreDotFiles(true)
            ->ignoreVCS(true)
        ;

        foreach ($finder as $file) {
            if ($file->isFile()) {
                $newFile = str_replace($basePath, $legacyPath, $file->getPathname());
                $fs->copy($file->getPathname(), $newFile);
            }
        }
    }
}

With this solution every time I run the php app/console doctrine:fixtures:load command I have new users with their own pictures and the right sfGuardUserGroup associated. Adding the —append option to the command you can keep existing data loaded from the snapshot of your production server!

To conclude, if you don’t use fixtures in your project, then you should. It is the easiest way to test your development and getting feedback about what your are doing. Also, you should take as much care of your fixtures code as your production code or testing code because you will have to maintain them and may need to reuse them from one test case to another.

Liked this article?