Passed
Push — master ( f2d2bc...8e9d39 )
by Anton
02:55
created

DatabaseMapper::compare()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 3
eloc 3
nc 3
nop 2
dl 0
loc 7
rs 10
c 1
b 0
f 0
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\Mapper;
13
14
use Cycle\ORM\Command\CommandInterface;
15
use Cycle\ORM\Command\ContextCarrierInterface;
16
use Cycle\ORM\Command\Database\Delete;
17
use Cycle\ORM\Command\Database\Insert;
18
use Cycle\ORM\Command\Database\Update;
19
use Cycle\ORM\Context\ConsumerInterface;
20
use Cycle\ORM\Exception\MapperException;
21
use Cycle\ORM\Heap\Node;
22
use Cycle\ORM\Heap\State;
23
use Cycle\ORM\MapperInterface;
24
use Cycle\ORM\ORMInterface;
25
use Cycle\ORM\Schema;
26
use Cycle\ORM\Select;
27
28
/**
29
 * Provides basic capabilities to work with entities persisted in SQL databases.
30
 */
31
abstract class DatabaseMapper implements MapperInterface
32
{
33
    /** @var Select\SourceInterface */
34
    protected $source;
35
36
    /** @var ORMInterface */
37
    protected $orm;
38
39
    /** @var string */
40
    protected $role;
41
42
    /** @var array */
43
    protected $columns;
44
45
    /** @var array */
46
    protected $fields;
47
48
    /** @var string */
49
    protected $primaryKey;
50
51
    /** @var string */
52
    protected $primaryColumn;
53
54
    /**
55
     * @param ORMInterface $orm
56
     * @param string       $role
57
     */
58
    public function __construct(ORMInterface $orm, string $role)
59
    {
60
        if (!$orm instanceof Select\SourceProviderInterface) {
0 ignored issues
show
introduced by
$orm is always a sub-type of Cycle\ORM\Select\SourceProviderInterface.
Loading history...
61
            throw new MapperException('Source factory is missing');
62
        }
63
64
        $this->orm = $orm;
65
        $this->role = $role;
66
67
        $this->source = $orm->getSource($role);
68
        $this->columns = $orm->getSchema()->define($role, Schema::COLUMNS);
69
        $this->primaryKey = $orm->getSchema()->define($role, Schema::PRIMARY_KEY);
70
        $this->primaryColumn = $this->columns[$this->primaryKey] ?? $this->primaryKey;
71
72
        // Resolve field names
73
        foreach ($this->columns as $name => $column) {
74
            $this->fields[] = is_numeric($name) ? $column : $name;
75
        }
76
    }
77
78
    /**
79
     * @inheritdoc
80
     */
81
    public function getRole(): string
82
    {
83
        return $this->role;
84
    }
85
86
    /**
87
     * @inheritdoc
88
     */
89
    public function queueCreate($entity, Node $node, State $state): ContextCarrierInterface
90
    {
91
        $columns = $this->fetchFields($entity);
92
93
        // sync the state
94
        $state->setStatus(Node::SCHEDULED_INSERT);
95
        $state->setData($columns);
96
97
        $columns[$this->primaryKey] = $columns[$this->primaryKey] ?? $this->nextPrimaryKey();
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->nextPrimaryKey() targeting Cycle\ORM\Mapper\DatabaseMapper::nextPrimaryKey() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
98
        if ($columns[$this->primaryKey] === null) {
99
            unset($columns[$this->primaryKey]);
100
        }
101
102
        $insert = new Insert(
103
            $this->source->getDatabase(),
104
            $this->source->getTable(),
105
            $this->mapColumns($columns),
106
            $this->primaryColumn
107
        );
108
109
        $key = isset($columns[$this->primaryKey]) ? $this->primaryKey : Insert::INSERT_ID;
110
        $insert->forward($key, $state, $this->primaryKey);
111
112
        return $insert;
113
    }
114
115
    /**
116
     * @inheritdoc
117
     */
118
    public function queueUpdate($entity, Node $node, State $state): ContextCarrierInterface
119
    {
120
        $data = $this->fetchFields($entity);
121
122
        // in a future mapper must support solid states
123
        $changes = array_udiff_assoc($data, $state->getData(), [Node::class, 'compare']);
124
        unset($changes[$this->primaryKey]);
125
126
        $changedColumns = $this->mapColumns($changes);
127
128
        $update = new Update($this->source->getDatabase(), $this->source->getTable(), $changedColumns);
129
        $state->setStatus(Node::SCHEDULED_UPDATE);
130
        $state->setData($changes);
131
132
        // we are trying to update entity without PK right now
133
        $state->forward(
134
            $this->primaryKey,
135
            $update,
136
            $this->primaryColumn,
137
            true,
138
            ConsumerInterface::SCOPE
139
        );
140
141
        return $update;
142
    }
143
144
    /**
145
     * @inheritdoc
146
     */
147
    public function queueDelete($entity, Node $node, State $state): CommandInterface
148
    {
149
        $delete = new Delete($this->source->getDatabase(), $this->source->getTable());
150
        $state->setStatus(Node::SCHEDULED_DELETE);
151
        $state->decClaim();
152
153
        $delete->waitScope($this->primaryColumn);
154
        $state->forward(
155
            $this->primaryKey,
156
            $delete,
157
            $this->primaryColumn,
158
            true,
159
            ConsumerInterface::SCOPE
160
        );
161
162
        return $delete;
163
    }
164
165
    /**
166
     * Generate next sequential entity ID. Return null to use autoincrement value.
167
     *
168
     * @return mixed|null
169
     */
170
    protected function nextPrimaryKey()
171
    {
172
        return null;
173
    }
174
175
    /**
176
     * Get entity columns.
177
     *
178
     * @param object $entity
179
     * @return array
180
     */
181
    abstract protected function fetchFields($entity): array;
182
183
    /**
184
     * Map internal field names to database specific column names.
185
     *
186
     * @param array $columns
187
     * @return array
188
     */
189
    protected function mapColumns(array $columns): array
190
    {
191
        $result = [];
192
        foreach ($columns as $column => $value) {
193
            $result[$this->columns[$column] ?? $column] = $value;
194
        }
195
196
        return $result;
197
    }
198
}
199