I'm making a command in Laravel 7 that creates some migrations in a folder inside database/migrations and a seeder. Then, it runs dumpAutoloads() to make sure that the created migrations and seeder are registered in the autoload classmap. Then, my command calls the php artisan migrate command and consecutively the php artisan db:seed command with the --class flag to only seed the created seeder.
Everything runs fine until the db:seed command is called. The seeder is indeed created, but it keeps throwing me the next exception:
Illuminate\Contracts\Container\BindingResolutionException
Target class [StudentOperationTypesSeeder] does not exist
This is obviously just an example, but I have checked that the name of the created Seeder is exactly the same as the one the exception shows and it matches. Also, just after this exception is thrown to me, I run by myself the db:seed --class=StudentOperationTypesSeeder command and it works!
This makes me think that maybe the autoload classmap isn't updated until the process of the command is finished or something... I really have no idea.
My code is the following:
TimeMachineGeneratorCommand.php
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Str;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Facades\DB;
class TimeMachineGeneratorCommand extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'time-machine:generate {table_name}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Generates the tables and structure of a time machine for the given table.';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
// Convierte a snake case singular el nombre de la entidad.
$entity = Str::singular(
Str::snake(class_basename($this->argument('table_name')))
);
// Revisa que el nombre del modelo esté en el formato adecuado.
$modelName = Str::singular(
Str::studly($this->argument('table_name'))
);
$seederClassName = "{$modelName}OperationTypesSeeder";
// Crea las migraciones para la estructura de la máquina del tiempo.
$this->createMigrations($entity);
// Genera el Seeder del los operation types básicos.
$this->createSeeder($seederClassName);
// Para asegurarse que las migrations y el seeder está registrada en
// los class loaders se ejecuta el dump-autoload.
$this->line("<fg=yellow>Dumping Autoloads...</>");
$this->laravel->composer->dumpAutoloads();
$this->info("Autoloads dumped.");
// Ejecuta las migraciones.
$this->call('migrate', [
'--path' => '/database/migrations/'.Str::plural(Str::snake($modelName)).'/'
]);
// Ejecuta el seeder recién creado.
$this->call('db:seed', [
'--class' => $seederClassName
]);
(...) // <-- Some other code that isn't executed because of the exception
}
/**
* Genera los archivos de código de las migraciones necesarias
* para la estructura de la máquina del tiempo.
*/
private function createMigrations($entity)
{
// Genera la migración para los tipos de operación de la entidad.
$this->call('time-machine:operation-type-migration', [
'entity' => $entity
]);
// Genera la migración para los logs de la entidad.
$this->call('time-machine:log-migration', [
'entity' => $entity
]);
// Genera la migración para los logs de la entidad.
$this->call('time-machine:required-fields-migration', [
'entity' => $entity
]);
}
/**
* Crea el seeder de operationt types basado en el
* template diseñado para la máquina del tiempo.
*
* @param string | $seederClassName | El nombre de la clase para el seeder.
* @return void.
*/
private function createSeeder($seederClassName)
{
$this->call('time-machine:seeder', [
'name' => $seederClassName
]);
// Agrega el seeder recién creado al DatabaseSeeder.php.
$this->updateDatabaseSeeder($seederClassName);
}
/**
* Agrega el seeder recién creado al DatabaseSeeder.php.
*
* @var $seederClassName.
* @return void.
*/
private function updateDatabaseSeeder($seederClassName)
{
$filePath = base_path().'\\database\\seeds\\DatabaseSeeder.php';
// Lee el archivo del DataBaseSeeder.
$seeder = file_get_contents($filePath);
// Si el seeder no ha sido agregado ya al DatabaseSeeder.php...
if(preg_match('/\$this\-\>call\('.$seederClassName.'\:\:class\)\;/', $seeder) == 0) {
// Agrega el seeder recién creado.
$newContent = preg_replace(
'/public function run\(\)\s*\{/',
"public function run()
{
\$this->call({$seederClassName}::class);",
$seeder, 1
);
// Guarda el contenido del archivo.
file_put_contents($filePath, $newContent);
$this->info('Seeder added to DatabaseSeeder.php.');
} else {
$this->error('Seeder is already in DataBaseSeeder.php.');
}
}
}
Also, this is the autoload section of my composer.json (I read there may be something with that or something, I couldn't find a solution for my issue anyway).
"autoload": {
"psr-4": {
"App\\": "app/"
},
"classmap": [
"database/seeds",
"database/factories"
]
},
StudentOperationTypesSeeder.php
<?php
use Illuminate\Database\Seeder;
class StudentOperationTypesSeeder extends Seeder
{
/**
* Run the database seeds.
*
* @return void
*/
public function run()
{
$studentOperationType = new \App\StudentOperationType();
$studentOperationType->description = "Created";
$studentOperationType->save();
$studentOperationType = new \App\StudentOperationType();
$studentOperationType->description = "Updated";
$studentOperationType->save();
$studentOperationType = new \App\StudentOperationType();
$studentOperationType->description = "Deleted";
$studentOperationType->save();
}
}
Please help. The command generates succesfully the migrations and the seeder, then runs the migrations and everything works exactly as intended, except for the seeder and I haven't find why.
Note: The other "time-machine" commands that I call in the functions of the TimeMachineGeneratorCommand.php are other custom commands that I created that literally only extend the existing migration commands of the vendor and change the stub to a custom one.
I followed the approach that lagbox suggested and found something that works.
I included the generated file adding the next line of code before calling the bd:seed command, obviously after the line that generates the seeder.
include base_path()."\\database\\seeds\\".$seederClassName.".php";
After doing this the seeder is executed correctly and the whole command works just as expected.
The final handle method looks as follows.
/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
// Convierte a snake case singular el nombre de la entidad.
$entity = Str::singular(
Str::snake(class_basename($this->argument('table_name')))
);
// Revisa que el nombre del modelo esté en el formato adecuado.
$modelName = Str::singular(
Str::studly($this->argument('table_name'))
);
$seederClassName = "{$modelName}OperationTypesSeeder";
// Crea las migraciones para la estructura de la máquina del tiempo.
$this->createMigrations($entity);
// Genera el Seeder del los operation types básicos.
$this->createSeeder($seederClassName);
// Para asegurarse que las migrations y el seeder está registrada en
// los class loaders se ejecuta el dump-autoload.
$this->line("<fg=yellow>Dumping Autoloads...</>");
$this->laravel->composer->dumpAutoloads();
$this->info("Autoloads dumped.");
// Ejecuta las migraciones.
$this->call('migrate', [
'--path' => '/database/migrations/'.Str::plural(Str::snake($modelName)).'/'
]);
include base_path()."\\database\\seeds\\".$seederClassName.".php";
// Ejecuta el seeder recién creado.
$this->call('db:seed', [
'--class' => $seederClassName
]);
(...) // <-- Some other code that isn't executed because of the exception
}