1 | <?php |
||||||
2 | |||||||
3 | namespace HexMakina\Crudites; |
||||||
4 | |||||||
5 | use HexMakina\BlackBox\Database\ConnectionInterface; |
||||||
6 | use HexMakina\BlackBox\Database\RowInterface; |
||||||
7 | use HexMakina\BlackBox\Database\ResultInterface; |
||||||
0 ignored issues
–
show
|
|||||||
8 | |||||||
9 | use HexMakina\Crudites\CruditesException; |
||||||
10 | |||||||
11 | class Row implements RowInterface |
||||||
12 | { |
||||||
13 | private string $table; |
||||||
14 | |||||||
15 | private ConnectionInterface $connection; |
||||||
16 | |||||||
17 | /** @var array<int|string,mixed>|null $load from database */ |
||||||
18 | private ?array $load = null; |
||||||
19 | |||||||
20 | /** @var array<int|string,mixed> $fresh from the constructor */ |
||||||
21 | private array $fresh = []; |
||||||
22 | |||||||
23 | /** @var array<int|string,mixed> $alterations during lifecycle */ |
||||||
24 | private array $alterations = []; |
||||||
25 | |||||||
26 | /** @var ResultInterface|null $result the result from the last executed query */ |
||||||
27 | private ?ResultInterface $result = null; |
||||||
28 | |||||||
29 | |||||||
30 | /** @param array<string,mixed> $datass */ |
||||||
31 | /** |
||||||
32 | * Represents a row in a table. |
||||||
33 | * |
||||||
34 | * @param ConnectionInterface $connection The database connection. |
||||||
35 | * @param string $table The table name. |
||||||
36 | * @param array $fresh The fresh data for the row. |
||||||
37 | */ |
||||||
38 | public function __construct(ConnectionInterface $connection, string $table, array $fresh = []) |
||||||
39 | { |
||||||
40 | $this->connection = $connection; |
||||||
41 | $this->table = $table; |
||||||
42 | $this->fresh = $fresh; |
||||||
43 | } |
||||||
44 | |||||||
45 | // property overloading |
||||||
46 | public function __get($name) |
||||||
47 | { |
||||||
48 | return $this->alterations[$name] |
||||||
49 | ?? $this->fresh[$name] |
||||||
50 | ?? $this->load[$name] |
||||||
51 | ?? null; |
||||||
52 | } |
||||||
53 | |||||||
54 | public function __isset($name) |
||||||
55 | { |
||||||
56 | return isset($this->alterations[$name]) |
||||||
57 | || isset($this->fresh[$name]) |
||||||
58 | || isset($this->load[$name]); |
||||||
59 | } |
||||||
60 | |||||||
61 | public function __set(string $name, $value = null) |
||||||
62 | { |
||||||
63 | if ( |
||||||
64 | $value === $this->$name |
||||||
65 | || !$this->connection->schema()->hasColumn($this->table, $name) |
||||||
66 | ) { |
||||||
67 | return; |
||||||
68 | } |
||||||
69 | |||||||
70 | $attributes = $this->connection->schema()->attributes($this->table, $name); |
||||||
71 | |||||||
72 | // skip auto_incremented columns |
||||||
73 | if ($attributes->isAuto()) { |
||||||
74 | return; |
||||||
75 | } |
||||||
76 | |||||||
77 | // Replace empty strings with null if the column is nullable |
||||||
78 | if (trim((string)$value) === '' && $attributes->nullable()) { |
||||||
79 | $value = null; |
||||||
80 | } |
||||||
81 | |||||||
82 | // checks for changes with loaded data. using == instead of === is risky but needed |
||||||
83 | if ($this->isNew() || $this->load[$name] != $value) { |
||||||
84 | $this->alterations[$name] = $value; |
||||||
85 | } |
||||||
86 | |||||||
87 | } |
||||||
88 | |||||||
89 | public function __unset($name) |
||||||
90 | { |
||||||
91 | unset($this->alterations[$name]); |
||||||
92 | } |
||||||
93 | |||||||
94 | // output |
||||||
95 | public function __toString() |
||||||
96 | { |
||||||
97 | return PHP_EOL . 'load: ' |
||||||
98 | . json_encode($this->load) |
||||||
99 | . PHP_EOL . 'alterations: ' |
||||||
100 | . json_encode(array_keys($this->alterations)); |
||||||
101 | } |
||||||
102 | |||||||
103 | public function __debugInfo() |
||||||
104 | { |
||||||
105 | return [ |
||||||
106 | 'table' => $this->table, |
||||||
107 | 'load' => $this->load, |
||||||
108 | 'fresh' => $this->fresh, |
||||||
109 | 'alterations' => $this->alterations, |
||||||
110 | 'result' => $this->result, |
||||||
111 | ]; |
||||||
112 | } |
||||||
113 | |||||||
114 | public function import(array $dat_ass): RowInterface |
||||||
115 | { |
||||||
116 | foreach ($dat_ass as $k => $v) { |
||||||
117 | $this->$k = $v; |
||||||
118 | } |
||||||
119 | |||||||
120 | return $this; |
||||||
121 | } |
||||||
122 | |||||||
123 | |||||||
124 | public function export(): array |
||||||
125 | { |
||||||
126 | return array_merge((array)$this->load, $this->fresh, $this->alterations); |
||||||
127 | } |
||||||
128 | |||||||
129 | |||||||
130 | |||||||
131 | public function table(): string |
||||||
132 | { |
||||||
133 | return $this->table; |
||||||
0 ignored issues
–
show
The expression
return $this->table returns the type string which is incompatible with the return type mandated by HexMakina\BlackBox\Database\RowInterface::table() of HexMakina\BlackBox\Database\TableInterface .
In the issue above, the returned value is violating the contract defined by the mentioned interface. Let's take a look at an example: interface HasName {
/** @return string */
public function getName();
}
class Name {
public $name;
}
class User implements HasName {
/** @return string|Name */
public function getName() {
return new Name('foo'); // This is a violation of the ``HasName`` interface
// which only allows a string value to be returned.
}
}
![]() |
|||||||
134 | } |
||||||
135 | |||||||
136 | public function isNew(): bool |
||||||
137 | { |
||||||
138 | return empty($this->load); |
||||||
139 | } |
||||||
140 | |||||||
141 | public function isAltered(): bool |
||||||
142 | { |
||||||
143 | return !empty($this->alterations); |
||||||
144 | } |
||||||
145 | |||||||
146 | public function load(?array $datass = null): Rowinterface |
||||||
147 | { |
||||||
148 | $unique_match = $this->connection->schema()->matchUniqueness($this->table, $datass ?? $this->export()); |
||||||
149 | if (empty($unique_match)) { |
||||||
150 | return $this; |
||||||
151 | } |
||||||
152 | |||||||
153 | $query = $this->connection->schema()->select($this->table); |
||||||
154 | $query->where()->andFields($unique_match, $this->table, '='); |
||||||
155 | |||||||
156 | try{ |
||||||
157 | $this->result = $this->connection->result($query); |
||||||
158 | } |
||||||
159 | catch(\Throwable $t){ |
||||||
0 ignored issues
–
show
Coding Style
Comprehensibility
introduced
by
|
|||||||
160 | } |
||||||
161 | |||||||
162 | $res = $this->result->retOne(\PDO::FETCH_ASSOC); |
||||||
0 ignored issues
–
show
The method
retOne() 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
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. ![]() |
|||||||
163 | $this->load = $res === false ? null : $res; |
||||||
164 | |||||||
165 | return $this; |
||||||
166 | } |
||||||
167 | |||||||
168 | /** |
||||||
169 | * @return array<string,string> an array of errors, column name => message |
||||||
170 | */ |
||||||
171 | public function save(): array |
||||||
172 | { |
||||||
173 | if (!$this->isNew() && !$this->isAltered()) { // existing record with no alterations |
||||||
174 | return []; |
||||||
175 | } |
||||||
176 | |||||||
177 | if (!empty($errors = $this->validate())) { // Table level validation |
||||||
178 | return $errors; |
||||||
179 | } |
||||||
180 | try { |
||||||
181 | if ($this->isNew()) { |
||||||
182 | $this->create($this->connection); |
||||||
0 ignored issues
–
show
The call to
HexMakina\Crudites\Row::create() has too many arguments starting with $this->connection .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue. If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above. ![]() |
|||||||
183 | } else { |
||||||
184 | $this->update($this->connection); |
||||||
0 ignored issues
–
show
The call to
HexMakina\Crudites\Row::update() has too many arguments starting with $this->connection .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue. If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above. ![]() |
|||||||
185 | } |
||||||
186 | } catch (CruditesException $cruditesException) { |
||||||
187 | return [$this->table => $cruditesException->getMessage()]; |
||||||
188 | } |
||||||
189 | |||||||
190 | return []; |
||||||
191 | } |
||||||
192 | |||||||
193 | |||||||
194 | /** |
||||||
195 | * Deletes the current record from the database. |
||||||
196 | * |
||||||
197 | * @return bool true if the record was deleted, false otherwise. |
||||||
198 | * @throws CruditesException if a unique match is not found. |
||||||
199 | */ |
||||||
200 | public function wipe(): bool |
||||||
201 | { |
||||||
202 | $datass = $this->load ?? $this->fresh ?? $this->alterations; |
||||||
203 | |||||||
204 | // need The Primary key, then you can wipe at ease |
||||||
205 | if (!empty($pk_match = $this->connection->schema()->matchPrimaryKeys($this->table, $datass))) { |
||||||
206 | $query = $this->connection->schema()->delete($this->table, $pk_match); |
||||||
207 | |||||||
208 | $this->result = $this->connection->result($query); |
||||||
209 | return $this->result->ran(); |
||||||
210 | } |
||||||
211 | |||||||
212 | return false; |
||||||
213 | } |
||||||
214 | |||||||
215 | |||||||
216 | /** |
||||||
217 | * Creates a new record in the database. |
||||||
218 | * Executes an insert query with the current data and updates the alterations tracker with the auto-incremented primary key value if applicable. |
||||||
219 | */ |
||||||
220 | private function create(): void |
||||||
221 | { |
||||||
222 | $query = $this->connection->schema()->insert($this->table, $this->export()); |
||||||
223 | $this->result = $this->connection->result($query); |
||||||
224 | |||||||
225 | // creation might lead to auto_incremented changes |
||||||
226 | // recovering auto_incremented value and pushing it in alterations tracker |
||||||
227 | $aipk = $this->connection->schema()->autoIncrementedPrimaryKey($this->table); |
||||||
228 | if ($aipk !== null) { |
||||||
229 | $this->$aipk = $this->result->lastInsertId(); |
||||||
230 | } |
||||||
231 | } |
||||||
232 | |||||||
233 | /** |
||||||
234 | * Updates the existing record in the database with the current alterations. |
||||||
235 | * |
||||||
236 | * @throws CruditesException if a unique match is not found. |
||||||
237 | */ |
||||||
238 | private function update(): void |
||||||
239 | { |
||||||
240 | $unique_match = $this->connection->schema()->matchUniqueness($this->table, $this->load); |
||||||
241 | |||||||
242 | if (empty($unique_match)) { |
||||||
243 | throw new CruditesException('NO_UNIQUE_MATCH_IN_LOAD_ARRAY'); |
||||||
244 | } |
||||||
245 | |||||||
246 | $query = $this->connection->schema()->update($this->table, $this->alterations, $unique_match); |
||||||
247 | $this->result = $this->connection->result($query); |
||||||
248 | } |
||||||
249 | |||||||
250 | |||||||
251 | //------------------------------------------------------------ type:data validation |
||||||
252 | /** |
||||||
253 | * @return array<mixed,string> containing all invalid data, indexed by field name, or empty if all valid |
||||||
254 | */ |
||||||
255 | public function validate(): array |
||||||
256 | { |
||||||
257 | $errors = []; |
||||||
258 | $datass = $this->export(); |
||||||
259 | |||||||
260 | foreach ($this->connection->schema()->columns($this->table) as $column_name) { |
||||||
261 | |||||||
262 | $attribute = $this->connection->schema()->attributes($this->table, $column_name); |
||||||
263 | $column_errors = $attribute->validateValue($datass[$column_name] ?? null); |
||||||
264 | |||||||
265 | if (!empty($column_errors)) { |
||||||
266 | $errors[$column_name] = $column_errors; |
||||||
267 | } |
||||||
268 | } |
||||||
269 | |||||||
270 | return $errors; |
||||||
271 | } |
||||||
272 | } |
||||||
273 |
The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g.
excluded_paths: ["lib/*"]
, you can move it to the dependency path list as follows:For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths