Skip to content

Instantly share code, notes, and snippets.

@DarkGhostHunter
Created January 22, 2024 19:40
Show Gist options
  • Save DarkGhostHunter/61a2ee12cb172d43cf3b1aab64a67a38 to your computer and use it in GitHub Desktop.
Save DarkGhostHunter/61a2ee12cb172d43cf3b1aab64a67a38 to your computer and use it in GitHub Desktop.
Larawiz Seeder
<?php
namespace Database\Seeders;
use Closure;
use Illuminate\Console\View\Components\TwoColumnDetail;
use Illuminate\Contracts\Container\Container;
use Illuminate\Contracts\Database\Query\Builder;
use Illuminate\Database\Eloquent\Collection as EloquentCollection;
use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Seeder as LaravelSeeder;
use Illuminate\Support\Arr;
use Illuminate\Support\Collection;
use Illuminate\Support\Str;
use ReflectionMethod;
use ReflectionObject;
use RuntimeException;
use function array_flip;
use function class_uses_recursive;
use function floor;
use function get_class;
use function is_array;
use function is_iterable;
use function iterator_to_array;
use function method_exists;
use function microtime;
use function number_format;
use function value;
use function with;
abstract class Seeder extends LaravelSeeder
{
/**
* The model that creates the factory.
*
* @var class-string<\Illuminate\Database\Eloquent\Model>
*/
protected string $model;
/**
* If the seeder should use transactions.
*
* @var bool
*/
protected bool $transactions;
/**
* Seed the application's database.
*
* @param \Illuminate\Contracts\Container\Container $container
* @param array<string, array> $parameters
* @return void
*/
public function run(Container $container, array $parameters = []): void
{
$resolveFactory = function (): Factory {
return isset(array_flip(class_uses_recursive($this->model))[HasFactory::class])
? ($this->model)::factory()
: Factory::factoryForModel($this->model);
};
if (method_exists($this, 'before')) {
$factory = $resolveFactory();
$container->call($this->before(...), [get_class($factory) => $factory]);
}
Collection::make((new ReflectionObject($this))->getMethods())
->filter(static function (ReflectionMethod $method): bool {
return Str::startsWith($method->getName(), 'seed')
&& $method->isPublic()
&& !$method->isAbstract()
&& !$method->isStatic();
})
->map(function (ReflectionMethod $method) use ($container, $resolveFactory, $parameters): Closure {
return function () use ($method, $container, $resolveFactory, $parameters): void {
$factory = $resolveFactory();
// If an array of parameters for this method exist, use them.
if (isset($parameters[$method->getName()]) && is_array($parameters[$method->getName()])) {
$parameters = $parameters[$method->getName()];
}
$result = $container->call([$this, $method->getName()], [
get_class($factory) => $factory,
...$parameters
]);
if ($result instanceof Factory) {
$result->create();
} elseif ($result instanceof EloquentCollection) {
$result->each->push();
} elseif (is_iterable($result)) {
($this->model)::query()->insert(iterator_to_array($result));
}
};
})
->when(function (): bool {
if (isset($this->transactions)) {
return $this->transactions;
}
$connection = (new $this->model)->getConnection();
// Only run transactions when SQLite uses a file database, for better performance.
return $connection->getDriverName() === 'sqlite'
&& $connection->getConfig('database') !== ':memory:';
})->map(function (Closure $callback): Closure {
return function () use ($callback): void {
(new $this->model)->getConnection()->transaction($callback);
};
})
->each(static function (Closure $callback): void {
try {
$callback();
} catch (RuntimeException $exception) {
if ($exception->getMessage() === 'Seeder has been skipped.') {
return;
}
throw $exception;
}
});
if (method_exists($this, 'after')) {
$factory = $resolveFactory();
$container->call($this->after(...), [get_class($factory) => $factory]);
}
}
/**
* Return a number of pages for the model.
*
* @param int|float $pages
* @return int
*/
protected function pages(int|float $pages): int
{
return floor((new $this->model)->getPerPage() * $pages);
}
/**
* Skips the current seed method, or the entire seeder if invoked on "before".
*
* @return never
*/
protected function skip(): never
{
throw new RuntimeException('Seeder has been skipped.');
}
/**
* Skips the current seed method if the condition is truthy, or the entire seeder if invoked on "before".
*/
protected function skipWhen(mixed $condition): void
{
if ($condition instanceof Builder) {
$condition = $condition->exists(...);
}
if (value($condition, $this)) {
$this->skip();
}
}
/**
* Skips the current seed method if the condition is falsy, or the entire seeder if invoked on "before".
*/
protected function skipUnless(mixed $condition): void
{
$this->skipWhen(static function (Seeder $seeder) use ($condition): bool {
return ! value($condition, $seeder);
});
}
/**
* Run the given seeder class.
*
* @param array|string $class
* @param bool $silent
* @param array $parameters
* @return $this
*/
public function call($class, $silent = false, array $parameters = [])
{
$classes = Arr::wrap($class);
foreach ($classes as $class) {
$seeder = $this->resolve($class);
$name = get_class($seeder);
if ($silent === false && isset($this->command)) {
with(new TwoColumnDetail($this->command->getOutput()))->render(
$name,
'<fg=yellow;options=bold>RUNNING</>'
);
}
$startTime = microtime(true);
// Hijack the invocation to skip the seeder altogether
try {
$seeder->__invoke($parameters);
} catch (RuntimeException $exception) {
if ($exception->getMessage() === 'Seeder has been skipped.') {
if ($silent === false && isset($this->command)) {
with(new TwoColumnDetail($this->command->getOutput()))->render(
$name,
'<fg=blue;options=bold>SKIPPED</>'
);
$this->command->getOutput()->writeln('');
}
static::$called[] = $class;
continue;
}
throw $exception;
}
if ($silent === false && isset($this->command)) {
$runTime = number_format((microtime(true) - $startTime) * 1000, 2);
with(new TwoColumnDetail($this->command->getOutput()))->render(
$name,
"<fg=gray>$runTime ms</> <fg=green;options=bold>DONE</>"
);
$this->command->getOutput()->writeln('');
}
static::$called[] = $class;
}
return $this;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment