Created
January 22, 2024 19:40
-
-
Save DarkGhostHunter/61a2ee12cb172d43cf3b1aab64a67a38 to your computer and use it in GitHub Desktop.
Larawiz Seeder
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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