MultipleRelation::withContext()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 10
rs 9.9332
c 0
b 0
f 0
cc 1
nc 1
nop 3
1
<?php
2
/**
3
 * Spiral, Core Components
4
 *
5
 * @author Wolfy-J
6
 */
7
8
namespace Spiral\ORM\Entities\Relations;
9
10
use Spiral\ORM\Entities\RecordIterator;
11
use Spiral\ORM\Entities\Relations\Traits\MatchTrait;
12
use Spiral\ORM\Entities\Relations\Traits\PartialTrait;
13
use Spiral\ORM\RecordInterface;
14
use Spiral\ORM\RelationInterface;
15
16
/**
17
 * Relation with multiple related instances.
18
 */
19
abstract class MultipleRelation extends AbstractRelation
20
{
21
    use MatchTrait, PartialTrait;
22
23
    /**
24
     * Loaded list of records. SplObjectStorage?
25
     *
26
     * @var RecordInterface[]
27
     */
28
    protected $instances = [];
29
30
    /**
31
     * {@inheritdoc}
32
     *
33
     * We have to init relations right after initialization, this might be optimized in a future.
34
     */
35
    public function withContext(
36
        RecordInterface $parent,
37
        bool $loaded = false,
38
        array $data = null
39
    ): RelationInterface {
40
        $relation = parent::withContext($parent, $loaded, $data);
41
42
        /** @var self $relation */
43
        return $relation->initInstances();
44
    }
45
46
    /**
47
     * {@inheritdoc}
48
     */
49
    public function hasRelated(): bool
50
    {
51
        if (!$this->isLoaded()) {
52
            //Lazy loading our relation data
53
            $this->loadData(true);
54
        }
55
56
        return !empty($this->instances);
57
    }
58
59
    /**
60
     * Such relations will represent themselves.
61
     *
62
     * @return $this
63
     */
64
    public function getRelated()
65
    {
66
        return $this;
67
    }
68
69
    /**
70
     * Iterate over instance set.
71
     *
72
     * @return \ArrayIterator
73
     */
74
    public function getIterator()
75
    {
76
        return new \ArrayIterator($this->loadData(true)->instances);
77
    }
78
79
    /**
80
     * @return int
81
     */
82
    public function count()
83
    {
84
        return count($this->loadData(true)->instances);
85
    }
86
87
    /**
88
     * Method will autoload data.
89
     *
90
     * @param array|RecordInterface|mixed $query Fields, entity or PK.
91
     *
92
     * @return bool
93
     */
94
    public function has($query): bool
95
    {
96
        return !empty($this->matchOne($query));
97
    }
98
99
    /**
100
     * Fine one entity for a given query or return null. Method will autoload data.
101
     *
102
     * Example: ->matchOne(['value' => 'something', ...]);
103
     *
104
     * @param array|RecordInterface|mixed $query Fields, entity or PK.
105
     *
106
     * @return RecordInterface|null
107
     */
108
    public function matchOne($query)
109
    {
110
        foreach ($this->loadData(true)->instances as $instance) {
111
            if ($this->match($instance, $query)) {
112
                return $instance;
113
            }
114
        }
115
116
        return null;
117
    }
118
119
    /**
120
     * Return only instances matched given query, performed in memory! Only simple conditions are
121
     * allowed. Not "find" due trademark violation. Method will autoload data.
122
     *
123
     * Example: ->matchMultiple(['value' => 'something', ...]);
124
     *
125
     * @param array|RecordInterface|mixed $query Fields, entity or PK.
126
     *
127
     * @return \ArrayIterator
128
     */
129
    public function matchMultiple($query)
130
    {
131
        $result = [];
132
        foreach ($this->loadData(true)->instances as $instance) {
133
            if ($this->match($instance, $query)) {
134
                $result[] = $instance;
135
            }
136
        }
137
138
        return new \ArrayIterator($result);
139
    }
140
141
    /**
142
     * {@inheritdoc}
143
     *
144
     * @return self
145
     *
146
     * @throws \Spiral\ORM\Exceptions\SelectorException
147
     * @throws \Spiral\Database\Exceptions\QueryException (needs wrapping)
148
     */
149
    protected function loadData(bool $autoload = true): self
150
    {
151
        if ($this->loaded) {
152
            return $this;
153
        }
154
155
        $this->loaded = true;
156
157
        if (empty($this->data) || !is_array($this->data)) {
158
            if ($this->autoload && $autoload) {
159
                //Only for non partial selections (excluded already selected)
160
                $this->data = $this->loadRelated();
161
            } else {
162
                $this->data = [];
163
            }
164
        }
165
166
        return $this->initInstances();
167
    }
168
169
    /**
170
     * Init pre-loaded data.
171
     *
172
     * @return HasManyRelation|self
173
     */
174
    protected function initInstances(): self
175
    {
176
        if (is_array($this->data) && !empty($this->data)) {
177
            //Iterates and instantiate records
178
            $iterator = new RecordIterator($this->data, $this->class, $this->orm);
179
180
            foreach ($iterator as $item) {
181
                if (in_array($item, $this->instances)) {
182
                    //Skip duplicates
183
                    continue;
184
                }
185
186
                $this->instances[] = $item;
187
            }
188
        }
189
190
        //Memory free
191
        $this->data = null;
192
193
        return $this;
194
    }
195
196
    /**
197
     * Fetch relation data from database.
198
     *
199
     * @return array
200
     */
201
    abstract protected function loadRelated(): array;
202
}