Extending PHP Faker Library to define custom data structures using Laravel 11
Tags: laravel, php, faker, testing
Published: 2024-06-07 00:33:09

## Introduction

If you work with Laravel at a daily basis (specially if you apply TDD), you might have figured out that during the 'test writing phase', sometimes we face situations that requires us to write custom array structures to fill our models, or some kind of data that cannot be easily faked and had to be written manually inside the Factory class.

Recently I've dealt with [EditorJS](https://editorjs.io/) and had to save the JSON data it provides in my database, so I ended up having to fake some of the data in order to easily make some assertions during testing.

For the following examples, I have a `users` table with a JSON column called `custom_data`.

## The Problem

Many of us end up doing the obvious approach:

```php
$user = User::factory()
    ->create([
        'custom_data' => [
        'id'   => uniqid(),
        'data' => [
            'first_index'  => 'first_value',
            'second_index' => 'second_value',
            'third_index'  => 'third_value',
        ],
        'type' => 'very_complex',
    ],
]);
```

The output of `$user->custom_data` of course is:
```
array:3 [
  "id" => "666251e06b701"
  "data" => array:3 [
    "first_index" => "first_value"
    "second_index" => "second_value"
    "third_index" => "third_value"
  ]
  "type" => "very_complex"
] // app/Console/Commands/Playground.php:29
```

Or maybe some of us will take a more elegant, but yet sometimes problematic, solution: 

```php
$user = User::factory()
    ->withCustomData()
    ->create();
```

In `UserFactory.php`:
```php
public function withCustomData(): self
{
    return $this->state([
       'custom_data' => [
           'id'   => uniqid(),
           'data' => [
               'first_index'  => 'first_value',
               'second_index' => 'second_value',
               'third_index'  => 'third_value',
           ],
           'type' => 'very_complex',
       ],
    ]);
}
```

And as you may expect, the output is the same as above.

At first glance these examples may seem good enough - at the end of the day, they work - but when we consider that this array can be different sometimes, it can quickly become a cumbersome to maintain. For instance, what if the "type" index has a different value and your application behavior depends on that? Will you pass a different parameter every time? Or even create another factory state, thus making the code larger and larger?

I mean, there is no problem to define factory states, but the thing here is to "separate responsibilities" and make our code smaller and more readable.

The issue with this approach is that the UserFactory has the responsibility of taking care of which types of data (and all data variations) it actually supports, when in reality the users table doesn't care about it. The JSON column accepts anything that is valid, regardless of what it is.

## The Solution

This said, we can agree that nothing better than having a place to generate these exact data variations and complex things with a few lines of code.

For this, we are going to extend the Faker library by creating a custom provider, so we will be able to call:

`$this->faker->complexArray()` or `fake()->complexArray()`

And that will get the job done.

In order to accomplish that, I have written a class inside `Tests/Faker/Providers` namespace, named `CustomComplexArrayProvider.php`

The aim here is to shelter all that logic regarding this specific array and all things that may differ inside it.

In `tests/Faker/Providers`:
```php
<?php

namespace Tests\Faker\Providers;

use Faker\Provider\Base;

class CustomComplexArrayProvider extends Base
{
    public function complexArray(): array
    {
        return [
            'id'   => uniqid(),
            'data' => [
                'first_index'  => 'first_value',
                'second_index' => 'second_value',
                'third_index'  => 'third_value',
            ],
            'type' => 'complex',
        ];
    }
}
```

As you can see, we have the `complexArray` method definition and it returns a multidimensional array with 3 indexes. Of course this is a very simple and straightforward example. You can receive parameters inside this method and make it suit your testing needs.

You can even create a method for each type of data, just to make the code more readable:

In `CustomComplexArrayProvider.php`:
```php
public function notTooComplexArray(): array
{
    return [
        'id'   => uniqid(),
        'data' => [
            'first_index' => 'first_value',
        ],
        'type' => 'not_too_complex',
    ];
}
```

Okay, fair enough, but how do we use this?

To access these guys and use them within your tests, we must tell Laravel to add this data Provider in the `Faker/Generator` class.

For that, I've written a Service Provider called `TestServiceProvider`.

> You can generate Provider classes with `php artisan make:provider`

```php
<?php

namespace App\Providers;

use Faker\Factory;
use Faker\Generator;
use Illuminate\Support\ServiceProvider;
use Tests\Faker\Providers\CustomComplexArrayProvider;

class TestServiceProvider extends ServiceProvider
{
    public function register(): void
    {
        if (!$this->app->environment(['local', 'testing'])) {
            return;
        }

        $this->app->singleton(
            abstract: Generator::class,
            concrete: function (): Generator {
                $factory = Factory::create();

                $factory->addProvider(new CustomComplexArrayProvider($factory));

                return $factory;
            }
        );

        $this->app->bind(
            abstract: Generator::class . ':' . config('app.faker_locale'),
            concrete: Generator::class
        );
    }
}
```

Then we have to register the Provider in our application, in `bootstrap/providers.php`:
```php
return [
    App\Providers\AppServiceProvider::class,
    App\Providers\TestServiceProvider::class,
];
```

As you can see, we registered a binding in the Service Container telling Laravel that it must add our custom class as a Faker provider, so now Faker is aware of the methods we've just created.

And of course, this is intended to be applied only in local/testing environments, so there's a check at the beginning of the code just to avoid this code to run in the production environment.

The `singleton` call aims to allow us to use our custom class when calling them from a `Faker/Generator` object, and the `bind` does the same thing, but when calling from the `fake()` helper.

This way, the result is:

```php
dd(fake()->complexArray());
```

Output:
```
array:3 [
  "id" => "6662549ca6be9"
  "data" => array:3 [
    "first_index" => "first_value"
    "second_index" => "second_value"
    "third_index" => "third_value"
  ]
  "type" => "complex"
] // app/Console/Commands/Playground.php:37
```

Now you can use this method inside your factories, or even combine them with factory states. That's up to you now.

I hope this article helps you to make your code even easier to understand in 6 months from now 🙌

Thanks for reading!