lthn.io/app/Core/Database/Seeders/CoreDatabaseSeeder.php
Claude 41a90cbff8
feat: lthn.io API serving live chain data
Fixed: basePath self→static binding, namespace detection, event wiring,
SQLite cache, file cache driver. All Mod Boot classes converted to
$listens pattern for lifecycle event discovery.

Working endpoints:
- /v1/explorer/info — live chain height, difficulty, aliases
- /v1/explorer/stats — formatted chain statistics
- /v1/names/directory — alias directory grouped by type
- /v1/names/available/{name} — name availability check
- /v1/names/lookup/{name} — name details

Co-Authored-By: Charon <charon@lethean.io>
2026-04-03 17:17:42 +01:00

376 lines
8.9 KiB
PHP

<?php
/*
* Core PHP Framework
*
* Licensed under the European Union Public Licence (EUPL) v1.2.
* See LICENSE file for details.
*/
declare(strict_types=1);
namespace Core\Database\Seeders;
use Illuminate\Console\Command;
use Illuminate\Database\Seeder;
/**
* Base database seeder with auto-discovery support.
*
* This class automatically discovers and runs all module seeders in the
* correct order based on their priority and dependency declarations.
*
* ## Usage
*
* Apps can extend this class directly:
*
* ```php
* class DatabaseSeeder extends CoreDatabaseSeeder
* {
* // Optionally override paths or excluded seeders
* protected function getSeederPaths(): array
* {
* return [
* app_path('Core'),
* app_path('Mod'),
* base_path('packages/my-package/src'),
* ];
* }
* }
* ```
*
* Or use the class directly:
*
* ```bash
* php artisan db:seed --class=Core\\Database\\Seeders\\CoreDatabaseSeeder
* ```
*
* ## Filtering
*
* Seeders can be filtered using Artisan command options:
*
* - `--exclude=SeederName` - Skip specific seeders
* - `--only=SeederName` - Run only specific seeders
*
* Multiple filters can be specified by repeating the option.
*
*
* @see SeederDiscovery For the discovery mechanism
* @see SeederRegistry For manual seeder registration
*/
class CoreDatabaseSeeder extends Seeder
{
/**
* The seeder discovery instance.
*/
protected ?SeederDiscovery $discovery = null;
/**
* The seeder registry for manual registrations.
*/
protected ?SeederRegistry $registry = null;
/**
* Whether to use auto-discovery.
*/
protected bool $autoDiscover = true;
/**
* Run the database seeds.
*/
public function run(): void
{
$seeders = $this->getSeedersToRun();
if (empty($seeders)) {
$this->info('No seeders found to run.');
return;
}
$this->info(sprintf('Running %d seeders...', count($seeders)));
$this->newLine();
foreach ($seeders as $seeder) {
$shortName = $this->getShortName($seeder);
$this->info("Running: {$shortName}");
$this->call($seeder);
}
$this->newLine();
$this->info('Database seeding completed successfully.');
}
/**
* Get the list of seeders to run.
*
* @return array<string> Ordered list of seeder class names
*/
protected function getSeedersToRun(): array
{
$seeders = $this->discoverSeeders();
// Apply filters
$seeders = $this->applyExcludeFilter($seeders);
$seeders = $this->applyOnlyFilter($seeders);
return $seeders;
}
/**
* Discover all seeders.
*
* @return array<string> Ordered list of seeder class names
*/
protected function discoverSeeders(): array
{
// Check if auto-discovery is enabled
if (! $this->shouldAutoDiscover()) {
return $this->getManualSeeders();
}
$discovery = $this->getDiscovery();
return $discovery->discover();
}
/**
* Get manually registered seeders.
*
* @return array<string>
*/
protected function getManualSeeders(): array
{
$registry = $this->getRegistry();
return $registry->getOrdered();
}
/**
* Get the seeder discovery instance.
*/
protected function getDiscovery(): SeederDiscovery
{
if ($this->discovery === null) {
$this->discovery = new SeederDiscovery(
$this->getSeederPaths(),
$this->getExcludedSeeders()
);
}
return $this->discovery;
}
/**
* Get the seeder registry instance.
*/
protected function getRegistry(): SeederRegistry
{
if ($this->registry === null) {
$this->registry = new SeederRegistry;
$this->registerSeeders($this->registry);
}
return $this->registry;
}
/**
* Register seeders manually when auto-discovery is disabled.
*
* Override this method in subclasses to add seeders.
*
* @param SeederRegistry $registry The registry to add seeders to
*/
protected function registerSeeders(SeederRegistry $registry): void
{
// Override in subclasses
}
/**
* Get paths to scan for seeders.
*
* Override this method to customize seeder paths.
*
* @return array<string>
*/
protected function getSeederPaths(): array
{
// Use config if available, otherwise use defaults
$config = config('core.seeders.paths');
if (is_array($config) && ! empty($config)) {
return $config;
}
return [
app_path('Core'),
app_path('Mod'),
app_path('Website'),
];
}
/**
* Get seeders to exclude.
*
* Override this method to customize excluded seeders.
*
* @return array<string>
*/
protected function getExcludedSeeders(): array
{
return config('core.seeders.exclude', []);
}
/**
* Check if auto-discovery should be used.
*/
protected function shouldAutoDiscover(): bool
{
if (! $this->autoDiscover) {
return false;
}
return config('core.seeders.auto_discover', true);
}
/**
* Apply the --exclude filter.
*
* @param array<string> $seeders List of seeder classes
* @return array<string> Filtered list
*/
protected function applyExcludeFilter(array $seeders): array
{
$excludes = $this->getCommandOption('exclude');
if (empty($excludes)) {
return $seeders;
}
$excludePatterns = is_array($excludes) ? $excludes : [$excludes];
return array_filter($seeders, function ($seeder) use ($excludePatterns) {
foreach ($excludePatterns as $pattern) {
if ($this->matchesPattern($seeder, $pattern)) {
return false;
}
}
return true;
});
}
/**
* Apply the --only filter.
*
* @param array<string> $seeders List of seeder classes
* @return array<string> Filtered list
*/
protected function applyOnlyFilter(array $seeders): array
{
$only = $this->getCommandOption('only');
if (empty($only)) {
return $seeders;
}
$onlyPatterns = is_array($only) ? $only : [$only];
return array_values(array_filter($seeders, function ($seeder) use ($onlyPatterns) {
foreach ($onlyPatterns as $pattern) {
if ($this->matchesPattern($seeder, $pattern)) {
return true;
}
}
return false;
}));
}
/**
* Check if a seeder matches a pattern.
*
* Patterns can be:
* - Full class name: Core\Tenant\Database\Seeders\FeatureSeeder
* - Short name: FeatureSeeder
* - Partial match: Feature (matches FeatureSeeder)
*
* @param string $seeder Full class name
* @param string $pattern Pattern to match
*/
protected function matchesPattern(string $seeder, string $pattern): bool
{
// Exact match
if ($seeder === $pattern) {
return true;
}
// Short name match
$shortName = $this->getShortName($seeder);
if ($shortName === $pattern) {
return true;
}
// Partial match (contains)
if (str_contains($shortName, $pattern) || str_contains($seeder, $pattern)) {
return true;
}
return false;
}
/**
* Get a command option value.
*
* @param string $name Option name
*/
protected function getCommandOption(string $name): mixed
{
if (! $this->command instanceof Command) {
return null;
}
// Check if the option exists before getting it
if (! $this->command->hasOption($name)) {
return null;
}
return $this->command->option($name);
}
/**
* Get the short (class only) name of a seeder.
*
* @param string $class Fully qualified class name
* @return string Class name without namespace
*/
protected function getShortName(string $class): string
{
$parts = explode('\\', $class);
return end($parts);
}
/**
* Output an info message.
*
* @param string $message Message to output
*/
protected function info(string $message): void
{
if ($this->command instanceof Command) {
$this->command->info($message);
}
}
/**
* Output a newline.
*/
protected function newLine(): void
{
if ($this->command instanceof Command) {
$this->command->newLine();
}
}
}