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

TightModel::extract()   A

Complexity

Conditions 5
Paths 4

Size

Total Lines 16
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

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