Skip to content

Commit 83a3cac

Browse files
authored
fix(laravel): use controller if declared (#7687)
1 parent 1706c3c commit 83a3cac

File tree

6 files changed

+162
-3
lines changed

6 files changed

+162
-3
lines changed

src/Laravel/ApiPlatformProvider.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@
7979
use ApiPlatform\Laravel\ApiResource\ValidationError;
8080
use ApiPlatform\Laravel\Controller\DocumentationController;
8181
use ApiPlatform\Laravel\Controller\EntrypointController;
82+
use ApiPlatform\Laravel\Controller\NotExposedController;
8283
use ApiPlatform\Laravel\Eloquent\Filter\JsonApi\SortFilterParameterProvider;
8384
use ApiPlatform\Laravel\Eloquent\Metadata\Factory\Property\EloquentAttributePropertyMetadataFactory;
8485
use ApiPlatform\Laravel\Eloquent\Metadata\Factory\Property\EloquentPropertyMetadataFactory;
@@ -668,6 +669,12 @@ public function register(): void
668669
);
669670
});
670671

672+
// Note: this class is not included it is used as a string alias as Errors declare this controller
673+
$this->app->alias(\ApiPlatform\Symfony\Action\NotExposedAction::class, 'api_platform.action.not_exposed');
674+
$this->app->singleton('api_platform.action.not_exposed', function () {
675+
return new NotExposedController();
676+
});
677+
671678
$this->app->singleton(DocumentationController::class, function (Application $app) {
672679
/** @var ConfigRepository */
673680
$config = $app['config'];
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the API Platform project.
5+
*
6+
* (c) Kévin Dunglas <dunglas@gmail.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
declare(strict_types=1);
13+
14+
namespace ApiPlatform\Laravel\Controller;
15+
16+
use ApiPlatform\Metadata\Exception\NotExposedHttpException;
17+
use Illuminate\Http\Request;
18+
19+
final class NotExposedController
20+
{
21+
/**
22+
* Display a listing of the resource.
23+
*/
24+
public function __invoke(Request $request): never
25+
{
26+
$message = 'This route does not aim to be called.';
27+
if ('api_genid' === $request->attributes->get('_route')) {
28+
$message = 'This route is not exposed on purpose. It generates an IRI for a collection resource without identifier nor item operation.';
29+
}
30+
31+
throw new NotExposedHttpException($message);
32+
}
33+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the API Platform project.
5+
*
6+
* (c) Kévin Dunglas <dunglas@gmail.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
declare(strict_types=1);
13+
14+
namespace ApiPlatform\Laravel\Tests;
15+
16+
use ApiPlatform\Laravel\Test\ApiTestAssertionsTrait;
17+
use Orchestra\Testbench\Concerns\WithWorkbench;
18+
use Orchestra\Testbench\TestCase;
19+
20+
class CustomControllerTest extends TestCase
21+
{
22+
use ApiTestAssertionsTrait;
23+
use WithWorkbench;
24+
25+
public function testCustomController(): void
26+
{
27+
$this->withoutExceptionHandling();
28+
29+
$response = $this->get('/api/with_custom_controller/42', ['Accept' => ['application/ld+json']]);
30+
$response->assertStatus(200);
31+
32+
$data = $response->json();
33+
$this->assertArrayHasKey('id', $data);
34+
$this->assertSame(42, $data['id']);
35+
$this->assertArrayHasKey('name', $data);
36+
$this->assertSame('Custom Controller Response', $data['name']);
37+
$this->assertArrayHasKey('custom', $data);
38+
$this->assertTrue($data['custom']);
39+
}
40+
41+
public function testCustomControllerWithDifferentFormat(): void
42+
{
43+
$response = $this->get('/api/with_custom_controller/123', ['Accept' => ['application/json']]);
44+
$response->assertStatus(200);
45+
46+
$data = $response->json();
47+
$this->assertArrayHasKey('id', $data);
48+
$this->assertSame(123, $data['id']);
49+
$this->assertArrayHasKey('name', $data);
50+
$this->assertSame('Custom Controller Response', $data['name']);
51+
$this->assertTrue($data['custom']);
52+
}
53+
}

src/Laravel/routes/api.php

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,9 @@
2929
$domain = config()->get('api-platform.routes.domain', '');
3030

3131
Route::domain($domain)->middleware($globalMiddlewares)->group(function (): void {
32-
$resourceNameCollectionFactory = app()->make(ResourceNameCollectionFactoryInterface::class);
33-
$resourceMetadataFactory = app()->make(ResourceMetadataCollectionFactoryInterface::class);
32+
$app = app();
33+
$resourceNameCollectionFactory = $app->make(ResourceNameCollectionFactoryInterface::class);
34+
$resourceMetadataFactory = $app->make(ResourceMetadataCollectionFactoryInterface::class);
3435

3536
foreach ($resourceNameCollectionFactory->create() as $resourceClass) {
3637
foreach ($resourceMetadataFactory->create($resourceClass) as $resourceMetadata) {
@@ -46,7 +47,13 @@
4647
$uriTemplate = str_replace('{._format}', '{_format?}', $operation->getUriTemplate());
4748

4849
/* @var HttpOperation $operation */
49-
$route = Route::addRoute($operation->getMethod(), $uriTemplate, ['uses' => ApiPlatformController::class, 'prefix' => $operation->getRoutePrefix() ?? ''])
50+
$controller = $operation->getController() ?? ApiPlatformController::class;
51+
52+
if (!class_exists($controller)) {
53+
$controller = $app->make($controller);
54+
}
55+
56+
$route = Route::addRoute($operation->getMethod(), $uriTemplate, ['uses' => $controller, 'prefix' => $operation->getRoutePrefix() ?? ''])
5057
->where([
5158
'_format' => '^\.[a-zA-Z]+',
5259
] + ($operation->getRequirements() ?? []))
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the API Platform project.
5+
*
6+
* (c) Kévin Dunglas <dunglas@gmail.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
declare(strict_types=1);
13+
14+
namespace Workbench\App\ApiResource;
15+
16+
use ApiPlatform\Metadata\Get;
17+
use Workbench\App\Http\Controllers\WithCustomControllerController;
18+
19+
#[Get(uriTemplate: '/with_custom_controller/{id}', controller: WithCustomControllerController::class)]
20+
class WithCustomController
21+
{
22+
public function __construct(public ?int $id = null, public ?string $name = null)
23+
{
24+
}
25+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the API Platform project.
5+
*
6+
* (c) Kévin Dunglas <dunglas@gmail.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
declare(strict_types=1);
13+
14+
namespace Workbench\App\Http\Controllers;
15+
16+
use Illuminate\Http\JsonResponse;
17+
use Illuminate\Http\Request;
18+
use Illuminate\Routing\Controller;
19+
20+
class WithCustomControllerController extends Controller
21+
{
22+
public function __invoke(Request $request): JsonResponse
23+
{
24+
$id = (int) $request->route('id');
25+
26+
// Plain controller - no API Platform features
27+
// Just return whatever you want
28+
return new JsonResponse([
29+
'id' => $id,
30+
'name' => 'Custom Controller Response',
31+
'custom' => true,
32+
]);
33+
}
34+
}

0 commit comments

Comments
 (0)