From 0ce6329f91057d9183610bc143c481f06dcb0a9c Mon Sep 17 00:00:00 2001 From: Christoph Rumpel Date: Thu, 10 Sep 2020 15:33:14 +0200 Subject: [PATCH 01/17] wip --- composer.json | 16 +- config/factories-reloaded.php | 2 +- .../app/Models/DifferentLocation/Comment.php | 3 + example/app/Models/Group.php | 3 + example/app/Models/Ingredient.php | 3 + .../app/Models/ModelsWithArrayState/Book.php | 3 + example/app/Models/Recipe.php | 3 + example/database/factories/BookFactory.php | 49 +++-- example/database/factories/GroupFactory.php | 36 +++- example/database/factories/RecipeFactory.php | 95 +++++---- src/FactoryFile.php | 31 --- src/LaravelFactoryExtractor.php | 194 ++++++++++++------ tests/FactoryCollectionTest.php | 2 + tests/TestCase.php | 2 +- 14 files changed, 282 insertions(+), 160 deletions(-) diff --git a/composer.json b/composer.json index 08a7b77..a1aa692 100644 --- a/composer.json +++ b/composer.json @@ -17,16 +17,17 @@ ], "require": { "php": "^7.4", - "christophrumpel/laravel-command-file-picker": "^1.0", - "illuminate/support": "^6.0|^7.0", - "laravel/framework": "^6.0|^7.0", - "nikic/php-parser": "^4.3" + "christophrumpel/laravel-command-file-picker": "^1.1", + "illuminate/support": "^6.0|^7.0|^8.0", + "laravel/framework": "^6.0|^7.0|^8.0", + "nikic/php-parser": "^4.3", + "roave/better-reflection": "^4.9" }, "require-dev": { "friendsofphp/php-cs-fixer": "^2.16", "mockery/mockery": "^1.2", - "orchestra/testbench": "^4.0|^5.0", - "phpunit/phpunit": "^8.4" + "orchestra/testbench": "^4.0|^5.0|^6.0", + "phpunit/phpunit": "^8.4|^9.3" }, "autoload": { "psr-4": { @@ -40,7 +41,8 @@ "psr-4": { "Christophrumpel\\LaravelFactoriesReloaded\\Tests\\": "tests", "ExampleApp\\": "example/app/", - "ExampleAppTests\\": "example/tests/" + "ExampleAppTests\\": "example/tests/", + "Database\\Factories\\": "database/factories/" } }, "scripts": { diff --git a/config/factories-reloaded.php b/config/factories-reloaded.php index 80cf0bf..bfa28e8 100644 --- a/config/factories-reloaded.php +++ b/config/factories-reloaded.php @@ -6,7 +6,7 @@ * Will be used to find your models while generating new factories. */ 'models_paths' => [ - base_path('app'), + base_path('app/Models'), ], /** diff --git a/example/app/Models/DifferentLocation/Comment.php b/example/app/Models/DifferentLocation/Comment.php index 19be686..b5091a6 100644 --- a/example/app/Models/DifferentLocation/Comment.php +++ b/example/app/Models/DifferentLocation/Comment.php @@ -2,10 +2,13 @@ namespace ExampleApp\Models\DifferentLocation; +use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; class Comment extends Model { + use HasFactory; + protected $fillable = [ 'content', ]; diff --git a/example/app/Models/Group.php b/example/app/Models/Group.php index 31a3c81..3ab59f3 100644 --- a/example/app/Models/Group.php +++ b/example/app/Models/Group.php @@ -2,11 +2,14 @@ namespace ExampleApp\Models; +use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\HasMany; class Group extends Model { + use HasFactory; + protected $fillable = [ 'name', 'size', diff --git a/example/app/Models/Ingredient.php b/example/app/Models/Ingredient.php index 9251ccd..c5c309e 100644 --- a/example/app/Models/Ingredient.php +++ b/example/app/Models/Ingredient.php @@ -2,9 +2,12 @@ namespace ExampleApp\Models; +use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; class Ingredient extends Model { + use HasFactory; + protected $fillable = ['name', 'description']; } diff --git a/example/app/Models/ModelsWithArrayState/Book.php b/example/app/Models/ModelsWithArrayState/Book.php index 9e83879..56848fa 100644 --- a/example/app/Models/ModelsWithArrayState/Book.php +++ b/example/app/Models/ModelsWithArrayState/Book.php @@ -2,9 +2,12 @@ namespace ExampleApp\Models\ModelsWithArrayState; +use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; class Book extends Model { + use HasFactory; + protected $fillable = ['name']; } diff --git a/example/app/Models/Recipe.php b/example/app/Models/Recipe.php index 44425e9..cda68cf 100644 --- a/example/app/Models/Recipe.php +++ b/example/app/Models/Recipe.php @@ -2,12 +2,15 @@ namespace ExampleApp\Models; +use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\BelongsToMany; class Recipe extends Model { + use HasFactory; + protected $fillable = ['name', 'description', 'group_id']; public function group(): BelongsTo diff --git a/example/database/factories/BookFactory.php b/example/database/factories/BookFactory.php index 6a28d1f..5a7c0d9 100644 --- a/example/database/factories/BookFactory.php +++ b/example/database/factories/BookFactory.php @@ -1,19 +1,40 @@ define(\ExampleApp\Models\ModelsWithArrayState\Book::class, function (Faker $faker) { - return [ - 'name' => $faker->word, - ]; -}); +class BookFactory extends Factory +{ -$factory->state( - \ExampleApp\Models\ModelsWithArrayState\Book::class, - 'customName', - [ - 'name' => 'custom-name', - ] -); + /** + * The name of the factory's corresponding model. + * + * @var string + */ + protected $model = Book::class; + + /** + * Define the model's default state. + * + * @return array + */ + public function definition() + { + return ['name' => $this->faker->word]; + } + + /** + * Indicate that the user is suspended. + * + * @return Factory + */ + public function customName(): Factory + { + return $this->state([ + 'name' => 'custom-name', + ]); + } + +} diff --git a/example/database/factories/GroupFactory.php b/example/database/factories/GroupFactory.php index 4fa1a96..b4ce739 100644 --- a/example/database/factories/GroupFactory.php +++ b/example/database/factories/GroupFactory.php @@ -1,13 +1,31 @@ define(Group::class, function (Faker $faker) { - return [ - 'name' => $faker->word, - 'size' => $faker->numberBetween(1, 10), - ]; -}); +use Illuminate\Database\Eloquent\Factories\Factory; + +class GroupFactory extends Factory +{ + + /** + * The name of the factory's corresponding model. + * + * @var string + */ + protected $model = Group::class; + + /** + * Define the model's default state. + * + * @return array + */ + public function definition() + { + return [ + 'name' => $this->faker->word, + 'size' => $this->faker->numberBetween(1, 10), + ]; + } +} + diff --git a/example/database/factories/RecipeFactory.php b/example/database/factories/RecipeFactory.php index c925287..149aa7a 100644 --- a/example/database/factories/RecipeFactory.php +++ b/example/database/factories/RecipeFactory.php @@ -1,40 +1,65 @@ define(Recipe::class, function (Faker $faker) { - return [ - 'name' => $faker->word, - 'description' => $faker->sentence, - ]; -}); - -$factory->state(Recipe::class, 'withGroup', function () { - return [ - 'group_id' => factory(Group::class), - ]; -}); - -$factory->state(Recipe::class, 'withDifferentGroup', function () { - $group = factory(Group::class)->create(); - - return [ - 'group_id' => $group->id, - ]; -}); - -$factory->state(Recipe::class, 'withOneLineGroup', function () { - return ['group_id' => factory(Group::class)]; -}); - -$factory->state(Recipe::class, 'withReturnGroupName', function () { - return ['group_name' => 'return all']; -}); - -$factory->state(Recipe::class, 'withSquareBracketGroupName', function () { - return ['group_name' => 'something];']; -}); +use Illuminate\Database\Eloquent\Factories\Factory; + +class RecipeFactory extends Factory +{ + + /** + * The name of the factory's corresponding model. + * + * @var string + */ + protected $model = Recipe::class; + + /** + * Define the model's default state. + * + * @return array + */ + public function definition() + { + return [ + 'name' => $this->faker->word, + 'description' => $this->faker->sentence, + ]; + } + + public function withGroup(): Factory + { + return $this->state([ + 'group_id' => Group::factory(), + ]); + } + + public function withDifferentGroup(): Factory + { + $group = GroupFactory::new() + ->create(); + + return $this->state([ + 'group_id' => $group->id, + ]); + } + + public function withOneLineGroup(): Factory + { + return $this->state(['group_id' => Group::factory()]); + } + + public function withReturnGroupName(): Factory + { + return $this->state(['group_name' => 'return all']); + } + + public function withSquareBracketGroupName(): Factory + { + return $this->state(['group_name' => 'something];']); + } + +} + diff --git a/src/FactoryFile.php b/src/FactoryFile.php index d9ac5f2..ee5983e 100644 --- a/src/FactoryFile.php +++ b/src/FactoryFile.php @@ -69,37 +69,6 @@ public function getTargetClassPath(): string public function render(): string { - // Fallback for Laravel 6.x - if (! method_exists(Str::class, 'of')) { - $stub = str_replace([ - 'DummyNamespace', - 'DummyFullModelClass', - 'DummyModelClass', - 'DummyFactory', - '{{ uses }}', - '{{ dummyData }}', - '{{ states }}', - ], [ - config('factories-reloaded.factories_namespace'), - $this->modelClass, - class_basename($this->modelClass), - $this->getTargetClassName(), - $this->uses, - $this->defaults, - $this->withStates ? $this->states : '', - ], $this->getStub()); - - if (preg_match('/(?P(?:use [^;]+;$\n?)+)/m', $stub, $match)) { - $imports = explode("\n", trim($match['imports'])); - - sort($imports); - - return str_replace(trim($match['imports']), implode("\n", $imports), $stub); - } - - return $stub; - } - return Str::of($this->getStub()) ->replace('DummyNamespace', config('factories-reloaded.factories_namespace')) ->replace('DummyFullModelClass', $this->modelClass) diff --git a/src/LaravelFactoryExtractor.php b/src/LaravelFactoryExtractor.php index 9a37f20..8879860 100644 --- a/src/LaravelFactoryExtractor.php +++ b/src/LaravelFactoryExtractor.php @@ -3,9 +3,11 @@ namespace Christophrumpel\LaravelFactoriesReloaded; use Faker\Generator; -use Illuminate\Database\Eloquent\Factory; +use Illuminate\Database\Eloquent\Factories\Factory; use Illuminate\Support\Str; use ReflectionFunction; +use Roave\BetterReflection\BetterReflection; +use Roave\BetterReflection\Reflection\ReflectionMethod; use SplFileObject; class LaravelFactoryExtractor @@ -14,12 +16,19 @@ class LaravelFactoryExtractor protected string $className; - protected ObjectPrybar $factory; + protected $factory; public function __construct(string $className) { $this->className = $className; - $this->factory = new ObjectPrybar(Factory::construct(app(Generator::class), config('factories-reloaded.vanilla_factories_path'))); + //$classNameForFactory = 'ExampleApp\Group'; + $factoryName = Factory::resolveFactoryName(class_basename($className)); + + if (class_exists($factoryName)) { + $this->factory = Factory::factoryForModel(class_basename($className)); + } + + //$this->factory = new ObjectPrybar(Factory::construct(app(Generator::class), config('factories-reloaded.vanilla_factories_path'))); } public static function from(string $className): self @@ -34,35 +43,46 @@ public function exists(): bool protected function vanillaFactoryPath(): string { - return config('factories-reloaded.vanilla_factories_path') . '/' . class_basename($this->className) . 'Factory.php'; + return config('factories-reloaded.vanilla_factories_path').'/'.class_basename($this->className).'Factory.php'; } public function getUses(): string { - return collect($this->uses)->map(function ($use) { - if (in_array($use['class'], ['Faker\\Generator', $this->className], true)) { - return; - } + return collect($this->uses) + ->map(function ($use) { + if (in_array($use['class'], [ + 'Faker\\Generator', + $this->className, + ], true)) { + return; + } - if ($use['class'] === $use['as']) { - return 'use ' . $use['class'] . ';'; - } + if ($use['class'] === $use['as']) { + return 'use '.$use['class'].';'; + } - return 'use ' . $use['class'] . ' as ' . $use['as'] . ';'; - })->filter()->implode("\n"); + return 'use '.$use['class'].' as '.$use['as'].';'; + }) + ->filter() + ->implode("\n"); } public function getDefinitions(): string { - $definition = collect( - $this->factory->getProperty('definitions') - )->get($this->className); - - return ltrim( - collect( - $this->getClosureContent($definition instanceof \Closure ? $definition : $definition['default']) - )->implode(' ') - ); + $classInfo = (new \Roave\BetterReflection\BetterReflection())->classReflector() + ->reflect(get_class($this->factory)); + + return $classInfo->getMethod('definition') + ->getBodyCode(); + //$definition = collect( + // $this->factory->getProperty('definitions') + //)->get($this->className); + // + //return ltrim( + // collect( + // $this->getClosureContent($definition instanceof \Closure ? $definition : $definition['default']) + // )->implode(' ') + //); } protected function getClosureContent(callable $closure): array @@ -85,6 +105,7 @@ protected function getClosureContent(callable $closure): array /** * @see https://gist.github.com/Zeronights/7b7d90fcf8d4daf9db0c + * * @param $reflection */ protected function parseUseStatements($reflection) @@ -182,7 +203,6 @@ protected function parseUseStatements($reflection) } } - // Make sure the as key has the name of the class even // if there is no alias in the use statement. foreach ($useStatements as &$useStatement) { @@ -196,60 +216,110 @@ protected function parseUseStatements($reflection) public function getStates(): string { + $factoryReflection = (new BetterReflection())->classReflector() + ->reflect(get_class($this->factory)); + + $factoryFileName = $factoryReflection->getFileName(); + + $factoryMethods = $factoryReflection->getMethods(); + + return collect($factoryMethods) + ->filter(function (ReflectionMethod $method) use ($factoryFileName) { + return Str::of($method->getBodyCode()) + ->contains('$this->state(') && $method->getFileName() === $factoryFileName; + }) + ->map(function (ReflectionMethod $method) { + $bodyLines = Str::of($method->getBodyCode()) + ->explode(";\n"); + + $body = collect($bodyLines) + ->filter(fn ($line) => ! empty($line)) + ->map(function ($line) { + if (Str::of($line) + ->contains('return $this->state(')) { + return (string) Str::of($line) + ->replace('return $this->state(', ' return tap(clone $this)->overwriteDefaults('); + } + + return ' '.$line.";\n\n"; + })->implode(''); + + return "\n ".$this->getMethodVisibility($method)." function ".$method->getName()."(): self\n {\n $body \n }"; + })->implode("\n"); + + return $states; + $states = collect($this->factory->getProperty('states')); if (! $states->has($this->className)) { return ''; } - return collect($states->get($this->className))->map(function ($closure, $state) { - throw_if( - ! is_callable($closure), - new \RuntimeException('One of your factory states is defined as an array. It must be of the type closure to import it.') - ); - - $lines = collect($this->getClosureContent($closure))->filter()->map(fn ($item) => str_replace("\n", '', $item)); - $firstLine = $lines->shift(); - $lastLine = $lines->pop(); - - if (Str::startsWith(ltrim($firstLine), 'return')) { - if ($lastLine === null) { - $firstLine = Str::replaceLast('];', ']);', $firstLine); - } else { - $lines->push(Str::replaceLast('];', ']);', $lastLine)); + return collect($states->get($this->className)) + ->map(function ($closure, $state) { + $lines = collect($this->getClosureContent($closure)) + ->filter() + ->map(fn ($item) => str_replace("\n", '', $item)); + $firstLine = $lines->shift(); + $lastLine = $lines->pop(); + + if (Str::startsWith(ltrim($firstLine), 'return')) { + if ($lastLine === null) { + $firstLine = Str::replaceLast('];', ']);', $firstLine); + } else { + $lines->push(Str::replaceLast('];', ']);', $lastLine)); + } + $lines->push('}'); + + return $lines->prepend([ + '', + 'public function '.$this->getStateMethodName($state).'(): '.class_basename($this->className).'Factory', + '{', + Str::replaceFirst('return ', 'return tap(clone $this)->overwriteDefaults(', $firstLine), + ]) + ->toArray(); } - $lines->push('}'); - return $lines->prepend([ + return collect([ '', - 'public function ' . $this->getStateMethodName($state) . '(): ' . class_basename($this->className) . 'Factory', + 'public function '.$this->getStateMethodName($state).'(): '.class_basename($this->className).'Factory', '{', - Str::replaceFirst('return ', 'return tap(clone $this)->overwriteDefaults(', $firstLine), - ])->toArray(); - } - - return collect([ - '', - 'public function ' . $this->getStateMethodName($state) . '(): ' . class_basename($this->className) . 'Factory', - '{', - ' return tap(clone $this)->overwriteDefaults(function() {', - ' ' . $firstLine, - ])->merge($lines->map(fn ($line) => ' ' . $line))->merge([ - ' ' . $lastLine, - ' });', - '}', - ]); - })->flatten()->map(function ($line) { - if (ltrim($line) === '') { - return ''; - } + ' return tap(clone $this)->overwriteDefaults(function() {', + ' '.$firstLine, + ]) + ->merge($lines->map(fn ($line) => ' '.$line)) + ->merge([ + ' '.$lastLine, + ' });', + '}', + ]); + }) + ->flatten() + ->map(function ($line) { + if (ltrim($line) === '') { + return ''; + } - return ' ' . $line; - })->implode("\n"); + return ' '.$line; + }) + ->implode("\n"); } protected function getStateMethodName(string $state): string { return lcfirst(Str::studly($state)); } + + private function getMethodVisibility(ReflectionMethod $method): string + { + if ($method->isPrivate()) { + return 'private'; + } + + if ($method->isProtected()) { + return 'protected'; + } + + return 'public'; + } } diff --git a/tests/FactoryCollectionTest.php b/tests/FactoryCollectionTest.php index 29576de..12e41dd 100644 --- a/tests/FactoryCollectionTest.php +++ b/tests/FactoryCollectionTest.php @@ -4,6 +4,7 @@ use Christophrumpel\LaravelFactoriesReloaded\FactoryCollection; use Christophrumpel\LaravelFactoriesReloaded\FactoryFile; +use Christophrumpel\LaravelFactoriesReloaded\LaravelFactoryExtractor; use ExampleApp\Models\DifferentLocation\Comment; use ExampleApp\Models\Group; use ExampleApp\Models\Ingredient; @@ -85,6 +86,7 @@ public function it_writes_factory_class_to_file_with_states(): void $this->assertFileExists($this->exampleFactoriesPath('RecipeFactory.php')); $generatedRecipeFactoryContent = file_get_contents($this->exampleFactoriesPath('RecipeFactory.php')); + //dd($generatedRecipeFactoryContent); $this->assertTrue(Str::containsAll($generatedRecipeFactoryContent, [ 'public function withGroup', diff --git a/tests/TestCase.php b/tests/TestCase.php index 6a307d1..d2d1015 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -33,7 +33,7 @@ public function setUp(): void } $this->loadMigrationsFrom($this->basePath . '/database/migrations'); - $this->withFactories($this->basePath . '/database/factories'); + //$this->withFactories($this->basePath . '/database/factories'); } public function tearDown(): void From 44b0ccf2c93c1b36b3ee9ece0ee1258a1d77a653 Mon Sep 17 00:00:00 2001 From: christophrumpel Date: Thu, 10 Sep 2020 13:34:03 +0000 Subject: [PATCH 02/17] Apply php-cs-fixer changes --- example/database/factories/BookFactory.php | 2 -- example/database/factories/GroupFactory.php | 2 -- example/database/factories/RecipeFactory.php | 3 --- src/BaseFactory.php | 8 ++++---- tests/FactoryCollectionTest.php | 1 - tests/FactoryTest.php | 6 +++--- 6 files changed, 7 insertions(+), 15 deletions(-) diff --git a/example/database/factories/BookFactory.php b/example/database/factories/BookFactory.php index 5a7c0d9..572513b 100644 --- a/example/database/factories/BookFactory.php +++ b/example/database/factories/BookFactory.php @@ -7,7 +7,6 @@ class BookFactory extends Factory { - /** * The name of the factory's corresponding model. * @@ -36,5 +35,4 @@ public function customName(): Factory 'name' => 'custom-name', ]); } - } diff --git a/example/database/factories/GroupFactory.php b/example/database/factories/GroupFactory.php index b4ce739..ba7e278 100644 --- a/example/database/factories/GroupFactory.php +++ b/example/database/factories/GroupFactory.php @@ -7,7 +7,6 @@ class GroupFactory extends Factory { - /** * The name of the factory's corresponding model. * @@ -28,4 +27,3 @@ public function definition() ]; } } - diff --git a/example/database/factories/RecipeFactory.php b/example/database/factories/RecipeFactory.php index 149aa7a..19101f5 100644 --- a/example/database/factories/RecipeFactory.php +++ b/example/database/factories/RecipeFactory.php @@ -8,7 +8,6 @@ class RecipeFactory extends Factory { - /** * The name of the factory's corresponding model. * @@ -60,6 +59,4 @@ public function withSquareBracketGroupName(): Factory { return $this->state(['group_name' => 'something];']); } - } - diff --git a/src/BaseFactory.php b/src/BaseFactory.php index bec339a..bd1a10e 100644 --- a/src/BaseFactory.php +++ b/src/BaseFactory.php @@ -40,7 +40,7 @@ protected function build(array $extra = [], string $creationType = 'create') $modelData = $this->transformModelFields( array_merge($this->getDefaults($this->faker), $this->overwriteDefaults, $extra) ); - $model = $this->unguardedIfNeeded(fn() => $this->modelClass::$creationType($modelData)); + $model = $this->unguardedIfNeeded(fn () => $this->modelClass::$creationType($modelData)); if ($this->relatedModelFactories->isEmpty()) { return $model; @@ -51,7 +51,7 @@ protected function build(array $extra = [], string $creationType = 'create') protected function unguardedIfNeeded(\Closure $closure) { - if (!config('factories-reloaded.unguard_models')) { + if (! config('factories-reloaded.unguard_models')) { return $closure(); } @@ -79,7 +79,7 @@ public function withFactory(FactoryInterface $relatedFactory, string $relationsh $clone->relatedModelFactories = clone $clone->relatedModelFactories; $clone->relatedModelFactories[$relationshipName] ??= collect(); $clone->relatedModelFactories[$relationshipName] = $clone->relatedModelFactories[$relationshipName]->merge( - collect()->times($times, fn() => $relatedFactory) + collect()->times($times, fn () => $relatedFactory) ); return $clone; @@ -126,7 +126,7 @@ private function buildRelationsForModel(Model $model, string $creationType): Mod if (method_exists($relation, 'associate')) { $relatedModels = $factories->map->$creationType(); - $relatedModels->each(fn($related) => $relation->associate($related)); + $relatedModels->each(fn ($related) => $relation->associate($related)); if ($creationType === 'create') { $model->save(); diff --git a/tests/FactoryCollectionTest.php b/tests/FactoryCollectionTest.php index 12e41dd..5c1415b 100644 --- a/tests/FactoryCollectionTest.php +++ b/tests/FactoryCollectionTest.php @@ -4,7 +4,6 @@ use Christophrumpel\LaravelFactoriesReloaded\FactoryCollection; use Christophrumpel\LaravelFactoriesReloaded\FactoryFile; -use Christophrumpel\LaravelFactoriesReloaded\LaravelFactoryExtractor; use ExampleApp\Models\DifferentLocation\Comment; use ExampleApp\Models\Group; use ExampleApp\Models\Ingredient; diff --git a/tests/FactoryTest.php b/tests/FactoryTest.php index 7e7201a..b6ffd4d 100644 --- a/tests/FactoryTest.php +++ b/tests/FactoryTest.php @@ -208,8 +208,8 @@ public function it_lets_you_use_a_closure_for_defining_default_data(): void public function it_lets_you_use_a_closure_for_overriding_default_data(): void { $ingredient = IngredientFactoryUsingClosure::new()->create([ - 'name' => fn(array $ingredient) => 'Basil', - 'description' => fn(array $ingredient) => "Super delicious {$ingredient['name']}", + 'name' => fn (array $ingredient) => 'Basil', + 'description' => fn (array $ingredient) => "Super delicious {$ingredient['name']}", ]); $this->assertIsString($ingredient->name); @@ -355,7 +355,7 @@ public function it_lets_you_define_extras_for_multiple_related_models_at_once_us ->create(); $this->assertEquals(3, $group->recipes()->count()); - $group->recipes->each(fn($recipe) => $this->assertEquals('my-name', $recipe->name)); + $group->recipes->each(fn ($recipe) => $this->assertEquals('my-name', $recipe->name)); } /** @test */ From 289d57214ed7e09ed40e644c58e6d8305ee2390f Mon Sep 17 00:00:00 2001 From: Christoph Rumpel Date: Fri, 11 Sep 2020 17:51:31 +0200 Subject: [PATCH 03/17] wip --- tests/FactoryCollectionTest.php | 1 - tests/FactoryCommandTest.php | 1 - 2 files changed, 2 deletions(-) diff --git a/tests/FactoryCollectionTest.php b/tests/FactoryCollectionTest.php index 12e41dd..8cdb5a6 100644 --- a/tests/FactoryCollectionTest.php +++ b/tests/FactoryCollectionTest.php @@ -86,7 +86,6 @@ public function it_writes_factory_class_to_file_with_states(): void $this->assertFileExists($this->exampleFactoriesPath('RecipeFactory.php')); $generatedRecipeFactoryContent = file_get_contents($this->exampleFactoriesPath('RecipeFactory.php')); - //dd($generatedRecipeFactoryContent); $this->assertTrue(Str::containsAll($generatedRecipeFactoryContent, [ 'public function withGroup', diff --git a/tests/FactoryCommandTest.php b/tests/FactoryCommandTest.php index bf4307f..723f494 100644 --- a/tests/FactoryCommandTest.php +++ b/tests/FactoryCommandTest.php @@ -75,7 +75,6 @@ public function it_creates_factories_with_immutable_states(): void $createdFactoryClassName = $this->exampleFactoriesNamespace().'\RecipeFactory'; $recipeFactory = $createdFactoryClassName::new(); - $recipeOne = $recipeFactory->withGroup()->make(); $recipeTwo = $recipeFactory->make(); From cc7d7dca8a9d4f80c865b97357febdb027973162 Mon Sep 17 00:00:00 2001 From: Christoph Rumpel Date: Mon, 28 Sep 2020 09:20:55 +0200 Subject: [PATCH 04/17] Make tests pass with L8 factories --- composer.json | 2 +- .../app/Models/DifferentLocation/Comment.php | 2 +- example/app/Models/Group.php | 2 +- example/app/Models/Ingredient.php | 2 +- .../app/Models/ModelsWithArrayState/Book.php | 2 +- example/app/Models/Recipe.php | 2 +- example/database/factories/BookFactory.php | 2 +- example/database/factories/GroupFactory.php | 2 +- example/database/factories/RecipeFactory.php | 4 +- example/tests/Factories/GroupFactory.php | 2 +- .../Factories/GroupFactoryUsingFaker.php | 2 +- example/tests/Factories/IngredientFactory.php | 2 +- .../IngredientFactoryUsingClosure.php | 2 +- example/tests/Factories/RecipeFactory.php | 6 +- ...cipeFactoryUsingFactoryForRelationship.php | 2 +- ...toryUsingLaravelFactoryForRelationship.php | 6 +- src/BaseFactory.php | 1 + src/LaravelFactoryExtractor.php | 17 +++-- src/TranslatesFactoryData.php | 3 +- tests/FactoryCollectionTest.php | 8 +-- tests/FactoryCommandTest.php | 6 +- tests/FactoryFileTest.php | 65 +++++++------------ tests/FactoryTest.php | 16 +++-- 23 files changed, 74 insertions(+), 84 deletions(-) diff --git a/composer.json b/composer.json index a1aa692..189facb 100644 --- a/composer.json +++ b/composer.json @@ -40,7 +40,7 @@ ], "psr-4": { "Christophrumpel\\LaravelFactoriesReloaded\\Tests\\": "tests", - "ExampleApp\\": "example/app/", + "App\\": "example/app/", "ExampleAppTests\\": "example/tests/", "Database\\Factories\\": "database/factories/" } diff --git a/example/app/Models/DifferentLocation/Comment.php b/example/app/Models/DifferentLocation/Comment.php index b5091a6..f71eed6 100644 --- a/example/app/Models/DifferentLocation/Comment.php +++ b/example/app/Models/DifferentLocation/Comment.php @@ -1,6 +1,6 @@ overwriteDefaults([ - 'group_id' => factory(Group::class), + 'group_id' => Group::factory(), ]); } } diff --git a/example/tests/Factories/RecipeFactoryUsingFactoryForRelationship.php b/example/tests/Factories/RecipeFactoryUsingFactoryForRelationship.php index b128b1d..d73b205 100644 --- a/example/tests/Factories/RecipeFactoryUsingFactoryForRelationship.php +++ b/example/tests/Factories/RecipeFactoryUsingFactoryForRelationship.php @@ -3,7 +3,7 @@ namespace ExampleAppTests\Factories; use Christophrumpel\LaravelFactoriesReloaded\BaseFactory; -use ExampleApp\Models\Recipe; +use App\Models\Recipe; use Faker\Generator; class RecipeFactoryUsingFactoryForRelationship extends BaseFactory diff --git a/example/tests/Factories/RecipeFactoryUsingLaravelFactoryForRelationship.php b/example/tests/Factories/RecipeFactoryUsingLaravelFactoryForRelationship.php index 31194ba..4c9f982 100644 --- a/example/tests/Factories/RecipeFactoryUsingLaravelFactoryForRelationship.php +++ b/example/tests/Factories/RecipeFactoryUsingLaravelFactoryForRelationship.php @@ -3,8 +3,8 @@ namespace ExampleAppTests\Factories; use Christophrumpel\LaravelFactoriesReloaded\BaseFactory; -use ExampleApp\Models\Group; -use ExampleApp\Models\Recipe; +use App\Models\Group; +use App\Models\Recipe; use Faker\Generator; class RecipeFactoryUsingLaravelFactoryForRelationship extends BaseFactory @@ -26,7 +26,7 @@ public function getDefaults(Generator $faker): array return [ 'name' => 'Lasagne', 'description' => 'Our family lasagne recipe.', - 'group_id' => factory(Group::class), + 'group_id' => Group::factory(), ]; } } diff --git a/src/BaseFactory.php b/src/BaseFactory.php index bec339a..8a3c018 100644 --- a/src/BaseFactory.php +++ b/src/BaseFactory.php @@ -40,6 +40,7 @@ protected function build(array $extra = [], string $creationType = 'create') $modelData = $this->transformModelFields( array_merge($this->getDefaults($this->faker), $this->overwriteDefaults, $extra) ); + $model = $this->unguardedIfNeeded(fn() => $this->modelClass::$creationType($modelData)); if ($this->relatedModelFactories->isEmpty()) { diff --git a/src/LaravelFactoryExtractor.php b/src/LaravelFactoryExtractor.php index 8879860..d75922d 100644 --- a/src/LaravelFactoryExtractor.php +++ b/src/LaravelFactoryExtractor.php @@ -12,6 +12,7 @@ class LaravelFactoryExtractor { + protected ?array $uses = null; protected string $className; @@ -233,7 +234,7 @@ public function getStates(): string ->explode(";\n"); $body = collect($bodyLines) - ->filter(fn ($line) => ! empty($line)) + ->filter(fn($line) => ! empty($line)) ->map(function ($line) { if (Str::of($line) ->contains('return $this->state(')) { @@ -242,16 +243,18 @@ public function getStates(): string } return ' '.$line.";\n\n"; - })->implode(''); + }) + ->implode(''); - return "\n ".$this->getMethodVisibility($method)." function ".$method->getName()."(): self\n {\n $body \n }"; - })->implode("\n"); + return "\n ".$this->getMethodVisibility($method)." function ".$method->getName()."(): ".class_basename($this->className) . 'Factory'."\n {\n $body\n }"; + }) + ->implode("\n"); return $states; $states = collect($this->factory->getProperty('states')); - if (! $states->has($this->className)) { + if ( ! $states->has($this->className)) { return ''; } @@ -259,7 +262,7 @@ public function getStates(): string ->map(function ($closure, $state) { $lines = collect($this->getClosureContent($closure)) ->filter() - ->map(fn ($item) => str_replace("\n", '', $item)); + ->map(fn($item) => str_replace("\n", '', $item)); $firstLine = $lines->shift(); $lastLine = $lines->pop(); @@ -287,7 +290,7 @@ public function getStates(): string ' return tap(clone $this)->overwriteDefaults(function() {', ' '.$firstLine, ]) - ->merge($lines->map(fn ($line) => ' '.$line)) + ->merge($lines->map(fn($line) => ' '.$line)) ->merge([ ' '.$lastLine, ' });', diff --git a/src/TranslatesFactoryData.php b/src/TranslatesFactoryData.php index 5dda622..7d20562 100644 --- a/src/TranslatesFactoryData.php +++ b/src/TranslatesFactoryData.php @@ -2,13 +2,14 @@ namespace Christophrumpel\LaravelFactoriesReloaded; +use Illuminate\Database\Eloquent\Factories\Factory; use Illuminate\Database\Eloquent\FactoryBuilder; trait TranslatesFactoryData { private static function isFactory($item): bool { - return $item instanceof BaseFactory || $item instanceof FactoryBuilder; + return $item instanceof Factory || $item instanceof BaseFactory; } private static function isCallable($field): bool diff --git a/tests/FactoryCollectionTest.php b/tests/FactoryCollectionTest.php index 8cdb5a6..98f4f26 100644 --- a/tests/FactoryCollectionTest.php +++ b/tests/FactoryCollectionTest.php @@ -5,10 +5,10 @@ use Christophrumpel\LaravelFactoriesReloaded\FactoryCollection; use Christophrumpel\LaravelFactoriesReloaded\FactoryFile; use Christophrumpel\LaravelFactoriesReloaded\LaravelFactoryExtractor; -use ExampleApp\Models\DifferentLocation\Comment; -use ExampleApp\Models\Group; -use ExampleApp\Models\Ingredient; -use ExampleApp\Models\Recipe; +use App\Models\DifferentLocation\Comment; +use App\Models\Group; +use App\Models\Ingredient; +use App\Models\Recipe; use Illuminate\Support\Facades\Config; use Illuminate\Support\Facades\File; use Illuminate\Support\Str; diff --git a/tests/FactoryCommandTest.php b/tests/FactoryCommandTest.php index 723f494..105902f 100644 --- a/tests/FactoryCommandTest.php +++ b/tests/FactoryCommandTest.php @@ -2,8 +2,8 @@ namespace Christophrumpel\LaravelFactoriesReloaded\Tests; -use ExampleApp\Models\Group; -use ExampleApp\Models\Recipe; +use App\Models\Group; +use App\Models\Recipe; use Illuminate\Support\Facades\Config; use Illuminate\Support\Facades\File; use Illuminate\Support\Str; @@ -207,7 +207,7 @@ public function it_accepts_config_as_options(): void public function it_can_find_models_in_option_passed_models_path(): void { $factoryPath = $this->exampleFactoriesPath('IngredientFactory.php'); - $this->assertFileNotExists($factoryPath); + $this->assertFileDoesNotExist($factoryPath); $this->artisan('make:factory-reloaded Ingredient') ->assertExitCode(0); diff --git a/tests/FactoryFileTest.php b/tests/FactoryFileTest.php index a1b8f92..9163fa3 100644 --- a/tests/FactoryFileTest.php +++ b/tests/FactoryFileTest.php @@ -3,14 +3,15 @@ namespace Christophrumpel\LaravelFactoriesReloaded\Tests; use Christophrumpel\LaravelFactoriesReloaded\FactoryFile; -use ExampleApp\Models\Group; -use ExampleApp\Models\Ingredient; -use ExampleApp\Models\ModelsWithArrayState\Book; -use ExampleApp\Models\Recipe; +use App\Models\Group; +use App\Models\Ingredient; +use App\Models\ModelsWithArrayState\Book; +use App\Models\Recipe; use Illuminate\Support\Str; class FactoryFileTest extends TestCase { + /** @test * */ public function it_can_be_created_from_static_method(): void { @@ -63,7 +64,8 @@ public function it_can_overwrite_an_existing_factory(): void $ingredientFactoryFile->write(); $originalContent = file_get_contents($ingredientFactoryFile->getTargetClassPath()); - $ingredientFactoryFile->withoutStates()->write(true); + $ingredientFactoryFile->withoutStates() + ->write(true); $this->assertNotSame($originalContent, file_get_contents($ingredientFactoryFile->getTargetClassPath())); } @@ -87,62 +89,42 @@ public function it_can_add_default_states(): void $recipeFactoryFile = FactoryFile::forModel(Recipe::class); $content = $recipeFactoryFile->render(); - // where the state php closure simply returns an array - but was over multiple lines of code $this->assertTrue(Str::contains($content, ' public function withGroup(): RecipeFactory { - return tap(clone $this)->overwriteDefaults([ - \'group_id\' => factory(Group::class), - ]); + return tap(clone $this)->overwriteDefaults([\'group_id\' => \App\Models\Group::factory()]); }')); - - // where the state php closure does some work before returning its array of values $this->assertTrue(Str::contains($content, ' public function withDifferentGroup(): RecipeFactory { - return tap(clone $this)->overwriteDefaults(function() { - $group = factory(Group::class)->create(); + $group = \Database\Factories\GroupFactory::new()->create(); - return [ - \'group_id\' => $group->id, - ]; - }); + return tap(clone $this)->overwriteDefaults([\'group_id\' => $group->id]); }')); - - // where the state php closure simply returns an array and was on one line - $this->assertTrue(Str::contains($content, ' public function withOneLineGroup(): RecipeFactory + // where the state php closure simply returns an array and was on one line + $this->assertTrue(Str::contains($content, ' public function withOneLineGroup(): RecipeFactory { - return tap(clone $this)->overwriteDefaults([\'group_id\' => factory(Group::class)]); + return tap(clone $this)->overwriteDefaults([\'group_id\' => \App\Models\Group::factory()]); }')); - - // where the state php closure simply returns an array and was on one line - and contains the string "return " within its values - $this->assertTrue(Str::contains($content, ' public function withReturnGroupName(): RecipeFactory + // where the state php closure simply returns an array and was on one line - and contains the string "return " within its values + $this->assertTrue(Str::contains($content, ' public function withReturnGroupName(): RecipeFactory { return tap(clone $this)->overwriteDefaults([\'group_name\' => \'return all\']); }')); - // where the state php closure simply returns an array and was on one line - and contains the string "];" within its values - $this->assertTrue(Str::contains($content, ' public function withSquareBracketGroupName(): RecipeFactory + // where the state php closure simply returns an array and was on one line - and contains the string "];" within its values + $this->assertTrue(Str::contains($content, ' public function withSquareBracketGroupName(): RecipeFactory { return tap(clone $this)->overwriteDefaults([\'group_name\' => \'something];\']); }')); } - /** @test * */ - public function it_throws_error_if_state_uses_array_instead_closure(): void - { - try { - FactoryFile::forModel(Book::class); - } catch (\RuntimeException $exception) { - $this->assertEquals('One of your factory states is defined as an array. It must be of the type closure to import it.', $exception->getMessage()); - } - } - /** @test * */ public function it_can_ignore_states(): void { $recipeFactoryFile = FactoryFile::forModel(Recipe::class); - $content = $recipeFactoryFile->withoutStates()->render(); + $content = $recipeFactoryFile->withoutStates() + ->render(); $this->assertFalse(Str::containsAll($content, [ 'public function withGroup', @@ -150,21 +132,22 @@ public function it_can_ignore_states(): void ])); } - /** @test **/ + /** @test * */ public function it_gives_factory_path(): void { $recipeFactoryFile = FactoryFile::forModel(Recipe::class); $this->assertEquals($this->exampleFactoriesPath('RecipeFactory.php'), $recipeFactoryFile->getTargetClassPath()); } - /** @test **/ + /** @test * */ public function it_gives_factory_class_full_name(): void { $recipeFactoryFile = FactoryFile::forModel(Recipe::class); - $this->assertEquals(config('factories-reloaded.factories_namespace').'\RecipeFactory', $recipeFactoryFile->getTargetClassFullName()); + $this->assertEquals(config('factories-reloaded.factories_namespace').'\RecipeFactory', + $recipeFactoryFile->getTargetClassFullName()); } - /** @test **/ + /** @test * */ public function it_gives_factory_class_name(): void { $recipeFactoryFile = FactoryFile::forModel(Recipe::class); diff --git a/tests/FactoryTest.php b/tests/FactoryTest.php index 7e7201a..8e2ac05 100644 --- a/tests/FactoryTest.php +++ b/tests/FactoryTest.php @@ -2,9 +2,9 @@ namespace Christophrumpel\LaravelFactoriesReloaded\Tests; -use ExampleApp\Models\Group; -use ExampleApp\Models\Ingredient; -use ExampleApp\Models\Recipe; +use App\Models\Group; +use App\Models\Ingredient; +use App\Models\Recipe; use ExampleAppTests\Factories\GroupFactory; use ExampleAppTests\Factories\GroupFactoryUsingFaker; use ExampleAppTests\Factories\IngredientFactoryUsingClosure; @@ -415,7 +415,7 @@ public function it_works_with_laravel_factory_as_relationship_in_state(): void /** @test */ public function it_works_with_laravel_factory_as_relationship_in_extras(): void { - $recipe = RecipeFactory::new()->create(['group_id' => factory(Group::class)]); + $recipe = RecipeFactory::new()->create(['group_id' => Group::factory()]); $this->assertInstanceOf(Recipe::class, $recipe); $this->assertEquals(1, $recipe->group_id); @@ -434,10 +434,12 @@ public function it_works_with_factory_as_relationship_for_creating_multiple_mode } /** @test */ - public function it_lets_you_add_related_models_when_creating_multiple() + public function it_lets_you_add_related_models_when_creating_multiple(): void { + Config::set('factories-reloaded.factories_namespace', 'ExampleAppTests\Factories'); + $groups = GroupFactory::new() - ->with(Recipe::class, 'recipes', 5, true) + ->with(Recipe::class, 'recipes', 5) ->times(3) ->create(); @@ -449,7 +451,7 @@ public function it_lets_you_add_related_models_when_creating_multiple() } /** @test */ - public function it_lets_you_create_many_related_models_at_once() + public function it_lets_you_create_many_related_models_at_once(): void { Config::set('factories-reloaded.factories_namespace', 'ExampleAppTests\Factories'); From c714960bdece7c862a85e2665f078a3f8b4c0774 Mon Sep 17 00:00:00 2001 From: Christoph Rumpel Date: Mon, 28 Sep 2020 09:21:37 +0200 Subject: [PATCH 05/17] Styles --- tests/FactoryTest.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/FactoryTest.php b/tests/FactoryTest.php index 8e2ac05..534a321 100644 --- a/tests/FactoryTest.php +++ b/tests/FactoryTest.php @@ -208,8 +208,8 @@ public function it_lets_you_use_a_closure_for_defining_default_data(): void public function it_lets_you_use_a_closure_for_overriding_default_data(): void { $ingredient = IngredientFactoryUsingClosure::new()->create([ - 'name' => fn(array $ingredient) => 'Basil', - 'description' => fn(array $ingredient) => "Super delicious {$ingredient['name']}", + 'name' => fn (array $ingredient) => 'Basil', + 'description' => fn (array $ingredient) => "Super delicious {$ingredient['name']}", ]); $this->assertIsString($ingredient->name); @@ -355,7 +355,7 @@ public function it_lets_you_define_extras_for_multiple_related_models_at_once_us ->create(); $this->assertEquals(3, $group->recipes()->count()); - $group->recipes->each(fn($recipe) => $this->assertEquals('my-name', $recipe->name)); + $group->recipes->each(fn ($recipe) => $this->assertEquals('my-name', $recipe->name)); } /** @test */ From d241c2549d3000b7cc8c08b8e30229f8adef43d1 Mon Sep 17 00:00:00 2001 From: Christoph Rumpel Date: Tue, 20 Oct 2020 18:07:09 +0200 Subject: [PATCH 06/17] wip --- example/database/factories/RecipeFactory.php | 55 ++++++++------ example/tests/Factories/RecipeFactory.php | 1 + src/LaravelFactoryExtractor.php | 40 ++++++++--- tests/FactoryCommandTest.php | 5 ++ tests/FactoryFileTest.php | 75 ++++++++++++++------ 5 files changed, 121 insertions(+), 55 deletions(-) diff --git a/example/database/factories/RecipeFactory.php b/example/database/factories/RecipeFactory.php index fecc50f..0b8da9b 100644 --- a/example/database/factories/RecipeFactory.php +++ b/example/database/factories/RecipeFactory.php @@ -36,30 +36,39 @@ public function withGroup(): Factory ]); } - public function withDifferentGroup(): Factory - { - $group = GroupFactory::new() - ->create(); + //public function withDifferentGroup(): Factory + //{ + // $group = GroupFactory::new() + // ->create(); + // + // return $this->state([ + // 'group_id' => $group->id, + // ]); + //} - return $this->state([ - 'group_id' => $group->id, - ]); - } - - public function withOneLineGroup(): Factory - { - return $this->state(['group_id' => Group::factory()]); - } - - public function withReturnGroupName(): Factory - { - return $this->state(['group_name' => 'return all']); - } - - public function withSquareBracketGroupName(): Factory - { - return $this->state(['group_name' => 'something];']); - } + //public function withOneLineGroup(): Factory + //{ + // return $this->state(['group_id' => Group::factory()]); + //} + // + //public function withReturnGroupName(): Factory + //{ + // return $this->state(['group_name' => 'return all']); + //} + // + //public function withClosureGroupName(): Factory + //{ + // return $this->state(function (array $attributes) { + // return [ + // 'name' => $attributes['name'] . ' New Name', + // ]; + // }); + //} + // + //public function withSquareBracketGroupName(): Factory + //{ + // return $this->state(['group_name' => 'something];']); + //} } diff --git a/example/tests/Factories/RecipeFactory.php b/example/tests/Factories/RecipeFactory.php index 24de5a9..9bce113 100644 --- a/example/tests/Factories/RecipeFactory.php +++ b/example/tests/Factories/RecipeFactory.php @@ -56,4 +56,5 @@ public function withLaravelGroup(): self 'group_id' => Group::factory(), ]); } + } diff --git a/src/LaravelFactoryExtractor.php b/src/LaravelFactoryExtractor.php index d75922d..4d72c6e 100644 --- a/src/LaravelFactoryExtractor.php +++ b/src/LaravelFactoryExtractor.php @@ -12,7 +12,6 @@ class LaravelFactoryExtractor { - protected ?array $uses = null; protected string $className; @@ -230,23 +229,44 @@ public function getStates(): string ->contains('$this->state(') && $method->getFileName() === $factoryFileName; }) ->map(function (ReflectionMethod $method) { - $bodyLines = Str::of($method->getBodyCode()) - ->explode(";\n"); - $body = collect($bodyLines) - ->filter(fn($line) => ! empty($line)) + $body = collect($method->getBodyCode()) + ->filter(fn ($line) => ! empty($line)) ->map(function ($line) { + // If it calls the "return with state method", replace it with overwrite if (Str::of($line) ->contains('return $this->state(')) { return (string) Str::of($line) ->replace('return $this->state(', ' return tap(clone $this)->overwriteDefaults('); } - return ' '.$line.";\n\n"; + return $line.";\n\n"; + }) + ->map(function ($line) { + + // Get lines by splitting over new line snippet + $lines = collect(explode(PHP_EOL, $line)); + + + //$firstLine = $lines->shift(); + + //$givenSpaces = Str::of($firstLine)->before('return'); + //$givenSpaces = Str::of($firstLine)->match('/^(\s+)/'); + // + //return $lines->map(function ($line) use ($givenSpaces) { + // $currentSpaces = Str::of($line)->match('/^(\s+)/')->length(); + // if ($currentSpaces <= $givenSpaces->length()) { + // return $givenSpaces.$givenSpaces.$line; + // } + // + // return ' '.$line; + //}) + // ->prepend($firstLine)->implode(PHP_EOL); }) + ->dd() ->implode(''); - return "\n ".$this->getMethodVisibility($method)." function ".$method->getName()."(): ".class_basename($this->className) . 'Factory'."\n {\n $body\n }"; + return "\n ".$this->getMethodVisibility($method)." function ".$method->getName()."(): ".class_basename($this->className).'Factory'."\n {\n $body\n }"; }) ->implode("\n"); @@ -254,7 +274,7 @@ public function getStates(): string $states = collect($this->factory->getProperty('states')); - if ( ! $states->has($this->className)) { + if (! $states->has($this->className)) { return ''; } @@ -262,7 +282,7 @@ public function getStates(): string ->map(function ($closure, $state) { $lines = collect($this->getClosureContent($closure)) ->filter() - ->map(fn($item) => str_replace("\n", '', $item)); + ->map(fn ($item) => str_replace("\n", '', $item)); $firstLine = $lines->shift(); $lastLine = $lines->pop(); @@ -290,7 +310,7 @@ public function getStates(): string ' return tap(clone $this)->overwriteDefaults(function() {', ' '.$firstLine, ]) - ->merge($lines->map(fn($line) => ' '.$line)) + ->merge($lines->map(fn ($line) => ' '.$line)) ->merge([ ' '.$lastLine, ' });', diff --git a/tests/FactoryCommandTest.php b/tests/FactoryCommandTest.php index 105902f..176e8a0 100644 --- a/tests/FactoryCommandTest.php +++ b/tests/FactoryCommandTest.php @@ -4,6 +4,7 @@ use App\Models\Group; use App\Models\Recipe; +use ExampleAppTests\Factories\Tmp\RecipeFactory; use Illuminate\Support\Facades\Config; use Illuminate\Support\Facades\File; use Illuminate\Support\Str; @@ -58,6 +59,10 @@ public function it_asks_user_to_integrate_old_factory_states_if_given_which_he_a $this->assertFileExists($this->exampleFactoriesPath('RecipeFactory.php')); $this->assertTrue(method_exists($this->exampleFactoriesNamespace().'\RecipeFactory', 'withGroup')); + $factoryClass = $this->exampleFactoriesNamespace().'\RecipeFactory'; + $factory = $factoryClass::new(); + $this->assertInstanceOf(RecipeFactory::class, $factory->withGroup()); + } /** @test */ diff --git a/tests/FactoryFileTest.php b/tests/FactoryFileTest.php index 9163fa3..b0c9dad 100644 --- a/tests/FactoryFileTest.php +++ b/tests/FactoryFileTest.php @@ -2,16 +2,14 @@ namespace Christophrumpel\LaravelFactoriesReloaded\Tests; -use Christophrumpel\LaravelFactoriesReloaded\FactoryFile; use App\Models\Group; use App\Models\Ingredient; -use App\Models\ModelsWithArrayState\Book; use App\Models\Recipe; +use Christophrumpel\LaravelFactoriesReloaded\FactoryFile; use Illuminate\Support\Str; class FactoryFileTest extends TestCase { - /** @test * */ public function it_can_be_created_from_static_method(): void { @@ -94,27 +92,58 @@ public function it_can_add_default_states(): void { return tap(clone $this)->overwriteDefaults([\'group_id\' => \App\Models\Group::factory()]); }')); - $this->assertTrue(Str::contains($content, ' public function withDifferentGroup(): RecipeFactory - { - $group = \Database\Factories\GroupFactory::new()->create(); - return tap(clone $this)->overwriteDefaults([\'group_id\' => $group->id]); - }')); - // where the state php closure simply returns an array and was on one line - $this->assertTrue(Str::contains($content, ' public function withOneLineGroup(): RecipeFactory - { - return tap(clone $this)->overwriteDefaults([\'group_id\' => \App\Models\Group::factory()]); - }')); - // where the state php closure simply returns an array and was on one line - and contains the string "return " within its values - $this->assertTrue(Str::contains($content, ' public function withReturnGroupName(): RecipeFactory + // // where the state php closure simply returns an array and was on one line + // $this->assertTrue(Str::contains($content, ' public function withOneLineGroup(): RecipeFactory + //{ + // return tap(clone $this)->overwriteDefaults([\'group_id\' => \App\Models\Group::factory()]); + //}')); + // // where the state php closure simply returns an array and was on one line - and contains the string "return " within its values + // $this->assertTrue(Str::contains($content, ' public function withReturnGroupName(): RecipeFactory + //{ + // return tap(clone $this)->overwriteDefaults([\'group_name\' => \'return all\']); + //}')); + // + // // where the state php closure simply returns an array and was on one line - and contains the string "];" within its values + // $this->assertTrue(Str::contains($content, ' public function withSquareBracketGroupName(): RecipeFactory + //{ + // return tap(clone $this)->overwriteDefaults([\'group_name\' => \'something];\']); + //}')); + // + // // state with closure + // // + // //return $this->state(function () { + // //return [ + // //'name' => 'New Name', + // //]; + // //}); + // + // $this->assertTrue(Str::contains($content, ' public function withClosureGroupName(): RecipeFactory + //{ + // return tap(clone $this)->overwriteDefaults([ + // \'name\' => \'New Name\' + // ]); + //}')); + + // state with closure and using given $attribute + // Possible? because when we override defaults, they are merged before building, so we cannot access them yet? + //return $this->state(function (array $attributes) { + //return [ + //'name' => $attributes['name'] . ' New Name', + //]; + //}); + } + + /** @test * */ + public function it_can_add_default_state_withDifferentGroup(): void { - return tap(clone $this)->overwriteDefaults([\'group_name\' => \'return all\']); - }')); + $recipeFactoryFile = FactoryFile::forModel(Recipe::class); - // where the state php closure simply returns an array and was on one line - and contains the string "];" within its values - $this->assertTrue(Str::contains($content, ' public function withSquareBracketGroupName(): RecipeFactory + $content = $recipeFactoryFile->render(); + $this->assertTrue(Str::contains($content, ' public function withDifferentGroup(): RecipeFactory { - return tap(clone $this)->overwriteDefaults([\'group_name\' => \'something];\']); + $group = \Database\Factories\GroupFactory::new()->create(); + return tap(clone $this)->overwriteDefaults([\'group_id\' => $group->id]); }')); } @@ -143,8 +172,10 @@ public function it_gives_factory_path(): void public function it_gives_factory_class_full_name(): void { $recipeFactoryFile = FactoryFile::forModel(Recipe::class); - $this->assertEquals(config('factories-reloaded.factories_namespace').'\RecipeFactory', - $recipeFactoryFile->getTargetClassFullName()); + $this->assertEquals( + config('factories-reloaded.factories_namespace').'\RecipeFactory', + $recipeFactoryFile->getTargetClassFullName() + ); } /** @test * */ From 927d0a6e3a2963ec39cb7d4042928e59cb961d91 Mon Sep 17 00:00:00 2001 From: Christoph Rumpel Date: Tue, 20 Oct 2020 18:36:25 +0200 Subject: [PATCH 07/17] First two states test working --- example/database/factories/RecipeFactory.php | 18 +-- src/LaravelFactoryExtractor.php | 140 +++++++------------ 2 files changed, 58 insertions(+), 100 deletions(-) diff --git a/example/database/factories/RecipeFactory.php b/example/database/factories/RecipeFactory.php index 0b8da9b..120eb20 100644 --- a/example/database/factories/RecipeFactory.php +++ b/example/database/factories/RecipeFactory.php @@ -36,15 +36,15 @@ public function withGroup(): Factory ]); } - //public function withDifferentGroup(): Factory - //{ - // $group = GroupFactory::new() - // ->create(); - // - // return $this->state([ - // 'group_id' => $group->id, - // ]); - //} + public function withDifferentGroup(): Factory + { + $group = GroupFactory::new() + ->create(); + + return $this->state([ + 'group_id' => $group->id, + ]); + } //public function withOneLineGroup(): Factory //{ diff --git a/src/LaravelFactoryExtractor.php b/src/LaravelFactoryExtractor.php index 4d72c6e..15a936d 100644 --- a/src/LaravelFactoryExtractor.php +++ b/src/LaravelFactoryExtractor.php @@ -225,109 +225,67 @@ public function getStates(): string return collect($factoryMethods) ->filter(function (ReflectionMethod $method) use ($factoryFileName) { + // Get only methods where state method is used return Str::of($method->getBodyCode()) ->contains('$this->state(') && $method->getFileName() === $factoryFileName; }) ->map(function (ReflectionMethod $method) { + // Replace Laravel state method with overwrite method + $methodBody = $method->getBodyCode(); - $body = collect($method->getBodyCode()) - ->filter(fn ($line) => ! empty($line)) - ->map(function ($line) { - // If it calls the "return with state method", replace it with overwrite - if (Str::of($line) - ->contains('return $this->state(')) { - return (string) Str::of($line) - ->replace('return $this->state(', ' return tap(clone $this)->overwriteDefaults('); - } - - return $line.";\n\n"; - }) - ->map(function ($line) { - - // Get lines by splitting over new line snippet - $lines = collect(explode(PHP_EOL, $line)); - - - //$firstLine = $lines->shift(); - - //$givenSpaces = Str::of($firstLine)->before('return'); - //$givenSpaces = Str::of($firstLine)->match('/^(\s+)/'); - // - //return $lines->map(function ($line) use ($givenSpaces) { - // $currentSpaces = Str::of($line)->match('/^(\s+)/')->length(); - // if ($currentSpaces <= $givenSpaces->length()) { - // return $givenSpaces.$givenSpaces.$line; - // } - // - // return ' '.$line; - //}) - // ->prepend($firstLine)->implode(PHP_EOL); - }) - ->dd() - ->implode(''); - - return "\n ".$this->getMethodVisibility($method)." function ".$method->getName()."(): ".class_basename($this->className).'Factory'."\n {\n $body\n }"; - }) - ->implode("\n"); - - return $states; - - $states = collect($this->factory->getProperty('states')); - - if (! $states->has($this->className)) { - return ''; - } + $newMethodBody = Str::of($methodBody) + ->replace('return $this->state(', ' return tap(clone $this)->overwriteDefaults('); - return collect($states->get($this->className)) - ->map(function ($closure, $state) { - $lines = collect($this->getClosureContent($closure)) - ->filter() - ->map(fn ($item) => str_replace("\n", '', $item)); - $firstLine = $lines->shift(); - $lastLine = $lines->pop(); - - if (Str::startsWith(ltrim($firstLine), 'return')) { - if ($lastLine === null) { - $firstLine = Str::replaceLast('];', ']);', $firstLine); - } else { - $lines->push(Str::replaceLast('];', ']);', $lastLine)); - } - $lines->push('}'); - - return $lines->prepend([ - '', - 'public function '.$this->getStateMethodName($state).'(): '.class_basename($this->className).'Factory', - '{', - Str::replaceFirst('return ', 'return tap(clone $this)->overwriteDefaults(', $firstLine), - ]) - ->toArray(); + $lines = explode(PHP_EOL, $newMethodBody); + if (count($lines) > 1) { + $newMethodBody = collect(explode(PHP_EOL, $newMethodBody))->map(function ($line) { + return Str::of($line)->ltrim(' ')->prepend(' '); + })->implode(PHP_EOL); } - return collect([ - '', - 'public function '.$this->getStateMethodName($state).'(): '.class_basename($this->className).'Factory', - '{', - ' return tap(clone $this)->overwriteDefaults(function() {', - ' '.$firstLine, - ]) - ->merge($lines->map(fn ($line) => ' '.$line)) - ->merge([ - ' '.$lastLine, - ' });', - '}', - ]); - }) - ->flatten() - ->map(function ($line) { - if (ltrim($line) === '') { - return ''; - } - - return ' '.$line; + // Put new method body in method + return "\n ".$this->getMethodVisibility($method)." function ".$method->getName()."(): ".class_basename($this->className).'Factory'."\n {\n$newMethodBody\n }"; }) ->implode("\n"); } + //$body = collect($method->getBodyCode()) + // ->filter(fn ($line) => ! empty($line)) + // ->map(function ($line) { + // // If it calls the "return with state method", replace it with overwrite + // if (Str::of($line) + // ->contains('return $this->state(')) { + // return (string) Str::of($line) + // ->replace('return $this->state(', ' return tap(clone $this)->overwriteDefaults('); + // } + // + // return $line.";\n\n"; + // }) + // ->map(function ($line) { + // + // // Get lines by splitting over new line snippet + // $lines = collect(explode(PHP_EOL, $line)); + + //$firstLine = $lines->shift(); + + //$givenSpaces = Str::of($firstLine)->before('return'); + //$givenSpaces = Str::of($firstLine)->match('/^(\s+)/'); + // + //return $lines->map(function ($line) use ($givenSpaces) { + // $currentSpaces = Str::of($line)->match('/^(\s+)/')->length(); + // if ($currentSpaces <= $givenSpaces->length()) { + // return $givenSpaces.$givenSpaces.$line; + // } + // + // return ' '.$line; + //}) + // ->prepend($firstLine)->implode(PHP_EOL); + // }) + // ->dd() + // ->implode(''); + // + //return "\n ".$this->getMethodVisibility($method)." function ".$method->getName()."(): ".class_basename($this->className).'Factory'."\n {\n $body\n }"; + protected function getStateMethodName(string $state): string { return lcfirst(Str::studly($state)); From 37c6cb1057644394385e59c425672572006d5a47 Mon Sep 17 00:00:00 2001 From: Christoph Rumpel Date: Tue, 20 Oct 2020 19:02:33 +0200 Subject: [PATCH 08/17] Add two more working state tests --- example/database/factories/RecipeFactory.php | 36 ++++---- src/LaravelFactoryExtractor.php | 18 +++- tests/FactoryFileTest.php | 88 ++++++++++++++------ 3 files changed, 94 insertions(+), 48 deletions(-) diff --git a/example/database/factories/RecipeFactory.php b/example/database/factories/RecipeFactory.php index 120eb20..772324f 100644 --- a/example/database/factories/RecipeFactory.php +++ b/example/database/factories/RecipeFactory.php @@ -46,24 +46,24 @@ public function withDifferentGroup(): Factory ]); } - //public function withOneLineGroup(): Factory - //{ - // return $this->state(['group_id' => Group::factory()]); - //} - // - //public function withReturnGroupName(): Factory - //{ - // return $this->state(['group_name' => 'return all']); - //} - // - //public function withClosureGroupName(): Factory - //{ - // return $this->state(function (array $attributes) { - // return [ - // 'name' => $attributes['name'] . ' New Name', - // ]; - // }); - //} + public function withOneLineGroup(): Factory + { + return $this->state(['group_id' => Group::factory()]); + } + + public function withReturnGroupName(): Factory + { + return $this->state(['group_name' => 'return all']); + } + + public function withClosureGroupName(): Factory + { + return $this->state(function (array $attributes) { + return [ + 'name' => $attributes['name'] . ' New Name', + ]; + }); + } // //public function withSquareBracketGroupName(): Factory //{ diff --git a/src/LaravelFactoryExtractor.php b/src/LaravelFactoryExtractor.php index 15a936d..ee395d5 100644 --- a/src/LaravelFactoryExtractor.php +++ b/src/LaravelFactoryExtractor.php @@ -236,11 +236,23 @@ public function getStates(): string $newMethodBody = Str::of($methodBody) ->replace('return $this->state(', ' return tap(clone $this)->overwriteDefaults('); + // If the method body contains multiple lines, format them $lines = explode(PHP_EOL, $newMethodBody); + $lineBefore = ''; if (count($lines) > 1) { - $newMethodBody = collect(explode(PHP_EOL, $newMethodBody))->map(function ($line) { - return Str::of($line)->ltrim(' ')->prepend(' '); - })->implode(PHP_EOL); + $newMethodBody = collect(explode(PHP_EOL, $newMethodBody)) + ->map(function ($line) use (&$lineBefore) { + $prepend = ' '; + // Add indention if the line before opens a function + if (Str::of($lineBefore)->endsWith('{')) { + $prepend .= ' '; + } + + $lineBefore = $line; + + return Str::of($line)->ltrim(' ')->prepend($prepend); + }) + ->implode(PHP_EOL); } // Put new method body in method diff --git a/tests/FactoryFileTest.php b/tests/FactoryFileTest.php index b0c9dad..e0426cd 100644 --- a/tests/FactoryFileTest.php +++ b/tests/FactoryFileTest.php @@ -93,37 +93,31 @@ public function it_can_add_default_states(): void return tap(clone $this)->overwriteDefaults([\'group_id\' => \App\Models\Group::factory()]); }')); - // // where the state php closure simply returns an array and was on one line - // $this->assertTrue(Str::contains($content, ' public function withOneLineGroup(): RecipeFactory - //{ - // return tap(clone $this)->overwriteDefaults([\'group_id\' => \App\Models\Group::factory()]); - //}')); - // // where the state php closure simply returns an array and was on one line - and contains the string "return " within its values - // $this->assertTrue(Str::contains($content, ' public function withReturnGroupName(): RecipeFactory - //{ - // return tap(clone $this)->overwriteDefaults([\'group_name\' => \'return all\']); - //}')); - // - // // where the state php closure simply returns an array and was on one line - and contains the string "];" within its values // $this->assertTrue(Str::contains($content, ' public function withSquareBracketGroupName(): RecipeFactory //{ // return tap(clone $this)->overwriteDefaults([\'group_name\' => \'something];\']); //}')); - // - // // state with closure - // // - // //return $this->state(function () { - // //return [ - // //'name' => 'New Name', - // //]; - // //}); - // - // $this->assertTrue(Str::contains($content, ' public function withClosureGroupName(): RecipeFactory - //{ - // return tap(clone $this)->overwriteDefaults([ - // \'name\' => \'New Name\' - // ]); - //}')); + // // where the state php closure simply returns an array and was on one line + // $this->assertTrue(Str::contains($content, ' public function withOneLineGroup(): RecipeFactory + //{ + // return tap(clone $this)->overwriteDefaults([\'group_id\' => \App\Models\Group::factory()]); + //}')); + // + // + // // state with closure + // // + // //return $this->state(function () { + // //return [ + // //'name' => 'New Name', + // //]; + // //}); + // + // $this->assertTrue(Str::contains($content, ' public function withClosureGroupName(): RecipeFactory + //{ + // return tap(clone $this)->overwriteDefaults([ + // \'name\' => \'New Name\' + // ]); + //}')); // state with closure and using given $attribute // Possible? because when we override defaults, they are merged before building, so we cannot access them yet? @@ -147,6 +141,46 @@ public function it_can_add_default_state_withDifferentGroup(): void }')); } + /** @test * */ + public function it_can_add_default_where_method_body_is_on_one_line(): void + { + $recipeFactoryFile = FactoryFile::forModel(Recipe::class); + + $content = $recipeFactoryFile->render(); + $this->assertTrue(Str::contains($content, ' public function withOneLineGroup(): RecipeFactory + { + return tap(clone $this)->overwriteDefaults([\'group_id\' => \App\Models\Group::factory()]); + }')); + + } + + /** @test * */ + public function it_can_add_state_where_return_string_is_contained(): void + { + $recipeFactoryFile = FactoryFile::forModel(Recipe::class); + + $content = $recipeFactoryFile->render(); + $this->assertTrue(Str::contains($content, ' public function withReturnGroupName(): RecipeFactory + { + return tap(clone $this)->overwriteDefaults([\'group_name\' => \'return all\']); + }')); + } + + /** @test * */ + public function it_can_add_state_with_closure_used(): void + { + $recipeFactoryFile = FactoryFile::forModel(Recipe::class); + + $content = $recipeFactoryFile->render(); + $this->assertTrue(Str::contains($content, ' public function withClosureGroupName(): RecipeFactory + { + return tap(clone $this)->overwriteDefaults(function (array $attributes) { + return [\'name\' => $attributes[\'name\'] . \' New Name\']; + }); + }')); + + } + /** @test * */ public function it_can_ignore_states(): void { From 9638cf3aa8508abb5edf7ae3a173e306029c24b7 Mon Sep 17 00:00:00 2001 From: Christoph Rumpel Date: Tue, 20 Oct 2020 19:05:32 +0200 Subject: [PATCH 09/17] Another working state test --- example/database/factories/RecipeFactory.php | 10 +++++----- tests/FactoryFileTest.php | 17 +++++++++++++---- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/example/database/factories/RecipeFactory.php b/example/database/factories/RecipeFactory.php index 772324f..b209ceb 100644 --- a/example/database/factories/RecipeFactory.php +++ b/example/database/factories/RecipeFactory.php @@ -64,11 +64,11 @@ public function withClosureGroupName(): Factory ]; }); } - // - //public function withSquareBracketGroupName(): Factory - //{ - // return $this->state(['group_name' => 'something];']); - //} + + public function withSquareBracketGroupName(): Factory + { + return $this->state(['group_name' => 'something];']); + } } diff --git a/tests/FactoryFileTest.php b/tests/FactoryFileTest.php index e0426cd..d6a21cf 100644 --- a/tests/FactoryFileTest.php +++ b/tests/FactoryFileTest.php @@ -93,10 +93,7 @@ public function it_can_add_default_states(): void return tap(clone $this)->overwriteDefaults([\'group_id\' => \App\Models\Group::factory()]); }')); - // $this->assertTrue(Str::contains($content, ' public function withSquareBracketGroupName(): RecipeFactory - //{ - // return tap(clone $this)->overwriteDefaults([\'group_name\' => \'something];\']); - //}')); + // // where the state php closure simply returns an array and was on one line // $this->assertTrue(Str::contains($content, ' public function withOneLineGroup(): RecipeFactory //{ @@ -166,6 +163,18 @@ public function it_can_add_state_where_return_string_is_contained(): void }')); } + /** @test * */ + public function it_can_add_state_where_square_brackets_being_used_ind_string(): void + { + $recipeFactoryFile = FactoryFile::forModel(Recipe::class); + + $content = $recipeFactoryFile->render(); + $this->assertTrue(Str::contains($content, ' public function withSquareBracketGroupName(): RecipeFactory + { + return tap(clone $this)->overwriteDefaults([\'group_name\' => \'something];\']); + }')); + } + /** @test * */ public function it_can_add_state_with_closure_used(): void { From f00ea65c40361b4cdfa954c85914ed6772428469 Mon Sep 17 00:00:00 2001 From: Christoph Rumpel Date: Tue, 20 Oct 2020 19:47:44 +0200 Subject: [PATCH 10/17] Add new line to created state methods if needed --- src/LaravelFactoryExtractor.php | 25 +++++++++++++++++++++++-- tests/FactoryFileTest.php | 32 +------------------------------- 2 files changed, 24 insertions(+), 33 deletions(-) diff --git a/src/LaravelFactoryExtractor.php b/src/LaravelFactoryExtractor.php index ee395d5..f5d82c3 100644 --- a/src/LaravelFactoryExtractor.php +++ b/src/LaravelFactoryExtractor.php @@ -244,13 +244,34 @@ public function getStates(): string ->map(function ($line) use (&$lineBefore) { $prepend = ' '; // Add indention if the line before opens a function - if (Str::of($lineBefore)->endsWith('{')) { + if (Str::of($lineBefore) + ->endsWith('{')) { $prepend .= ' '; } $lineBefore = $line; - return Str::of($line)->ltrim(' ')->prepend($prepend); + return Str::of($line) + ->ltrim(' ') + ->prepend($prepend); + }) + ->map(function ($line, $key) use (&$lineBefore) { + if ($key === 0) { + $lineBefore = ''; + } + + if ($key !== 0 + && ! Str::of($lineBefore)->endsWith('{') + && Str::of($line) + ->ltrim(' ') + ->startsWith('return')) { + $line = Str::of($line) + ->prepend(PHP_EOL); + } + + $lineBefore = $line; + + return $line; }) ->implode(PHP_EOL); } diff --git a/tests/FactoryFileTest.php b/tests/FactoryFileTest.php index d6a21cf..76d9006 100644 --- a/tests/FactoryFileTest.php +++ b/tests/FactoryFileTest.php @@ -92,37 +92,6 @@ public function it_can_add_default_states(): void { return tap(clone $this)->overwriteDefaults([\'group_id\' => \App\Models\Group::factory()]); }')); - - - // // where the state php closure simply returns an array and was on one line - // $this->assertTrue(Str::contains($content, ' public function withOneLineGroup(): RecipeFactory - //{ - // return tap(clone $this)->overwriteDefaults([\'group_id\' => \App\Models\Group::factory()]); - //}')); - // - // - // // state with closure - // // - // //return $this->state(function () { - // //return [ - // //'name' => 'New Name', - // //]; - // //}); - // - // $this->assertTrue(Str::contains($content, ' public function withClosureGroupName(): RecipeFactory - //{ - // return tap(clone $this)->overwriteDefaults([ - // \'name\' => \'New Name\' - // ]); - //}')); - - // state with closure and using given $attribute - // Possible? because when we override defaults, they are merged before building, so we cannot access them yet? - //return $this->state(function (array $attributes) { - //return [ - //'name' => $attributes['name'] . ' New Name', - //]; - //}); } /** @test * */ @@ -134,6 +103,7 @@ public function it_can_add_default_state_withDifferentGroup(): void $this->assertTrue(Str::contains($content, ' public function withDifferentGroup(): RecipeFactory { $group = \Database\Factories\GroupFactory::new()->create(); + return tap(clone $this)->overwriteDefaults([\'group_id\' => $group->id]); }')); } From ea7b29feec2fe0b5e075dc46274143fc68e3829f Mon Sep 17 00:00:00 2001 From: Christoph Rumpel Date: Tue, 20 Oct 2020 19:56:10 +0200 Subject: [PATCH 11/17] Improve test for imported default values --- tests/FactoryFileTest.php | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/FactoryFileTest.php b/tests/FactoryFileTest.php index 76d9006..f9489b4 100644 --- a/tests/FactoryFileTest.php +++ b/tests/FactoryFileTest.php @@ -74,10 +74,9 @@ public function it_imports_old_factory_default_data(): void $recipeFactoryFile = FactoryFile::forModel(Recipe::class); $content = $recipeFactoryFile->render(); - $this->assertTrue(Str::containsAll($content, [ - "'name' =>", - "'description' =>", + '\'name\' => $this->faker->word,', + '\'description\' => $this->faker->sentence', ])); } From 6f6e6c557ef889dc1589b142aa028c552b009568 Mon Sep 17 00:00:00 2001 From: christophrumpel Date: Wed, 21 Oct 2020 06:24:42 +0000 Subject: [PATCH 12/17] Apply php-cs-fixer changes --- example/tests/Factories/GroupFactory.php | 2 +- example/tests/Factories/GroupFactoryUsingFaker.php | 2 +- example/tests/Factories/IngredientFactory.php | 2 +- example/tests/Factories/IngredientFactoryUsingClosure.php | 2 +- example/tests/Factories/RecipeFactory.php | 1 - .../Factories/RecipeFactoryUsingFactoryForRelationship.php | 2 +- .../RecipeFactoryUsingLaravelFactoryForRelationship.php | 2 +- src/TranslatesFactoryData.php | 1 - tests/FactoryCollectionTest.php | 4 ++-- tests/FactoryCommandTest.php | 1 - tests/FactoryFileTest.php | 2 -- 11 files changed, 8 insertions(+), 13 deletions(-) diff --git a/example/tests/Factories/GroupFactory.php b/example/tests/Factories/GroupFactory.php index 9c122f3..581cde9 100644 --- a/example/tests/Factories/GroupFactory.php +++ b/example/tests/Factories/GroupFactory.php @@ -2,8 +2,8 @@ namespace ExampleAppTests\Factories; -use Christophrumpel\LaravelFactoriesReloaded\BaseFactory; use App\Models\Group; +use Christophrumpel\LaravelFactoriesReloaded\BaseFactory; use Faker\Generator; class GroupFactory extends BaseFactory diff --git a/example/tests/Factories/GroupFactoryUsingFaker.php b/example/tests/Factories/GroupFactoryUsingFaker.php index 76bce82..883c983 100644 --- a/example/tests/Factories/GroupFactoryUsingFaker.php +++ b/example/tests/Factories/GroupFactoryUsingFaker.php @@ -2,8 +2,8 @@ namespace ExampleAppTests\Factories; -use Christophrumpel\LaravelFactoriesReloaded\BaseFactory; use App\Models\Group; +use Christophrumpel\LaravelFactoriesReloaded\BaseFactory; use Faker\Generator; class GroupFactoryUsingFaker extends BaseFactory diff --git a/example/tests/Factories/IngredientFactory.php b/example/tests/Factories/IngredientFactory.php index a0bc8f6..febf482 100644 --- a/example/tests/Factories/IngredientFactory.php +++ b/example/tests/Factories/IngredientFactory.php @@ -2,8 +2,8 @@ namespace ExampleAppTests\Factories; -use Christophrumpel\LaravelFactoriesReloaded\BaseFactory; use App\Models\Ingredient; +use Christophrumpel\LaravelFactoriesReloaded\BaseFactory; use Faker\Generator; class IngredientFactory extends BaseFactory diff --git a/example/tests/Factories/IngredientFactoryUsingClosure.php b/example/tests/Factories/IngredientFactoryUsingClosure.php index 636e3b3..d53adde 100644 --- a/example/tests/Factories/IngredientFactoryUsingClosure.php +++ b/example/tests/Factories/IngredientFactoryUsingClosure.php @@ -2,8 +2,8 @@ namespace ExampleAppTests\Factories; -use Christophrumpel\LaravelFactoriesReloaded\BaseFactory; use App\Models\Ingredient; +use Christophrumpel\LaravelFactoriesReloaded\BaseFactory; use Faker\Generator; class IngredientFactoryUsingClosure extends BaseFactory diff --git a/example/tests/Factories/RecipeFactory.php b/example/tests/Factories/RecipeFactory.php index 9bce113..24de5a9 100644 --- a/example/tests/Factories/RecipeFactory.php +++ b/example/tests/Factories/RecipeFactory.php @@ -56,5 +56,4 @@ public function withLaravelGroup(): self 'group_id' => Group::factory(), ]); } - } diff --git a/example/tests/Factories/RecipeFactoryUsingFactoryForRelationship.php b/example/tests/Factories/RecipeFactoryUsingFactoryForRelationship.php index d73b205..1c1129b 100644 --- a/example/tests/Factories/RecipeFactoryUsingFactoryForRelationship.php +++ b/example/tests/Factories/RecipeFactoryUsingFactoryForRelationship.php @@ -2,8 +2,8 @@ namespace ExampleAppTests\Factories; -use Christophrumpel\LaravelFactoriesReloaded\BaseFactory; use App\Models\Recipe; +use Christophrumpel\LaravelFactoriesReloaded\BaseFactory; use Faker\Generator; class RecipeFactoryUsingFactoryForRelationship extends BaseFactory diff --git a/example/tests/Factories/RecipeFactoryUsingLaravelFactoryForRelationship.php b/example/tests/Factories/RecipeFactoryUsingLaravelFactoryForRelationship.php index 4c9f982..18d3438 100644 --- a/example/tests/Factories/RecipeFactoryUsingLaravelFactoryForRelationship.php +++ b/example/tests/Factories/RecipeFactoryUsingLaravelFactoryForRelationship.php @@ -2,9 +2,9 @@ namespace ExampleAppTests\Factories; -use Christophrumpel\LaravelFactoriesReloaded\BaseFactory; use App\Models\Group; use App\Models\Recipe; +use Christophrumpel\LaravelFactoriesReloaded\BaseFactory; use Faker\Generator; class RecipeFactoryUsingLaravelFactoryForRelationship extends BaseFactory diff --git a/src/TranslatesFactoryData.php b/src/TranslatesFactoryData.php index 7d20562..db059d5 100644 --- a/src/TranslatesFactoryData.php +++ b/src/TranslatesFactoryData.php @@ -3,7 +3,6 @@ namespace Christophrumpel\LaravelFactoriesReloaded; use Illuminate\Database\Eloquent\Factories\Factory; -use Illuminate\Database\Eloquent\FactoryBuilder; trait TranslatesFactoryData { diff --git a/tests/FactoryCollectionTest.php b/tests/FactoryCollectionTest.php index a6adb16..cbe88b8 100644 --- a/tests/FactoryCollectionTest.php +++ b/tests/FactoryCollectionTest.php @@ -2,12 +2,12 @@ namespace Christophrumpel\LaravelFactoriesReloaded\Tests; +use App\Models\DifferentLocation\Comment; +use App\Models\Group; use App\Models\Ingredient; use App\Models\Recipe; use Christophrumpel\LaravelFactoriesReloaded\FactoryCollection; use Christophrumpel\LaravelFactoriesReloaded\FactoryFile; -use App\Models\DifferentLocation\Comment; -use App\Models\Group; use Illuminate\Support\Facades\Config; use Illuminate\Support\Facades\File; use Illuminate\Support\Str; diff --git a/tests/FactoryCommandTest.php b/tests/FactoryCommandTest.php index 176e8a0..5f88cc9 100644 --- a/tests/FactoryCommandTest.php +++ b/tests/FactoryCommandTest.php @@ -62,7 +62,6 @@ public function it_asks_user_to_integrate_old_factory_states_if_given_which_he_a $factoryClass = $this->exampleFactoriesNamespace().'\RecipeFactory'; $factory = $factoryClass::new(); $this->assertInstanceOf(RecipeFactory::class, $factory->withGroup()); - } /** @test */ diff --git a/tests/FactoryFileTest.php b/tests/FactoryFileTest.php index f9489b4..d2e42ae 100644 --- a/tests/FactoryFileTest.php +++ b/tests/FactoryFileTest.php @@ -117,7 +117,6 @@ public function it_can_add_default_where_method_body_is_on_one_line(): void { return tap(clone $this)->overwriteDefaults([\'group_id\' => \App\Models\Group::factory()]); }')); - } /** @test * */ @@ -156,7 +155,6 @@ public function it_can_add_state_with_closure_used(): void return [\'name\' => $attributes[\'name\'] . \' New Name\']; }); }')); - } /** @test * */ From ba958ec289ff5c1d3596c005f5117d8fca498da4 Mon Sep 17 00:00:00 2001 From: Christoph Rumpel Date: Wed, 21 Oct 2020 08:29:24 +0200 Subject: [PATCH 13/17] Update github actions to run L8 --- .github/workflows/run-tests.yml | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 1f33bea..99c7a79 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -10,13 +10,12 @@ jobs: fail-fast: true matrix: php: [7.4] - laravel: [6.*, 7.*] + laravel: [8.*] dependency-version: [prefer-stable] include: - - laravel: 6.* - testbench: 4.* - - laravel: 7.* - testbench: 5.* + - laravel: 8.* + testbench: 6.* + name: P${{ matrix.php }} - L${{ matrix.laravel }} - ${{ matrix.dependency-version }} From 1cd900bbe35fe8f9af9548998b80468bec744eb5 Mon Sep 17 00:00:00 2001 From: Christoph Rumpel Date: Wed, 21 Oct 2020 08:42:37 +0200 Subject: [PATCH 14/17] Remove unused code --- src/LaravelFactoryExtractor.php | 49 --------------------------------- 1 file changed, 49 deletions(-) diff --git a/src/LaravelFactoryExtractor.php b/src/LaravelFactoryExtractor.php index f5d82c3..43013c0 100644 --- a/src/LaravelFactoryExtractor.php +++ b/src/LaravelFactoryExtractor.php @@ -2,7 +2,6 @@ namespace Christophrumpel\LaravelFactoriesReloaded; -use Faker\Generator; use Illuminate\Database\Eloquent\Factories\Factory; use Illuminate\Support\Str; use ReflectionFunction; @@ -27,8 +26,6 @@ public function __construct(string $className) if (class_exists($factoryName)) { $this->factory = Factory::factoryForModel(class_basename($className)); } - - //$this->factory = new ObjectPrybar(Factory::construct(app(Generator::class), config('factories-reloaded.vanilla_factories_path'))); } public static function from(string $className): self @@ -74,15 +71,6 @@ public function getDefinitions(): string return $classInfo->getMethod('definition') ->getBodyCode(); - //$definition = collect( - // $this->factory->getProperty('definitions') - //)->get($this->className); - // - //return ltrim( - // collect( - // $this->getClosureContent($definition instanceof \Closure ? $definition : $definition['default']) - // )->implode(' ') - //); } protected function getClosureContent(callable $closure): array @@ -282,43 +270,6 @@ public function getStates(): string ->implode("\n"); } - //$body = collect($method->getBodyCode()) - // ->filter(fn ($line) => ! empty($line)) - // ->map(function ($line) { - // // If it calls the "return with state method", replace it with overwrite - // if (Str::of($line) - // ->contains('return $this->state(')) { - // return (string) Str::of($line) - // ->replace('return $this->state(', ' return tap(clone $this)->overwriteDefaults('); - // } - // - // return $line.";\n\n"; - // }) - // ->map(function ($line) { - // - // // Get lines by splitting over new line snippet - // $lines = collect(explode(PHP_EOL, $line)); - - //$firstLine = $lines->shift(); - - //$givenSpaces = Str::of($firstLine)->before('return'); - //$givenSpaces = Str::of($firstLine)->match('/^(\s+)/'); - // - //return $lines->map(function ($line) use ($givenSpaces) { - // $currentSpaces = Str::of($line)->match('/^(\s+)/')->length(); - // if ($currentSpaces <= $givenSpaces->length()) { - // return $givenSpaces.$givenSpaces.$line; - // } - // - // return ' '.$line; - //}) - // ->prepend($firstLine)->implode(PHP_EOL); - // }) - // ->dd() - // ->implode(''); - // - //return "\n ".$this->getMethodVisibility($method)." function ".$method->getName()."(): ".class_basename($this->className).'Factory'."\n {\n $body\n }"; - protected function getStateMethodName(string $state): string { return lcfirst(Str::studly($state)); From e14bd904074f412ee313cdebf13b8c7c8054e08c Mon Sep 17 00:00:00 2001 From: Christoph Rumpel Date: Wed, 21 Oct 2020 08:49:17 +0200 Subject: [PATCH 15/17] Refactor factory filter method --- src/LaravelFactoryExtractor.php | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/LaravelFactoryExtractor.php b/src/LaravelFactoryExtractor.php index 43013c0..d5adce1 100644 --- a/src/LaravelFactoryExtractor.php +++ b/src/LaravelFactoryExtractor.php @@ -208,15 +208,10 @@ public function getStates(): string ->reflect(get_class($this->factory)); $factoryFileName = $factoryReflection->getFileName(); - $factoryMethods = $factoryReflection->getMethods(); return collect($factoryMethods) - ->filter(function (ReflectionMethod $method) use ($factoryFileName) { - // Get only methods where state method is used - return Str::of($method->getBodyCode()) - ->contains('$this->state(') && $method->getFileName() === $factoryFileName; - }) + ->filter(fn (ReflectionMethod $factoryMethod) => $this->isLaravelStateMethod($factoryMethod, $factoryFileName)) ->map(function (ReflectionMethod $method) { // Replace Laravel state method with overwrite method $methodBody = $method->getBodyCode(); @@ -287,4 +282,10 @@ private function getMethodVisibility(ReflectionMethod $method): string return 'public'; } + + private function isLaravelStateMethod(ReflectionMethod $factoryMethod, string $factoryFileName): bool + { + return Str::of($factoryMethod->getBodyCode()) + ->contains('$this->state(') && $factoryMethod->getFileName() === $factoryFileName; + } } From 909bbd52c4d830a00abca90130df6845807365e6 Mon Sep 17 00:00:00 2001 From: Christoph Rumpel Date: Wed, 21 Oct 2020 09:47:55 +0200 Subject: [PATCH 16/17] Refactor multiple lines method formatting --- src/LaravelFactoryExtractor.php | 81 ++++++++++++++++++--------------- 1 file changed, 44 insertions(+), 37 deletions(-) diff --git a/src/LaravelFactoryExtractor.php b/src/LaravelFactoryExtractor.php index d5adce1..6b94c92 100644 --- a/src/LaravelFactoryExtractor.php +++ b/src/LaravelFactoryExtractor.php @@ -4,6 +4,7 @@ use Illuminate\Database\Eloquent\Factories\Factory; use Illuminate\Support\Str; +use Illuminate\Support\Stringable; use ReflectionFunction; use Roave\BetterReflection\BetterReflection; use Roave\BetterReflection\Reflection\ReflectionMethod; @@ -66,7 +67,7 @@ public function getUses(): string public function getDefinitions(): string { - $classInfo = (new \Roave\BetterReflection\BetterReflection())->classReflector() + $classInfo = (new BetterReflection())->classReflector() ->reflect(get_class($this->factory)); return $classInfo->getMethod('definition') @@ -213,50 +214,17 @@ public function getStates(): string return collect($factoryMethods) ->filter(fn (ReflectionMethod $factoryMethod) => $this->isLaravelStateMethod($factoryMethod, $factoryFileName)) ->map(function (ReflectionMethod $method) { - // Replace Laravel state method with overwrite method + // Transform method to new Factory Reloaded Overwrite Defaults method $methodBody = $method->getBodyCode(); + // Replace Laravel state method with overwrite method $newMethodBody = Str::of($methodBody) ->replace('return $this->state(', ' return tap(clone $this)->overwriteDefaults('); // If the method body contains multiple lines, format them $lines = explode(PHP_EOL, $newMethodBody); - $lineBefore = ''; if (count($lines) > 1) { - $newMethodBody = collect(explode(PHP_EOL, $newMethodBody)) - ->map(function ($line) use (&$lineBefore) { - $prepend = ' '; - // Add indention if the line before opens a function - if (Str::of($lineBefore) - ->endsWith('{')) { - $prepend .= ' '; - } - - $lineBefore = $line; - - return Str::of($line) - ->ltrim(' ') - ->prepend($prepend); - }) - ->map(function ($line, $key) use (&$lineBefore) { - if ($key === 0) { - $lineBefore = ''; - } - - if ($key !== 0 - && ! Str::of($lineBefore)->endsWith('{') - && Str::of($line) - ->ltrim(' ') - ->startsWith('return')) { - $line = Str::of($line) - ->prepend(PHP_EOL); - } - - $lineBefore = $line; - - return $line; - }) - ->implode(PHP_EOL); + $newMethodBody = $this->formatMultipleLinesFactoryMethod($newMethodBody); } // Put new method body in method @@ -288,4 +256,43 @@ private function isLaravelStateMethod(ReflectionMethod $factoryMethod, string $f return Str::of($factoryMethod->getBodyCode()) ->contains('$this->state(') && $factoryMethod->getFileName() === $factoryFileName; } + + private function formatMultipleLinesFactoryMethod(Stringable $newMethodBody): string + { + $lineBefore = ''; + + return collect(explode(PHP_EOL, $newMethodBody)) + ->map(function ($line) use (&$lineBefore) { + $prepend = ' '; + // Add indention if the line before opens a function + if (Str::of($lineBefore) + ->endsWith('{')) { + $prepend .= ' '; + } + + $lineBefore = $line; + + return Str::of($line) + ->ltrim(' ') + ->prepend($prepend); + }) + ->map(function ($line, $key) use (&$lineBefore) { + if ($key === 0) { + $lineBefore = ''; + } + + if ($key !== 0 && ! Str::of($lineBefore) + ->endsWith('{') && Str::of($line) + ->ltrim(' ') + ->startsWith('return')) { + $line = Str::of($line) + ->prepend(PHP_EOL); + } + + $lineBefore = $line; + + return $line; + }) + ->implode(PHP_EOL); + } } From 4705972214f075c0a52f59dd855b846ae79ea970 Mon Sep 17 00:00:00 2001 From: Christoph Rumpel Date: Wed, 21 Oct 2020 09:49:50 +0200 Subject: [PATCH 17/17] Naming --- src/LaravelFactoryExtractor.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/LaravelFactoryExtractor.php b/src/LaravelFactoryExtractor.php index 6b94c92..7757c1a 100644 --- a/src/LaravelFactoryExtractor.php +++ b/src/LaravelFactoryExtractor.php @@ -263,18 +263,18 @@ private function formatMultipleLinesFactoryMethod(Stringable $newMethodBody): st return collect(explode(PHP_EOL, $newMethodBody)) ->map(function ($line) use (&$lineBefore) { - $prepend = ' '; + $prependSpaces = ' '; // Add indention if the line before opens a function if (Str::of($lineBefore) ->endsWith('{')) { - $prepend .= ' '; + $prependSpaces .= ' '; } $lineBefore = $line; return Str::of($line) ->ltrim(' ') - ->prepend($prepend); + ->prepend($prependSpaces); }) ->map(function ($line, $key) use (&$lineBefore) { if ($key === 0) {