Skip to content

Commit

Permalink
Option to disable generation of separate resource collection class (#723
Browse files Browse the repository at this point in the history
)
  • Loading branch information
nicodevs authored Dec 30, 2024
1 parent f35f399 commit 6e9221b
Show file tree
Hide file tree
Showing 9 changed files with 226 additions and 8 deletions.
12 changes: 12 additions & 0 deletions config/blueprint.php
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,18 @@
*/
'property_promotion' => false,

/*
|--------------------------------------------------------------------------
| Generate Resource Collection Classes
|--------------------------------------------------------------------------
|
| By default, Blueprint generates resource collection classes to manage resource
| collections (e.g. `PostResourceCollection`). You may disable this option to
| generate only resource classes and use their `collection` method instead.
|
*/
'generate_resource_collection_classes' => true,

/*
|--------------------------------------------------------------------------
| Generators
Expand Down
2 changes: 1 addition & 1 deletion src/Generators/ControllerGenerator.php
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@ protected function buildMethods(Controller $controller): string
$statement instanceof InertiaStatement => 'Inertia\Response',
$statement instanceof RenderStatement => 'Illuminate\View\View',
$statement instanceof RedirectStatement => 'Illuminate\Http\RedirectResponse',
$statement instanceof ResourceStatement => config('blueprint.namespace') . '\\Http\\Resources\\' . ($controller->namespace() ? $controller->namespace() . '\\' : '') . $statement->name(),
$statement instanceof ResourceStatement => $statement->collection() && !$statement->generateCollectionClass() ? 'Illuminate\Http\Resources\Json\ResourceCollection' : config('blueprint.namespace') . '\\Http\\Resources\\' . ($controller->namespace() ? $controller->namespace() . '\\' : '') . $statement->name(),
default => 'Illuminate\Http\Response'
};

Expand Down
10 changes: 5 additions & 5 deletions src/Generators/Statements/ResourceGenerator.php
Original file line number Diff line number Diff line change
Expand Up @@ -60,14 +60,14 @@ protected function populateStub(string $stub, Controller $controller, ResourceSt
. ($controller->namespace() ? '\\' . $controller->namespace() : '');

$imports = ['use Illuminate\\Http\\Request;'];
$imports[] = $resource->collection() ? 'use Illuminate\\Http\\Resources\\Json\\ResourceCollection;' : 'use Illuminate\\Http\\Resources\\Json\\JsonResource;';
$imports[] = $resource->collection() && $resource->generateCollectionClass() ? 'use Illuminate\\Http\\Resources\\Json\\ResourceCollection;' : 'use Illuminate\\Http\\Resources\\Json\\JsonResource;';

$stub = str_replace('{{ namespace }}', $namespace, $stub);
$stub = str_replace('{{ imports }}', implode(PHP_EOL, $imports), $stub);
$stub = str_replace('{{ parentClass }}', $resource->collection() ? 'ResourceCollection' : 'JsonResource', $stub);
$stub = str_replace('{{ parentClass }}', $resource->collection() && $resource->generateCollectionClass() ? 'ResourceCollection' : 'JsonResource', $stub);
$stub = str_replace('{{ class }}', $resource->name(), $stub);
$stub = str_replace('{{ parentClass }}', $resource->collection() ? 'ResourceCollection' : 'JsonResource', $stub);
$stub = str_replace('{{ resource }}', $resource->collection() ? 'resource collection' : 'resource', $stub);
$stub = str_replace('{{ parentClass }}', $resource->collection() && $resource->generateCollectionClass() ? 'ResourceCollection' : 'JsonResource', $stub);
$stub = str_replace('{{ resource }}', $resource->collection() && $resource->generateCollectionClass() ? 'resource collection' : 'resource', $stub);
$stub = str_replace('{{ body }}', $this->buildData($resource), $stub);

return $stub;
Expand All @@ -83,7 +83,7 @@ protected function buildData(ResourceStatement $resource): string
$model = $this->tree->modelForContext($context, true);

$data = [];
if ($resource->collection()) {
if ($resource->collection() && $resource->generateCollectionClass()) {
$data[] = 'return [';
$data[] = self::INDENT . '\'data\' => $this->collection,';
$data[] = ' ];';
Expand Down
11 changes: 10 additions & 1 deletion src/Models/Statements/ResourceStatement.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public function __construct(string $reference, bool $collection = false, bool $p

public function name(): string
{
if ($this->collection()) {
if ($this->collection() && $this->generateCollectionClass()) {
return Str::studly(Str::singular($this->reference)) . 'Collection';
}

Expand All @@ -43,8 +43,17 @@ public function paginate(): bool
return $this->paginate;
}

public function generateCollectionClass(): bool
{
return config('blueprint.generate_resource_collection_classes', true);
}

public function output(array $properties = []): string
{
if ($this->collection() && !$this->generateCollectionClass()) {
return sprintf('return %s::collection(%s);', $this->name(), $this->buildArgument($properties));
}

return sprintf('return new %s(%s);', $this->name(), $this->buildArgument($properties));
}

Expand Down
27 changes: 27 additions & 0 deletions tests/Feature/Generators/ControllerGeneratorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,33 @@ public function output_generates_controller_with_authorize_resource(): void
$this->assertEquals(['created' => [$path]], $this->subject->output($tree));
}

#[Test]
public function output_generates_controller_without_generating_resource_collection_classes(): void
{
config(['blueprint.generate_resource_collection_classes' => false]);

$definition = 'drafts/api-resource-pagination.yaml';
$path = 'app/Http/Controllers/PostController.php';
$controller = 'controllers/without-generating-resource-collection-classes.php';

$this->filesystem->expects('stub')
->with('controller.class.stub')
->andReturn($this->stub('controller.class.stub'));
$this->filesystem->expects('stub')
->with('controller.method.stub')
->andReturn($this->stub('controller.method.stub'));

$this->filesystem->expects('exists')
->with(dirname($path))
->andReturnTrue();
$this->filesystem->expects('put')
->with($path, $this->fixture($controller));

$tokens = $this->blueprint->parse($this->fixture($definition));
$tree = $this->blueprint->analyze($tokens);
$this->assertEquals(['created' => [$path]], $this->subject->output($tree));
}

public static function controllerTreeDataProvider(): array
{
return [
Expand Down
102 changes: 102 additions & 0 deletions tests/Feature/Generators/Statements/ResourceGeneratorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,38 @@ public function output_writes_resources_for_render_statements(): void
$this->assertEquals(['created' => ['app/Http/Resources/UserCollection.php', 'app/Http/Resources/UserResource.php']], $this->subject->output($tree));
}

#[Test]
public function output_writes_resource_for_resource_statements_whitout_generating_resource_collection_classes(): void
{
config(['blueprint.generate_resource_collection_classes' => false]);

$template = $this->stub('resource.stub');
$this->filesystem->expects('stub')
->with('resource.stub')
->andReturn($template);

$this->filesystem->shouldReceive('exists')
->with('app/Http/Resources')
->andReturns(false, true);
$this->filesystem->expects('makeDirectory')
->with('app/Http/Resources', 0755, true);

$this->filesystem->shouldReceive('exists')
->with('app/Http/Resources/UserResource.php')
->andReturns(false, true);
$this->filesystem->expects('put')
->with('app/Http/Resources/UserResource.php', $this->fixture('resources/user.php'));

$this->filesystem->shouldReceive('put')
->with('app/Http/Resources/UserCollection.php')
->never();

$tokens = $this->blueprint->parse($this->fixture('drafts/resource-statements.yaml'));
$tree = $this->blueprint->analyze($tokens);

$this->assertEquals(['created' => ['app/Http/Resources/UserResource.php']], $this->subject->output($tree));
}

#[Test]
public function output_writes_namespaced_classes(): void
{
Expand Down Expand Up @@ -164,6 +196,40 @@ public function output_writes_nested_resource(): void
], $this->subject->output($tree));
}

#[Test]
public function output_writes_nested_resource_without_generating_resource_collection_classes(): void
{
config(['blueprint.generate_resource_collection_classes' => false]);

$this->filesystem->expects('stub')
->with('resource.stub')
->andReturn(file_get_contents('stubs/resource.stub'));

$this->filesystem->expects('exists')
->with('app/Http/Resources/Api')
->andReturns(false);
$this->filesystem->expects('makeDirectory')
->with('app/Http/Resources/Api', 0755, true);

$this->filesystem->expects('exists')
->times(4)
->with('app/Http/Resources/Api/CertificateResource.php')
->andReturns(false, true, true, true);
$this->filesystem->expects('put')
->with('app/Http/Resources/Api/CertificateResource.php', $this->fixture('resources/certificate-with-nested-resource.php'));

$this->filesystem->shouldReceive('put')
->with('app/Http/Resources/Api/CertificateCollection.php')
->never();

$tokens = $this->blueprint->parse($this->fixture('drafts/resource-nested.yaml'));
$tree = $this->blueprint->analyze($tokens);

$this->assertEquals([
'created' => ['app/Http/Resources/Api/CertificateResource.php'],
], $this->subject->output($tree));
}

#[Test]
public function output_api_resource_pagination(): void
{
Expand Down Expand Up @@ -198,4 +264,40 @@ public function output_api_resource_pagination(): void
'created' => ['app/Http/Resources/PostCollection.php', 'app/Http/Resources/PostResource.php'],
], $this->subject->output($tree));
}

#[Test]
public function output_api_resource_pagination_without_generating_resource_collection_classes(): void
{
config(['blueprint.generate_resource_collection_classes' => false]);

$this->files->expects('stub')
->with('resource.stub')
->andReturn(file_get_contents('stubs/resource.stub'));

$this->files->expects('exists')
->with('app/Http/Resources')
->andReturns(false, true);

$this->files->expects('makeDirectory')
->with('app/Http/Resources', 0755, true);

$this->files->expects('exists')
->times(4)
->with('app/Http/Resources/PostResource.php')
->andReturns(false, true, true, true);

$this->files->expects('put')
->with('app/Http/Resources/PostResource.php', $this->fixture('resources/api-post-resource.php'));

$this->files->expects('put')
->with('app/Http/Resources/PostCollection.php')
->never();

$tokens = $this->blueprint->parse($this->fixture('drafts/api-resource-pagination.yaml'));
$tree = $this->blueprint->analyze($tokens);

$this->assertEquals([
'created' => ['app/Http/Resources/PostResource.php'],
], $this->subject->output($tree));
}
}
22 changes: 21 additions & 1 deletion tests/Feature/Lexers/StatementLexerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
use Blueprint\Models\Statements\ValidateStatement;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\TestCase;
use Tests\TestCase;

/**
* @see StatementLexer
Expand Down Expand Up @@ -679,6 +679,26 @@ public function it_returns_a_resource_collection_statement_with_pagination(): vo
$this->assertTrue($actual[0]->paginate());
}

#[Test]
public function it_returns_a_resource_collection_statement_without_generating_a_resource_collection_class(): void
{
config(['blueprint.generate_resource_collection_classes' => false]);

$tokens = [
'resource' => 'collection:users',
];

$actual = $this->subject->analyze($tokens);

$this->assertCount(1, $actual);
$this->assertInstanceOf(ResourceStatement::class, $actual[0]);

$this->assertEquals('UserResource', $actual[0]->name());
$this->assertEquals('users', $actual[0]->reference());
$this->assertTrue($actual[0]->collection());
$this->assertFalse($actual[0]->paginate());
}

public static function sessionTokensProvider(): array
{
return [
Expand Down
1 change: 1 addition & 0 deletions tests/TestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ protected function getEnvironmentSetUp($app)
$app['config']->set('blueprint.generate_phpdocs', false);
$app['config']->set('blueprint.use_constraints', false);
$app['config']->set('blueprint.fake_nullables', true);
$app['config']->set('blueprint.generate_resource_collection_classes', true);
$app['config']->set('database.default', 'testing');
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<?php

namespace App\Http\Controllers;

use App\Http\Requests\PostStoreRequest;
use App\Http\Requests\PostUpdateRequest;
use App\Http\Resources\PostResource;
use App\Models\Post;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\ResourceCollection;
use Illuminate\Http\Response;

class PostController extends Controller
{
public function index(Request $request): ResourceCollection
{
$posts = Post::paginate();

return PostResource::collection($posts);
}

public function store(PostStoreRequest $request): PostResource
{
$post = Post::create($request->validated());

return new PostResource($post);
}

public function show(Request $request, Post $post): PostResource
{
return new PostResource($post);
}

public function update(PostUpdateRequest $request, Post $post): PostResource
{
$post->update($request->validated());

return new PostResource($post);
}

public function destroy(Request $request, Post $post): Response
{
$post->delete();

return response()->noContent();
}
}

0 comments on commit 6e9221b

Please sign in to comment.