Skip to content

Commit 0c66a49

Browse files
authored
fix(laravel): cache metadata, add trace on debug mode (#6555)
1 parent a1dd0b5 commit 0c66a49

File tree

8 files changed

+311
-86
lines changed

8 files changed

+311
-86
lines changed

src/Laravel/ApiPlatformProvider.php

Lines changed: 161 additions & 83 deletions
Large diffs are not rendered by default.

src/Laravel/ApiResource/Error.php

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,11 @@
5959
)]
6060
class Error extends \Exception implements ProblemExceptionInterface, HttpExceptionInterface
6161
{
62+
/**
63+
* @var array<int, mixed>
64+
*/
65+
private array $originalTrace;
66+
6267
/**
6368
* @param array<string, string> $headers
6469
* @param array<int, mixed> $originalTrace
@@ -67,12 +72,18 @@ public function __construct(
6772
private readonly string $title,
6873
private readonly string $detail,
6974
#[ApiProperty(identifier: true)] private int $status,
70-
private readonly array $originalTrace,
75+
array $originalTrace,
7176
private readonly ?string $instance = null,
7277
private string $type = 'about:blank',
7378
private array $headers = []
7479
) {
7580
parent::__construct();
81+
82+
$this->originalTrace = [];
83+
foreach ($originalTrace as $i => $t) {
84+
unset($t['args']); // we don't want arguments in our JSON traces, especially with xdebug
85+
$this->originalTrace[$i] = $t;
86+
}
7687
}
7788

7889
/**

src/Laravel/Exception/ErrorHandler.php

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,8 @@ public function __construct(
5050
private readonly ?IdentifiersExtractorInterface $identifiersExtractor = null,
5151
private readonly ?ResourceClassResolverInterface $resourceClassResolver = null,
5252
?Negotiator $negotiator = null,
53-
private readonly ?array $exceptionToStatus = null
53+
private readonly ?array $exceptionToStatus = null,
54+
private readonly ?bool $debug = false
5455
) {
5556
$this->resourceMetadataCollectionFactory = $resourceMetadataCollectionFactory;
5657
$this->negotiator = $negotiator;
@@ -135,7 +136,7 @@ public function register(): void
135136
}
136137

137138
if (!isset($normalizationContext[AbstractObjectNormalizer::IGNORED_ATTRIBUTES])) {
138-
$normalizationContext[AbstractObjectNormalizer::IGNORED_ATTRIBUTES] = ['trace', 'file', 'line', 'code', 'message', 'originalTrace'];
139+
$normalizationContext[AbstractObjectNormalizer::IGNORED_ATTRIBUTES] = true === $this->debug ? [] : ['originalTrace'];
139140
}
140141

141142
$operation = $operation->withNormalizationContext($normalizationContext);
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the API Platform project.
5+
*
6+
* (c) Kévin Dunglas <[email protected]>
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\Metadata;
15+
16+
use ApiPlatform\Metadata\ApiProperty;
17+
use ApiPlatform\Metadata\Property\Factory\PropertyMetadataFactoryInterface;
18+
use Illuminate\Support\Facades\Cache;
19+
20+
final readonly class CachePropertyMetadataFactory implements PropertyMetadataFactoryInterface
21+
{
22+
public function __construct(
23+
private PropertyMetadataFactoryInterface $decorated,
24+
private string $cacheStore
25+
) {
26+
}
27+
28+
public function create(string $resourceClass, string $property, array $options = []): ApiProperty
29+
{
30+
$key = hash('xxh3', serialize(['resource_class' => $resourceClass, 'property' => $property] + $options));
31+
32+
return Cache::store($this->cacheStore)->rememberForever($key, function () use ($resourceClass, $property, $options) {
33+
return $this->decorated->create($resourceClass, $property, $options);
34+
});
35+
}
36+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the API Platform project.
5+
*
6+
* (c) Kévin Dunglas <[email protected]>
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\Metadata;
15+
16+
use ApiPlatform\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface;
17+
use ApiPlatform\Metadata\Property\PropertyNameCollection;
18+
use Illuminate\Support\Facades\Cache;
19+
20+
final readonly class CachePropertyNameCollectionMetadataFactory implements PropertyNameCollectionFactoryInterface
21+
{
22+
public function __construct(
23+
private PropertyNameCollectionFactoryInterface $decorated,
24+
private string $cacheStore
25+
) {
26+
}
27+
28+
public function create(string $resourceClass, array $options = []): PropertyNameCollection
29+
{
30+
$key = hash('xxh3', serialize(['resource_class' => $resourceClass] + $options));
31+
32+
return Cache::store($this->cacheStore)->rememberForever($key, function () use ($resourceClass, $options) {
33+
return $this->decorated->create($resourceClass, $options);
34+
});
35+
}
36+
}
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 <[email protected]>
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\Metadata;
15+
16+
use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface;
17+
use ApiPlatform\Metadata\Resource\ResourceMetadataCollection;
18+
use Illuminate\Support\Facades\Cache;
19+
20+
final readonly class CacheResourceCollectionMetadataFactory implements ResourceMetadataCollectionFactoryInterface
21+
{
22+
public function __construct(
23+
private ResourceMetadataCollectionFactoryInterface $decorated,
24+
private string $cacheStore
25+
) {
26+
}
27+
28+
public function create(string $resourceClass): ResourceMetadataCollection
29+
{
30+
return Cache::store($this->cacheStore)->rememberForever($resourceClass, function () use ($resourceClass) {
31+
return $this->decorated->create($resourceClass);
32+
});
33+
}
34+
}

src/Laravel/Tests/JsonLdTest.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -291,4 +291,12 @@ public function testVisible(): void
291291
$response->assertHeader('content-type', 'application/ld+json; charset=utf-8');
292292
$this->assertStringNotContainsString('internalNote', (string) $response->getContent());
293293
}
294+
295+
public function testError(): void
296+
{
297+
$response = $this->post('/api/books', ['content-type' => 'application/vnd.api+json']);
298+
$response->assertStatus(415);
299+
$content = $response->json();
300+
$this->assertArrayHasKey('trace', $content);
301+
}
294302
}

src/Laravel/Tests/JsonProblemTest.php

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,9 @@
1313

1414
namespace ApiPlatform\Laravel\Tests;
1515

16+
use Illuminate\Foundation\Application;
1617
use Illuminate\Foundation\Testing\RefreshDatabase;
18+
use Orchestra\Testbench\Attributes\DefineEnvironment;
1719
use Orchestra\Testbench\Concerns\WithWorkbench;
1820
use Orchestra\Testbench\TestCase;
1921

@@ -37,4 +39,23 @@ public function testNotFound(): void
3739
'detail' => 'Not Found',
3840
]);
3941
}
42+
43+
/**
44+
* @param Application $app
45+
*/
46+
protected function useProductionMode($app): void
47+
{
48+
$app['config']->set('app.debug', false);
49+
}
50+
51+
#[DefineEnvironment('useProductionMode')]
52+
public function testProductionError(): void
53+
{
54+
$response = $this->post('/api/books', ['content-type' => 'application/vnd.api+json']);
55+
$response->assertStatus(415);
56+
$content = $response->json();
57+
$this->assertArrayNotHasKey('trace', $content);
58+
$this->assertArrayNotHasKey('line', $content);
59+
$this->assertArrayNotHasKey('file', $content);
60+
}
4061
}

0 commit comments

Comments
 (0)