Skip to content
16 changes: 16 additions & 0 deletions src/Dto/EntityDto.php
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,22 @@ public function isToManyAssociation(string $propertyName): bool
return \in_array($associationType, [ClassMetadataInfo::ONE_TO_MANY, ClassMetadataInfo::MANY_TO_MANY], true);
}

public function getEmbeddedTargetClassName(string $propertyName): bool
{
return $this->getEmbeddedPropertyMetadata($propertyName)['class'];
}

public function getEmbeddedPropertyMetadata(string $propertyName): array
{
if (!$this->isEmbeddedClassProperty($propertyName)) {
throw new \LogicException(sprintf('The property "%s" is not an embedded class property of class "%s".', $propertyName, $this->getFqcn()));
}

$propertyNameParts = explode('.', $propertyName, 2);

return $this->metadata->embeddedClasses[$propertyNameParts[0]];
}

public function isEmbeddedClassProperty(string $propertyName): bool
{
$propertyNameParts = explode('.', $propertyName, 2);
Expand Down
50 changes: 47 additions & 3 deletions src/Factory/FilterFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
final class FilterFactory
{
private $adminContextProvider;
private $entityFactory;
private $filterConfigurators;
private static $doctrineTypeToFilterClass = [
'json_array' => ArrayFilter::class,
Expand Down Expand Up @@ -52,17 +53,28 @@ final class FilterFactory
Types::TEXT => TextFilter::class,
];

public function __construct(AdminContextProvider $adminContextProvider, iterable $filterConfigurators)
public function __construct(AdminContextProvider $adminContextProvider, EntityFactory $entityFactory, iterable $filterConfigurators)
{
$this->adminContextProvider = $adminContextProvider;
$this->entityFactory = $entityFactory;
$this->filterConfigurators = $filterConfigurators;
}

public function create(FilterConfigDto $filterConfig, FieldCollection $fields, EntityDto $entityDto): FilterCollection
{
$builtFilters = [];
$filters = $filterConfig->all();

/** @var FilterInterface|string|array $filter */
foreach ($filters as $key => $filter) {
if (\is_array($filter)) {
$filters = array_merge($filters, $this->normalizeEmbeddedFilters($key, $filter));
unset($filters[$key]);
}
}

/** @var FilterInterface|string $filter */
foreach ($filterConfig->all() as $property => $filter) {
foreach ($filters as $property => $filter) {
if (\is_string($filter)) {
$guessedFilterClass = $this->guessFilterClass($entityDto, $property);
/** @var FilterInterface $filter */
Expand All @@ -86,12 +98,26 @@ public function create(FilterConfigDto $filterConfig, FieldCollection $fields, E
return FilterCollection::new($builtFilters);
}

private function guessFilterClass(EntityDto $entityDto, string $propertyName): string
private function guessFilterClass(EntityDto $entityDto, string $propertyName, array $context = []): string
{
if ($entityDto->isAssociation($propertyName)) {
return EntityFilter::class;
}

if ($entityDto->isEmbeddedClassProperty($propertyName)) {
$properties = explode('.', $propertyName, 2);
$context['root_entity'] = $context['root_entity'] ?? $entityDto;
$context['root_property'] = $context['root_property'] ?? $propertyName;
$embeddedEntity = $this->entityFactory->create($entityDto->getEmbeddedTargetClassName($propertyName));
$embeddedProperty = $properties[1] ?? null;

if (!$embeddedProperty) {
throw new \LogicException(sprintf('Missing embedded property name for the property "%s" in entity class "%s".', $context['root_property'], $context['root_entity']->getFqcn()));
}

return $this->guessFilterClass($embeddedEntity, $properties, $context);
}

$metadata = $entityDto->getPropertyMetadata($propertyName);

if ($metadata->isEmpty()) {
Expand All @@ -100,4 +126,22 @@ private function guessFilterClass(EntityDto $entityDto, string $propertyName): s

return self::$doctrineTypeToFilterClass[$metadata->get('type')] ?? TextFilter::class;
}

private function normalizeEmbeddedFilters(string $rootPropertyName, array $embeddedFilters = []): array
{
$filters = [];

foreach ($embeddedFilters as $propertyName => $embeddedFilter) {
if (\is_array($embeddedFilter)) {
$filters = array_merge($filters, $this->normalizeEmbeddedFilters("$rootPropertyName.$propertyName", $embeddedFilter));

continue;
}

$embeddedFilter->getAsDto()->setProperty("$rootPropertyName.$propertyName");
$filters["$rootPropertyName.$propertyName"] = $embeddedFilter;
}

return $filters;
}
}
22 changes: 22 additions & 0 deletions src/Factory/FormFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,28 @@ public function createNewForm(EntityDto $entityDto, KeyValueStore $formOptions,

public function createFiltersForm(FilterCollection $filters, Request $request): FormInterface
{
// To avoid errors on embedded class property fields
foreach ($filters as $filter) {
if (false !== strpos($filter->getProperty(), '.')) {
$normalizedFilterName = str_replace('.', '_', $filter->getProperty());
$propertyPath = $filter->getFormTypeOption('property_path');

if (!$propertyPath) {
// The property accessor sets values on array.
// So we must replace object path to array path.
$paths = explode('.', $filter->getProperty());
foreach ($paths as $key => $path) {
$paths[$key] = "[$path]";
}

// We set the property path as form option
$filter->setFormTypeOption('property_path', implode('', $paths));
}

$filter->setProperty($normalizedFilterName);
}
}

$filtersForm = $this->symfonyFormFactory->createNamed('filters', FiltersFormType::class, null, [
'method' => 'GET',
'action' => $request->query->get(EA::REFERRER, ''),
Expand Down
20 changes: 19 additions & 1 deletion src/Orm/EntityRepository.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
use EasyCorp\Bundle\EasyAdminBundle\Contracts\Orm\EntityRepositoryInterface;
use EasyCorp\Bundle\EasyAdminBundle\Dto\EntityDto;
use EasyCorp\Bundle\EasyAdminBundle\Dto\FilterDataDto;
use EasyCorp\Bundle\EasyAdminBundle\Dto\FilterDto;
use EasyCorp\Bundle\EasyAdminBundle\Dto\SearchDto;
use EasyCorp\Bundle\EasyAdminBundle\Factory\EntityFactory;
use EasyCorp\Bundle\EasyAdminBundle\Factory\FormFactory;
Expand Down Expand Up @@ -186,7 +187,7 @@ private function addFilterClause(QueryBuilder $queryBuilder, SearchDto $searchDt
foreach ($filtersForm as $filterForm) {
$propertyName = $filterForm->getName();

$filter = $configuredFilters->get($propertyName);
$filter = $this->resolveFilterDto($configuredFilters, $propertyName);
// this filter is not defined or not applied
if (null === $filter || !isset($appliedFilters[$propertyName])) {
continue;
Expand All @@ -211,4 +212,21 @@ private function addFilterClause(QueryBuilder $queryBuilder, SearchDto $searchDt
++$i;
}
}

private function resolveFilterDto(FilterCollection $configuredFilters, string $propertyName): ?FilterDto
{
$filter = $configuredFilters->get($propertyName);

if (!$filter) {
foreach ($configuredFilters as $filteredPropertyName => $filter) {
if ($propertyName === $filter->getProperty()) {
$filter->setProperty($filteredPropertyName);

return $filter;
}
}
}

return null;
}
}
3 changes: 2 additions & 1 deletion src/Resources/config/services.php
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,8 @@

->set(FilterFactory::class)
->arg(0, new Reference(AdminContextProvider::class))
->arg(1, \function_exists('tagged')
->arg(1, new Reference(EntityFactory::class))
->arg(2, \function_exists('tagged')
? tagged(EasyAdminExtension::TAG_FILTER_CONFIGURATOR)
: tagged_iterator(EasyAdminExtension::TAG_FILTER_CONFIGURATOR))

Expand Down