Completed
Branch feature/pre-split (825af5)
by Anton
03:36
created

MultipleRelation::loadData()   B

Complexity

Conditions 6
Paths 4

Size

Total Lines 19
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

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