🏭This package lets you create factory classes for your Laravel project.

Overview

Laravel Factories Reloaded 🏭

Latest Version on Packagist v1 Total Downloads MIT Licensed

This package generates class-based model factories, which you can use instead of the ones provided by Laravel.

Screenshot of the command

Laravel 8 Support

The new version v2 now supports Laravel 8. Since L8 changed their own implementation of factories a lot, this new v2 version ONLY works with Laravel 8. If you are not using L8 yet, use the latest 1.* version of this package.

Benefits

  • use the features you already love from Laravel factories (create, make, times, states)
  • automatically create new class factories for specific or All your models
  • automatically import defined default data and states from your Laravel factories
  • and many more...

📺 I've recorded some videos to give you an overview of the features.

⚠️ Note: Interested in WHY you need class-based factories? Read here.

Installation

You can install the package via composer:

composer require --dev christophrumpel/laravel-factories-reloaded

To publish the config file run:

php artisan vendor:publish --provider="Christophrumpel\LaravelFactoriesReloaded\LaravelFactoriesReloadedServiceProvider"

It will provide the package's config file where you can define multiple paths of your models, the path of the newly generated factories, the namespace of your old Laravel factories, as well as where your old Laravel factories are located.

Configuration

Laravel Factories Namespace

Factories are namespaced since Laravel 8, and the default factories namespace is Database\Factories. If your laravel factories namespace is Database\Factories\ClassFactories, you can customize it at the config file as below:

'vanilla_factories_namespace' => 'Database\Factories\ClassFactories',

It will resolve your old laravel factory class as Database\Factories\ClassFactories\UserFactory, instead of Database\Factories\UserFactory.

Usage

Generate Factories

First, you need to create a new factory class for one of your models. This is done via a newly provided command called make:factory-reloaded.

php artisan make:factory-reloaded

You can pick one of the found models or create factories for all of them.

Command Options

If you want to define options through the command itself, you can do that as well:

php artisan make:factory-reloaded --models_path="app/Models"  --factories_path="tests/ClassFactories" --factories_namespace="Tests\ClassFactories"

Currently, you can only define one location for your models this way.

Define Default Model Data

Similar to Laravel factories, you can define default data for your model instances. Inside your new factories, there is a getDefaults method defined for that. The Faker helper to create dummy data is available as well.

public function getDefaults(Faker $faker): array
{
    return [
        'name' => $faker->name,
        'email' => $faker->unique()->safeEmail,
        'email_verified_at' => now(),
        'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi',
        'remember_token' => Str::random(10),
        'active' => false,
    ];
}

Use New Factories

Let's say you have created a new user factory. You can now start using it and create a new user instance. Similar to Laravel factories, the create method will persist in a new model.

$user = UserFactory::new()->create();

If you like to get an instance that is not persisted yet, you can choose the make method.

$user = UserFactory::new()->make();

To create multiple instances, you chain the times() method before the create or make method.

$users = UserFactory::new()
    ->times(4)
    ->create();

States

You may have defined states in your old Laravel factories.

$factory->state(User::class, 'active', function () {
    return [
        'active' => true,
    ];
});

While creating a new class factory, you will be asked if you like those states to be imported to your new factories. If you agree, you can immediately use them. The state active is now a method on your UserFactory.

$user = UserFactory::new()
    ->active()
    ->create();

Relations

Often you will need to create a new model instance with related models. This is now pretty simple by using the with method:

$user = UserFactory::new()
    ->with(Recipe::class, 'recipes', 3)
    ->create();

Here were are getting a user instance that has three related recipes attached. The second argument here defines the relationship name.

⚠️ Note: For this to work, you need to have a new RecipeFactory already created.

You can also define extras for the related models when using related model factories directly.

$user = UserFactory::new()
    ->withFactory(RecipeFactory::new()->withCustomName(), 'recipes', 3)
    ->create();

