1 | <?php |
||
2 | namespace HexMakina\TightORM; |
||
3 | |||
4 | use HexMakina\BlackBox\Database\SelectInterface; |
||
5 | use HexMakina\BlackBox\ORM\ModelInterface; |
||
6 | use HexMakina\Traitor\Traitor; |
||
7 | |||
8 | abstract class TightModel extends TableModel implements ModelInterface |
||
9 | { |
||
10 | use Traitor; |
||
11 | |||
12 | public function __toString() |
||
13 | { |
||
14 | return static::class_short_name() . ' #' . $this->id(); |
||
15 | } |
||
16 | |||
17 | public function nid(): string |
||
18 | { |
||
19 | return static::class_short_name(); |
||
20 | } |
||
21 | |||
22 | public function urn(): string |
||
23 | { |
||
24 | return $this->nid() . ':' . $this->id(); |
||
25 | } |
||
26 | |||
27 | public function immortal(): bool |
||
28 | { |
||
29 | return self::IMMORTAL_BY_DEFAULT; |
||
30 | } |
||
31 | |||
32 | // creates new instance, copy data from $this |
||
33 | // does not copy fields with default and AI field |
||
34 | public function copy() |
||
35 | { |
||
36 | $class = get_called_class(); |
||
37 | $clone = new $class(); |
||
38 | |||
39 | foreach ($class::table()->columns() as $column_name => $column) { |
||
40 | if (!is_null($column->default())) { |
||
41 | continue; |
||
42 | } |
||
43 | if ($column->isAutoIncremented()) { |
||
44 | continue; |
||
45 | } |
||
46 | |||
47 | $clone->set($column_name, $this->get($column_name)); |
||
48 | } |
||
49 | |||
50 | // TODO: assuming created_by, must be a function that returns the creation tracking field, make an interface for heaven's sake |
||
51 | unset($clone->created_by); |
||
52 | return $clone; |
||
53 | } |
||
54 | |||
55 | public function validate(): array |
||
56 | { |
||
57 | return []; // no errors |
||
58 | } |
||
59 | |||
60 | public function before_save(): array |
||
61 | { |
||
62 | return []; |
||
63 | } |
||
64 | |||
65 | public function after_save() |
||
66 | { |
||
67 | return true; |
||
68 | } |
||
69 | |||
70 | // return array of errors on failure |
||
71 | // updates the model on success |
||
72 | public function save($operator_id) |
||
73 | { |
||
74 | try { |
||
75 | // errors detection befire saving |
||
76 | $errors = []; |
||
0 ignored issues
–
show
Unused Code
introduced
by
![]() |
|||
77 | if (empty($errors = $this->traitor('before_save'))) { |
||
78 | if (empty($errors = $this->before_save())) { |
||
79 | $errors = $this->validate(); |
||
80 | } |
||
81 | } |
||
82 | if (!empty($errors)) { |
||
83 | return $errors; |
||
84 | } |
||
85 | |||
86 | |||
87 | // a tight model *always* match a single table row |
||
88 | $table_row = $this->to_table_row($operator_id); |
||
89 | |||
90 | if ($table_row->isAltered()) { // someting to save ? |
||
91 | |||
92 | $errors = $table_row->persist(); |
||
93 | |||
94 | if (!empty($errors)) { |
||
95 | return $errors; |
||
96 | } |
||
97 | |||
98 | $refreshed_row = static::table()->restore($table_row->export()); |
||
99 | $this->import($refreshed_row->export()); |
||
100 | } |
||
101 | |||
102 | $this->traitor('after_save'); |
||
103 | $this->after_save(); |
||
104 | } catch (\Exception $e) { |
||
105 | return [$e->getMessage()]; |
||
106 | } |
||
107 | |||
108 | return []; |
||
109 | } |
||
110 | |||
111 | // returns false on failure or last executed delete query |
||
112 | public function before_destroy(): bool |
||
113 | { |
||
114 | if ($this->isNew() || $this->immortal()) { |
||
115 | return false; |
||
116 | } |
||
117 | |||
118 | $this->traitor(__FUNCTION__); |
||
119 | |||
120 | return true; |
||
121 | } |
||
122 | |||
123 | public function after_destroy() |
||
124 | { |
||
125 | $this->traitor(__FUNCTION__); |
||
126 | } |
||
127 | |||
128 | public function destroy($operator_id): bool |
||
129 | { |
||
130 | if ($this->before_destroy() === false) { |
||
131 | return false; |
||
132 | } |
||
133 | $table_row = static::table()->restore(get_object_vars($this)); |
||
134 | |||
135 | if ($table_row->wipe() === false) { |
||
136 | return false; |
||
137 | } |
||
138 | |||
139 | $this->after_destroy(); |
||
140 | |||
141 | return true; |
||
142 | } |
||
143 | |||
144 | //------------------------------------------------------------ Data Retrieval |
||
145 | public static function filter($filters = [], $options = []): SelectInterface |
||
146 | { |
||
147 | $class = static::class; |
||
148 | $query = (new TightModelSelector(new $class()))->select($filters, $options); |
||
149 | return $query; |
||
150 | } |
||
151 | |||
152 | //------------------------------------------------------------ Introspection & Data Validation |
||
153 | /** |
||
154 | * Cascade of table name guessing goes: |
||
155 | * 1. Constant 'TABLE_ALIAS' defined in class |
||
156 | * 2. lower-case class name |
||
157 | * |
||
158 | */ |
||
159 | public static function tableAlias(): string |
||
160 | { |
||
161 | if (defined(get_called_class() . '::TABLE_ALIAS')) { |
||
162 | return get_called_class()::TABLE_ALIAS; |
||
163 | } |
||
164 | |||
165 | return static::model_type(); |
||
166 | } |
||
167 | |||
168 | public static function model_type(): string |
||
169 | { |
||
170 | if (defined(get_called_class() . '::MODEL_TYPE')) { |
||
171 | return get_called_class()::MODEL_TYPE; |
||
172 | } |
||
173 | |||
174 | return strtolower(self::class_short_name()); |
||
175 | } |
||
176 | |||
177 | |||
178 | public static function class_short_name(): string |
||
179 | { |
||
180 | return (new \ReflectionClass(get_called_class()))->getShortName(); |
||
181 | } |
||
182 | |||
183 | |||
184 | public static function selectAlso() |
||
185 | { |
||
186 | return ['*']; |
||
187 | } |
||
188 | } |
||
189 |