Skip to content

Commit

Permalink
fix(laravel): cache metadata, add trace on debug mode (#6555)
Browse files Browse the repository at this point in the history
  • Loading branch information
soyuka authored Aug 29, 2024
1 parent a1dd0b5 commit 0c66a49
Show file tree
Hide file tree
Showing 8 changed files with 311 additions and 86 deletions.
244 changes: 161 additions & 83 deletions src/Laravel/ApiPlatformProvider.php

Large diffs are not rendered by default.

13 changes: 12 additions & 1 deletion src/Laravel/ApiResource/Error.php
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,11 @@
)]
class Error extends \Exception implements ProblemExceptionInterface, HttpExceptionInterface
{
/**
* @var array<int, mixed>
*/
private array $originalTrace;

/**
* @param array<string, string> $headers
* @param array<int, mixed> $originalTrace
Expand All @@ -67,12 +72,18 @@ public function __construct(
private readonly string $title,
private readonly string $detail,
#[ApiProperty(identifier: true)] private int $status,
private readonly array $originalTrace,
array $originalTrace,
private readonly ?string $instance = null,
private string $type = 'about:blank',
private array $headers = []
) {
parent::__construct();

$this->originalTrace = [];
foreach ($originalTrace as $i => $t) {
unset($t['args']); // we don't want arguments in our JSON traces, especially with xdebug
$this->originalTrace[$i] = $t;
}
}

/**
Expand Down
5 changes: 3 additions & 2 deletions src/Laravel/Exception/ErrorHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,8 @@ public function __construct(
private readonly ?IdentifiersExtractorInterface $identifiersExtractor = null,
private readonly ?ResourceClassResolverInterface $resourceClassResolver = null,
?Negotiator $negotiator = null,
private readonly ?array $exceptionToStatus = null
private readonly ?array $exceptionToStatus = null,
private readonly ?bool $debug = false
) {
$this->resourceMetadataCollectionFactory = $resourceMetadataCollectionFactory;
$this->negotiator = $negotiator;
Expand Down Expand Up @@ -135,7 +136,7 @@ public function register(): void
}

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

$operation = $operation->withNormalizationContext($normalizationContext);
Expand Down
36 changes: 36 additions & 0 deletions src/Laravel/Metadata/CachePropertyMetadataFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php

/*
* This file is part of the API Platform project.
*
* (c) Kévin Dunglas <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace ApiPlatform\Laravel\Metadata;

use ApiPlatform\Metadata\ApiProperty;
use ApiPlatform\Metadata\Property\Factory\PropertyMetadataFactoryInterface;
use Illuminate\Support\Facades\Cache;

final readonly class CachePropertyMetadataFactory implements PropertyMetadataFactoryInterface
{
public function __construct(
private PropertyMetadataFactoryInterface $decorated,
private string $cacheStore
) {
}

public function create(string $resourceClass, string $property, array $options = []): ApiProperty
{
$key = hash('xxh3', serialize(['resource_class' => $resourceClass, 'property' => $property] + $options));

return Cache::store($this->cacheStore)->rememberForever($key, function () use ($resourceClass, $property, $options) {
return $this->decorated->create($resourceClass, $property, $options);
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php

/*
* This file is part of the API Platform project.
*
* (c) Kévin Dunglas <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace ApiPlatform\Laravel\Metadata;

use ApiPlatform\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface;
use ApiPlatform\Metadata\Property\PropertyNameCollection;
use Illuminate\Support\Facades\Cache;

final readonly class CachePropertyNameCollectionMetadataFactory implements PropertyNameCollectionFactoryInterface
{
public function __construct(
private PropertyNameCollectionFactoryInterface $decorated,
private string $cacheStore
) {
}

public function create(string $resourceClass, array $options = []): PropertyNameCollection
{
$key = hash('xxh3', serialize(['resource_class' => $resourceClass] + $options));

return Cache::store($this->cacheStore)->rememberForever($key, function () use ($resourceClass, $options) {
return $this->decorated->create($resourceClass, $options);
});
}
}
34 changes: 34 additions & 0 deletions src/Laravel/Metadata/CacheResourceCollectionMetadataFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php

/*
* This file is part of the API Platform project.
*
* (c) Kévin Dunglas <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace ApiPlatform\Laravel\Metadata;

use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface;
use ApiPlatform\Metadata\Resource\ResourceMetadataCollection;
use Illuminate\Support\Facades\Cache;

final readonly class CacheResourceCollectionMetadataFactory implements ResourceMetadataCollectionFactoryInterface
{
public function __construct(
private ResourceMetadataCollectionFactoryInterface $decorated,
private string $cacheStore
) {
}

public function create(string $resourceClass): ResourceMetadataCollection
{
return Cache::store($this->cacheStore)->rememberForever($resourceClass, function () use ($resourceClass) {
return $this->decorated->create($resourceClass);
});
}
}
8 changes: 8 additions & 0 deletions src/Laravel/Tests/JsonLdTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -291,4 +291,12 @@ public function testVisible(): void
$response->assertHeader('content-type', 'application/ld+json; charset=utf-8');
$this->assertStringNotContainsString('internalNote', (string) $response->getContent());
}

public function testError(): void
{
$response = $this->post('/api/books', ['content-type' => 'application/vnd.api+json']);
$response->assertStatus(415);
$content = $response->json();
$this->assertArrayHasKey('trace', $content);
}
}
21 changes: 21 additions & 0 deletions src/Laravel/Tests/JsonProblemTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@

namespace ApiPlatform\Laravel\Tests;

use Illuminate\Foundation\Application;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Orchestra\Testbench\Attributes\DefineEnvironment;
use Orchestra\Testbench\Concerns\WithWorkbench;
use Orchestra\Testbench\TestCase;

Expand All @@ -37,4 +39,23 @@ public function testNotFound(): void
'detail' => 'Not Found',
]);
}

/**
* @param Application $app
*/
protected function useProductionMode($app): void
{
$app['config']->set('app.debug', false);
}

#[DefineEnvironment('useProductionMode')]
public function testProductionError(): void
{
$response = $this->post('/api/books', ['content-type' => 'application/vnd.api+json']);
$response->assertStatus(415);
$content = $response->json();
$this->assertArrayNotHasKey('trace', $content);
$this->assertArrayNotHasKey('line', $content);
$this->assertArrayNotHasKey('file', $content);
}
}

0 comments on commit 0c66a49

Please sign in to comment.