1 | <?php |
||
2 | |||
3 | namespace HexMakina\TightORM; |
||
4 | |||
5 | use HexMakina\Crudites\{CruditesException}; |
||
6 | use HexMakina\Crudites\Interfaces\TableManipulationInterface; |
||
7 | use HexMakina\Tracer\TraceableInterface; |
||
0 ignored issues
–
show
|
|||
8 | use HexMakina\Crudites\Interfaces\SelectInterface; |
||
9 | use HexMakina\TightORM\Interfaces\ModelInterface; |
||
10 | use HexMakina\Traitor\Traitor; |
||
11 | |||
12 | abstract class TightModel extends TableModel implements ModelInterface, TraceableInterface |
||
13 | { |
||
14 | use Traitor; |
||
15 | |||
16 | public function __toString() |
||
17 | { |
||
18 | return static::class_short_name() . ' #' . $this->get_id(); |
||
19 | } |
||
20 | |||
21 | public function traceable(): bool |
||
22 | { |
||
23 | return true; |
||
24 | } |
||
25 | |||
26 | public function traces(): array |
||
27 | { |
||
28 | return []; |
||
29 | } |
||
30 | |||
31 | public function immortal(): bool |
||
32 | { |
||
33 | return self::IMMORTAL_BY_DEFAULT; |
||
34 | } |
||
35 | |||
36 | public function extract(ModelInterface $extract_model, $ignore_nullable = false) |
||
37 | { |
||
38 | $extraction_class = get_class($extract_model); |
||
39 | |||
40 | $extraction_table = $extraction_class::table(); |
||
41 | foreach ($extraction_table->columns() as $column_name => $column) { |
||
42 | $probe_name = $extraction_class::table_alias() . '_' . $column_name; |
||
43 | |||
44 | if (!is_null($probe_res = $this->get($probe_name))) { |
||
45 | $extract_model->set($column_name, $probe_res); |
||
46 | } elseif (!$column->is_nullable() && $ignore_nullable === false) { |
||
47 | return null; |
||
48 | } |
||
49 | } |
||
50 | |||
51 | return $extract_model; |
||
52 | } |
||
53 | |||
54 | public function copy() |
||
55 | { |
||
56 | $class = get_called_class(); |
||
57 | $clone = new $class(); |
||
58 | |||
59 | foreach ($class::table()->columns() as $column_name => $column) { |
||
60 | if (!is_null($column->default())) { |
||
61 | continue; |
||
62 | } |
||
63 | if ($column->is_auto_incremented()) { |
||
64 | continue; |
||
65 | } |
||
66 | |||
67 | $clone->set($column_name, $this->get($column_name)); |
||
68 | } |
||
69 | |||
70 | unset($clone->created_by); |
||
71 | return $clone; |
||
72 | } |
||
73 | |||
74 | public function toggle($prop_name) |
||
75 | { |
||
76 | parent::toggle_boolean(static::table(), $prop_name, $this->get_id()); |
||
77 | } |
||
78 | |||
79 | |||
80 | public function validate(): array |
||
81 | { |
||
82 | return []; // no errors |
||
83 | } |
||
84 | |||
85 | public function before_save(): array |
||
86 | { |
||
87 | return []; |
||
88 | } |
||
89 | |||
90 | public function after_save() |
||
91 | { |
||
92 | return true; |
||
93 | } |
||
94 | |||
95 | // return array of errors |
||
96 | public function save($operator_id, $tracer = null): ?array |
||
97 | { |
||
98 | try { |
||
99 | if (!empty($errors = $this->search_and_execute_trait_methods('before_save'))) { |
||
100 | return $errors; |
||
101 | } |
||
102 | |||
103 | if (!empty($errors = $this->before_save())) { |
||
104 | return $errors; |
||
105 | } |
||
106 | |||
107 | if (!empty($errors = $this->validate())) { // Model level validation |
||
108 | return $errors; |
||
109 | } |
||
110 | |||
111 | //1 tight model *always* match a single table row |
||
112 | $table_row = $this->to_table_row($operator_id); |
||
113 | |||
114 | |||
115 | if ($table_row->is_altered()) { // someting to save ? |
||
116 | if (!empty($persistence_errors = $table_row->persist())) { // validate and persist |
||
117 | $errors = []; |
||
118 | foreach ($persistence_errors as $column_name => $err) { |
||
119 | $errors[sprintf('MODEL_%s_FIELD_%s', static::model_type(), $column_name)] = $err; |
||
120 | } |
||
121 | |||
122 | return $errors; |
||
123 | } |
||
124 | |||
125 | if (!is_null($tracer) && $this->traceable()) { |
||
126 | $tracer->trace($table_row->last_alter_query(), $operator_id, $this->get_id()); |
||
127 | } |
||
128 | |||
129 | // reload row |
||
130 | $refreshed_row = static::table()->restore($table_row->export()); |
||
131 | |||
132 | // update model |
||
133 | $this->import($refreshed_row->export()); |
||
134 | } |
||
135 | |||
136 | $this->search_and_execute_trait_methods('after_save'); |
||
137 | $this->after_save(); |
||
138 | } catch (\Exception $e) { |
||
139 | return [$e->getMessage()]; |
||
140 | } |
||
141 | |||
142 | return []; |
||
143 | } |
||
144 | |||
145 | // returns false on failure or last executed delete query |
||
146 | public function before_destroy(): bool |
||
147 | { |
||
148 | if ($this->is_new() || $this->immortal()) { |
||
149 | return false; |
||
150 | } |
||
151 | |||
152 | $this->search_and_execute_trait_methods(__FUNCTION__); |
||
153 | |||
154 | return true; |
||
155 | } |
||
156 | |||
157 | public function after_destroy() |
||
158 | { |
||
159 | $this->search_and_execute_trait_methods(__FUNCTION__); |
||
160 | } |
||
161 | |||
162 | public function destroy($operator_id, $tracer = null): bool |
||
163 | { |
||
164 | if ($this->before_destroy() === false) { |
||
165 | return false; |
||
166 | } |
||
167 | |||
168 | $table_row = static::table()->restore(get_object_vars($this)); |
||
169 | |||
170 | if ($table_row->wipe() === false) { |
||
171 | return false; |
||
172 | } |
||
173 | |||
174 | if (!is_null($tracer) && $this->traceable()) { |
||
175 | $tracer->trace($table_row->last_query(), $operator_id, $this->get_id()); |
||
176 | } |
||
177 | |||
178 | $this->after_destroy(); |
||
179 | |||
180 | return true; |
||
181 | } |
||
182 | |||
183 | //------------------------------------------------------------ Data Retrieval |
||
184 | public static function query_retrieve($filters = [], $options = []): SelectInterface |
||
185 | { |
||
186 | $class = get_called_class(); |
||
187 | $query = (new TightModelSelector(new $class()))->select($filters, $options); |
||
188 | // $query_old = self::query_retrieve_old($filters,$options); |
||
189 | // |
||
190 | // if($res = $query->compare($query_old) !== true) |
||
191 | // { |
||
192 | // vd($res); |
||
193 | // vd($query->statement(), 'new statement'); |
||
194 | // vd($query_old->statement(), 'old statement'); |
||
195 | // ddt('different'); |
||
196 | // } |
||
197 | return $query; |
||
198 | } |
||
199 | |||
200 | public static function exists($arg1, $arg2 = null) |
||
201 | { |
||
202 | try { |
||
203 | return self::one($arg1, $arg2); |
||
204 | } catch (CruditesException $e) { |
||
205 | return null; |
||
206 | } |
||
207 | } |
||
208 | |||
209 | /* USAGE |
||
210 | * one($primary_key_value) |
||
211 | * one($unique_column, $value) |
||
212 | */ |
||
213 | public static function one($arg1, $arg2 = null) |
||
214 | { |
||
215 | $mixed_info = is_null($arg2) ? $arg1 : [$arg1 => $arg2]; |
||
216 | |||
217 | $unique_identifiers = get_called_class()::table()->match_uniqueness($mixed_info); |
||
218 | |||
219 | if (empty($unique_identifiers)) { |
||
220 | throw new CruditesException('UNIQUE_IDENTIFIER_NOT_FOUND'); |
||
221 | } |
||
222 | |||
223 | $Query = static::query_retrieve([], ['eager' => true])->aw_fields_eq($unique_identifiers); |
||
224 | switch (count($res = static::retrieve($Query))) { |
||
225 | case 0: |
||
226 | throw new CruditesException('INSTANCE_NOT_FOUND'); |
||
227 | case 1: |
||
228 | return current($res); |
||
229 | default: |
||
230 | throw new CruditesException('SINGULAR_INSTANCE_ERROR'); |
||
231 | } |
||
232 | } |
||
233 | |||
234 | public static function any($field_exact_values, $options = []) |
||
235 | { |
||
236 | $Query = static::query_retrieve([], $options)->aw_fields_eq($field_exact_values); |
||
237 | return static::retrieve($Query); |
||
238 | } |
||
239 | |||
240 | public static function filter($filters = [], $options = []): array |
||
241 | { |
||
242 | return static::retrieve(static::query_retrieve($filters, $options)); |
||
243 | } |
||
244 | |||
245 | public static function listing($filters = [], $options = []): array |
||
246 | { |
||
247 | return static::retrieve(static::query_retrieve($filters, $options)); // listing as arrays for templates |
||
248 | } |
||
249 | |||
250 | // success: return PK-indexed array of results (associative array or object) |
||
251 | public static function retrieve(SelectInterface $Query): array |
||
252 | { |
||
253 | $ret = []; |
||
254 | $pk_name = implode('_', array_keys($Query->table()->primary_keys())); |
||
255 | |||
256 | if (count($pks = $Query->table()->primary_keys()) > 1) { |
||
257 | $concat_pk = sprintf('CONCAT(%s) as %s', implode(',', $pks), $pk_name); |
||
258 | $Query->select_also([$concat_pk]); |
||
259 | } |
||
260 | |||
261 | try { |
||
262 | $Query->run(); |
||
263 | } catch (CruditesException $e) { |
||
264 | return []; |
||
265 | } |
||
266 | |||
267 | if ($Query->is_success()) { |
||
268 | foreach ($Query->ret_obj(get_called_class()) as $rec) { |
||
269 | $ret[$rec->get($pk_name)] = $rec; |
||
270 | } |
||
271 | } |
||
272 | |||
273 | return $ret; |
||
274 | } |
||
275 | |||
276 | public static function get_many_by_AIPK($aipk_values) |
||
277 | { |
||
278 | if (!empty($aipk_values) && !is_null($AIPK = static::table()->auto_incremented_primary_key())) { |
||
279 | return static::retrieve(static::table()->select()->aw_numeric_in($AIPK, $aipk_values)); |
||
280 | } |
||
281 | |||
282 | return null; |
||
283 | } |
||
284 | |||
285 | |||
286 | //------------------------------------------------------------ Introspection & Data Validation |
||
287 | /** |
||
288 | * Cascade of table name guessing goes: |
||
289 | * 1. Constant 'TABLE_ALIAS' defined in class |
||
290 | * 2. lower-case class name |
||
291 | * |
||
292 | * @throws CruditesException, if ever called from Crudites class, must be inherited call |
||
293 | */ |
||
294 | public static function table_alias(): string |
||
295 | { |
||
296 | return defined(get_called_class() . '::TABLE_ALIAS') ? static::TABLE_ALIAS : static::model_type(); |
||
297 | } |
||
298 | |||
299 | public static function class_short_name() |
||
300 | { |
||
301 | return (new \ReflectionClass(get_called_class()))->getShortName(); |
||
302 | } |
||
303 | |||
304 | public static function model_type(): string |
||
305 | { |
||
306 | return strtolower(self::class_short_name()); |
||
307 | } |
||
308 | |||
309 | public static function select_also() |
||
310 | { |
||
311 | return ['*']; |
||
312 | } |
||
313 | } |
||
314 |
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