Passed
Push — 2.x ( 0b5227...cb81b7 )
by butschster
16:17
created

ManyToManyLoader::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 19
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 10
nc 1
nop 6
dl 0
loc 19
ccs 6
cts 6
cp 1
crap 1
rs 9.9332
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Cycle\ORM\Select\Loader;
6
7
use Cycle\Database\Injection\Parameter;
8
use Cycle\Database\Query\SelectQuery;
9
use Cycle\ORM\Exception\LoaderException;
10
use Cycle\ORM\FactoryInterface;
11
use Cycle\ORM\Parser\AbstractNode;
12
use Cycle\ORM\Parser\SingularNode;
13
use Cycle\ORM\Service\SourceProviderInterface;
14
use Cycle\ORM\Relation;
15
use Cycle\ORM\SchemaInterface;
16
use Cycle\ORM\Select\JoinableLoader;
17
use Cycle\ORM\Select\LoaderInterface;
18
use Cycle\ORM\Select\Traits\OrderByTrait;
19
use Cycle\ORM\Select\Traits\WhereTrait;
20
21
/**
22
 * @internal
23
 */
24
class ManyToManyLoader extends JoinableLoader
25
{
26
    use OrderByTrait;
27
    use WhereTrait;
28
29
    /**
30
     * Default set of relation options. Child implementation might defined their of default options.
31
     */
32
    protected array $options = [
33
        'load' => false,
34
        'scope' => true,
35
        'method' => self::POSTLOAD,
36
        'minify' => true,
37
        'as' => null,
38
        'using' => null,
39
        'where' => null,
40
        'orderBy' => null,
41
        'pivot' => null,
42
    ];
43
44
    protected PivotLoader $pivot;
45
46 816
    public function __construct(
47
        SchemaInterface $ormSchema,
48
        SourceProviderInterface $sourceProvider,
49
        FactoryInterface $factory,
50
        string $name,
51
        string $target,
52
        array $schema
53
    ) {
54 816
        parent::__construct($ormSchema, $sourceProvider, $factory, $name, $target, $schema);
55 816
        $this->pivot = new PivotLoader(
56
            $ormSchema,
57
            $sourceProvider,
58
            $factory,
59
            'pivot',
60 816
            $schema[Relation::THROUGH_ENTITY],
61
            $schema
62
        );
63 816
        $this->options['where'] = $schema[Relation::WHERE] ?? [];
64 816
        $this->options['orderBy'] = $schema[Relation::ORDER_BY] ?? [];
65
    }
66
67
    /**
68
     * Make sure that pivot loader is always carried with parent relation.
69
     */
70 816
    public function __clone()
71
    {
72 816
        parent::__clone();
73 816
        $this->pivot = clone $this->pivot;
74
    }
75
76 816
    public function withContext(LoaderInterface $parent, array $options = []): static
77
    {
78
        /** @var ManyToManyLoader $loader */
79 816
        $loader = parent::withContext($parent, $options);
80 816
        $loader->pivot = $loader->pivot->withContext(
81
            $loader,
82
            [
83 816
                'load' => $loader->isLoaded(),
84 816
                'method' => $options['method'] ?? self::JOIN,
85 816
            ] + ($options['pivot'] ?? [])
86
        );
87
88 816
        return $loader;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $loader returns the type Cycle\ORM\Select\AbstractLoader which includes types incompatible with the type-hinted return Cycle\ORM\Select\Loader\ManyToManyLoader.
Loading history...
89
    }
90
91 88
    public function loadRelation(
92
        string|LoaderInterface $relation,
93
        array $options,
94
        bool $join = false,
95
        bool $load = false
96
    ): LoaderInterface {
97 88
        if ($relation === '@' || $relation === '@.@') {
98 64
            unset($options['method']);
99 64
            if ($options !== []) {
100
                // re-configure
101
                $this->pivot = $this->pivot->withContext($this, $options);
102
            }
103
104 64
            return $this->pivot;
105
        }
106
107 72
        return parent::loadRelation($relation, $options, $join, $load);
108
    }
109
110 816
    public function configureQuery(SelectQuery $query, array $outerKeys = []): SelectQuery
111
    {
112 816
        if ($this->isLoaded() && $this->isJoined() && (int) $query->getLimit() !== 0) {
113 8
            throw new LoaderException('Unable to load data using join with limit on parent query');
114
        }
115
116 808
        if ($this->options['using'] !== null) {
117
            // use pre-defined query
118
            return parent::configureQuery($this->pivot->configureQuery($query), $outerKeys);
119
        }
120
121
122 808
        $localPrefix = $this->getAlias() . '.';
123 808
        $pivotPrefix = $this->pivot->getAlias() . '.';
124
125
        // Manually join pivoted table
126 808
        if ($this->isJoined()) {
127 192
            $parentKeys = (array)$this->schema[Relation::INNER_KEY];
128 192
            $throughOuterKeys = (array)$this->pivot->schema[Relation::THROUGH_OUTER_KEY];
129 192
            $parentPrefix = $this->parent->getAlias() . '.';
0 ignored issues
show
Bug introduced by
The method getAlias() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

129
            $parentPrefix = $this->parent->/** @scrutinizer ignore-call */ getAlias() . '.';

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
130 192
            $on = [];
131 192
            foreach ((array)$this->pivot->schema[Relation::THROUGH_INNER_KEY] as $i => $key) {
132 192
                $field = $pivotPrefix . $this->pivot->fieldAlias($key);
133 192
                $on[$field] = $parentPrefix . $this->parent->fieldAlias($parentKeys[$i]);
134
            }
135
136 192
            $query->join(
137 192
                $this->getJoinMethod(),
138 192
                $this->pivot->getJoinTable()
139 192
            )->on($on);
140
141 192
            $on = [];
142 192
            foreach ((array)$this->schema[Relation::OUTER_KEY] as $i => $key) {
143 192
                $field = $localPrefix . $this->fieldAlias($key);
144 192
                $on[$field] = $pivotPrefix . $this->pivot->fieldAlias($throughOuterKeys[$i]);
145
            }
146
147 192
            $query->join(
148 192
                $this->getJoinMethod(),
149 192
                $this->getJoinTable()
150 192
            )->on($on);
151 640
        } elseif ($outerKeys !== []) {
152
            // reset all the columns when query is isolated (we have to do it manually
153
            // since underlying loader believes it's loaded)
154 640
            $query->columns([]);
155
156 640
            $outerKeyList = (array)$this->schema[Relation::OUTER_KEY];
157 640
            $on = [];
158 640
            foreach ((array)$this->pivot->schema[Relation::THROUGH_OUTER_KEY] as $i => $key) {
159 640
                $field = $pivotPrefix . $this->pivot->fieldAlias($key);
160 640
                $on[$field] = $localPrefix . $this->fieldAlias($outerKeyList[$i]);
161
            }
162
163 640
            $query->join(
164 640
                $this->getJoinMethod(),
165 640
                $this->pivot->getJoinTable()
166 640
            )->on($on);
167
168 640
            $fields = [];
169 640
            foreach ((array)$this->pivot->schema[Relation::THROUGH_INNER_KEY] as $key) {
170 640
                $fields[] = $pivotPrefix . $this->pivot->fieldAlias($key);
171
            }
172
173 640
            if (\count($fields) === 1) {
174 576
                $query->andWhere($fields[0], 'IN', new Parameter(array_column($outerKeys, key($outerKeys[0]))));
175
            } else {
176 64
                $query->andWhere(
177 64
                    static function (SelectQuery $select) use ($outerKeys, $fields) {
178 64
                        foreach ($outerKeys as $set) {
179 64
                            $select->orWhere(array_combine($fields, array_values($set)));
180
                        }
181
                    }
182
                );
183
            }
184
        }
185
186
        // user specified WHERE conditions
187 808
        $this->setWhere(
188
            $query,
189 808
            $this->isJoined() ? 'onWhere' : 'where',
190 808
            $this->options['where'] ?? $this->schema[Relation::WHERE] ?? []
191
        );
192
193
        // user specified ORDER_BY rules
194 808
        $this->setOrderBy(
195
            $query,
196 808
            $this->getAlias(),
197 808
            $this->options['orderBy'] ?? $this->schema[Relation::ORDER_BY] ?? []
198
        );
199
200 808
        return parent::configureQuery($this->pivot->configureQuery($query));
201
    }
202
203 768
    public function createNode(): AbstractNode
204
    {
205 768
        $node = $this->pivot->createNode();
206 768
        $node->joinNode('@', parent::createNode());
207
208 768
        return $node;
209
    }
210
211 760
    protected function loadChild(AbstractNode $node, bool $includeRole = false): void
212
    {
213 760
        $rootNode = $node->getNode('@');
214 760
        foreach ($this->load as $relation => $loader) {
215 24
            $loader->loadData($rootNode->getNode($relation), $includeRole);
216
        }
217
218 760
        $this->pivot->loadChild($node, $includeRole);
219
    }
220
221 760
    protected function mountColumns(
222
        SelectQuery $query,
223
        bool $minify = false,
224
        string $prefix = '',
225
        bool $overwrite = false
0 ignored issues
show
Unused Code introduced by
The parameter $overwrite is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

225
        /** @scrutinizer ignore-unused */ bool $overwrite = false

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
226
    ): SelectQuery {
227
        // columns are reset on earlier stage to allow pivot loader mount it's own aliases
228 760
        return parent::mountColumns($query, $minify, $prefix, false);
229
    }
230
231 768
    protected function initNode(): AbstractNode
232
    {
233 768
        return new SingularNode(
234 768
            $this->columnNames(),
235 768
            (array)$this->define(SchemaInterface::PRIMARY_KEY),
236 768
            (array)$this->schema[Relation::OUTER_KEY],
237 768
            (array)$this->schema[Relation::THROUGH_OUTER_KEY]
238
        );
239
    }
240
}
241