Skip to content

Commit

Permalink
Introduce a Factory maker and integrate with Laravel Factories
Browse files Browse the repository at this point in the history
  • Loading branch information
wouterj committed Jul 29, 2023
1 parent 0b19220 commit d910dea
Show file tree
Hide file tree
Showing 13 changed files with 442 additions and 0 deletions.
6 changes: 6 additions & 0 deletions config/maker.xml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@
<tag name="maker.command"/>
</service>

<service id="wouterj_eloquent.maker.factory" class="WouterJ\EloquentBundle\Maker\MakeFactory">
<argument type="service" id="maker.file_manager"/>

<tag name="maker.command"/>
</service>

<service id="wouterj_eloquent.maker.migration" class="WouterJ\EloquentBundle\Maker\MakeMigration">
<argument type="service" id="wouterj_eloquent.migrations.creator"/>
<argument>%wouterj_eloquent.migration_path%</argument>
Expand Down
28 changes: 28 additions & 0 deletions src/Factory/Factory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php

namespace WouterJ\EloquentBundle\Factory;

use Illuminate\Database\Eloquent\Factories\Factory as IlluminateFactory;
use Illuminate\Database\Eloquent\Model;

/**
* @template TModel of Model
* @extends IlluminateFactory<TModel>
*/
abstract class Factory extends IlluminateFactory
{
public function modelName(): string
{
/** @psalm-suppress RedundantPropertyInitializationCheck */
$resolver = static::$modelNameResolver ?? function (self $factory) {
$name = $factory::class;
if (str_ends_with($name, 'Factory')) {
$name = substr($name, 0, -7);
}

return str_replace('\\Factory\\', '\\Model\\', $name);
};

return $this->model ?? $resolver($this);
}
}
115 changes: 115 additions & 0 deletions src/Maker/MakeFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
<?php

namespace WouterJ\EloquentBundle\Maker;

use Illuminate\Database\Console\Factories\FactoryMakeCommand;
use Illuminate\Database\Eloquent\Factories\Factory as IlluminateFactory;
use Symfony\Bundle\MakerBundle\ConsoleStyle;
use Symfony\Bundle\MakerBundle\DependencyBuilder;
use Symfony\Bundle\MakerBundle\FileManager;
use Symfony\Bundle\MakerBundle\Generator;
use Symfony\Bundle\MakerBundle\InputConfiguration;
use Symfony\Bundle\MakerBundle\Maker\AbstractMaker;
use Symfony\Bundle\MakerBundle\Str;
use Symfony\Bundle\MakerBundle\Util\ClassNameDetails;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use WouterJ\EloquentBundle\Factory\Factory;

/**
* @final
* @internal
* @author Wouter de Jong <wouter@wouterj.nl>
*/
class MakeFactory extends AbstractMaker
{
private $fileManager;

public function __construct(FileManager $fileManager)
{
$this->fileManager = $fileManager;
}

public static function getCommandName(): string
{
return 'make:factory';
}

public static function getCommandDescription(): string
{
return 'Create a new Eloquent model factory';
}

public function configureCommand(Command $command, InputConfiguration $inputConfig): void
{
$command
->setDescription(self::getCommandDescription())
->addArgument('name', InputArgument::REQUIRED, 'The name of the factory')
->addOption('model', 'm', InputOption::VALUE_REQUIRED, 'The name of the model')
;
}

public function configureDependencies(DependencyBuilder $dependencies): void
{
}

public function generate(InputInterface $input, ConsoleStyle $io, Generator $generator): void
{
$factoryClassDetails = $generator->createClassNameDetails($input->getArgument('name'), 'Factory', 'Factory');
if (class_exists($factoryClassDetails->getFullName())) {
$io->error(sprintf('Factory "%s" already exists!', $factoryClassDetails->getFullName()));

return;
}

$factoryFqcn = $factoryClassDetails->getFullName();
$factory = $factoryClassDetails->getRelativeNameWithoutSuffix();

$modelFqcn = $generator->createClassNameDetails($input->getOption('model') ?? $this->guessModelName($generator, $factoryClassDetails), 'Model')->getFullName();
$model = Str::getShortClassName($modelFqcn);

$stubPath = dirname((new \ReflectionClass(FactoryMakeCommand::class))->getFileName()).'/stubs';
$stub = file_get_contents($stubPath.'/factory.stub');

$replace = [
'NamespacedDummyModel' => $modelFqcn,
'{{ namespacedModel }}' => $modelFqcn,
'{{namespacedModel}}' => $modelFqcn,
'DummyModel' => $model,
'{{ model }}' => $model,
'{{model}}' => $model,
'{{ factory }}' => $factory,
'{{factory}}' => $factory,
IlluminateFactory::class => Factory::class
];

if ($namespace = Str::getNamespace($factoryFqcn)) {
$replace['{{ factoryNamespace }}'] = $namespace;
}

$stub = str_replace(array_keys($replace), array_values($replace), $stub);

$path = $this->fileManager->getRelativePathForFutureClass($factoryFqcn);
if (null === $path) {
throw new \LogicException(sprintf('Could not determine where to locate the new class "%s", maybe try with a full namespace like "\\My\\Full\\Namespace\\%s"', $factoryFqcn, $factoryClassDetails->getShortName()));
}

$this->fileManager->dumpFile($path, $stub);
}

private function guessModelName(Generator $generator, ClassNameDetails $factoryClassDetails): string
{
$name = $factoryClassDetails->getRelativeNameWithoutSuffix();

$modelClassDetails = $generator->createClassNameDetails($name, 'Model');

if (class_exists($modelClassDetails->getFullName())) {
return '\\'.$modelClassDetails->getFullName();
}


return '\\'.$generator->getRootNamespace().'\\Model';
}
}
20 changes: 20 additions & 0 deletions tests/Fixtures/factories/PersonFactory-8.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

