Completed
Branch feature/pre-split (d91fae)
by Anton
05:19
created

ManyToMorphed::saveAssociation()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 10
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 5
Bugs 0 Features 0
Metric Value
cc 3
eloc 5
nc 3
nop 1
dl 0
loc 10
rs 9.4285
c 5
b 0
f 0
1
<?php
2
/**
3
 * Spiral Framework.
4
 *
5
 * @license   MIT
6
 * @author    Anton Titov (Wolfy-J)
7
 */
8
9
namespace Spiral\ORM\Entities\Relations;
10
11
use Spiral\Database\Entities\Table;
12
use Spiral\Models\EntityInterface;
13
use Spiral\ORM\Entities\RecordIterator;
14
use Spiral\ORM\Exceptions\RelationException;
15
use Spiral\ORM\ORM;
16
use Spiral\ORM\RecordEntity;
17
use Spiral\ORM\RecordInterface;
18
use Spiral\ORM\RelationInterface;
19
20
/**
21
 * ManyToMorphed relation used to aggregate multiple ManyToMany relations based on their role type.
22
 * In addition it can route some function to specified nested ManyToMany relation based on record
23
 * role.
24
 *
25
 * @see ManyToMany
26
 */
27
class ManyToMorphed implements RelationInterface
28
{
29
    /**
30
     * Nested ManyToMany relations.
31
     *
32
     * @var ManyToMany[]
33
     */
34
    private $relations = [];
35
36
    /**
37
     * Parent Record caused relation to be created.
38
     *
39
     * @var RecordInterface
40
     */
41
    protected $parent = null;
42
43
    /**
44
     * Relation definition fetched from ORM schema. Must already be normalized by RelationSchema.
45
     *
46
     * @invisible
47
     *
48
     * @var array
49
     */
50
    protected $definition = [];
51
52
    /**
53
     * @invisible
54
     *
55
     * @var ORM
56
     */
57
    protected $orm = null;
58
59
    /**
60
     * {@inheritdoc}
61
     */
62
    public function __construct(
63
        ORM $orm,
64
        RecordInterface $parent,
65
        array $definition,
66
        $data = null,
67
        $loaded = false
68
    ) {
69
        $this->orm = $orm;
70
        $this->parent = $parent;
71
        $this->definition = $definition;
72
    }
73
74
    /**
75
     * {@inheritdoc}
76
     */
77
    public function isLoaded()
78
    {
79
        //Never loader
80
        return false;
81
    }
82
83
    /**
84
     * {@inheritdoc}
85
     *
86
     * We can return self:
87
     * $tag->tagged->users->count();
88
     */
89
    public function getRelated()
90
    {
91
        return $this;
92
    }
93
94
    /**
95
     * {@inheritdoc}
96
     */
97
    public function associate(EntityInterface $related = null)
98
    {
99
        throw new RelationException('Unable to associate with morphed relation.');
100
    }
101
102
    /**
103
     * {@inheritdoc}
104
     */
105
    public function saveRelated($validate = true)
106
    {
107
        foreach ($this->relations as $relation) {
108
            if (!$relation->saveRelated($validate)) {
109
                return false;
110
            }
111
        }
112
113
        return true;
114
    }
115
116
    /**
117
     * {@inheritdoc}
118
     */
119
    public function reset(array $data = [], $loaded = false)
120
    {
121
        foreach ($this->relations as $relation) {
122
            //Can be only flushed
123
            $relation->reset([], false);
124
        }
125
126
        //Dropping relations
127
        $this->relations = [];
128
    }
129
130
    /**
131
     * {@inheritdoc}
132
     */
133
    public function isValid()
134
    {
135
        foreach ($this->relations as $alias => $relation) {
136
            if (!$relation->isValid()) {
137
                return false;
138
            }
139
        }
140
141
        return true;
142
    }
143
144
    /**
145
     * {@inheritdoc}
146
     */
147
    public function hasErrors()
148
    {
149
        foreach ($this->relations as $alias => $relation) {
150
            if ($relation->hasErrors()) {
151
                return true;
152
            }
153
        }
154
155
        return false;
156
    }
157
158
    /**
159
     * {@inheritdoc}
160
     */
161
    public function getErrors($reset = false)
0 ignored issues
show
Unused Code introduced by
The parameter $reset is not used and could be removed.

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

Loading history...
162
    {
163
        $result = [];
164
        foreach ($this->relations as $alias => $relation) {
165
            if (!empty($errors = $relation->getErrors())) {
166
                $result[$alias] = $errors;
167
            }
168
        }
169
170
        return $result;
171
    }
172
173
    /**
174
     * Count method will work with pivot table directly.
175
     *
176
     * @return int
177
     */
178
    public function count()
179
    {
180
        $innerKey = $this->definition[RecordEntity::INNER_KEY];
181
182
        return $this->pivotTable()->where([
0 ignored issues
show
Documentation Bug introduced by
The method where does not exist on object<Spiral\Database\Entities\Table>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
183
            $this->definition[RecordEntity::THOUGHT_INNER_KEY] => $this->parent->getField($innerKey),
184
        ])->count();
185
    }
186
187
    /**
188
     * Get access to data instance stored in nested relation.
189
     *
190
     * Example:
191
     * $tag->tagged->users;
192
     * $tag->tagged->posts;
193
     *
194
     * @param string $alias
195
     *
196
     * @return RecordEntity|RecordIterator
197
     */
198
    public function __get($alias)
199
    {
200
        return $this->morphed($alias)->getRelated();
201
    }
202
203
    /**
204
     * Get access to sub relation.
205
     *
206
     * Example:
207
     * $tag->tagged->users()->count();
208
     * foreach($tag->tagged->users()->find(["status" => "active"]) as $user)
209
     * {
210
     * }
211
     *
212
     * @param string $alias
213
     * @param array  $arguments
214
     *
215
     * @return ManyToMany
216
     */
217
    public function __call($alias, array $arguments)
218
    {
219
        return $this->morphed($alias);
220
    }
221
222
    /**
223
     * Link morphed record to relation. Method will bypass request to appropriate nested relation.
224
     *
225
     * @param RecordInterface $record
226
     * @param array           $pivotData Custom pivot data.
227
     */
228
    public function link(RecordInterface $record, array $pivotData = [])
229
    {
230
        $this->morphed($record->recordRole())->link($record, $pivotData);
231
    }
232
233
    /**
234
     * Unlink morphed record from relation.
235
     *
236
     * @param RecordInterface $record
237
     *
238
     * @return int
239
     */
240
    public function unlink(RecordInterface $record)
241
    {
242
        return $this->morphed($record->recordRole())->unlink($record);
243
    }
244
245
    /**
246
     * Unlink every associated record, method will return amount of affected rows. Method will
247
     * unlink only records matched WHERE_PIVOT by default. Set wherePivot to false to unlink every
248
     * record.
249
     *
250
     * @param bool $wherePivot Use conditions specified by WHERE_PIVOT, enabled by default.
251
     *
252
     * @return int
253
     */
254
    public function unlinkAll($wherePivot = true)
255
    {
256
        $innerKey = $this->definition[RecordEntity::INNER_KEY];
257
258
        $query = [
259
            $this->definition[RecordEntity::THOUGHT_INNER_KEY] => $this->parent->getField($innerKey),
260
        ];
261
262 View Code Duplication
        if ($wherePivot && !empty($this->definition[RecordEntity::WHERE_PIVOT])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
263
            $query = $query + $this->definition[RecordEntity::WHERE_PIVOT];
264
        }
265
266
        return $this->pivotTable()->delete($query)->run();
267
    }
268
269
    /**
270
     * Get nested-relation associated with one of record morph aliases.
271
     *
272
     * @param string $alias
273
     *
274
     * @return ManyToMany
275
     */
276
    protected function morphed($alias)
277
    {
278
        if (isset($this->relations[$alias])) {
279
            //Already loaded
280
            return $this->relations[$alias];
281
        }
282
283
        if (
284
            !isset($this->definition[RecordEntity::MORPHED_ALIASES][$alias])
285
            && !empty(
286
            $reversed = array_search($alias, $this->definition[RecordEntity::MORPHED_ALIASES])
287
            )
288
        ) {
289
            //Requested by singular form of role name, let's reverse mapping
290
            $alias = $reversed;
291
        }
292
293
        if (!isset($this->definition[RecordEntity::MORPHED_ALIASES][$alias])) {
294
            throw new RelationException("No such morphed-relation or method '{$alias}'.");
295
        }
296
297
        //We have to create custom definition
298
        $definition = $this->definition;
299
300
        $roleName = $this->definition[RecordEntity::MORPHED_ALIASES][$alias];
301
        $definition[RecordEntity::MANY_TO_MANY] = $definition[RecordEntity::MANY_TO_MORPHED][$roleName];
302
303
        unset($definition[RecordEntity::MANY_TO_MORPHED], $definition[RecordEntity::MORPHED_ALIASES]);
304
305
        //Creating many-to-many relation
306
        $this->relations[$alias] = new ManyToMany($this->orm, $this->parent, $definition);
307
308
        //We have to force role name
309
        $this->relations[$alias]->setRole($roleName);
310
311
        return $this->relations[$alias];
312
    }
313
314
    /**
315
     * Instance of DBAL\Table associated with relation pivot table.
316
     *
317
     * @return Table
318
     */
319
    protected function pivotTable()
320
    {
321
        return $this->orm->database($this->definition[ORM::R_DATABASE])->table(
322
            $this->definition[RecordEntity::PIVOT_TABLE]
323
        );
324
    }
325
}
326