Completed
Push — master ( e1ce3b...f144d1 )
by Anton
14s queued 12s
created

src/Select/Loader/ManyToManyLoader.php (1 issue)

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

189
        /** @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...
190
    ): SelectQuery {
191
        // columns are reset on earlier stage to allow pivot loader mount it's own aliases
192
        return parent::mountColumns($query, $minify, $prefix, false);
193
    }
194
195
    /**
196
     * {@inheritdoc}
197
     */
198
    protected function initNode(): AbstractNode
199
    {
200
        $node = new SingularNode(
201
            $this->columnNames(),
202
            $this->define(Schema::PRIMARY_KEY),
203
            $this->schema[Relation::OUTER_KEY],
204
            $this->schema[Relation::THROUGH_OUTER_KEY]
205
        );
206
207
        $typecast = $this->define(Schema::TYPECAST);
208
        if ($typecast !== null) {
209
            $node->setTypecast(new Typecast($typecast, $this->getSource()->getDatabase()));
210
        }
211
212
        return $node;
213
    }
214
}
215