Skip to content

Commit 91ffbe9

Browse files
Fix ArrayFilter crashing when the filtered property is not a displayed field
ArrayFilter::apply() dereferenced its FieldDto argument without checking it for null, even though the parameter is nullable and EntityRepository passes null whenever the filtered property is not configured as a field of the current page. Submitting such a filter caused a 500 error.
1 parent 8205de9 commit 91ffbe9

2 files changed

Lines changed: 72 additions & 1 deletion

File tree

src/Filter/ArrayFilter.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,9 @@ public function apply(QueryBuilder $queryBuilder, FilterDataDto $filterDataDto,
7171
$parameterName = $filterDataDto->getParameterName();
7272
$value = $filterDataDto->getValue();
7373

74-
$useQuotes = Types::SIMPLE_ARRAY === $fieldDto->getDoctrineMetadata()->get('type');
74+
// the filtered property is not necessarily configured as a displayed field
75+
// of the current page, so $fieldDto can be null
76+
$useQuotes = Types::SIMPLE_ARRAY === $fieldDto?->getDoctrineMetadata()->get('type');
7577

7678
if (null === $value || [] === $value) {
7779
$queryBuilder->andWhere(sprintf('%s.%s %s', $alias, $property, $comparison));
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
<?php
2+
3+
namespace EasyCorp\Bundle\EasyAdminBundle\Tests\Unit\Filter;
4+
5+
use Doctrine\DBAL\Types\Types;
6+
use Doctrine\ORM\EntityManagerInterface;
7+
use Doctrine\ORM\Mapping\ClassMetadata;
8+
use Doctrine\ORM\QueryBuilder;
9+
use EasyCorp\Bundle\EasyAdminBundle\Dto\EntityDto;
10+
use EasyCorp\Bundle\EasyAdminBundle\Dto\FilterDataDto;
11+
use EasyCorp\Bundle\EasyAdminBundle\Field\TextField;
12+
use EasyCorp\Bundle\EasyAdminBundle\Filter\ArrayFilter;
13+
use EasyCorp\Bundle\EasyAdminBundle\Form\Type\ComparisonType;
14+
use PHPUnit\Framework\TestCase;
15+
16+
class ArrayFilterTest extends TestCase
17+
{
18+
public function testApplyWithoutFieldDto(): void
19+
{
20+
// the filtered property is not necessarily configured as a displayed field of the
21+
// current page, so the FieldDto argument can be null (see EntityRepository::addFilterClause())
22+
$queryBuilder = $this->createQueryBuilder();
23+
$filterDataDto = $this->createFilterDataDto(['foo']);
24+
25+
ArrayFilter::new('tags')->apply($queryBuilder, $filterDataDto, null, $this->createEntityDto());
26+
27+
$parameters = $queryBuilder->getParameters();
28+
$this->assertCount(1, $parameters);
29+
$this->assertSame('%foo%', $parameters->first()->getValue());
30+
}
31+
32+
public function testApplyWithSimpleArrayFieldUsesQuotedPattern(): void
33+
{
34+
$queryBuilder = $this->createQueryBuilder();
35+
$filterDataDto = $this->createFilterDataDto(['foo']);
36+
37+
$fieldDto = TextField::new('tags')->getAsDto();
38+
$fieldDto->setDoctrineMetadata(['type' => Types::SIMPLE_ARRAY]);
39+
40+
ArrayFilter::new('tags')->apply($queryBuilder, $filterDataDto, $fieldDto, $this->createEntityDto());
41+
42+
$parameters = $queryBuilder->getParameters();
43+
$this->assertCount(1, $parameters);
44+
$this->assertSame('%"foo"%', $parameters->first()->getValue());
45+
}
46+
47+
private function createQueryBuilder(): QueryBuilder
48+
{
49+
return new QueryBuilder($this->createMock(EntityManagerInterface::class));
50+
}
51+
52+
/**
53+
* @param list<string> $value
54+
*/
55+
private function createFilterDataDto(array $value): FilterDataDto
56+
{
57+
$filterDto = ArrayFilter::new('tags')->getAsDto();
58+
59+
return FilterDataDto::new(0, $filterDto, 'entity', [
60+
'comparison' => ComparisonType::CONTAINS,
61+
'value' => $value,
62+
]);
63+
}
64+
65+
private function createEntityDto(): EntityDto
66+
{
67+
return new EntityDto(\stdClass::class, new ClassMetadata(\stdClass::class));
68+
}
69+
}

0 commit comments

Comments
 (0)