Issues (178)

src/Command/Database/Insert.php (4 issues)

1
<?php
2
3
declare(strict_types=1);
4
5
namespace Cycle\ORM\Command\Database;
6
7
use Cycle\Database\DatabaseInterface;
8
use Cycle\Database\Query\ReturningInterface;
9
use Cycle\ORM\Command\StoreCommand;
10
use Cycle\ORM\Command\Traits\ErrorTrait;
11
use Cycle\ORM\Command\Traits\MapperTrait;
12
use Cycle\ORM\Heap\State;
13
use Cycle\ORM\MapperInterface;
14
use Cycle\ORM\Schema\GeneratedField;
15
16
/**
17
 * Insert data into associated table and provide lastInsertID promise.
18
 */
19
final class Insert extends StoreCommand
20
{
21
    use ErrorTrait;
0 ignored issues
show
The trait Cycle\ORM\Command\Traits\ErrorTrait requires some properties which are not provided by Cycle\ORM\Command\Database\Insert: $waitScope, $waitContext
Loading history...
22
    use MapperTrait;
23 1710
24
    /**
25
     * @param non-empty-string $table
0 ignored issues
show
Documentation Bug introduced by
The doc comment non-empty-string at position 0 could not be parsed: Unknown type name 'non-empty-string' at position 0 in non-empty-string.
Loading history...
26
     * @param string[] $primaryKeys
27
     * @param non-empty-string|null $pkColumn
28
     * @param array<non-empty-string, int> $generated
29
     */
30
    public function __construct(
31
        DatabaseInterface $db,
32 1710
        string $table,
33 1710
        State $state,
34
        ?MapperInterface $mapper,
35
        private array $primaryKeys = [],
36 2
        private ?string $pkColumn = null,
37
        private array $generated = [],
38 2
    ) {
39
        parent::__construct($db, $table, $state);
40
        $this->mapper = $mapper;
41 1700
    }
42
43 1700
    public function isReady(): bool
44
    {
45
        return true;
46
    }
47
48
    public function hasData(): bool
49
    {
50
        return match (true) {
51
            $this->columns !== [],
52
            $this->state->getData() !== [],
53
            $this->hasGeneratedFields() => true,
54
            default => false,
55
        };
56
    }
57
58
    public function getStoreData(): array
59 1698
    {
60
        if ($this->appendix !== []) {
61 1698
            $this->state->setData($this->appendix);
62
            $this->appendix = [];
63 1698
        }
64 56
        $data = $this->state->getData();
65
        return array_merge($this->columns, $this->mapper?->mapColumns($data) ?? $data);
66
    }
67 1698
68
    /**
69
     * Insert data into associated table.
70 1698
     */
71 1698
    public function execute(): void
72 1402
    {
73
        $state = $this->state;
74
        $returningFields = [];
75 1698
76
        if ($this->appendix !== []) {
77 1698
            $state->setData($this->appendix);
78 1698
        }
79 1698
80
        $uncasted = $data = $state->getData();
81 1698
82 370
        // filter PK null values
83
        foreach ($this->primaryKeys as $key) {
84
            if (!isset($uncasted[$key])) {
85 1698
                unset($uncasted[$key]);
86
            }
87 1682
        }
88 1466
        // unset db-generated fields if they are null
89 1466
        foreach ($this->generated as $column => $mode) {
90 1386
            if (($mode & GeneratedField::ON_INSERT) === 0x0) {
91
                continue;
92 1386
            }
93
94
            $returningFields[$column] = $mode;
95
            if (!isset($uncasted[$column])) {
96
                unset($uncasted[$column]);
97 1682
            }
98
        }
99 1682
        $uncasted = $this->prepareData($uncasted);
100
101
        $insert = $this->db
102
            ->insert($this->table)
0 ignored issues
show
It seems like $this->table can also be of type null; however, parameter $table of Cycle\Database\DatabaseInterface::insert() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

102
            ->insert(/** @scrutinizer ignore-type */ $this->table)
Loading history...
103
            ->values(\array_merge($this->columns, $uncasted));
104
105
        if ($this->pkColumn !== null && $returningFields === []) {
106
            $returningFields[$this->primaryKeys[0]] ??= $this->pkColumn;
107
        }
108
109
        if ($insert instanceof ReturningInterface && $returningFields !== []) {
110
            // Map generated fields to columns
111
            $returning = $this->mapper->mapColumns($returningFields);
0 ignored issues
show
The method mapColumns() 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

111
            /** @scrutinizer ignore-call */ 
112
            $returning = $this->mapper->mapColumns($returningFields);

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...
112
            // Array of [field name => column name]
113
            $returning = \array_combine(\array_keys($returningFields), \array_keys($returning));
114
115
            $insert->returning(...\array_values($returning));
116
117
            $insertID = $insert->run();
118
119
            if (\count($returning) === 1) {
120
                $field = \array_key_first($returning);
121
                $state->register(
122
                    $field,
123
                    $this->mapper === null ? $insertID : $this->mapper->cast([$field => $insertID])[$field],
124
                );
125
            } else {
126
                foreach ($this->mapper->cast($insertID) as $field => $value) {
127
                    $state->register($field, $value);
128
                }
129
            }
130
        } else {
131
            $insertID = $insert->run();
132
133
            if ($insertID !== null && \count($this->primaryKeys) === 1) {
134
                $fpk = $this->primaryKeys[0]; // first PK
135
                if (!isset($data[$fpk])) {
136
                    $state->register(
137
                        $fpk,
138
                        $this->mapper === null ? $insertID : $this->mapper->cast([$fpk => $insertID])[$fpk]
139
                    );
140
                }
141
            }
142
        }
143
144
        $state->updateTransactionData();
145
146
        parent::execute();
147
    }
148
149
    public function register(string $key, mixed $value): void
150
    {
151
        $this->state->register($key, $value);
152
    }
153
154
    /**
155
     * Has fields that weren't provided but will be generated by the database or PHP.
156
     */
157
    private function hasGeneratedFields(): bool
158
    {
159
        if ($this->generated === []) {
160
            return false;
161
        }
162
163
        $data = $this->state->getData();
164
165
        foreach ($this->generated as $field => $mode) {
166
            if (($mode & (GeneratedField::ON_INSERT | GeneratedField::BEFORE_INSERT)) === 0x0) {
167
                continue;
168
            }
169
170
            if (!isset($data[$field])) {
171
                return true;
172
            }
173
        }
174
175
        return false;
176
    }
177
}
178