zero-to-prod/data-model-factory

Build a Factory for Anything

v71.3.2 2024-10-26 13:04 UTC

README

Repo tests Packagist Downloads php Packagist Version License

Introduction

This package is a fresh take on how to set the state of your DTOs in a simple and delightful way.

The API is takes some hints from Laravel's Eloquent Factories, but adds some niceties such as setting state via dot syntax and using the set() helper method on the fly.

This package does not require any other dependencies, allowing you to make a factory for anything.

The examples use the DataModel trait, making easier to build your DTOs, but it is not required.

Installation

Install the package via Composer:

composer require zero-to-prod/data-model-factory

Additional Packages

Usage

This example makes use of the DataModel trait to instantiate the User class.

You can install the DataModel package like this:

composer require zero-to-prod/data-model

If you don't want to use this trait, you can customize the class instantiation this way.

  1. Include the Factory trait in your factory class.

  2. Set the $model property to the class you want to instantiate.

  3. Implement a definition() method that returns an array of default values.

  4. NOTE: The $this->state() method accepts dot syntax, arrays, or a callback.

class User
{
    use \Zerotoprod\DataModelFactory\DataModel;

    public $first_name;
    public $last_name;
    public $address;
    
    public static function factory(array $states = []): UserFactory
    {
        return new UserFactory($states);
    }
}

class UserFactory
{
    use \Zerotoprod\DataModelFactory\Factory;

    /* This is the class to be instantiated with the make() method */
    protected $model = User::class;

    protected function definition(): array
    {
        return [
            'first_name' => 'John',
            'last_name' => 'N/A',
            'address' => [
                'street' => 'Memory Lane'
            ]
        ];
    }
    
    public function setStreet(string $value): self
    {
        /** Dot Syntax */
        return $this->state('address.street', $value);
    }
    
    public function setFirstName(string $value): self
    {
        /** Array Syntax */
        return $this->state(['first_name' => $value]);
    }
    
    public function setLastName(): self
    {
        /** Closure Syntax */
        return $this->state(function ($context) {
            return ['first_name' => $context['last_name']];
        });
    }
    
    /* Optionally implement for better static analysis */
    public function make(): User
    {
        return $this->instantiate();
    }
}

$User = UserFactory::factory([User::last_name => 'Doe'])
            ->setFirstName('Jane')
            ->make();
            
User::factory([User::last_name => 'Doe'])->make(); // Also works for this example

echo $User->first_name; // 'Jane'
echo $User->last_name;  // 'Doe'

Using the set() Method

You can use the set() helper method to fluently modify the state of your model in a convenient way.

This is a great way to modify a model without having to implement a method in the factory.

$User = User::factory()
            ->set('first_name', 'John')
            ->set(['last_name' => 'Doe'])
            ->set(function ($context) {
                return ['surname' => $context['last_name']];
            })
            ->set('address.postal_code', '46789') // dot syntax for nested values 
            ->make();

echo $User->first_name;             // John
echo $User->last_name;              // Doe
echo $User->surname;                // Doe
echo $User->address->postal_code;   // 46789

Custom Class Instantiation

To customize instantiation, override the make() method.

class User
{
    public function __construct(public string $fist_name, public string $last_name)
    {
    }
}

class UserFactory
{
    use \Zerotoprod\DataModelFactory\Factory;

    private function definition(): array
    {
        return [
            'first_name' => 'John',
            'last_name' => 'Doe',
        ];
    }

    private function make(): User
    {
        return new User($this->context['first_name'], $this->context['last_name']);
    }
}

$User = UserFactory::factory()->make();

echo $User->first_name; // 'Jane'
echo $User->last_name;  // 'Doe'