Passed
Push — main ( f3a02f...f6abf4 )
by Sammy
02:15
created

TightModel.php (1 issue)

Labels
Severity
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
The type HexMakina\Tracer\TraceableInterface was not found. Maybe you did not declare it correctly or list all dependencies?

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:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
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