Passed
Branch main (d0474b)
by Sammy
03:57 queued 02:09
created

TightModel.php (1 issue)

Severity
1
<?php
2
3
namespace HexMakina\TightORM;
4
5
use HexMakina\Crudites\Interfaces\SelectInterface;
6
use HexMakina\TightORM\Interfaces\ModelInterface;
7
use HexMakina\Traitor\Traitor;
8
9
abstract class TightModel extends TableModel implements ModelInterface
10
{
11
    use Traitor;
12
13
    public function __toString()
14
    {
15
        return static::class_short_name() . ' #' . $this->get_id();
16
    }
17
18
    public function immortal(): bool
19
    {
20
        return self::IMMORTAL_BY_DEFAULT;
21
    }
22
23
    public function extract(ModelInterface $extract_model, $ignore_nullable = false)
24
    {
25
        $extraction_class = get_class($extract_model);
26
27
        $extraction_table = $extraction_class::table();
28
        foreach ($extraction_table->columns() as $column_name => $column) {
29
            $probe_name = $extraction_class::table_alias() . '_' . $column_name;
30
31
            if (!is_null($probe_res = $this->get($probe_name))) {
32
                $extract_model->set($column_name, $probe_res);
33
            } elseif (!$column->is_nullable() && $ignore_nullable === false) {
34
                return null;
35
            }
36
        }
37
38
        return $extract_model;
39
    }
40
41
    public function copy()
42
    {
43
        $class = get_called_class();
44
        $clone = new $class();
45
46
        foreach ($class::table()->columns() as $column_name => $column) {
47
            if (!is_null($column->default())) {
48
                continue;
49
            }
50
            if ($column->is_auto_incremented()) {
51
                continue;
52
            }
53
54
            $clone->set($column_name, $this->get($column_name));
55
        }
56
57
        unset($clone->created_by);
58
        return $clone;
59
    }
60
61
    public function toggle($prop_name)
62
    {
63
        parent::toggle_boolean(static::table(), $prop_name, $this->get_id());
64
    }
65
66
67
    public function validate(): array
68
    {
69
        return []; // no errors
70
    }
71
72
    public function before_save(): array
73
    {
74
        return [];
75
    }
76
77
    public function after_save()
78
    {
79
        return true;
80
    }
81
82
    // return array of errors
83
    public function save($operator_id)
84
    {
85
        try {
86
            if (!empty($errors = $this->search_and_execute_trait_methods('before_save'))) {
87
                return $errors;
88
            }
89
90
            if (!empty($errors = $this->before_save())) {
91
                return $errors;
92
            }
93
94
            if (!empty($errors = $this->validate())) { // Model level validation
95
                return $errors;
96
            }
97
98
            //1 tight model *always* match a single table row
99
            $table_row = $this->to_table_row($operator_id);
100
101
102
            if ($table_row->is_altered()) { // someting to save ?
103
                if (!empty($persistence_errors = $table_row->persist())) { // validate and persist
104
                    $errors = [];
105
                    foreach ($persistence_errors as $column_name => $err) {
106
                        $errors[sprintf('MODEL_%s_FIELD_%s', static::model_type(), $column_name)] = $err;
107
                    }
108
109
                    return $errors;
110
                }
111
112
                // reload row
113
                $refreshed_row = static::table()->restore($table_row->export());
114
115
                // update model
116
                $this->import($refreshed_row->export());
117
            }
118
119
            $this->search_and_execute_trait_methods('after_save');
120
            $this->after_save();
121
        } catch (\Exception $e) {
122
            return [$e->getMessage()];
123
        }
124
125
        return [];
126
    }
127
128
    // returns false on failure or last executed delete query
129
    public function before_destroy(): bool
130
    {
131
        if ($this->is_new() || $this->immortal()) {
132
            return false;
133
        }
134
135
        $this->search_and_execute_trait_methods(__FUNCTION__);
136
137
        return true;
138
    }
139
140
    public function after_destroy()
141
    {
142
        $this->search_and_execute_trait_methods(__FUNCTION__);
143
    }
144
145
    public function destroy($operator_id): bool
146
    {
147
        if ($this->before_destroy() === false) {
148
            return false;
149
        }
150
151
        $table_row = static::table()->restore(get_object_vars($this));
152
153
        if ($table_row->wipe() === false) {
154
            return false;
155
        }
156
157
        $this->after_destroy();
158
159
        return true;
160
    }
161
162
    //------------------------------------------------------------  Data Retrieval
163
    public static function query_retrieve($filters = [], $options = []): SelectInterface
164
    {
165
        $class = get_called_class();
166
        $query = (new TightModelSelector(new $class()))->select($filters, $options);
167
        // $query_old = self::query_retrieve_old($filters,$options);
168
        //
169
        // if($res = $query->compare($query_old) !== true)
170
        // {
171
        //   vd($res);
172
        //   vd($query->statement(), 'new statement');
173
        //   vd($query_old->statement(), 'old statement');
174
        //   ddt('different');
175
        // }
176
        return $query;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $query returns the type HexMakina\Crudites\Interfaces\SelectInterface which is incompatible with the return type mandated by HexMakina\TightORM\TableModel::query_retrieve() of HexMakina\TightORM\SelectInterface.

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.
    }
}
Loading history...
177
    }
178
179
    //------------------------------------------------------------  Introspection & Data Validation
180
    /**
181
     * Cascade of table name guessing goes:
182
     * 1. Constant 'TABLE_ALIAS' defined in class
183
     * 2. lower-case class name
184
     *
185
     */
186
    public static function table_alias(): string
187
    {
188
        return defined(get_called_class() . '::TABLE_ALIAS') ? static::TABLE_ALIAS : static::model_type();
189
    }
190
191
    public static function class_short_name()
192
    {
193
        return (new \ReflectionClass(get_called_class()))->getShortName();
194
    }
195
196
    public static function model_type(): string
197
    {
198
        return strtolower(self::class_short_name());
199
    }
200
201
    public static function select_also()
202
    {
203
        return ['*'];
204
    }
205
}
206