Issues (188)

src/Mapper/DatabaseMapper.php (5 issues)

1
<?php
2
3
declare(strict_types=1);
4
5
namespace Cycle\ORM\Mapper;
6
7
use Cycle\ORM\Command\CommandInterface;
8
use Cycle\ORM\Command\Database\Delete;
9
use Cycle\ORM\Command\Database\Insert;
10
use Cycle\ORM\Command\Database\Update;
11
use Cycle\ORM\Heap\Node;
12
use Cycle\ORM\Heap\State;
13
use Cycle\ORM\MapperInterface;
14
use Cycle\ORM\ORMInterface;
15
use Cycle\ORM\Parser\CastableInterface;
16
use Cycle\ORM\Parser\TypecastInterface;
17
use Cycle\ORM\Parser\UncastableInterface;
18
use Cycle\ORM\Service\RelationProviderInterface;
19
use Cycle\ORM\Service\TypecastProviderInterface;
20
use Cycle\ORM\RelationMap;
21
use Cycle\ORM\SchemaInterface;
22
use Cycle\ORM\Select\SourceInterface;
23
24
/**
25
 * Provides basic capabilities to work with entities persisted in SQL databases.
26
 */
27
abstract class DatabaseMapper implements MapperInterface
28
{
29
    protected SourceInterface $source;
30
    protected array $columns = [];
31
    protected array $parentColumns = [];
32
33
    /** @var string[] */
34
    protected array $primaryColumns = [];
35
36
    /** @var string[] */
37
    protected array $primaryKeys;
38
39
    protected RelationMap $relationMap;
40
    private ?TypecastInterface $typecast;
41
42
    /** @var array<non-empty-string, int> */
0 ignored issues
show
Documentation Bug introduced by
The doc comment array<non-empty-string, int> at position 2 could not be parsed: Unknown type name 'non-empty-string' at position 2 in array<non-empty-string, int>.
Loading history...
43 6842
    private array $generatedFields;
44
45
    public function __construct(
46
        ORMInterface $orm,
47 6842
        protected string $role,
48 6842
    ) {
49 6842
        $this->source = $orm->getSource($role);
50
        $this->typecast = $orm->getService(TypecastProviderInterface::class)->getTypecast($role);
51 6842
        $this->relationMap = $orm->getService(RelationProviderInterface::class)->getRelationMap($role);
52 6842
53 6842
        $schema = $orm->getSchema();
54
        foreach ($schema->define($role, SchemaInterface::COLUMNS) as $property => $column) {
55
            $this->columns[\is_int($property) ? $column : $property] = $column;
56
        }
57 6842
58 6842
        $this->generatedFields = $schema->define($role, SchemaInterface::GENERATED_FIELDS) ?? [];
59 800
        // Parent's fields
60 800
        $parent = $schema->define($role, SchemaInterface::PARENT);
61
        while ($parent !== null) {
62 800
            foreach ($schema->define($parent, SchemaInterface::COLUMNS) as $property => $column) {
63
                $this->parentColumns[\is_int($property) ? $column : $property] = $column;
64
            }
65 6842
            $parent = $schema->define($parent, SchemaInterface::PARENT);
66 6842
        }
67 6842
68
        $this->primaryKeys = (array) $schema->define($role, SchemaInterface::PRIMARY_KEY);
69
        foreach ($this->primaryKeys as $PK) {
70
            $this->primaryColumns[] = $this->columns[$PK] ?? $PK;
71 1578
        }
72
    }
73 1578
74
    public function getRole(): string
75
    {
76 6598
        return $this->role;
77
    }
78 6598
79 3464
    public function cast(array $data): array
80
    {
81
        if ($this->typecast instanceof CastableInterface) {
82
            $data = $this->typecast->cast($data);
0 ignored issues
show
The method cast() does not exist on Cycle\ORM\Parser\TypecastInterface. It seems like you code against a sub-type of said class. However, the method does not exist in Cycle\ORM\Parser\UncastableInterface. Are you sure you never get one of those? ( Ignorable by Annotation )

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

82
            /** @scrutinizer ignore-call */ 
83
            $data = $this->typecast->cast($data);
Loading history...
The method cast() does not exist on null. ( Ignorable by Annotation )

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

82
            /** @scrutinizer ignore-call */ 
83
            $data = $this->typecast->cast($data);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
83 6598
        }
84 5188
85 3196
        // Cast relations
86
        foreach ($this->relationMap->getRelations() as $field => $relation) {
87 3530
            if (!\array_key_exists($field, $data)) {
88 3530
                continue;
89
            }
90
            $value = $data[$field];
91
            if (!\is_array($value) && $value !== null) {
92 3530
                continue;
93 3530
            }
94
            // break links
95
            unset($data[$field]);
96 6598
            $data[$field] = $relation->cast($value);
97
        }
98
99 2548
        return $data;
100
    }
101 2548
102 2476
    public function uncast(array $data): array
103
    {
104
        if (!$this->typecast instanceof UncastableInterface) {
105 224
            return $data;
106
        }
107
108 1700
        return $this->typecast->uncast($data);
0 ignored issues
show
The method uncast() does not exist on Cycle\ORM\Parser\TypecastInterface. It seems like you code against a sub-type of said class. However, the method does not exist in Cycle\ORM\Tests\Function...\Fixture\ParentTypecast or Cycle\ORM\Parser\CastableInterface or Cycle\ORM\Tests\Function...cast\Fixture\Typecaster or Cycle\ORM\Tests\Function...Case408\Type\TypeCaster or Cycle\ORM\Tests\Function...st\Fixture\UuidTypecast. Are you sure you never get one of those? ( Ignorable by Annotation )

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

108
        return $this->typecast->/** @scrutinizer ignore-call */ uncast($data);
Loading history...
109
    }
110 1700
111
    public function queueCreate(object $entity, Node $node, State $state): CommandInterface
112
    {
113 1700
        $values = $state->getData();
114
115 1700
        // sync the state
116 1700
        $state->setStatus(Node::SCHEDULED_INSERT);
117 1428
118 16
        foreach ($this->primaryKeys as $key) {
119
            if (!isset($values[$key])) {
120 1428
                foreach ($this->nextPrimaryKey() ?? [] as $pk => $value) {
0 ignored issues
show
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...
121
                    $state->register($pk, $value);
122
                }
123
                break;
124 1700
            }
125 1700
        }
126 1700
127
        return new Insert(
128
            $this->source->getDatabase(),
129 1700
            $this->source->getTable(),
130 1700
            $state,
131
            $this,
132
            $this->primaryKeys,
133
            \count($this->primaryColumns) === 1 ? $this->primaryColumns[0] : null,
134 1148
            $this->generatedFields,
135
        );
136 1148
    }
137
138 1148
    public function queueUpdate(object $entity, Node $node, State $state): CommandInterface
139 1148
    {
140 1148
        $fromData = $state->getTransactionData();
141
142
        $update = new Update(
143 1148
            $this->source->getDatabase(),
144
            $this->source->getTable(),
145
            $state,
146 1148
            $this,
147
            $this->primaryKeys,
148 1148
        );
149
150
        foreach ($this->primaryKeys as $pk) {
151 1148
            // set update criteria right now
152
            $update->setScope($pk, $fromData[$pk]);
153
        }
154 480
155
        return $update;
156 480
    }
157 480
158 480
    public function queueDelete(object $entity, Node $node, State $state): CommandInterface
159
    {
160
        $delete = new Delete(
161
            $this->source->getDatabase(),
162
            $this->source->getTable(),
163 480
            $state,
164
            $this,
165 480
        );
166 480
167 480
        $state->setStatus(Node::SCHEDULED_DELETE);
168
169 480
        $delete->waitScope(...$this->primaryKeys);
170
        $fromData = $node->getData();
171
        foreach ($this->primaryKeys as $pk) {
172 480
            // set update criteria right now
173
            $delete->setScope($pk, $fromData[$pk]);
174
        }
175
176
        return $delete;
177
    }
178 1412
179
    public function mapColumns(array &$values): array
180 1412
    {
181
        $result = [];
182
        foreach ($values as $column => $value) {
183 2548
            if (isset($this->columns[$column])) {
184
                $result[$this->columns[$column]] = $value;
185 2548
            } else {
186 2548
                unset($values[$column]);
187 2508
            }
188 2508
        }
189
190 168
        return $result;
191
    }
192
193
    /**
194 2548
     * Generate next sequential entity ID. Return null to use autoincrement value.
195
     */
196
    protected function nextPrimaryKey(): ?array
197
    {
198
        return null;
199
    }
200
}
201