Passed
Push — master ( 0a4fc8...34ac19 )
by Anton
04:11
created

Embedded::mapColumns()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 12
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 7
nc 3
nop 1
dl 0
loc 12
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * Spiral Framework.
4
 *
5
 * @license   MIT
6
 * @author    Anton Titov (Wolfy-J)
7
 */
8
declare(strict_types=1);
9
10
namespace Cycle\ORM\Relation;
11
12
use Cycle\ORM\Command\Branch\Nil;
13
use Cycle\ORM\Command\CommandInterface;
14
use Cycle\ORM\Command\ContextCarrierInterface as CC;
15
use Cycle\ORM\Exception\Relation\NullException;
16
use Cycle\ORM\Heap\Node;
17
use Cycle\ORM\Heap\State;
18
use Cycle\ORM\MapperInterface;
19
use Cycle\ORM\ORMInterface;
20
use Cycle\ORM\Promise\PromiseInterface;
21
use Cycle\ORM\Promise\ReferenceInterface;
22
use Cycle\ORM\Relation;
23
use Cycle\ORM\Schema;
24
use Cycle\ORM\Select\SourceProviderInterface;
25
26
/**
27
 * Embeds one object to another.
28
 */
29
final class Embedded implements RelationInterface
30
{
31
    use  Relation\Traits\NodeTrait;
32
33
    /** @var ORMInterface|SourceProviderInterface @internal */
34
    protected $orm;
35
36
    /** @var string */
37
    protected $name;
38
39
    /** @var string */
40
    protected $target;
41
42
    /** @var array */
43
    protected $schema;
44
45
    /** @var MapperInterface */
46
    protected $mapper;
47
48
    /** @var string */
49
    protected $primaryKey;
50
51
    /** @var array */
52
    protected $columns = [];
53
54
    /**
55
     * @param ORMInterface $orm
56
     * @param string       $name
57
     * @param string       $target
58
     * @param array        $schema
59
     */
60
    public function __construct(ORMInterface $orm, string $name, string $target, array $schema)
61
    {
62
        $this->orm = $orm;
63
        $this->name = $name;
64
        $this->target = $target;
65
        $this->schema = $schema;
66
        $this->mapper = $this->orm->getMapper($target);
67
68
        // this relation must manage column association manually, bypassing related mapper
69
        $this->primaryKey = $this->orm->getSchema()->define($target, Schema::PRIMARY_KEY);
70
        $this->columns = $this->orm->getSchema()->define($target, Schema::COLUMNS);
71
    }
72
73
    /**
74
     * @inheritdoc
75
     */
76
    public function getName(): string
77
    {
78
        return $this->name;
79
    }
80
81
    /**
82
     * @inheritDoc
83
     */
84
    public function isCascade(): bool
85
    {
86
        // always cascade
87
        return true;
88
    }
89
90
    /**
91
     * @inheritdoc
92
     */
93
    public function init(Node $node, array $data): array
94
    {
95
        // ensure proper object reference
96
        $data[$this->primaryKey] = $node->getData()[$this->primaryKey];
97
98
        $item = $this->orm->make($this->target, $data, Node::MANAGED);
0 ignored issues
show
Bug introduced by
The method make() does not exist on Cycle\ORM\Select\SourceProviderInterface. Since it exists in all sub-types, consider adding an abstract or default implementation to Cycle\ORM\Select\SourceProviderInterface. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

98
        /** @scrutinizer ignore-call */ 
99
        $item = $this->orm->make($this->target, $data, Node::MANAGED);
Loading history...
99
100
        return [$item, $item];
101
    }
102
103
    /**
104
     * @inheritdoc
105
     */
106
    public function initPromise(Node $parentNode): array
107
    {
108
        if (empty($primaryKey = $this->fetchKey($parentNode, $this->primaryKey))) {
109
            return [null, null];
110
        }
111
112
        /** @var ORMInterface $orm */
113
        $orm = $this->orm;
114
115
        $e = $orm->getHeap()->find($this->target, $this->primaryKey, $primaryKey);
116
        if ($e !== null) {
117
            return [$e, $e];
118
        }
119
120
        $r = $this->orm->promise($this->target, [$this->primaryKey => $primaryKey]);
0 ignored issues
show
Bug introduced by
The method promise() does not exist on Cycle\ORM\Select\SourceProviderInterface. Since it exists in all sub-types, consider adding an abstract or default implementation to Cycle\ORM\Select\SourceProviderInterface. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

120
        /** @scrutinizer ignore-call */ 
121
        $r = $this->orm->promise($this->target, [$this->primaryKey => $primaryKey]);
Loading history...
121
        return [$r, new Nil()];
122
    }
123
124
    /**
125
     * @inheritdoc
126
     */
127
    public function extract($data)
128
    {
129
        return $data;
130
    }
131
132
    /**
133
     * @inheritDoc
134
     */
135
    public function queue(CC $store, $entity, Node $node, $related, $original): CommandInterface
136
    {
137
        if ($related instanceof ReferenceInterface) {
138
            $related = $this->resolve($related);
139
        }
140
141
        if ($related === null) {
142
            throw new NullException("Embedded relation `{$this->name}` can't be null");
143
        }
144
145
        $state = $this->getNode($related)->getState();
146
147
        // calculate embedded node changes
148
        $changes = $this->getChanges($related, $state);
149
150
        // register node changes
151
        $state->setData($changes);
152
153
        // store embedded entity changes via parent command
154
        foreach ($this->mapColumns($changes) as $key => $value) {
155
            $store->register($key, $value, true);
156
        }
157
158
        // ensure related state
159
160
        return new Nil();
161
    }
162
163
    /**
164
     * @param mixed $related
165
     * @param State $state
166
     * @return array
167
     */
168
    protected function getChanges($related, State $state): array
169
    {
170
        $data = $this->mapper->extract($related);
171
172
        return array_udiff_assoc($data, $state->getData(), [static::class, 'compare']);
173
    }
174
175
    /**
176
     * Map internal field names to database specific column names.
177
     *
178
     * @param array $columns
179
     * @return array
180
     */
181
    protected function mapColumns(array $columns): array
182
    {
183
        $result = [];
184
        foreach ($columns as $column => $value) {
185
            if (array_key_exists($column, $this->columns)) {
186
                $result[$this->columns[$column]] = $value;
187
            } else {
188
                $result[$column] = $value;
189
            }
190
        }
191
192
        return $result;
193
    }
194
195
    /**
196
     * @param mixed $a
197
     * @param mixed $b
198
     * @return int
199
     */
200
    protected static function compare($a, $b): int
201
    {
202
        if ($a == $b) {
203
            return 0;
204
        }
205
206
        return ($a > $b) ? 1 : -1;
207
    }
208
209
    /**
210
     * Resolve the reference to the object.
211
     *
212
     * @param ReferenceInterface $reference
213
     * @return mixed|null
214
     */
215
    protected function resolve(ReferenceInterface $reference)
216
    {
217
        if ($reference instanceof PromiseInterface) {
218
            return $reference->__resolve();
219
        }
220
221
        $scope = $reference->__scope();
222
        return $this->orm->get($reference->__role(), key($scope), current($scope), true);
0 ignored issues
show
Bug introduced by
The method get() does not exist on Cycle\ORM\Select\SourceProviderInterface. Since it exists in all sub-types, consider adding an abstract or default implementation to Cycle\ORM\Select\SourceProviderInterface. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

222
        return $this->orm->/** @scrutinizer ignore-call */ get($reference->__role(), key($scope), current($scope), true);
Loading history...
223
    }
224
225
    /**
226
     * Fetch key from the state.
227
     *
228
     * @param Node   $state
229
     * @param string $key
230
     * @return mixed|null
231
     */
232
    protected function fetchKey(?Node $state, string $key)
233
    {
234
        if (is_null($state)) {
235
            return null;
236
        }
237
238
        return $state->getData()[$key] ?? null;
239
    }
240
}