Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

namespace ApiPlatform\Metadata\Resource\Factory;

use ApiPlatform\Doctrine\Common\Filter\ManagerRegistryAwareInterface;
use ApiPlatform\Doctrine\Common\Filter\PropertyAwareFilterInterface;
use ApiPlatform\Metadata\ApiProperty;
use ApiPlatform\Metadata\Exception\RuntimeException;
Expand Down Expand Up @@ -422,7 +423,16 @@ private function setDefaults(string $key, Parameter $parameter, ?object $filter,
try {
return $this->getLegacyFilterMetadata($parameter, $operation, $filter);
} catch (RuntimeException $exception) {
$this->logger?->alert($exception->getMessage(), ['exception' => $exception]);
// An inline filter instance (e.g. `new NumericFilter()`) passed to a QueryParameter is
// never wired with a ManagerRegistry, unlike a filter resolved through the filter locator
// as a service. Its getDescription() then fails on the Doctrine metadata lookup. This is
// expected and recoverable during cache warmup, so log it at debug level instead of the
// alarming ALERT reserved for genuinely unexpected registry failures.
if ($filter instanceof ManagerRegistryAwareInterface && !$filter->hasManagerRegistry()) {
$this->logger?->debug($exception->getMessage(), ['exception' => $exception]);
} else {
$this->logger?->alert($exception->getMessage(), ['exception' => $exception]);
}

return $parameter;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,11 @@

namespace ApiPlatform\Metadata\Tests\Resource\Factory;

use ApiPlatform\Doctrine\Common\Filter\ManagerRegistryAwareInterface;
use ApiPlatform\Doctrine\Common\Filter\ManagerRegistryAwareTrait;
use ApiPlatform\Metadata\ApiProperty;
use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\Exception\RuntimeException;
use ApiPlatform\Metadata\FilterInterface;
use ApiPlatform\Metadata\GetCollection;
use ApiPlatform\Metadata\Parameters;
Expand All @@ -29,6 +32,7 @@
use ApiPlatform\OpenApi\Model\Parameter;
use PHPUnit\Framework\TestCase;
use Psr\Container\ContainerInterface;
use Psr\Log\LoggerInterface;
use Symfony\Component\Serializer\NameConverter\CamelCaseToSnakeCaseNameConverter;
use Symfony\Component\TypeInfo\Type;

Expand Down Expand Up @@ -460,6 +464,120 @@ public function testSimplePropertyHasNoNestedPropertyInfo(): void
$this->assertNotNull($param);
$this->assertArrayNotHasKey('nested_properties_info', $param->getExtraProperties());
}

/**
* An inline filter instance (e.g. `new NumericFilter()`) passed to a QueryParameter is never wired
* with a ManagerRegistry. Its getDescription() then throws during cache warmup: this is expected and
* recoverable, so it must be logged at debug level rather than the alarming ALERT (see issue #7361).
*/
public function testInlineRegistryAwareFilterFailureIsLoggedAtDebug(): void
{
$nameCollection = $this->createStub(PropertyNameCollectionFactoryInterface::class);
$nameCollection->method('create')->willReturn(new PropertyNameCollection(['id', 'name']));

$propertyMetadata = $this->createStub(PropertyMetadataFactoryInterface::class);
$propertyMetadata->method('create')->willReturn(new ApiProperty(readable: true));

$filterLocator = $this->createStub(ContainerInterface::class);
$filterLocator->method('has')->willReturn(false);

$logger = $this->createMock(LoggerInterface::class);
$logger->expects($this->never())->method('alert');
$logger->expects($this->once())->method('debug');

$parameterFactory = new ParameterResourceMetadataCollectionFactory(
$nameCollection,
$propertyMetadata,
new AttributesResourceMetadataCollectionFactory(),
$filterLocator,
null,
$logger,
);

$parameterFactory->create(HasInlineRegistryAwareFilterParameter::class);
}

/**
* A filter that is not ManagerRegistry-aware (or is already wired) but still throws during
* getDescription() reflects an unexpected condition and must keep the ALERT level.
*/
public function testNonRegistryAwareFilterFailureIsLoggedAtAlert(): void
{
$nameCollection = $this->createStub(PropertyNameCollectionFactoryInterface::class);
$nameCollection->method('create')->willReturn(new PropertyNameCollection(['id', 'name']));

$propertyMetadata = $this->createStub(PropertyMetadataFactoryInterface::class);
$propertyMetadata->method('create')->willReturn(new ApiProperty(readable: true));

$filterLocator = $this->createStub(ContainerInterface::class);
$filterLocator->method('has')->willReturn(false);

$logger = $this->createMock(LoggerInterface::class);
$logger->expects($this->once())->method('alert');
$logger->expects($this->never())->method('debug');

$parameterFactory = new ParameterResourceMetadataCollectionFactory(
$nameCollection,
$propertyMetadata,
new AttributesResourceMetadataCollectionFactory(),
$filterLocator,
null,
$logger,
);

$parameterFactory->create(HasThrowingFilterParameter::class);
}
}

class InlineRegistryAwareThrowingFilter implements FilterInterface, ManagerRegistryAwareInterface
{
use ManagerRegistryAwareTrait;

public function getDescription(string $resourceClass): array
{
// Mimics an unwired Doctrine AbstractFilter reaching the metadata during cache warmup.
$this->getManagerRegistry();

return [];
}
}

class ThrowingFilter implements FilterInterface
{
public function getDescription(string $resourceClass): array
{
throw new RuntimeException('boom');
}
}

#[ApiResource(
operations: [
new GetCollection(
parameters: [
'name' => new QueryParameter(filter: new InlineRegistryAwareThrowingFilter()),
]
),
]
)]
class HasInlineRegistryAwareFilterParameter
{
public $id;
public $name;
}

#[ApiResource(
operations: [
new GetCollection(
parameters: [
'name' => new QueryParameter(filter: new ThrowingFilter()),
]
),
]
)]
class HasThrowingFilterParameter
{
public $id;
public $name;
}

class RepeatedPlaceholderExactFilter implements FilterInterface
Expand Down
Loading