Passed
Push — master ( e19e50...961f6c )
by Anton
02:47
created

ManyToManyLoader::loadChild()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 3
nc 2
nop 1
dl 0
loc 5
rs 10
c 0
b 0
f 0
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\WhereTrait;
24
use Spiral\Database\Injection\Parameter;
25
use Spiral\Database\Query\SelectQuery;
26
use Spiral\Database\StatementInterface;
27
28
class ManyToManyLoader extends JoinableLoader
29
{
30
    use WhereTrait;
31
32
    /**
33
     * Default set of relation options. Child implementation might defined their of default options.
34
     *
35
     * @var array
36
     */
37
    protected $options = [
38
        'load'      => false,
39
        'constrain' => true,
40
        'method'    => self::POSTLOAD,
41
        'minify'    => true,
42
        'as'        => null,
43
        'using'     => null,
44
        'where'     => null,
45
        'pivot'     => null
46
    ];
47
48
    /** @var PivotLoader */
49
    protected $pivot;
50
51
    /**
52
     * {@inheritdoc}
53
     */
54
    public function __construct(ORMInterface $orm, string $name, string $target, array $schema)
55
    {
56
        parent::__construct($orm, $name, $target, $schema);
57
        $this->pivot = new PivotLoader($orm, 'pivot', $schema[Relation::THROUGH_ENTITY], $schema);
58
    }
59
60
    /**
61
     * Make sure that pivot loader is always carried with parent relation.
62
     */
63
    public function __clone()
64
    {
65
        parent::__clone();
66
        $this->pivot = clone $this->pivot;
67
    }
68
69
    /**
70
     * @param LoaderInterface $parent
71
     * @param array           $options
72
     * @return LoaderInterface
73
     */
74
    public function withContext(LoaderInterface $parent, array $options = []): LoaderInterface
75
    {
76
        /** @var ManyToManyLoader $loader */
77
        $loader = parent::withContext($parent, $options);
78
        $loader->pivot = $loader->pivot->withContext(
79
            $loader,
80
            [
81
                'load'   => $loader->isLoaded(),
82
                'method' => $options['method'] ?? self::JOIN,
83
            ] + ($options['pivot'] ?? [])
84
        );
85
86
        return $loader;
87
    }
88
89
    /**
90
     * @param string $relation
91
     * @param array  $options
92
     * @param bool   $join
93
     * @param bool   $load
94
     * @return LoaderInterface
95
     */
96
    public function loadRelation(
97
        string $relation,
98
        array $options,
99
        bool $join = false,
100
        bool $load = false
101
    ): LoaderInterface {
102
        if ($relation === '@' || $relation === '@.@') {
103
            unset($options['method']);
104
            if ($options !== []) {
105
                // re-configure
106
                $this->pivot = $this->pivot->withContext($this, $options);
107
            }
108
109
            return $this->pivot;
110
        }
111
112
        return parent::loadRelation($relation, $options, $join, $load);
113
    }
114
115
    /**
116
     * {@inheritdoc}
117
     */
118
    public function configureQuery(SelectQuery $query, array $outerKeys = []): SelectQuery
119
    {
120
        if ($this->isLoaded() && $this->isJoined() && (int) $query->getLimit() !== 0) {
121
            throw new LoaderException('Unable to load data using join with limit on parent query');
122
        }
123
124
        if ($this->options['using'] !== null) {
125
            // use pre-defined query
126
            return parent::configureQuery($this->pivot->configureQuery($query), $outerKeys);
127
        }
128
129
        // Manually join pivoted table
130
        if ($this->isJoined()) {
131
            $query->join(
132
                $this->getJoinMethod(),
133
                $this->pivot->getJoinTable()
134
            )->on(
135
                $this->pivot->localKey(Relation::THROUGH_INNER_KEY),
136
                $this->parentKey(Relation::INNER_KEY)
137
            );
138
139
            $query->innerJoin(
140
                $this->getJoinTable()
141
            )->on(
142
                $this->localKey(Relation::OUTER_KEY),
143
                $this->pivot->localKey(Relation::THROUGH_OUTER_KEY)
144
            );
145
        } else {
146
            // reset all the columns when query is isolated (we have to do it manually
147
            // since underlying loader believes it's loaded)
148
            $query->columns([]);
149
150
            $query->innerJoin(
151
                $this->pivot->getJoinTable()
152
            )->on(
153
                $this->pivot->localKey(Relation::THROUGH_OUTER_KEY),
154
                $this->localKey(Relation::OUTER_KEY)
155
            )->where(
156
                $this->pivot->localKey(Relation::THROUGH_INNER_KEY),
157
                new Parameter($outerKeys)
158
            );
159
        }
160
161
        // user specified WHERE conditions
162
        $this->setWhere(
163
            $query,
164
            $this->getAlias(),
165
            $this->isJoined() ? 'onWhere' : 'where',
166
            $this->options['where'] ?? $this->schema[Relation::WHERE] ?? []
167
        );
168
169
        return parent::configureQuery($this->pivot->configureQuery($query));
170
    }
171
172
    /**
173
     * {@inheritdoc}
174
     */
175
    public function createNode(): AbstractNode
176
    {
177
        $node = $this->pivot->createNode();
178
        $node->joinNode('@', parent::createNode());
179
180
        return $node;
181
    }
182
183
    /**
184
     * @param AbstractNode $node
185
     */
186
    protected function loadChild(AbstractNode $node): void
187
    {
188
        $node = $node->getNode('@');
189
        foreach ($this->load as $relation => $loader) {
190
            $loader->loadData($node->getNode($relation));
191
        }
192
    }
193
194
    /**
195
     * {@inheritdoc}
196
     */
197
    protected function mountColumns(
198
        SelectQuery $query,
199
        bool $minify = false,
200
        string $prefix = '',
201
        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

201
        /** @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...
202
    ): SelectQuery {
203
        // columns are reset on earlier stage to allow pivot loader mount it's own aliases
204
        return parent::mountColumns($query, $minify, $prefix, false);
205
    }
206
207
    /**
208
     * {@inheritdoc}
209
     */
210
    protected function initNode(): AbstractNode
211
    {
212
        $node = new SingularNode(
213
            $this->columnNames(),
214
            $this->define(Schema::PRIMARY_KEY),
215
            $this->schema[Relation::OUTER_KEY],
216
            $this->schema[Relation::THROUGH_OUTER_KEY]
217
        );
218
219
        $typecast = $this->define(Schema::TYPECAST);
220
        if ($typecast !== null) {
221
            $node->setTypecast(new Typecast($typecast, $this->getSource()->getDatabase()));
222
        }
223
224
        return $node;
225
    }
226
}
227