Passed
Pull Request — master (#2)
by Alex
01:58
created

AbstractJoin::getAssociationMapping()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 11
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
eloc 9
c 1
b 0
f 0
nc 2
nop 2
dl 0
loc 11
rs 9.9666
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
    public const JOIN_INNER = 'innerJoin';
26
    public const JOIN_LEFT = 'leftJoin';
27
28
    /**
29
     * @param QueryBuilderInterface      $queryBuilder
30
     * @param string                     $fieldName
31
     * @param string                     $alias
32
     * @param null|string|Composite|Base $condition
33
     * @param string                     $joinType
34
     * @param string|null                $indexBy
35
     */
36
    abstract protected function applyJoin(
37
        QueryBuilderInterface $queryBuilder,
38
        string $fieldName,
39
        string $alias,
40
        $condition = null,
41
        string $joinType = Join::WITH,
42
        ?string $indexBy = null
43
    ): void;
44
45
    /**
46
     * @param QueryBuilderInterface $queryBuilder
47
     * @param MetadataInterface     $metadata
48
     * @param array                 $criteria
49
     *
50
     * @throws InvalidArgumentException
51
     * @throws QueryFilterException
52
     */
53
    public function filter(QueryBuilderInterface $queryBuilder, MetadataInterface $metadata, array $criteria): void
54
    {
55
        $fieldName = $this->resolveFieldName($criteria);
56
        $mapping = $this->getAssociationMapping($metadata, $fieldName);
57
58
        $alias = $criteria['alias'] ?? null;
59
        if (null === $alias) {
60
            throw new InvalidArgumentException(
61
                sprintf('The required \'alias\' criteria value is missing for filter \'%s\'', static::class)
62
            );
63
        }
64
65
        $conditions = $criteria['conditions'] ?? [];
66
        $condition = null;
67
68
        if (is_object($conditions)) {
69
            $condition = $conditions;
70
        } elseif (is_array($conditions) && !empty($conditions)) {
71
            $tempQueryBuilder = $queryBuilder->createQueryBuilder();
72
            $this->filterJoinConditions($tempQueryBuilder, $mapping['targetEntity'], $alias, $conditions);
73
            $condition = $this->mergeJoinConditions($queryBuilder, $tempQueryBuilder);
74
        }
75
76
        $parentAlias = $criteria['parent_alias'] ?? 'entity';
77
        $this->applyJoin(
78
            $queryBuilder,
79
            $parentAlias . '.' . $fieldName,
80
            $alias,
81
            $condition,
82
            $criteria['join_type'] ?? Join::WITH,
83
            $criteria['index_by'] ?? null
84
        );
85
    }
86
87
    /**
88
     * @param array  $conditions
89
     * @param string $alias
90
     *
91
     * @return array
92
     */
93
    private function createJoinFilters(array $conditions, string $alias): array
94
    {
95
        // Use the join alias as the default alias for conditions
96
        foreach ($conditions as $index => $condition) {
97
            if (is_array($condition) && empty($condition['alias'])) {
98
                $conditions[$index]['alias'] = $alias;
99
            }
100
        }
101
102
        return [
103
            'filters' => [
104
                [
105
                    'name'       => AndX::class,
106
                    'conditions' => $conditions,
107
                    'where'      => $criteria['filters']['where'] ?? null,
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $criteria seems to never exist and therefore isset should always be false.
Loading history...
108
                ],
109
            ],
110
        ];
111
    }
112
113
    /**
114
     * @param MetadataInterface $metadata
115
     * @param string            $fieldName
116
     *
117
     * @return array
118
     *
119
     * @throws InvalidArgumentException
120
     */
121
    private function getAssociationMapping(MetadataInterface $metadata, string $fieldName): array
122
    {
123
        try {
124
            return $metadata->getAssociationFiledMapping($fieldName);
125
        } catch (MappingException $e) {
126
            throw new InvalidArgumentException(
127
                sprintf(
128
                    'Failed to load association field mapping for field \'%s::%s\' in filter \'%s\'',
129
                    $metadata->getName(),
130
                    $fieldName,
131
                    static::class
132
                )
133
            );
134
        }
135
    }
136
137
    /**
138
     * @param QueryBuilderInterface $qb
139
     * @param string                $targetEntity
140
     * @param string                $alias
141
     * @param array                 $conditions
142
     *
143
     * @throws QueryFilterException
144
     */
145
    private function filterJoinConditions(
146
        QueryBuilderInterface $qb,
147
        string $targetEntity,
148
        string $alias,
149
        array $conditions
150
    ): void {
151
        try {
152
            $this->queryFilterManager->filter(
153
                $qb,
154
                $targetEntity,
155
                $this->createJoinFilters($conditions, $alias)
156
            );
157
        } catch (QueryFilterManagerException $e) {
158
            throw new QueryFilterException(
159
                sprintf(
160
                    'Failed to apply query filter \'%s\' conditions for target entity \'%s\': %s',
161
                    static::class,
162
                    $targetEntity,
163
                    $e->getMessage()
164
                ),
165
                $e->getCode(),
166
                $e
167
            );
168
        }
169
    }
170
171
    /**
172
     * @param QueryBuilderInterface $queryBuilder
173
     * @param QueryBuilderInterface $qb
174
     *
175
     * @return DoctrineAndX|DoctrineOrX|null
176
     */
177
    private function mergeJoinConditions(QueryBuilderInterface $queryBuilder, QueryBuilderInterface $qb): ?Composite
178
    {
179
        $parts = $qb->getQueryParts();
180
181
        if (!isset($parts['where'])) {
182
            return null;
183
        }
184
185
        if ($parts['where'] instanceof DoctrineAndx) {
186
            $condition = $queryBuilder->expr()->andX();
187
        } elseif ($parts['where'] instanceof DoctrineOrX) {
188
            $condition = $queryBuilder->expr()->orX();
189
        } else {
190
            return null;
191
        }
192
193
        $condition->addMultiple($parts['where']->getParts());
194
        $queryBuilder->mergeParameters($qb);
195
196
        return $condition;
197
    }
198
}
199