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