dev-resources.site
for different kinds of informations.
Migrate Laravel factories to class factories
Published at
7/12/2021
Categories
laravel
testing
Author
Luigui Moreno
I had been postponing the migration of my tests factories to the not so new now class based factories, well the day came, I needed some of the new factories features, but migrating hundreds of files was a tedious task, so I decided to spent an absurd amount of time flexing my not so good regex skills to make an script to automate that one off task. And now I'm sharing it with you:
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
use File;
class MigrateToNewFactories extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'factories:new';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Command description';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
private $modelsForFactory;
/**
* Execute the console command.
*
* @return int
*/
public function handle()
{
// move the database/factories to the database/factories-old directorie
// if you have a ModelFactory.php file with multiple factories, move it to the database directory
$this->modelsForFactory = collect();
$this->recreateFactories();
$this->factoryFunction('tests');
$this->factoryFunction('database/factories');
return 0;
}
public function factoryFunction($dir)
{
$files = \File::allFiles(base_path($dir));
foreach ($files as $file) {
$path = $file->getPathname();
if (Str::of($path)->endsWith('php')) {
$this->fixFactoryFunction($file->getPathname());
}
}
}
public function fixFactoryFunction($path)
{
$file = \File::get($path);
$regex = "/factory\(('(.+)'|\"(.+)\"|(.+)::class)(, *?(\d+))?\)/";
$fixed = preg_replace_callback($regex, function ($matches) {
$matches = collect($matches);
if (count($matches) <= 5) {
$modelClassNameMatch = $matches->last();
$modelClassName = $this->indetifyRelativeOrAbsoluteModelClassName($modelClassNameMatch);
return $modelClassName . '::factory()';
}
$matches = $matches->filter(fn($item) => !empty($item))->values();
$this->info($matches);
$modelClassNameMatch = $matches->get($matches->count() - 3);
$modelClassName = $this->indetifyRelativeOrAbsoluteModelClassName($modelClassNameMatch);
return $modelClassName . '::factory()->count(' . collect($matches)->last() . ')';
}, $file);
\File::put($path, $fixed);
$this->info("Done $path");
}
private function recreateFactories()
{
$files = \File::allFiles(base_path('database/factories-old'));
foreach ($files as $file) {
$modelName = Str::of($file->getFilename())->replace('Factory.php', '');
$this->createModelFactory($modelName);
}
$this->getModelFactoriesFromModelFactoryFile();
}
private function getModels($path)
{
$file = \File::get($path);
$regex = "/create\((.*\W)?(\w+)::class/";
preg_replace_callback($regex, function ($matches) {
$this->modelsForFactory->push(collect($matches)->last());
}, $file);
$regex2 = '/.*\W(\w+)::factory\(/';
preg_replace_callback($regex2, function ($matches) {
$this->modelsForFactory->push(collect($matches)->last());
}, $file);
}
private function createModelFactory(mixed $model)
{
$factoryClassName = "\\Database\\Factories\\{$model}Factory";
if (!class_exists($factoryClassName)) {
$this->getFactoryBodyFromFile($model);
$this->addHasFactoryToModel($model);
}
}
private function getFactoryBodyFromFile(mixed $model)
{
$path = database_path('factories-old/' . $model . 'Factory.php');
$fileContents = File::get($path);
$regex = "/\)\s*\{(.*)\}\);/s";
$matches = null;
preg_match_all($regex, $fileContents, $matches);
$this->info('collect($matches)');
$this->info($model);
$returnCode = $matches[1][0];
$returnCode = preg_replace('/\$faker/', '$this->faker', $returnCode);
$modelClassName = $this->inferModelClassName($model);
$factoryClassName = "{$model}Factory";
$factoryClassCode = sprintf($this->template(), $modelClassName, $factoryClassName, $model . '::class', $returnCode);
$this->info($path);
File::put(database_path('factories/' . $model . 'Factory.php'), $factoryClassCode);
}
private function getModelFactoriesFromModelFactoryFile()
{
$path = database_path('/ModelFactory.php');
$fileContents = File::get($path);
$factories = Str::of($fileContents)->split('/\$factory->/');
foreach ($factories as $key => $factory) {
// skip the start of the code
if ($key == 0) continue;
$modelMatches = null;
$modelRegex = "/define\((.+),/";
preg_match($modelRegex, $factory, $modelMatches);
$classNameCall = Str::of($modelMatches[1])->split('/\\\/')->last();
$model = Str::of($classNameCall)->split('/::/')->first();
$returnCodeMatches = null;
$returnCoderegex = "/\)\s*\{(.*)\}\);/s";
preg_match($returnCoderegex, $factory, $returnCodeMatches);
$returnCode = $returnCodeMatches[1];
$returnCode = preg_replace('/\$faker/', '$this->faker', $returnCode);
$factoryClassName = "{$model}Factory";
$factoryClassCode = sprintf($this->template(), $this->inferModelClassName($model), $factoryClassName, $model . '::class', $returnCode);
$factoryPath = database_path('factories/' . $model . 'Factory.php');
if (File::exists($factoryPath)) {
throw new \Exception("The factory already exists " . $model);
}
File::put($factoryPath, $factoryClassCode);
$this->addHasFactoryToModel($model);
}
}
private function template()
{
$text = <<<STR
<?php
namespace Database\Factories;
use %s;
use Illuminate\Database\Eloquent\Factories\Factory;
class %s extends Factory
{
/**
* The name of the factory's corresponding model.
*
* @var string
*/
protected \$model = %s;
/**
* Define the model's default state.
*
* @return array
*/
public function definition()
{
%s
}
}
STR;
return $text;
}
private function inferModelClassName($model)
{
$modelClassName = "App\\" . $model;
if (!class_exists($modelClassName)) {
$modelClassName = "App\\Models\\" . $model;
}
return $modelClassName;
}
private function addHasFactoryToModel(mixed $model)
{
$modelClassName = "App\\" . $model;
$modelFilePath = app_path($model . '.php');
if (!class_exists($modelClassName)) {
$modelFilePath = app_path('Models/' . $model . '.php');
}
$contents = File::get($modelFilePath);
if (!Str::of($contents)->contains('HasFactory')) {
$contents = preg_replace('/use Illuminate\\\Database\\\Eloquent\\\Model;/', "use Illuminate\Database\Eloquent\Model; \nuse Illuminate\Database\Eloquent\Factories\HasFactory;", $contents);
$contents = preg_replace_callback("/\{(.*)\}/s", fn($matched) => "{\n use HasFactory;" . $matched[1] . "\n}", $contents);
// $this->info($contents);
File::put($modelFilePath, $contents);
}
}
private function indetifyRelativeOrAbsoluteModelClassName($modelClassNameMatch)
{
$modelClassName = Str::of($modelClassNameMatch);
$splittedClassName = $modelClassName->split('/\\\/')->filter(fn($part) => $part !== "");
if ($splittedClassName->count() > 1) {
$modelClassName = $modelClassName->start('\\');
}
return $modelClassName;
}
}
Articles
9 articles in total
Laravel upgrade 10 to 11 Modifying Columns change
read article
Macbook air m1 vs macbook pro 15 2018 vs mac mini 2018
read article
Scroll to the end of x bookmarks
read article
Phpstorm Intellij search mode when focusing
read article
Laravel Websockets in valet cURL error 60: SSL certificate problem: unable to get local issuer certificate
read article
Migrate Laravel factories to class factories
currently reading
Clean your git branches local and remote
read article
Laravel model factories with relation sharing foreign keys
read article
Post de bienvenida
read article
Featured ones: