Passed
Push — master ( c0376c...0b9170 )
by Alex
36s queued 10s
created

AbstractJoin::createJoinFilters()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 14
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 4
eloc 7
c 2
b 0
f 0
nc 3
nop 3
dl 0
loc 14
rs 10
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Arp\LaminasDoctrine\Query\Filter;
6
7
use Arp\LaminasDoctrine\Query\Exception\InvalidArgumentException;
8
use Arp\LaminasDoctrine\Query\Exception\QueryFilterException;
9
use Arp\LaminasDoctrine\Query\Exception\QueryFilterManagerException;
10
use Arp\LaminasDoctrine\Query\Metadata\MetadataInterface;
11
use Arp\LaminasDoctrine\Query\QueryBuilderInterface;
12
use Doctrine\ORM\Mapping\MappingException;
13
use Doctrine\ORM\Query\Expr\Andx as DoctrineAndX;
14
use Doctrine\ORM\Query\Expr\Base;
15
use Doctrine\ORM\Query\Expr\Composite;
16
use Doctrine\ORM\Query\Expr\Join;
17
use Doctrine\ORM\Query\Expr\Orx as DoctrineOrX;
18
19
/**
20
 * @author  Alex Patterson <[email protected]>
21
 * @package Arp\LaminasDoctrine\Query\Filter
22
 */
23
abstract class AbstractJoin extends AbstractFilter
24
{
25
    /**
26
     * @param QueryBuilderInterface      $queryBuilder
27
     * @param string                     $fieldName
28
     * @param string                     $alias
29
     * @param null|string|Composite|Base $condition
30
     * @param string                     $joinType
31
     * @param string|null                $indexBy
32
     */
33
    abstract protected function applyJoin(
34
        QueryBuilderInterface $queryBuilder,
35
        string $fieldName,
36
        string $alias,
37
        $condition = null,
38
        string $joinType = Join::WITH,
39
        ?string $indexBy = null
40
    ): void;
41
42
    /**
43
     * @param QueryBuilderInterface $queryBuilder
44
     * @param MetadataInterface     $metadata
45
     * @param array                 $criteria
46
     *
47
     * @throws InvalidArgumentException
48
     * @throws QueryFilterException
49
     */
50
    public function filter(QueryBuilderInterface $queryBuilder, MetadataInterface $metadata, array $criteria): void
51
    {
52
        $fieldName = $this->resolveFieldName($criteria);
53
        $mapping = $this->getAssociationMapping($metadata, $fieldName);
54
55
        $alias = $criteria['alias'] ?? null;
56
        if (null === $alias) {
57
            throw new InvalidArgumentException(
58
                sprintf('The required \'alias\' criteria value is missing for filter \'%s\'', static::class)
59
            );
60
        }
61
62
        $conditions = $criteria['conditions'] ?? [];
63
        $condition = null;
64
65
        if (is_object($conditions)) {
66
            $condition = $conditions;
67
        } elseif (is_array($conditions) && !empty($conditions)) {
68
            $tempQueryBuilder = $queryBuilder->createQueryBuilder();
69
70
            $this->filterJoinCriteria(
71
                $tempQueryBuilder,
72
                $mapping['targetEntity'],
73
                ['filters' => $this->createJoinFilters($conditions, $alias, $criteria)]
74
            );
75
76
            $condition = $this->mergeJoinConditions($queryBuilder, $tempQueryBuilder);
77
        }
78
79
        $parentAlias = $criteria['parent_alias'] ?? 'entity';
80
        $this->applyJoin(
81
            $queryBuilder,
82
            $parentAlias . '.' . $fieldName,
83
            $alias,
84
            $condition,
85
            $criteria['join_type'] ?? Join::WITH,
86
            $criteria['index_by'] ?? null
87
        );
88
    }
89
90
    /**
91
     * @param MetadataInterface $metadata
92
     * @param string            $fieldName
93
     *
94
     * @return array
95
     *
96
     * @throws InvalidArgumentException
97
     */
98
    private function getAssociationMapping(MetadataInterface $metadata, string $fieldName): array
99
    {
100
        try {
101
            return $metadata->getAssociationFiledMapping($fieldName);
102
        } catch (MappingException $e) {
103
            throw new InvalidArgumentException(
104
                sprintf(
105
                    'Failed to load association field mapping for field \'%s::%s\' in filter \'%s\'',
106
                    $metadata->getName(),
107
                    $fieldName,
108
                    static::class
109
                )
110
            );
111
        }
112
    }
113
114
    /**
115
     * @param QueryBuilderInterface $qb
116
     * @param string                $targetEntity
117
     * @param array                 $criteria
118
     *
119
     * @throws QueryFilterException
120
     */
121
    private function filterJoinCriteria(QueryBuilderInterface $qb, string $targetEntity, array $criteria): void
122
    {
123
        try {
124
            $this->queryFilterManager->filter($qb, $targetEntity, $criteria);
125
        } catch (QueryFilterManagerException $e) {
126
            throw new QueryFilterException(
127
                sprintf(
128
                    'Failed to apply query filter \'%s\' conditions for target entity \'%s\': %s',
129
                    static::class,
130
                    $targetEntity,
131
                    $e->getMessage()
132
                ),
133
                $e->getCode(),
134
                $e
135
            );
136
        }
137
    }
138
139
    /**
140
     * @param array  $conditions
141
     * @param string $alias
142
     * @param array  $criteria
143
     *
144
     * @return array
145
     */
146
    private function createJoinFilters(array $conditions, string $alias, array $criteria): array
147
    {
148
        // Use the join alias as the default alias for conditions
149
        foreach ($conditions as $index => $condition) {
150
            if (is_array($condition) && empty($condition['alias'])) {
151
                $conditions[$index]['alias'] = $alias;
152
            }
153
        }
154
155
        return [
156
            [
157
                'name'       => AndX::class,
158
                'conditions' => $conditions,
159
                'where'      => $criteria['filters']['where'] ?? null,
160
            ],
161
        ];
162
    }
163
164
    /**
165
     * @param QueryBuilderInterface $queryBuilder
166
     * @param QueryBuilderInterface $qb
167
     *
168
     * @return DoctrineAndX|DoctrineOrX|null
169
     */
170
    private function mergeJoinConditions(QueryBuilderInterface $queryBuilder, QueryBuilderInterface $qb): ?Composite
171
    {
172
        $parts = $qb->getQueryParts();
173
174
        if (!isset($parts['where'])) {
175
            return null;
176
        }
177
178
        if ($parts['where'] instanceof DoctrineAndx) {
179
            $condition = $queryBuilder->expr()->andX();
180
        } elseif ($parts['where'] instanceof DoctrineOrX) {
181
            $condition = $queryBuilder->expr()->orX();
182
        } else {
183
            return null;
184
        }
185
186
        $condition->addMultiple($parts['where']->getParts());
187
        $queryBuilder->mergeParameters($qb);
188
189
        return $condition;
190
    }
191
}
192