Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
76 / 76
100.00% covered (success)
100.00%
7 / 7
CRAP
100.00% covered (success)
100.00%
1 / 1
EntityQuery
100.00% covered (success)
100.00%
76 / 76
100.00% covered (success)
100.00%
7 / 7
24
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
15 / 15
100.00% covered (success)
100.00%
1 / 1
13
 getWithoutPagination
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
1
 __toString
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 generateJoins
100.00% covered (success)
100.00%
15 / 15
100.00% covered (success)
100.00%
1 / 1
2
 generateWhere
100.00% covered (success)
100.00%
13 / 13
100.00% covered (success)
100.00%
1 / 1
2
 generateOrderBy
100.00% covered (success)
100.00%
17 / 17
100.00% covered (success)
100.00%
1 / 1
4
 generateOffset
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2namespace Apie\DoctrineEntityDatalayer\Query;
3
4use Apie\Core\BoundedContext\BoundedContextId;
5use Apie\Core\Datalayers\Search\QuerySearch;
6use Apie\Core\Entities\EntityInterface;
7use Apie\Core\IdentifierUtils;
8use Apie\DoctrineEntityDatalayer\Enums\SortingOrder;
9use Doctrine\ORM\EntityManagerInterface;
10use ReflectionClass;
11use Stringable;
12
13final class EntityQuery implements Stringable
14{
15    /** @var array<int, EntityQueryFilterInterface> */
16    private array $filters = [];
17    /** @var array<int, EntityQueryFilterInterface&AddsJoinFilterInterface> */
18    private array $joinFilters = [];
19    /** @var array<int, OrderByFilterInterface> */
20    private array $orderByFilters = [];
21
22    /**
23     * @param ReflectionClass<EntityInterface> $entityClass
24     */
25    public function __construct(
26        private readonly EntityManagerInterface $entityManager,
27        private readonly ReflectionClass $entityClass,
28        private readonly BoundedContextId $boundedContextId,
29        private readonly QuerySearch $querySearch,
30        EntityQueryFilterInterface... $filters
31    ) {
32        $searches = $querySearch->getSearches();
33        $orderBy = $this->querySearch->getOrderBy();
34        foreach ($filters as $filter) {
35            $name = is_callable([$filter, 'getFilterName']) ? $filter->getFilterName() : '';
36            if ($filter instanceof TextSearchFilterInterface && $querySearch->getTextSearch()) {
37            } elseif ($filter instanceof FieldSearchFilterInterface && !empty($searches[$name])) {
38            } elseif ($filter instanceof DefaultOptionFilter) {
39            } elseif ($filter instanceof OrderByFilterInterface && !empty($orderBy[$name])) {
40            } elseif ($filter instanceof RequiresPermissionFilter) {
41            } else {
42                continue;
43            }
44            $this->filters[] = $filter;
45            if ($filter instanceof AddsJoinFilterInterface) {
46                $this->joinFilters[] = $filter;
47            }
48            if ($filter instanceof OrderByFilterInterface) {
49                $this->orderByFilters[] = $filter;
50            }
51        }
52    }
53
54    public function getWithoutPagination(): string
55    {
56        return sprintf(
57            "SELECT DISTINCT entity.*
58            FROM apie_resource__%s_%s entity%s
59%s
60GROUP BY entity.id
61ORDER BY %s",
62            $this->boundedContextId,
63            IdentifierUtils::classNameToUnderscore($this->entityClass),
64            $this->generateJoins(),
65            $this->generateWhere(),
66            $this->generateOrderBy(),
67        );
68    }
69
70    public function __toString(): string
71    {
72        return $this->getWithoutPagination() . PHP_EOL . $this->generateOffset();
73    }
74    private function generateJoins(): string
75    {
76        if (empty($this->joinFilters)) {
77            return '';
78        }
79        $connection = $this->entityManager->getConnection();
80        $joinSql = implode(
81            PHP_EOL,
82            array_filter(
83                array_map(
84                    function (EntityQueryFilterInterface&AddsJoinFilterInterface $filter) use ($connection) {
85                        return $filter->createJoinQuery($this->querySearch, $connection);
86                    },
87                    $this->joinFilters
88                )
89            )
90        );
91        return PHP_EOL . $joinSql;
92    }
93
94    private function generateWhere(): string
95    {
96        if (empty($this->filters)) {
97            return '';
98        }
99        $connection = $this->entityManager->getConnection();
100        $whereSql = implode(
101            ')' . PHP_EOL . 'AND (',
102            array_map(
103                function (EntityQueryFilterInterface $filter) use ($connection) {
104                    return $filter->getWhereCondition($this->querySearch, $connection);
105                },
106                $this->filters
107            )
108        );
109        return 'WHERE (' . $whereSql . ')';
110    }
111
112    private function generateOrderBy(): string
113    {
114        $orderBy = $this->querySearch->getOrderBy()->toArray();
115        $query = implode(
116            ', ',
117            array_filter(
118                array_map(
119                    function (OrderByFilterInterface $filter) use ($orderBy) {
120                        if (is_callable([$filter, 'getFilterName'])) {
121                            $filterName = $filter->getFilterName();
122                            if (isset($orderBy[$filterName])) {
123                                return $filter->getOrderByCode(SortingOrder::from($orderBy[$filterName]));
124                            }
125                        }
126                        return $filter->getOrderByCode(SortingOrder::DESCENDING);
127                    },
128                    $this->orderByFilters
129                )
130            )
131        );
132        return empty($query) ? 'entity.created_at DESC, entity.id DESC' : $query;
133    }
134
135    private function generateOffset(): string
136    {
137        $platform = $this->entityManager->getConnection()->getDatabasePlatform();
138
139        return $platform->modifyLimitQuery(
140            '',
141            $this->querySearch->getItemsPerPage(),
142            ($this->querySearch->getPageIndex() * $this->querySearch->getItemsPerPage()),
143        );
144    }
145}