Browse Source

Merge branch 'master' into 2.0

jqh 4 years ago
parent
commit
96d12cb71f
3 changed files with 773 additions and 773 deletions
  1. 1 1
      README.md
  2. 326 326
      tests/Factory.php
  3. 446 446
      tests/FactoryBuilder.php

+ 1 - 1
README.md

@@ -68,7 +68,7 @@
 
 ## 环境
  - PHP >= 7.1.0
- - Laravel 5.5.0 ~ 7.*
+ - Laravel 5.5.0 ~ 8.*
  - Fileinfo PHP Extension
 
 ## 安装

+ 326 - 326
tests/Factory.php

@@ -1,326 +1,326 @@
-<?php
-
-namespace Tests;
-
-use ArrayAccess;
-use Faker\Generator as Faker;
-use Symfony\Component\Finder\Finder;
-
-class Factory implements ArrayAccess
-{
-    /**
-     * The model definitions in the container.
-     *
-     * @var array
-     */
-    protected $definitions = [];
-
-    /**
-     * The registered model states.
-     *
-     * @var array
-     */
-    protected $states = [];
-
-    /**
-     * The registered after making callbacks.
-     *
-     * @var array
-     */
-    protected $afterMaking = [];
-
-    /**
-     * The registered after creating callbacks.
-     *
-     * @var array
-     */
-    protected $afterCreating = [];
-
-    /**
-     * The Faker instance for the builder.
-     *
-     * @var \Faker\Generator
-     */
-    protected $faker;
-
-    /**
-     * Create a new factory instance.
-     *
-     * @param  \Faker\Generator  $faker
-     * @return void
-     */
-    public function __construct(Faker $faker)
-    {
-        $this->faker = $faker;
-    }
-
-    /**
-     * Create a new factory container.
-     *
-     * @param  \Faker\Generator  $faker
-     * @param  string|null  $pathToFactories
-     * @return static
-     */
-    public static function construct(Faker $faker, $pathToFactories = null)
-    {
-        $pathToFactories = $pathToFactories ?: database_path('factories');
-
-        return (new static($faker))->load($pathToFactories);
-    }
-
-    /**
-     * Define a class with a given short-name.
-     *
-     * @param  string  $class
-     * @param  string  $name
-     * @param  callable  $attributes
-     * @return $this
-     */
-    public function defineAs($class, $name, callable $attributes)
-    {
-        return $this->define($class, $attributes, $name);
-    }
-
-    /**
-     * Define a class with a given set of attributes.
-     *
-     * @param  string  $class
-     * @param  callable  $attributes
-     * @param  string  $name
-     * @return $this
-     */
-    public function define($class, callable $attributes, $name = 'default')
-    {
-        $this->definitions[$class][$name] = $attributes;
-
-        return $this;
-    }
-
-    /**
-     * Define a state with a given set of attributes.
-     *
-     * @param  string  $class
-     * @param  string  $state
-     * @param  callable|array  $attributes
-     * @return $this
-     */
-    public function state($class, $state, $attributes)
-    {
-        $this->states[$class][$state] = $attributes;
-
-        return $this;
-    }
-
-    /**
-     * Define a callback to run after making a model.
-     *
-     * @param  string  $class
-     * @param  callable  $callback
-     * @param  string  $name
-     * @return $this
-     */
-    public function afterMaking($class, callable $callback, $name = 'default')
-    {
-        $this->afterMaking[$class][$name][] = $callback;
-
-        return $this;
-    }
-
-    /**
-     * Define a callback to run after making a model with given state.
-     *
-     * @param  string  $class
-     * @param  string  $state
-     * @param  callable  $callback
-     * @return $this
-     */
-    public function afterMakingState($class, $state, callable $callback)
-    {
-        return $this->afterMaking($class, $callback, $state);
-    }
-
-    /**
-     * Define a callback to run after creating a model.
-     *
-     * @param  string  $class
-     * @param  callable  $callback
-     * @param  string $name
-     * @return $this
-     */
-    public function afterCreating($class, callable $callback, $name = 'default')
-    {
-        $this->afterCreating[$class][$name][] = $callback;
-
-        return $this;
-    }
-
-    /**
-     * Define a callback to run after creating a model with given state.
-     *
-     * @param  string  $class
-     * @param  string  $state
-     * @param  callable  $callback
-     * @return $this
-     */
-    public function afterCreatingState($class, $state, callable $callback)
-    {
-        return $this->afterCreating($class, $callback, $state);
-    }
-
-    /**
-     * Create an instance of the given model and persist it to the database.
-     *
-     * @param  string  $class
-     * @param  array  $attributes
-     * @return mixed
-     */
-    public function create($class, array $attributes = [])
-    {
-        return $this->of($class)->create($attributes);
-    }
-
-    /**
-     * Create an instance of the given model and type and persist it to the database.
-     *
-     * @param  string  $class
-     * @param  string  $name
-     * @param  array  $attributes
-     * @return mixed
-     */
-    public function createAs($class, $name, array $attributes = [])
-    {
-        return $this->of($class, $name)->create($attributes);
-    }
-
-    /**
-     * Create an instance of the given model.
-     *
-     * @param  string  $class
-     * @param  array  $attributes
-     * @return mixed
-     */
-    public function make($class, array $attributes = [])
-    {
-        return $this->of($class)->make($attributes);
-    }
-
-    /**
-     * Create an instance of the given model and type.
-     *
-     * @param  string  $class
-     * @param  string  $name
-     * @param  array  $attributes
-     * @return mixed
-     */
-    public function makeAs($class, $name, array $attributes = [])
-    {
-        return $this->of($class, $name)->make($attributes);
-    }
-
-    /**
-     * Get the raw attribute array for a given named model.
-     *
-     * @param  string  $class
-     * @param  string  $name
-     * @param  array  $attributes
-     * @return array
-     */
-    public function rawOf($class, $name, array $attributes = [])
-    {
-        return $this->raw($class, $attributes, $name);
-    }
-
-    /**
-     * Get the raw attribute array for a given model.
-     *
-     * @param  string  $class
-     * @param  array  $attributes
-     * @param  string  $name
-     * @return array
-     */
-    public function raw($class, array $attributes = [], $name = 'default')
-    {
-        return array_merge(
-            call_user_func($this->definitions[$class][$name], $this->faker), $attributes
-        );
-    }
-
-    /**
-     * Create a builder for the given model.
-     *
-     * @param  string  $class
-     * @param  string  $name
-     * @return FactoryBuilder
-     */
-    public function of($class, $name = 'default')
-    {
-        return new FactoryBuilder(
-            $class, $name, $this->definitions, $this->states,
-            $this->afterMaking, $this->afterCreating, $this->faker
-        );
-    }
-
-    /**
-     * Load factories from path.
-     *
-     * @param  string  $path
-     * @return $this
-     */
-    public function load($path)
-    {
-        $factory = $this;
-
-        if (is_dir($path)) {
-            foreach (Finder::create()->files()->name('*.php')->in($path) as $file) {
-                require $file->getRealPath();
-            }
-        }
-
-        return $factory;
-    }
-
-    /**
-     * Determine if the given offset exists.
-     *
-     * @param  string  $offset
-     * @return bool
-     */
-    public function offsetExists($offset)
-    {
-        return isset($this->definitions[$offset]);
-    }
-
-    /**
-     * Get the value of the given offset.
-     *
-     * @param  string  $offset
-     * @return mixed
-     */
-    public function offsetGet($offset)
-    {
-        return $this->make($offset);
-    }
-
-    /**
-     * Set the given offset to the given value.
-     *
-     * @param  string  $offset
-     * @param  callable  $value
-     * @return void
-     */
-    public function offsetSet($offset, $value)
-    {
-        $this->define($offset, $value);
-    }
-
-    /**
-     * Unset the value at the given offset.
-     *
-     * @param  string  $offset
-     * @return void
-     */
-    public function offsetUnset($offset)
-    {
-        unset($this->definitions[$offset]);
-    }
-}
+<?php
+
+namespace Tests;
+
+use ArrayAccess;
+use Faker\Generator as Faker;
+use Symfony\Component\Finder\Finder;
+
+class Factory implements ArrayAccess
+{
+    /**
+     * The model definitions in the container.
+     *
+     * @var array
+     */
+    protected $definitions = [];
+
+    /**
+     * The registered model states.
+     *
+     * @var array
+     */
+    protected $states = [];
+
+    /**
+     * The registered after making callbacks.
+     *
+     * @var array
+     */
+    protected $afterMaking = [];
+
+    /**
+     * The registered after creating callbacks.
+     *
+     * @var array
+     */
+    protected $afterCreating = [];
+
+    /**
+     * The Faker instance for the builder.
+     *
+     * @var \Faker\Generator
+     */
+    protected $faker;
+
+    /**
+     * Create a new factory instance.
+     *
+     * @param  \Faker\Generator  $faker
+     * @return void
+     */
+    public function __construct(Faker $faker)
+    {
+        $this->faker = $faker;
+    }
+
+    /**
+     * Create a new factory container.
+     *
+     * @param  \Faker\Generator  $faker
+     * @param  string|null  $pathToFactories
+     * @return static
+     */
+    public static function construct(Faker $faker, $pathToFactories = null)
+    {
+        $pathToFactories = $pathToFactories ?: database_path('factories');
+
+        return (new static($faker))->load($pathToFactories);
+    }
+
+    /**
+     * Define a class with a given short-name.
+     *
+     * @param  string  $class
+     * @param  string  $name
+     * @param  callable  $attributes
+     * @return $this
+     */
+    public function defineAs($class, $name, callable $attributes)
+    {
+        return $this->define($class, $attributes, $name);
+    }
+
+    /**
+     * Define a class with a given set of attributes.
+     *
+     * @param  string  $class
+     * @param  callable  $attributes
+     * @param  string  $name
+     * @return $this
+     */
+    public function define($class, callable $attributes, $name = 'default')
+    {
+        $this->definitions[$class][$name] = $attributes;
+
+        return $this;
+    }
+
+    /**
+     * Define a state with a given set of attributes.
+     *
+     * @param  string  $class
+     * @param  string  $state
+     * @param  callable|array  $attributes
+     * @return $this
+     */
+    public function state($class, $state, $attributes)
+    {
+        $this->states[$class][$state] = $attributes;
+
+        return $this;
+    }
+
+    /**
+     * Define a callback to run after making a model.
+     *
+     * @param  string  $class
+     * @param  callable  $callback
+     * @param  string  $name
+     * @return $this
+     */
+    public function afterMaking($class, callable $callback, $name = 'default')
+    {
+        $this->afterMaking[$class][$name][] = $callback;
+
+        return $this;
+    }
+
+    /**
+     * Define a callback to run after making a model with given state.
+     *
+     * @param  string  $class
+     * @param  string  $state
+     * @param  callable  $callback
+     * @return $this
+     */
+    public function afterMakingState($class, $state, callable $callback)
+    {
+        return $this->afterMaking($class, $callback, $state);
+    }
+
+    /**
+     * Define a callback to run after creating a model.
+     *
+     * @param  string  $class
+     * @param  callable  $callback
+     * @param  string $name
+     * @return $this
+     */
+    public function afterCreating($class, callable $callback, $name = 'default')
+    {
+        $this->afterCreating[$class][$name][] = $callback;
+
+        return $this;
+    }
+
+    /**
+     * Define a callback to run after creating a model with given state.
+     *
+     * @param  string  $class
+     * @param  string  $state
+     * @param  callable  $callback
+     * @return $this
+     */
+    public function afterCreatingState($class, $state, callable $callback)
+    {
+        return $this->afterCreating($class, $callback, $state);
+    }
+
+    /**
+     * Create an instance of the given model and persist it to the database.
+     *
+     * @param  string  $class
+     * @param  array  $attributes
+     * @return mixed
+     */
+    public function create($class, array $attributes = [])
+    {
+        return $this->of($class)->create($attributes);
+    }
+
+    /**
+     * Create an instance of the given model and type and persist it to the database.
+     *
+     * @param  string  $class
+     * @param  string  $name
+     * @param  array  $attributes
+     * @return mixed
+     */
+    public function createAs($class, $name, array $attributes = [])
+    {
+        return $this->of($class, $name)->create($attributes);
+    }
+
+    /**
+     * Create an instance of the given model.
+     *
+     * @param  string  $class
+     * @param  array  $attributes
+     * @return mixed
+     */
+    public function make($class, array $attributes = [])
+    {
+        return $this->of($class)->make($attributes);
+    }
+
+    /**
+     * Create an instance of the given model and type.
+     *
+     * @param  string  $class
+     * @param  string  $name
+     * @param  array  $attributes
+     * @return mixed
+     */
+    public function makeAs($class, $name, array $attributes = [])
+    {
+        return $this->of($class, $name)->make($attributes);
+    }
+
+    /**
+     * Get the raw attribute array for a given named model.
+     *
+     * @param  string  $class
+     * @param  string  $name
+     * @param  array  $attributes
+     * @return array
+     */
+    public function rawOf($class, $name, array $attributes = [])
+    {
+        return $this->raw($class, $attributes, $name);
+    }
+
+    /**
+     * Get the raw attribute array for a given model.
+     *
+     * @param  string  $class
+     * @param  array  $attributes
+     * @param  string  $name
+     * @return array
+     */
+    public function raw($class, array $attributes = [], $name = 'default')
+    {
+        return array_merge(
+            call_user_func($this->definitions[$class][$name], $this->faker), $attributes
+        );
+    }
+
+    /**
+     * Create a builder for the given model.
+     *
+     * @param  string  $class
+     * @param  string  $name
+     * @return FactoryBuilder
+     */
+    public function of($class, $name = 'default')
+    {
+        return new FactoryBuilder(
+            $class, $name, $this->definitions, $this->states,
+            $this->afterMaking, $this->afterCreating, $this->faker
+        );
+    }
+
+    /**
+     * Load factories from path.
+     *
+     * @param  string  $path
+     * @return $this
+     */
+    public function load($path)
+    {
+        $factory = $this;
+
+        if (is_dir($path)) {
+            foreach (Finder::create()->files()->name('*.php')->in($path) as $file) {
+                require $file->getRealPath();
+            }
+        }
+
+        return $factory;
+    }
+
+    /**
+     * Determine if the given offset exists.
+     *
+     * @param  string  $offset
+     * @return bool
+     */
+    public function offsetExists($offset)
+    {
+        return isset($this->definitions[$offset]);
+    }
+
+    /**
+     * Get the value of the given offset.
+     *
+     * @param  string  $offset
+     * @return mixed
+     */
+    public function offsetGet($offset)
+    {
+        return $this->make($offset);
+    }
+
+    /**
+     * Set the given offset to the given value.
+     *
+     * @param  string  $offset
+     * @param  callable  $value
+     * @return void
+     */
+    public function offsetSet($offset, $value)
+    {
+        $this->define($offset, $value);
+    }
+
+    /**
+     * Unset the value at the given offset.
+     *
+     * @param  string  $offset
+     * @return void
+     */
+    public function offsetUnset($offset)
+    {
+        unset($this->definitions[$offset]);
+    }
+}

