Passed
Pull Request — master (#239)
by
unknown
03:30
created

ManyToManyLoader   A

Complexity

Total Complexity 21

Size/Duplication

Total Lines 221
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 90
dl 0
loc 221
rs 10
c 1
b 0
f 0
wmc 21

10 Methods

Rating   Name   Duplication   Size   Complexity  
A __clone() 0 4 1
A __construct() 0 6 1
B configureQuery() 0 61 7
A withContext() 0 15 1
A loadRelation() 0 17 4
A createNode() 0 6 1
A mountColumns() 0 8 1
A initNode() 0 15 2
A isDataDuplicationPossible() 0 3 1
A loadChild() 0 8 2
1
<?php
2
3
/**
4
 * Cycle DataMapper ORM
5
 *
6
 * @license   MIT
7
 * @author    Anton Titov (Wolfy-J)
8
 */
9
10
declare(strict_types=1);
11
12
namespace Cycle\ORM\Select\Loader;
13
14
use Cycle\ORM\Exception\LoaderException;
15
use Cycle\ORM\ORMInterface;
16
use Cycle\ORM\Parser\AbstractNode;
17
use Cycle\ORM\Parser\SingularNode;
18
use Cycle\ORM\Parser\Typecast;
19
use Cycle\ORM\Relation;
20
use Cycle\ORM\Schema;
21
use Cycle\ORM\Select\JoinableLoader;
22
use Cycle\ORM\Select\LoaderInterface;
23
use Cycle\ORM\Select\Traits\OrderByTrait;
24
use Cycle\ORM\Select\Traits\WhereTrait;
25
use Spiral\Database\Injection\Parameter;
26
use Spiral\Database\Query\SelectQuery;
27
28
class ManyToManyLoader extends JoinableLoader
29
{
30
    use OrderByTrait;
31
    use WhereTrait;
32
33
    /**
34
     * Default set of relation options. Child implementation might defined their of default options.
35
     *
36
     * @var array
37
     */
38
    protected $options = [
39
        'load' => false,
40
        'scope' => true,
41
        'method' => self::POSTLOAD,
42
        'minify' => true,
43
        'as' => null,
44
        'using' => null,
45
        'where' => null,
46
        'orderBy' => null,
47
        'pivot' => null,
48
    ];
49
50
    /** @var PivotLoader */
51
    protected $pivot;
52
53
    /**
54
     * {@inheritdoc}
55
     */
56
    public function __construct(ORMInterface $orm, string $name, string $target, array $schema)
57
    {
58
        parent::__construct($orm, $name, $target, $schema);
59
        $this->pivot = new PivotLoader($orm, 'pivot', $schema[Relation::THROUGH_ENTITY], $schema);
60
        $this->options['where'] = $schema[Relation::WHERE] ?? [];
61
        $this->options['orderBy'] = $schema[Relation::ORDER_BY] ?? [];
62
    }
63
64
    /**
65
     * Make sure that pivot loader is always carried with parent relation.
66
     */
67
    public function __clone()
68
    {
69
        parent::__clone();
70
        $this->pivot = clone $this->pivot;
71
    }
72
73
    /**
74
     * @param LoaderInterface $parent
75
     * @param array           $options
76
     *
77
     * @return LoaderInterface
78
     */
79
    public function withContext(LoaderInterface $parent, array $options = []): LoaderInterface
80
    {
81
        $options = $this->prepareOptions($options);
82
83
        /** @var ManyToManyLoader $loader */
84
        $loader = parent::withContext($parent, $options);
85
        $loader->pivot = $loader->pivot->withContext(
86
            $loader,
87
            [
88
                'load' => $loader->isLoaded(),
89
                'method' => $options['method'] ?? self::JOIN,
90
            ] + ($options['pivot'] ?? [])
91
        );
92
93
        return $loader;
94
    }
95
96
    /**
97
     * @param string $relation
98
     * @param array  $options
99
     * @param bool   $join
100
     * @param bool   $load
101
     *
102
     * @return LoaderInterface
103
     */
104
    public function loadRelation(
105
        string $relation,
106
        array $options,
107
        bool $join = false,
108
        bool $load = false
109
    ): LoaderInterface {
110
        if ($relation === '@' || $relation === '@.@') {
111
            unset($options['method']);
112
            if ($options !== []) {
113
                // re-configure
114
                $this->pivot = $this->pivot->withContext($this, $options);
115
            }
116
117
            return $this->pivot;
118
        }
119
120
        return parent::loadRelation($relation, $options, $join, $load);
121
    }
122
123
    /**
124
     * {@inheritdoc}
125
     */
126
    public function configureQuery(SelectQuery $query, array $outerKeys = []): SelectQuery
127
    {
128
        if ($this->isLoaded() && $this->isJoined() && (int) $query->getLimit() !== 0) {
129
            throw new LoaderException('Unable to load data using join with limit on parent query');
130
        }
131
132
        if ($this->options['using'] !== null) {
133
            // use pre-defined query
134
            return parent::configureQuery($this->pivot->configureQuery($query), $outerKeys);
135
        }
136
137
        // Manually join pivoted table
138
        if ($this->isJoined()) {
139
            $query->join(
140
                $this->getJoinMethod(),
141
                $this->pivot->getJoinTable()
142
            )->on(
143
                $this->pivot->localKey(Relation::THROUGH_INNER_KEY),
144
                $this->parentKey(Relation::INNER_KEY)
145
            );
146
147
            $query->join(
148
                $this->getJoinMethod(),
149
                $this->getJoinTable()
150
            )->on(
151
                $this->localKey(Relation::OUTER_KEY),
152
                $this->pivot->localKey(Relation::THROUGH_OUTER_KEY)
153
            );
154
        } else {
155
            // reset all the columns when query is isolated (we have to do it manually
156
            // since underlying loader believes it's loaded)
157
            $query->columns([]);
158
159
            $query->join(
160
                $this->getJoinMethod(),
161
                $this->pivot->getJoinTable()
162
            )->on(
163
                $this->pivot->localKey(Relation::THROUGH_OUTER_KEY),
164
                $this->localKey(Relation::OUTER_KEY)
165
            )->where(
0 ignored issues
show
Bug introduced by
It seems like where() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

165
            )->/** @scrutinizer ignore-call */ where(
Loading history...
166
                $this->pivot->localKey(Relation::THROUGH_INNER_KEY),
167
                new Parameter($outerKeys)
168
            );
169
        }
170
171
        // user specified WHERE conditions
172
        $this->setWhere(
173
            $query,
174
            $this->getAlias(),
175
            $this->isJoined() ? 'onWhere' : 'where',
176
            $this->options['where'] ?? $this->schema[Relation::WHERE] ?? []
177
        );
178
179
        // user specified ORDER_BY rules
180
        $this->setOrderBy(
181
            $query,
182
            $this->getAlias(),
183
            $this->options['orderBy'] ?? $this->schema[Relation::ORDER_BY] ?? []
184
        );
185
186
        return parent::configureQuery($this->pivot->configureQuery($query));
187
    }
188
189
    /**
190
     * {@inheritdoc}
191
     */
192
    public function createNode(): AbstractNode
193
    {
194
        $node = $this->pivot->createNode();
195
        $node->joinNode('@', parent::createNode());
196
197
        return $node;
198
    }
199
200
    public function isDataDuplicationPossible(): bool
201
    {
202
        return true;
203
    }
204
205
    /**
206
     * @param AbstractNode $node
207
     */
208
    protected function loadChild(AbstractNode $node): void
209
    {
210
        $rootNode = $node->getNode('@');
211
        foreach ($this->load as $relation => $loader) {
212
            $loader->loadData($rootNode->getNode($relation));
213
        }
214
215
        $this->pivot->loadChild($node);
216
    }
217
218
    /**
219
     * {@inheritdoc}
220
     */
221
    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
        return parent::mountColumns($query, $minify, $prefix, false);
229
    }
230
231
    /**
232
     * {@inheritdoc}
233
     */
234
    protected function initNode(): AbstractNode
235
    {
236
        $node = new SingularNode(
237
            $this->columnNames(),
238
            $this->define(Schema::PRIMARY_KEY),
239
            $this->schema[Relation::OUTER_KEY],
240
            $this->schema[Relation::THROUGH_OUTER_KEY]
241
        );
242
243
        $typecast = $this->define(Schema::TYPECAST);
244
        if ($typecast !== null) {
245
            $node->setTypecast(new Typecast($typecast, $this->getSource()->getDatabase()));
246
        }
247
248
        return $node;
249
    }
250
}
251