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) { |
||
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 | if (!is_numeric($name)) { |
||
75 | $this->fields[] = $name; |
||
76 | } else { |
||
77 | $this->fields[] = $column; |
||
78 | } |
||
79 | } |
||
80 | } |
||
81 | |||
82 | /** |
||
83 | * @inheritdoc |
||
84 | */ |
||
85 | public function getRole(): string |
||
86 | { |
||
87 | return $this->role; |
||
88 | } |
||
89 | |||
90 | /** |
||
91 | * @inheritdoc |
||
92 | */ |
||
93 | public function queueCreate($entity, Node $node, State $state): ContextCarrierInterface |
||
94 | { |
||
95 | $columns = $this->fetchFields($entity); |
||
96 | |||
97 | // sync the state |
||
98 | $state->setStatus(Node::SCHEDULED_INSERT); |
||
99 | $state->setData($columns); |
||
100 | |||
101 | $columns[$this->primaryKey] = $columns[$this->primaryKey] ?? $this->nextPrimaryKey(); |
||
0 ignored issues
–
show
|
|||
102 | if ($columns[$this->primaryKey] === null) { |
||
103 | unset($columns[$this->primaryKey]); |
||
104 | } |
||
105 | |||
106 | $insert = new Insert( |
||
107 | $this->source->getDatabase(), |
||
108 | $this->source->getTable(), |
||
109 | $this->mapColumns($columns), |
||
110 | $this->primaryColumn |
||
111 | ); |
||
112 | |||
113 | if (!array_key_exists($this->primaryKey, $columns)) { |
||
114 | $insert->forward(Insert::INSERT_ID, $state, $this->primaryKey); |
||
115 | } else { |
||
116 | $insert->forward($this->primaryKey, $state, $this->primaryKey); |
||
117 | } |
||
118 | |||
119 | return $insert; |
||
120 | } |
||
121 | |||
122 | /** |
||
123 | * @inheritdoc |
||
124 | */ |
||
125 | public function queueUpdate($entity, Node $node, State $state): ContextCarrierInterface |
||
126 | { |
||
127 | $data = $this->fetchFields($entity); |
||
128 | |||
129 | // in a future mapper must support solid states |
||
130 | $changes = array_udiff_assoc($data, $state->getData(), [static::class, 'compare']); |
||
131 | unset($changes[$this->primaryKey]); |
||
132 | |||
133 | $changedColumns = $this->mapColumns($changes); |
||
134 | |||
135 | $update = new Update($this->source->getDatabase(), $this->source->getTable(), $changedColumns); |
||
136 | $state->setStatus(Node::SCHEDULED_UPDATE); |
||
137 | $state->setData($changes); |
||
138 | |||
139 | // we are trying to update entity without PK right now |
||
140 | $state->forward( |
||
141 | $this->primaryKey, |
||
142 | $update, |
||
143 | $this->primaryColumn, |
||
144 | true, |
||
145 | ConsumerInterface::SCOPE |
||
146 | ); |
||
147 | |||
148 | return $update; |
||
149 | } |
||
150 | |||
151 | /** |
||
152 | * @inheritdoc |
||
153 | */ |
||
154 | public function queueDelete($entity, Node $node, State $state): CommandInterface |
||
155 | { |
||
156 | $delete = new Delete($this->source->getDatabase(), $this->source->getTable()); |
||
157 | $state->setStatus(Node::SCHEDULED_DELETE); |
||
158 | $state->decClaim(); |
||
159 | |||
160 | $delete->waitScope($this->primaryColumn); |
||
161 | $state->forward( |
||
162 | $this->primaryKey, |
||
163 | $delete, |
||
164 | $this->primaryColumn, |
||
165 | true, |
||
166 | ConsumerInterface::SCOPE |
||
167 | ); |
||
168 | |||
169 | return $delete; |
||
170 | } |
||
171 | |||
172 | /** |
||
173 | * Generate next sequential entity ID. Return null to use autoincrement value. |
||
174 | * |
||
175 | * @return mixed|null |
||
176 | */ |
||
177 | protected function nextPrimaryKey() |
||
178 | { |
||
179 | return null; |
||
180 | } |
||
181 | |||
182 | /** |
||
183 | * Get entity columns. |
||
184 | * |
||
185 | * @param object $entity |
||
186 | * @return array |
||
187 | */ |
||
188 | abstract protected function fetchFields($entity): array; |
||
189 | |||
190 | /** |
||
191 | * Map internal field names to database specific column names. |
||
192 | * |
||
193 | * @param array $columns |
||
194 | * @return array |
||
195 | */ |
||
196 | protected function mapColumns(array $columns): array |
||
197 | { |
||
198 | $result = []; |
||
199 | foreach ($columns as $column => $value) { |
||
200 | if (array_key_exists($column, $this->columns)) { |
||
201 | $result[$this->columns[$column]] = $value; |
||
202 | } else { |
||
203 | $result[$column] = $value; |
||
204 | } |
||
205 | } |
||
206 | |||
207 | return $result; |
||
208 | } |
||
209 | |||
210 | /** |
||
211 | * @param mixed $a |
||
212 | * @param mixed $b |
||
213 | * @return int |
||
214 | */ |
||
215 | protected static function compare($a, $b): int |
||
216 | { |
||
217 | if ($a == $b) { |
||
218 | return 0; |
||
219 | } |
||
220 | |||
221 | return ($a > $b) ? 1 : -1; |
||
222 | } |
||
223 | } |
||
224 |
This check looks for function or method calls that always return null and whose return value is used.
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.