namespace App\Factory;

use WouterJ\EloquentBundle\Factory\Factory;

class PersonFactory extends Factory
{
/**
* Define the model's default state.
*
* @return array
*/
public function definition()
{
return [
//
];
}
}
23 changes: 23 additions & 0 deletions tests/Fixtures/factories/PersonFactory-9.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php

namespace App\Factory;

use WouterJ\EloquentBundle\Factory\Factory;

/**
* @extends \WouterJ\EloquentBundle\Factory\Factory<\App\Model\Person>
*/
class PersonFactory extends Factory
{
/**
* Define the model's default state.
*
* @return array<string, mixed>
*/
public function definition()
{
return [
//
];
}
}
23 changes: 23 additions & 0 deletions tests/Fixtures/factories/PersonFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php

namespace App\Factory;

use WouterJ\EloquentBundle\Factory\Factory;

/**
* @extends \WouterJ\EloquentBundle\Factory\Factory<\App\Model\Person>
*/
class PersonFactory extends Factory
{
/**
* Define the model's default state.
*
* @return array<string, mixed>
*/
public function definition(): array
{
return [
//
];
}
}
20 changes: 20 additions & 0 deletions tests/Fixtures/factories/PostFactory-8.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

namespace App\Factory;

use WouterJ\EloquentBundle\Factory\Factory;

class PostFactory extends Factory
{
/**
* Define the model's default state.
*
* @return array
*/
public function definition()
{
return [
//
];
}
}
23 changes: 23 additions & 0 deletions tests/Fixtures/factories/PostFactory-9.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php

namespace App\Factory;

use WouterJ\EloquentBundle\Factory\Factory;

/**
* @extends \WouterJ\EloquentBundle\Factory\Factory<\App\Model>
*/
class PostFactory extends Factory
{
/**
* Define the model's default state.
*
* @return array<string, mixed>
*/
public function definition()
{
return [
//
];
}
}
23 changes: 23 additions & 0 deletions tests/Fixtures/factories/PostFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php

namespace App\Factory;

use WouterJ\EloquentBundle\Factory\Factory;

/**
* @extends \WouterJ\EloquentBundle\Factory\Factory<\App\Model>
*/
class PostFactory extends Factory
{
/**
* Define the model's default state.
*
* @return array<string, mixed>
*/
public function definition(): array
{
return [
//
];
}
}
20 changes: 20 additions & 0 deletions tests/Fixtures/factories/TalkFactory-8.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

namespace App\Factory;

use WouterJ\EloquentBundle\Factory\Factory;

class TalkFactory extends Factory
{
/**
* Define the model's default state.
*
* @return array
*/
public function definition()
{
return [
//
];
}
}
23 changes: 23 additions & 0 deletions tests/Fixtures/factories/TalkFactory-9.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php

namespace App\Factory;

use WouterJ\EloquentBundle\Factory\Factory;

/**
* @extends \WouterJ\EloquentBundle\Factory\Factory<\App\Model\Talk>
*/
class TalkFactory extends Factory
{
/**
* Define the model's default state.
*
* @return array<string, mixed>
*/
public function definition()
{
return [
//
];
}
}
23 changes: 23 additions & 0 deletions tests/Fixtures/factories/TalkFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php

namespace App\Factory;

use WouterJ\EloquentBundle\Factory\Factory;

/**
* @extends \WouterJ\EloquentBundle\Factory\Factory<\App\Model\Talk>
*/
class TalkFactory extends Factory
{
/**
* Define the model's default state.
*
* @return array<string, mixed>
*/
public function definition(): array
{
return [
//
];
}
}
Loading

0 comments on commit d910dea

Please sign in to comment.