You can create many related models instances by chaining withs.

$recipe = RecipeFactory::new()
    ->with(Group::class, 'group')
    ->with(Ingredient::class, 'ingredients')
    ->with(Ingredient::class, 'ingredients', 3)
    ->create();

Here we are getting a recipe that has a group and four ingredients.

⚠️ Note: Up to the version 1.0.8, only the last with relation is built.

In Laravel factories, you could also define a related model in your default data like:

$factory->define(Ingredient::class, function (Faker $faker) {
    return [
        'name' => $faker->name,
        'recipe_id' => factory(Recipe::class),
    ];
});

This can also be achieved in our new factory classes.

public function getDefaults(Faker $faker): array
{
    return [
        'name' => $faker->name,
        'recipe_id' => factory(Recipe::class),
    ];
}

Or even better through an instance of a new factory class.

public function getDefaults(Faker $faker): array
{
    return [
        'name' => $faker->name,
        'recipe_id' => RecipeFactory::new(),
    ];
}

⚠️ Note: I wouldn't recommend any of these options because you do not see that additional models are persisted in your tests. Please use the given "with" method create a dedicated method for creating a relation yourself.

Callbacks

In Laravel, you are able to define factory callbacks for afterCreating and afterMaking. You can do something similar also with factory classes. Since both the make and create method are inside your factory class, you are free to add code there:

public function create(array $extra = []): Group
{
    return $this->build($extra);
}

public function make(array $extra = []): Group
{
    return $this->build($extra, 'make');
}

It depends on what you want to achive, but personally I would add a method to your factory which you call from within your test. This way it is more obvious what is happening.

Immutability

You might have noticed that when this package imports a state for you, it will clone the factory before returning.

public function active(): UserFactory
{
    return tap(clone $this)->overwriteDefaults([
        'active' => true,
    ]);
}

This is recommended for all methods which you will use to setup your test model. If you wouldn't clone the factory, you will always modify the factory itself. This could lead into problems when you use the same factory again.

To make a whole factory immutable by default, set the $immutable property to true. That way, every state change will automatically return a cloned instance.

class UserFactory
{
    protected string $modelClass = User::class;
    protected bool $immutable = true;

    // ...

    public function active(): UserFactory
    {
        return $this->overwriteDefaults([
            'active' => true,
        ]);
    }
}

In some context, you might want to use a standard factory as immutable. This can be done with the immutable method.

$factory = UserFactory::new()
    ->immutable();

$activeUser = $factory
    ->active()
    ->create();

$inactiveUser = $factory->create();

Note: with and withFactory methods are always immutable.

What Else

The best thing about those new factory classes is that you own them. You can create as many methods or properties as you like to help you create those specific instances that you need. Here is how a more complex factory call could look like:

UserFactory::new()
    ->active()
    ->onSubscriptionPlan(SubscriptionPlan::paid)
    ->withRecipesAndIngredients()
    ->times(10)
    ->create();

Using such a factory call will help your tests to stay clean and give everyone a good overview of what is happening here.

Why Class-Based Factories?

  • They give you much more flexibility on how to create your model instances.
  • They make your tests much cleaner because you can hide complex preparations inside the class.
  • They provide IDE auto-completion which you do not get have with Laravel factories.

Testing

composer test

Changelog

Please see CHANGELOG for more information about what has changed recently.

Contributing

Please see CONTRIBUTING for details.

Security

If you discover any security-related issues, please email [email protected] instead of using the issue tracker.

Credits

Some of the implementations are inspired by Brent's article about how they deal with factories at Spatie.

And a big thanks goes out to Adrian who helped me a lot with refactoring this package.

License

The MIT License (MIT). Please see License File for more information.

Comments
  • Using factories when creating relations

    Using factories when creating relations

    I have started using this package and the very first issue was that I could not create related models with overwritten defaults. Consider the following example:

    GroupFactory::new()->with(Recipe::class, 'recipes')->create();
    

    For example, we have no way of defining the name of the related Recipe that is being created.

    With the new implementation, you can use another factory adding full flexibility for defining the related model.

    GroupFactory::new()->withFactory(RecipeFactory::new()->withCustomName(), 'recipes')->create();
    

    The API is the same and one can create multiple instances with the same declaration, or chain those as required.

    opened by barnabaskecskes 14
  • Save related models where foreign key is not nullable

    Save related models where foreign key is not nullable

    The current implementation of with() and the examples in the docs only work when the foreign key is nullable() like group_id on the recipes table.

            Schema::create('recipes', function (Blueprint $table) {
                $table->bigIncrements('id');
                $table->unsignedBigInteger('group_id')->nullable();
    

    When it is not nullable it is not possible to create the related model before using it in saveMany().

    A function like make() on Laravel's FactoryBuilder would probably help.

    Would this be the right direction or is there another clean way you can think of?

    opened by thijsvdanker 13
  • Support relations stack & foreign relations

    Support relations stack & foreign relations

    Hi, This PR allows to create many related models at once. It also add support of BelongsTo and MorphTo relationship.

    $recipe = RecipeFactory::new()
                ->with(Group::class, 'group')
                ->andWith(Ingredient::class, 'ingredients', 3)
                ->create();
    
    $this->assertCount(1, Group::all());
    $this->assertCount(3, Ingredient::all());
    $this->assertTrue($recipe->group()->exists());
    $this->assertEquals(3, $recipe->ingredients()->count());
    

    To prevent a BC, the relation stack is reset every time with is called. With this implementation, the same relation doesn't add up and only the last o

    Here are some things that can be improved:

    • define the expected behaviour of the make creation type,
    • externalize the relations build mecanism,
    • define relation creation type according to the relation type (and not the method),
    • define if the same relation should stack:
    $recipe = RecipeFactory::new()
                ->with(Ingredient::class, 'ingredients', 1)
                ->andWith(Ingredient::class, 'ingredients', 3)
                ->create();
    $this->assertCount(3, Ingredient::all());
    $this->assertCount(4, Ingredient::all()); /* Or */ 
    

    What do you think about this @christophrumpel?

    opened by shaffe-fr 11
  • generate default attributes if there isn’t an existing factory to mig…

    generate default attributes if there isn’t an existing factory to mig…

    This will setup fake data for defaults rather than returning an empty array. The goal of this is to give developers a little more of a head start with defining the fake data for their columns. It's not the smartest, but it works well enough for simple column types.

    Currently it just supports the basic types that Doctrine DBal supports, since testing was proving a lot more difficult when trying to add support for more types, and some common column names. All the credit really goes to @mpociot's work in his laravel-test-factor-helper package.

    Let me know if you have any suggestions. The tests were the hardest part of all of this and I'm not 100% satisfied with them, but they do the job so 🤷‍♂️

    opened by JoshMoreno 11
  • Update to the way LaravelFactoryExtractor processes Laravel factory state methods

    Update to the way LaravelFactoryExtractor processes Laravel factory state methods

    Hi there. This PR updates the way LaravelFactoryExtractor's getStates method processes input php code.

    The problem:

    Currently when it analyses the code inside Laravel factory state methods, there are situations where incorrect output is generated.

    For example, the source:

    $factory->state(Recipe::class, 'someState', function () {
        return [ 'objective' => 'return something];' ];
    });
    

    Will generate:

        public function someState(): RecipeFactory
        {
            return tap(clone $this)->overwriteDefaults([ 'objective' => 'return tap(clone $this)->overwriteDefaults(something];' ];
    
        }
    

    There are three ways I identified where the input can be mis-interpreted:

    • The string "return " appears on the first line of code in more than one place,
    • When there's more than one line of code and the string "];" appears more than once on the last line,
    • When there's only one line of code,

    Also, when "return" isn't lowercase it won't be detected, and will fall-back to the longer closure return type.

    The solution:

    The proposed changes detect the return value differently (with a regex) and inserts that instead of piecing together the content from the exploded lines.

    The relevant test and README.md have been updated.

    Further thoughts:

    This code still strips out empty lines and adds indentation which also have the potential to also give incorrect results. I think that string values in the input that span multiple lines are not likely to be needed here and the user can always tweak the output, so the benefit of neater formatting is probably preferred.

    opened by code-distortion 11
  • Prevent showing factored model

    Prevent showing factored model

    I will show you an example of this PR.

    we use make:factory-reloaded command to fitch our models, considering we have these:

    [0] App\Employee
    [1] App\User
    [2] App\Zoo
    

    when I choose App\User, and run the command again make:factory-reloaded, let us see the results before and after this PR.

    Before

    it shows all the models again and again.

    [0] App\Employee
    [1] App\User
    [2] App\Zoo
    

    After

    it just shows the models that don't have factory,

    [0] App\Employee
    [2] App\Zoo
    

    and if you delete UserFactory, the User model will be represented in the array.

    [0] App\Employee
    [1] App\User
    [2] App\Zoo
    

    All tests are passed thank you.

    opened by MohammedAlkutrani 9
  • Support factories on overwritten default fields

    Support factories on overwritten default fields

    This PR allows to use the factory syntax in overwritten defaults fields.

    public function withGroup(): self
    {
        return tap(clone $this)->overwriteDefaults([
            'group_id' => GroupFactory::new(),
        ]);
    }
    

    It will also work with extra attributes:

    $recipe = RecipeFactory::new()->create(['group_id' => GroupFactory::new()]);
    

    On the current release, I had the following error when using factory: Error : Object of class Tests\Factories\GroupFactory could not be converted to string.

    The PR includes some tests. Two tests are failing. It looks like it's caused by my line \r\n line endings on Windows. Do you have any idea @christophrumpel? What do you think about this PR?

    Thanks

    opened by shaffe-fr 8
  • Shorter API using real time facades

    Shorter API using real time facades

    Hi @christophrumpel congratulations on your package.

    I was trying to remove the need to call the new static method and make the API shorter. Without changing any (almost) of the existing code, I could only do it using real-time facades.

    The only thing that is annoying me is that we loose type hinting, which also bothers me a lot. image

    So I'm not sure if this PR is useful or not.

    question 
    opened by diogogomeswww 8
  • Ready enough to actually show you what I'm thinking here

    Ready enough to actually show you what I'm thinking here

    @christophrumpel Here's what I've done so far:

    • Added a test to make sure that the command stops and errors if a factory exists without --force
    • Added a test to make sure that --force works
    • Added {model?} to the signature and use it as the model class if provided
    • Added an option to the signature corresponding to each of the 3 config options for on the fly configuration
    • Updated docs
    • Tested my changes to the command

    My thinking behind using options to override the configuration settings is that for apps that are set up to use domains these paths and namespaces can vary.

    I'm not married to my solutions here and would welcome any suggestions for a different way of going about any of it. Let me know if there's anything you'd like me to change or build out further. Thanks for a great package!

    This pull request is in reference to #11

    opened by edgrosvenor 8
  • Deep nested relationships don't get persisted

    Deep nested relationships don't get persisted

    Hi, I have quite a deep tree of objects I want to test and realized that only the outer object with ONE level deeper gets persisted but from the 2nd level on the objects are only relations to the parent object but are NOT in the database.

    My code looks like this:

    $root = FactoryA::new()
        ->withFactory(
            FactoryB::new()
                ->withFactory(
                    FactoryC::new()
                        ->withFactory(
                            FactoryD::new()
                            'd'
                        ),
                    'c'
                ),
            'b'
        )
        ->create();
    

    Now I want to run database tests on D records but they are not persisted. Is there a workaround to persist them afterwards, is this intended behavior or is this a bug?

    The problem lies here that deeper levels are called with "make" instead of "create": https://github.com/christophrumpel/laravel-factories-reloaded/blob/c889129c6505a44c5fdc3210b989849664cab795/src/BaseFactory.php#L129 because the $creationType parameter gets overwritten with "make"

    opened by simonschaufi 7
  • Creating a model instance with a relation but generating extra data without relation

    Creating a model instance with a relation but generating extra data without relation

    Hi, that's say I create n model instances and each with a relation, but there output 2n rows of data in the table, and n rows in the relation table.

    If I run ProductFactory::new()->withFactory(InfoFactory::new(),'info')->create(); from tinker. The products table:

    id    name    price
    1    wine    100
    2    bread    50
    

    The infos table:

    id    product_id    description
    1    1    lkdakdlkdl;ks
    

    If I run ProductFactory::new()->withFactory(InfoFactory::new(),'info',1)->times(10)->create(); from tinker. The products table:

    id    name    price
    1    wine    100
    2    bread    50
    3    meat    15
    ...(skip 4 to 18)...
    19 fish 20
    20 pizza 80
    

    The infos table:

    id    product_id    description
    1    1    yummy
    2    3    good
    3    5    soft
    4    7    bad
    ...(skip 5 to 7)
    8    15    healthy
    9    17    funny
    10    19    delicious
    

    These are two models:

    class Product extends Model
    {
        public function info()
        {
            return $this->hasMany(Info::class);
        }
    }
    
    class Info extends Model
    {
        public function product(){
            return $this->belongsTo(Product::class);
        }
    }
    

    The return of getDefaults method of ProductFactory:

    return [
        'name' => $faker->name,
        'price' => $faker->numberBetween(1, 100),
    ];
    

    The return of getDefaults method of InfoFactory:

    return [
        'product_id' => ProductFactory::new(),
        'description' => $faker->text(15),
    ];
    

    I don't know why, any idea?

    opened by onlyu-bot 7
  • PHP 8 Support

    PHP 8 Support

    Is there any chance you can bump require version to support PHP 8?

    It's great that you've made things compatible with with Laravel 8, perhaps something in keeping with their approach ("^7.4|^8.0")?

    Thanks for the hard work on this package, love it. Not a fan of the Laravel 8 factories approach.

    opened by DaJoTo 7
  • Nested model configuration

    Nested model configuration

    Is it currently possible to achieve something like this?

    We have the relationship:

    Order -> hasMany -> OrderItems
    

    I want to create an order with a status of new and then create 4 order items with a status of allocated. Is it possible to hook into the orderItemFactory method from within the with method, and apply the allocated status?

    // Test
    OrderFactory::new()
        ->withOrderItems(5)
        ->create();
    
    // OrderFactory.php
    public function withOrderItems($qty)
    {
        return $this->with(
            OrderItem::class,
            'orderItems',
            $qty
        );
    }
    
    // OrderItemFactory.php
    public function allocated()
    {
        return tap(clone $this)->overwriteDefaults([
            'state' => 'allocated',
        ]);
    }
    
    
    opened by booni3 5
Releases(v3.0.0)
Owner
Christoph Rumpel
Christoph Rumpel
This package lets you add uuid as primary key in your laravel applications

laravel-model-uuid A Laravel package to add uuid to models Table of contents Installation Configuration Model Uuid Publishing files / configurations I

salman zafar 10 May 17, 2022
The package lets you generate TypeScript interfaces from your Laravel models.

Laravel TypeScript The package lets you generate TypeScript interfaces from your Laravel models. Introduction Say you have a model which has several p

Boris Lepikhin 296 Dec 24, 2022
Laravel Larex lets you translate your whole Laravel application from a single CSV file.

Laravel Larex Translate Laravel Apps from a CSV File Laravel Larex lets you translate your whole Laravel application from a single CSV file. You can i

Luca Patera 68 Dec 12, 2022
Laravel 5 Model Factory Generator

Laravel 5 Model Factory Generator This package offers a lazy way to create a new model factory files, since Laravel (< 5.5) have no Artisan command to

Roni Yusuf Manalu 16 Feb 15, 2020
Declare database migrations and factory definitions inside Laravel models.

Lucid This package allows you to declare database migrations and factory definitions inside of your Laravel models. Running the lucid:migrate command

null 23 Jul 9, 2022
States allows you to create PHP classes following the State Pattern in PHP.

States allows you to create PHP classes following the State Pattern in PHP. This can be a cleaner way for an object to change its behavior at runtime without resorting to large monolithic conditional statements and this improve maintainability and workflows writing.

Teknoo Software 10 Nov 20, 2022
cybercog 996 Dec 28, 2022
This Laravel Nova tool lets you run artisan and bash commands directly from Nova 4 or higher.

Laravel Nova tool for running Artisan & Shell commands. This Nova tool lets you run artisan and bash commands directly from nova. This is an extended

Artem Stepanenko 17 Dec 15, 2022
Collection of classes you can use to standardize data formats in your Laravel application.

Laravel Formatters This package is a collection of classes you can use to standardize data formats in your Laravel application. It uses the Service Co

Michael Rubel 88 Dec 23, 2022
A Laravel chat package. You can use this package to create a chat/messaging Laravel application.

Chat Create a Chat application for your multiple Models Table of Contents Click to expand Introduction Installation Usage Adding the ability to partic

Tinashe Musonza 931 Dec 24, 2022
A university system that creates a timetable programm for subjects,classes and teachers which is used to create a programm for each semester. All this served as a website.

Timetable-System-Generator A university system that creates a timetable programm for subjects,classes and teachers which is used to create a programm

null 3 Nov 19, 2022
This package enables you to create and run a fully functioning WebSocket server in your Laravel app.

This package enables you to create and run a fully functioning WebSocket server in your Laravel app. It can optionally receive messages broadcast over ZeroMQ.

Asked.io 181 Oct 6, 2022
Foreman is a Laravel scaffolding application that automates common tasks you typically perform with each new Laravel app you create

Foreman is a Laravel scaffolding application that automates common tasks you typically perform with each new Laravel app you create. The directives you want Forman to perform are outlined in a JSON based template file.

Indatus 145 Apr 13, 2022
PHP components - collection of cross-project PHP classes

PHP components Collection of cross-project PHP classes. Install: $ composer require ansas/php-component Ansas\Component\Convert\ConvertPrice Convert "

null 1 Jan 5, 2022
Collection of the Laravel/Eloquent Model classes that allows you to get data directly from a Magento 2 database.

Laragento LAravel MAgento Micro services Magento 2 has legacy code based on abandoned Zend Framework 1 with really ugly ORM on top of outdated Zend_DB

Egor Shitikov 87 Nov 26, 2022
A light weight laravel package that facilitates dealing with arabic concepts using a set of classes and methods to make laravel speaks arabic

A light weight laravel package that facilitates dealing with arabic concepts using a set of classes and methods to make laravel speaks arabic! concepts like , Hijri Dates & Arabic strings and so on ..

Adnane Kadri 49 Jun 22, 2022
A laravel package to attach uuid to model classes

Laravel Model UUID A simple package to generate model uuid for laravel models Installation Require the package using composer: composer require touhid

null 10 Jan 20, 2022
Package with small support traits and classes for the Laravel Eloquent models

Contains a set of traits for the eloquent model. In future can contain more set of classes/traits for the eloquent database.

Martin Kluska 3 Feb 10, 2022
Effortlessly create a PHP preload script for your Laravel project.

This package has been superseeded by Laragear/Preload. Please migrate to the new package. Laraload Effortlessly create a PHP Preload Script for your L

Italo 209 Dec 7, 2022