| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446 | <?phpnamespace 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]);    }}
 |