+ 446 - 446
tests/FactoryBuilder.php

@@ -1,446 +1,446 @@
-<?php
-
-namespace Tests;
-
-use Faker\Generator as Faker;
-use Illuminate\Database\Eloquent\Model;
-use Illuminate\Support\Traits\Macroable;
-use InvalidArgumentException;
-
-class FactoryBuilder
-{
-    use Macroable;
-
-    /**
-     * The model definitions in the container.
-     *
-     * @var array
-     */
-    protected $definitions;
-
-    /**
-     * The model being built.
-     *
-     * @var string
-     */
-    protected $class;
-
-    /**
-     * The name of the model being built.
-     *
-     * @var string
-     */
-    protected $name = 'default';
-
-    /**
-     * The database connection on which the model instance should be persisted.
-     *
-     * @var string
-     */
-    protected $connection;
-
-    /**
-     * The model states.
-     *
-     * @var array
-     */
-    protected $states;
-
-    /**
-     * The model after making callbacks.
-     *
-     * @var array
-     */
-    protected $afterMaking = [];
-
-    /**
-     * The model after creating callbacks.
-     *
-     * @var array
-     */
-    protected $afterCreating = [];
-
-    /**
-     * The states to apply.
-     *
-     * @var array
-     */
-    protected $activeStates = [];
-
-    /**
-     * The Faker instance for the builder.
-     *
-     * @var \Faker\Generator
-     */
-    protected $faker;
-
-    /**
-     * The number of models to build.
-     *
-     * @var int|null
-     */
-    protected $amount = null;
-
-    /**
-     * Create an new builder instance.
-     *
-     * @param  string  $class
-     * @param  string  $name
-     * @param  array  $definitions
-     * @param  array  $states
-     * @param  array  $afterMaking
-     * @param  array  $afterCreating
-     * @param  \Faker\Generator  $faker
-     * @return void
-     */
-    public function __construct($class, $name, array $definitions, array $states,
-        array $afterMaking, array $afterCreating, Faker $faker)
-    {
-        $this->name = $name;
-        $this->class = $class;
-        $this->faker = $faker;
-        $this->states = $states;
-        $this->definitions = $definitions;
-        $this->afterMaking = $afterMaking;
-        $this->afterCreating = $afterCreating;
-    }
-
-    /**
-     * Set the amount of models you wish to create / make.
-     *
-     * @param  int  $amount
-     * @return $this
-     */
-    public function times($amount)
-    {
-        $this->amount = $amount;
-
-        return $this;
-    }
-
-    /**
-     * Set the state to be applied to the model.
-     *
-     * @param  string  $state
-     * @return $this
-     */
-    public function state($state)
-    {
-        return $this->states([$state]);
-    }
-
-    /**
-     * Set the states to be applied to the model.
-     *
-     * @param  array|mixed  $states
-     * @return $this
-     */
-    public function states($states)
-    {
-        $this->activeStates = is_array($states) ? $states : func_get_args();
-
-        return $this;
-    }
-
-    /**
-     * Set the database connection on which the model instance should be persisted.
-     *
-     * @param  string  $name
-     * @return $this
-     */
-    public function connection($name)
-    {
-        $this->connection = $name;
-
-        return $this;
-    }
-
-    /**
-     * Create a model and persist it in the database if requested.
-     *
-     * @param  array  $attributes
-     * @return \Closure
-     */
-    public function lazy(array $attributes = [])
-    {
-        return function () use ($attributes) {
-            return $this->create($attributes);
-        };
-    }
-
-    /**
-     * Create a collection of models and persist them to the database.
-     *
-     * @param  array  $attributes
-     * @return mixed
-     */
-    public function create(array $attributes = [])
-    {
-        $results = $this->make($attributes);
-
-        if ($results instanceof Model) {
-            $this->store(collect([$results]));
-
-            $this->callAfterCreating(collect([$results]));
-        } else {
-            $this->store($results);
-
-            $this->callAfterCreating($results);
-        }
-
-        return $results;
-    }
-
-    /**
-     * Set the connection name on the results and store them.
-     *
-     * @param  \Illuminate\Support\Collection  $results
-     * @return void
-     */
-    protected function store($results)
-    {
-        $results->each(function ($model) {
-            if (! isset($this->connection)) {
-                $model->setConnection($model->newQueryWithoutScopes()->getConnection()->getName());
-            }
-
-            $model->save();
-        });
-    }
-
-    /**
-     * Create a collection of models.
-     *
-     * @param  array  $attributes
-     * @return mixed
-     */
-    public function make(array $attributes = [])
-    {
-        if ($this->amount === null) {
-            return tap($this->makeInstance($attributes), function ($instance) {
-                $this->callAfterMaking(collect([$instance]));
-            });
-        }
-
-        if ($this->amount < 1) {
-            return (new $this->class)->newCollection();
-        }
-
-        $instances = (new $this->class)->newCollection(array_map(function () use ($attributes) {
-            return $this->makeInstance($attributes);
-        }, range(1, $this->amount)));
-
-        $this->callAfterMaking($instances);
-
-        return $instances;
-    }
-
-    /**
-     * Create an array of raw attribute arrays.
-     *
-     * @param  array  $attributes
-     * @return mixed
-     */
-    public function raw(array $attributes = [])
-    {
-        if ($this->amount === null) {
-            return $this->getRawAttributes($attributes);
-        }
-
-        if ($this->amount < 1) {
-            return [];
-        }
-
-        return array_map(function () use ($attributes) {
-            return $this->getRawAttributes($attributes);
-        }, range(1, $this->amount));
-    }
-
-    /**
-     * Get a raw attributes array for the model.
-     *
-     * @param  array  $attributes
-     * @return mixed
-     *
-     * @throws \InvalidArgumentException
-     */
-    protected function getRawAttributes(array $attributes = [])
-    {
-        if (! isset($this->definitions[$this->class][$this->name])) {
-            throw new InvalidArgumentException("Unable to locate factory with name [{$this->name}] [{$this->class}].");
-        }
-
-        $definition = call_user_func(
-            $this->definitions[$this->class][$this->name],
-            $this->faker, $attributes
-        );
-
-        return $this->expandAttributes(
-            array_merge($this->applyStates($definition, $attributes), $attributes)
-        );
-    }
-
-    /**
-     * Make an instance of the model with the given attributes.
-     *
-     * @param  array  $attributes
-     * @return \Illuminate\Database\Eloquent\Model
-     */
-    protected function makeInstance(array $attributes = [])
-    {
-        return Model::unguarded(function () use ($attributes) {
-            $instance = new $this->class(
-                $this->getRawAttributes($attributes)
-            );
-
-            if (isset($this->connection)) {
-                $instance->setConnection($this->connection);
-            }
-
-            return $instance;
-        });
-    }
-
-    /**
-     * Apply the active states to the model definition array.
-     *
-     * @param  array  $definition
-     * @param  array  $attributes
-     * @return array
-     *
-     * @throws \InvalidArgumentException
-     */
-    protected function applyStates(array $definition, array $attributes = [])
-    {
-        foreach ($this->activeStates as $state) {
-            if (! isset($this->states[$this->class][$state])) {
-                if ($this->stateHasAfterCallback($state)) {
-                    continue;
-                }
-
-                throw new InvalidArgumentException("Unable to locate [{$state}] state for [{$this->class}].");
-            }
-
-            $definition = array_merge(
-                $definition,
-                $this->stateAttributes($state, $attributes)
-            );
-        }
-
-        return $definition;
-    }
-
-    /**
-     * Get the state attributes.
-     *
-     * @param  string  $state
-     * @param  array  $attributes
-     * @return array
-     */
-    protected function stateAttributes($state, array $attributes)
-    {
-        $stateAttributes = $this->states[$this->class][$state];
-
-        if (! is_callable($stateAttributes)) {
-            return $stateAttributes;
-        }
-
-        return $stateAttributes($this->faker, $attributes);
-    }
-
-    /**
-     * Expand all attributes to their underlying values.
-     *
-     * @param  array  $attributes
-     * @return array
-     */
-    protected function expandAttributes(array $attributes)
-    {
-        foreach ($attributes as &$attribute) {
-            if (is_callable($attribute) && ! is_string($attribute) && ! is_array($attribute)) {
-                $attribute = $attribute($attributes);
-            }
-
-            if ($attribute instanceof static) {
-                $attribute = $attribute->create()->getKey();
-            }
-
-            if ($attribute instanceof Model) {
-                $attribute = $attribute->getKey();
-            }
-        }
-
-        return $attributes;
-    }
-
-    /**
-     * Run after making callbacks on a collection of models.
-     *
-     * @param  \Illuminate\Support\Collection  $models
-     * @return void
-     */
-    public function callAfterMaking($models)
-    {
-        $this->callAfter($this->afterMaking, $models);
-    }
-
-    /**
-     * Run after creating callbacks on a collection of models.
-     *
-     * @param  \Illuminate\Support\Collection  $models
-     * @return void
-     */
-    public function callAfterCreating($models)
-    {
-        $this->callAfter($this->afterCreating, $models);
-    }
-
-    /**
-     * Call after callbacks for each model and state.
-     *
-     * @param  array  $afterCallbacks
-     * @param  \Illuminate\Support\Collection  $models
-     * @return void
-     */
-    protected function callAfter(array $afterCallbacks, $models)
-    {
-        $states = array_merge([$this->name], $this->activeStates);
-
-        $models->each(function ($model) use ($states, $afterCallbacks) {
-            foreach ($states as $state) {
-                $this->callAfterCallbacks($afterCallbacks, $model, $state);
-            }
-        });
-    }
-
-    /**
-     * Call after callbacks for each model and state.
-     *
-     * @param  array  $afterCallbacks
-     * @param  \Illuminate\Database\Eloquent\Model  $model
-     * @param  string  $state
-     * @return void
-     */
-    protected function callAfterCallbacks(array $afterCallbacks, $model, $state)
-    {
-        if (! isset($afterCallbacks[$this->class][$state])) {
-            return;
-        }
-
-        foreach ($afterCallbacks[$this->class][$state] as $callback) {
-            $callback($model, $this->faker);
-        }
-    }
-
-    /**
-     * Determine if the given state has an "after" callback.
-     *
-     * @param  string  $state
-     * @return bool
-     */
-    protected function stateHasAfterCallback($state)
-    {
-        return isset($this->afterMaking[$this->class][$state]) ||
-            isset($this->afterCreating[$this->class][$state]);
-    }
-}
+<?php
+
+namespace Tests;
+
+use Faker\Generator as Faker;
+use Illuminate\Database\Eloquent\Model;
+use Illuminate\Support\Traits\Macroable;
+use InvalidArgumentException;
+
+class FactoryBuilder
+{
+    use Macroable;
+
+    /**
+     * The model definitions in the container.
+     *
+     * @var array
+     */
+    protected $definitions;
+
+    /**
+     * The model being built.
+     *
+     * @var string
+     */
+    protected $class;
+
+    /**
+     * The name of the model being built.
+     *
+     * @var string
+     */
+    protected $name = 'default';
+
+    /**
+     * The database connection on which the model instance should be persisted.
+     *
+     * @var string
+     */
+    protected $connection;
+
+    /**
+     * The model states.
+     *
+     * @var array
+     */
+    protected $states;
+
+    /**
+     * The model after making callbacks.
+     *
+     * @var array
+     */
+    protected $afterMaking = [];
+
+    /**
+     * The model after creating callbacks.
+     *
+     * @var array
+     */
+    protected $afterCreating = [];
+
+    /**
+     * The states to apply.
+     *
+     * @var array
+     */
+    protected $activeStates = [];
+
+    /**
+     * The Faker instance for the builder.
+     *
+     * @var \Faker\Generator
+     */
+    protected $faker;
+
+    /**
+     * The number of models to build.
+     *
+     * @var int|null
+     */
+    protected $amount = null;
+
+    /**
+     * Create an new builder instance.
+     *
+     * @param  string  $class
+     * @param  string  $name
+     * @param  array  $definitions
+     * @param  array  $states
+     * @param  array  $afterMaking
+     * @param  array  $afterCreating
+     * @param  \Faker\Generator  $faker
+     * @return void
+     */
+    public function __construct($class, $name, array $definitions, array $states,
+        array $afterMaking, array $afterCreating, Faker $faker)
+    {
+        $this->name = $name;
+        $this->class = $class;
+        $this->faker = $faker;
+        $this->states = $states;
+        $this->definitions = $definitions;
+        $this->afterMaking = $afterMaking;
+        $this->afterCreating = $afterCreating;
+    }
+
+    /**
+     * Set the amount of models you wish to create / make.
+     *
+     * @param  int  $amount
+     * @return $this
+     */
+    public function times($amount)
+    {
+        $this->amount = $amount;
+
+        return $this;
+    }
+
+    /**
+     * Set the state to be applied to the model.
+     *
+     * @param  string  $state
+     * @return $this
+     */
+    public function state($state)
+    {
+        return $this->states([$state]);
+    }
+
+    /**
+     * Set the states to be applied to the model.
+     *
+     * @param  array|mixed  $states
+     * @return $this
+     */
+    public function states($states)
+    {
+        $this->activeStates = is_array($states) ? $states : func_get_args();
+
+        return $this;
+    }
+
+    /**
+     * Set the database connection on which the model instance should be persisted.
+     *
+     * @param  string  $name
+     * @return $this
+     */
+    public function connection($name)
+    {
+        $this->connection = $name;
+
+        return $this;
+    }
+
+    /**
+     * Create a model and persist it in the database if requested.
+     *
+     * @param  array  $attributes
+     * @return \Closure
+     */
+    public function lazy(array $attributes = [])
+    {
+        return function () use ($attributes) {
+            return $this->create($attributes);
+        };
+    }
+
+    /**
+     * Create a collection of models and persist them to the database.
+     *
+     * @param  array  $attributes
+     * @return mixed
+     */
+    public function create(array $attributes = [])
+    {
+        $results = $this->make($attributes);
+
+        if ($results instanceof Model) {
+            $this->store(collect([$results]));
+
+            $this->callAfterCreating(collect([$results]));
+        } else {
+            $this->store($results);
+
+            $this->callAfterCreating($results);
+        }
+
+        return $results;
+    }
+
+    /**
+     * Set the connection name on the results and store them.
+     *
+     * @param  \Illuminate\Support\Collection  $results
+     * @return void
+     */
+    protected function store($results)
+    {
+        $results->each(function ($model) {
+            if (! isset($this->connection)) {
+                $model->setConnection($model->newQueryWithoutScopes()->getConnection()->getName());
+            }
+
+            $model->save();
+        });
+    }
+
+    /**
+     * Create a collection of models.
+     *
+     * @param  array  $attributes
+     * @return mixed
+     */
+    public function make(array $attributes = [])
+    {
+        if ($this->amount === null) {
+            return tap($this->makeInstance($attributes), function ($instance) {
+                $this->callAfterMaking(collect([$instance]));
+            });
+        }
+
+        if ($this->amount < 1) {
+            return (new $this->class)->newCollection();
+        }
+
+        $instances = (new $this->class)->newCollection(array_map(function () use ($attributes) {
+            return $this->makeInstance($attributes);
+        }, range(1, $this->amount)));
+
+        $this->callAfterMaking($instances);
+
+        return $instances;
+    }
+
+    /**
+     * Create an array of raw attribute arrays.
+     *
+     * @param  array  $attributes
+     * @return mixed
+     */
+    public function raw(array $attributes = [])
+    {
+        if ($this->amount === null) {
+            return $this->getRawAttributes($attributes);
+        }
+
+        if ($this->amount < 1) {
+            return [];
+        }
+
+        return array_map(function () use ($attributes) {
+            return $this->getRawAttributes($attributes);
+        }, range(1, $this->amount));
+    }
+
+    /**
+     * Get a raw attributes array for the model.
+     *
+     * @param  array  $attributes
+     * @return mixed
+     *
+     * @throws \InvalidArgumentException
+     */
+    protected function getRawAttributes(array $attributes = [])
+    {
+        if (! isset($this->definitions[$this->class][$this->name])) {
+            throw new InvalidArgumentException("Unable to locate factory with name [{$this->name}] [{$this->class}].");
+        }
+
+        $definition = call_user_func(
+            $this->definitions[$this->class][$this->name],
+            $this->faker, $attributes
+        );
+
+        return $this->expandAttributes(
+            array_merge($this->applyStates($definition, $attributes), $attributes)
+        );
+    }
+
+    /**
+     * Make an instance of the model with the given attributes.
+     *
+     * @param  array  $attributes
+     * @return \Illuminate\Database\Eloquent\Model
+     */
+    protected function makeInstance(array $attributes = [])
+    {
+        return Model::unguarded(function () use ($attributes) {
+            $instance = new $this->class(
+                $this->getRawAttributes($attributes)
+            );
+
+            if (isset($this->connection)) {
+                $instance->setConnection($this->connection);
+            }
+
+            return $instance;
+        });
+    }
+
+    /**
+     * Apply the active states to the model definition array.
+     *
+     * @param  array  $definition
+     * @param  array  $attributes
+     * @return array
+     *
+     * @throws \InvalidArgumentException
+     */
+    protected function applyStates(array $definition, array $attributes = [])
+    {
+        foreach ($this->activeStates as $state) {
+            if (! isset($this->states[$this->class][$state])) {
+                if ($this->stateHasAfterCallback($state)) {
+                    continue;
+                }
+
+                throw new InvalidArgumentException("Unable to locate [{$state}] state for [{$this->class}].");
+            }
+
+            $definition = array_merge(
+                $definition,
+                $this->stateAttributes($state, $attributes)
+            );
+        }
+
+        return $definition;
+    }
+
+    /**
+     * Get the state attributes.
+     *
+     * @param  string  $state
+     * @param  array  $attributes
+     * @return array
+     */
+    protected function stateAttributes($state, array $attributes)
+    {
+        $stateAttributes = $this->states[$this->class][$state];
+
+        if (! is_callable($stateAttributes)) {
+            return $stateAttributes;
+        }
+
+        return $stateAttributes($this->faker, $attributes);
+    }
+
+    /**
+     * Expand all attributes to their underlying values.
+     *
+     * @param  array  $attributes
+     * @return array
+     */
+    protected function expandAttributes(array $attributes)
+    {
+        foreach ($attributes as &$attribute) {
+            if (is_callable($attribute) && ! is_string($attribute) && ! is_array($attribute)) {
+                $attribute = $attribute($attributes);
+            }
+
+            if ($attribute instanceof static) {
+                $attribute = $attribute->create()->getKey();
+            }
+
+            if ($attribute instanceof Model) {
+                $attribute = $attribute->getKey();
+            }
+        }
+
+        return $attributes;
+    }
+
+    /**
+     * Run after making callbacks on a collection of models.
+     *
+     * @param  \Illuminate\Support\Collection  $models
+     * @return void
+     */
+    public function callAfterMaking($models)
+    {
+        $this->callAfter($this->afterMaking, $models);
+    }
+
+    /**
+     * Run after creating callbacks on a collection of models.
+     *
+     * @param  \Illuminate\Support\Collection  $models
+     * @return void
+     */
+    public function callAfterCreating($models)
+    {
+        $this->callAfter($this->afterCreating, $models);
+    }
+
+    /**
+     * Call after callbacks for each model and state.
+     *
+     * @param  array  $afterCallbacks
+     * @param  \Illuminate\Support\Collection  $models
+     * @return void
+     */
+    protected function callAfter(array $afterCallbacks, $models)
+    {
+        $states = array_merge([$this->name], $this->activeStates);
+
+        $models->each(function ($model) use ($states, $afterCallbacks) {
+            foreach ($states as $state) {
+                $this->callAfterCallbacks($afterCallbacks, $model, $state);
+            }
+        });
+    }
+
+    /**
+     * Call after callbacks for each model and state.
+     *
+     * @param  array  $afterCallbacks
+     * @param  \Illuminate\Database\Eloquent\Model  $model
+     * @param  string  $state
+     * @return void
+     */
+    protected function callAfterCallbacks(array $afterCallbacks, $model, $state)
+    {
+        if (! isset($afterCallbacks[$this->class][$state])) {
+            return;
+        }
+
+        foreach ($afterCallbacks[$this->class][$state] as $callback) {
+            $callback($model, $this->faker);
+        }
+    }
+
+    /**
+     * Determine if the given state has an "after" callback.
+     *
+     * @param  string  $state
+     * @return bool
+     */
+    protected function stateHasAfterCallback($state)
+    {
+        return isset($this->afterMaking[$this->class][$state]) ||
+            isset($this->afterCreating[$this->class][$state]);
+    }
+}