|
1
|
|
|
<?php |
|
2
|
|
|
|
|
3
|
|
|
namespace Pulsar; |
|
4
|
|
|
|
|
5
|
|
|
use ICanBoogie\Inflector; |
|
6
|
|
|
use Pulsar\Relation\Relationship; |
|
7
|
|
|
|
|
8
|
|
|
final class DefinitionBuilder |
|
9
|
|
|
{ |
|
10
|
|
|
const DEFAULT_ID_PROPERTY = [ |
|
11
|
|
|
'type' => Type::INTEGER, |
|
12
|
|
|
'mutable' => Property::IMMUTABLE, |
|
13
|
|
|
]; |
|
14
|
|
|
|
|
15
|
|
|
const AUTO_TIMESTAMPS = [ |
|
16
|
|
|
'created_at' => [ |
|
17
|
|
|
'type' => Type::DATE, |
|
18
|
|
|
'validate' => 'timestamp|db_timestamp', |
|
19
|
|
|
], |
|
20
|
|
|
'updated_at' => [ |
|
21
|
|
|
'type' => Type::DATE, |
|
22
|
|
|
'validate' => 'timestamp|db_timestamp', |
|
23
|
|
|
], |
|
24
|
|
|
]; |
|
25
|
|
|
|
|
26
|
|
|
const SOFT_DELETE_TIMESTAMPS = [ |
|
27
|
|
|
'deleted_at' => [ |
|
28
|
|
|
'type' => Type::DATE, |
|
29
|
|
|
'validate' => 'timestamp|db_timestamp', |
|
30
|
|
|
'null' => true, |
|
31
|
|
|
], |
|
32
|
|
|
]; |
|
33
|
|
|
|
|
34
|
|
|
/** @var Definition[] */ |
|
35
|
|
|
private static $definitions; |
|
36
|
|
|
|
|
37
|
|
|
/** |
|
38
|
|
|
* Gets the definition for a model. If needed the |
|
39
|
|
|
* definition will be generated. It will only be |
|
40
|
|
|
* generated once. |
|
41
|
|
|
*/ |
|
42
|
|
|
public static function get(string $modelClass): Definition |
|
43
|
|
|
{ |
|
44
|
|
|
/** @var Model $modelClass */ |
|
45
|
|
|
if (!isset(self::$definitions[$modelClass])) { |
|
46
|
|
|
self::$definitions[$modelClass] = $modelClass::buildDefinition(); |
|
47
|
|
|
} |
|
48
|
|
|
|
|
49
|
|
|
return self::$definitions[$modelClass]; |
|
50
|
|
|
} |
|
51
|
|
|
|
|
52
|
|
|
/** |
|
53
|
|
|
* Builds a model definition given certain parameters. |
|
54
|
|
|
*/ |
|
55
|
|
|
public static function build(array $properties, string $modelClass, bool $autoTimestamps, bool $softDelete): Definition |
|
56
|
|
|
{ |
|
57
|
|
|
/** @var Model $modelClass */ |
|
58
|
|
|
// add in the default ID property |
|
59
|
|
|
if (!isset($properties[Model::DEFAULT_ID_NAME]) && $modelClass::getIDProperties() == [Model::DEFAULT_ID_NAME]) { |
|
60
|
|
|
$properties[Model::DEFAULT_ID_NAME] = self::DEFAULT_ID_PROPERTY; |
|
61
|
|
|
} |
|
62
|
|
|
|
|
63
|
|
|
// generates created_at and updated_at timestamps |
|
64
|
|
|
if ($autoTimestamps) { |
|
65
|
|
|
$properties = array_replace(self::AUTO_TIMESTAMPS, $properties); |
|
66
|
|
|
} |
|
67
|
|
|
|
|
68
|
|
|
// generates deleted_at timestamps |
|
69
|
|
|
if ($softDelete) { |
|
70
|
|
|
$properties = array_replace(self::SOFT_DELETE_TIMESTAMPS, $properties); |
|
71
|
|
|
} |
|
72
|
|
|
|
|
73
|
|
|
$result = []; |
|
74
|
|
|
foreach ($properties as $k => $property) { |
|
75
|
|
|
// handle relationship shortcuts |
|
76
|
|
|
if (isset($property['relation']) && !isset($property['relation_type'])) { |
|
77
|
|
|
self::buildBelongsToLegacy($k, $property); |
|
78
|
|
|
} elseif (isset($property['belongs_to'])) { |
|
79
|
|
|
self::buildBelongsTo($k, $property, $result); |
|
80
|
|
|
} elseif (isset($property['has_one'])) { |
|
81
|
|
|
self::buildHasOne($property, $modelClass); |
|
82
|
|
|
} elseif (isset($property['belongs_to_many'])) { |
|
83
|
|
|
self::buildBelongsToMany($property, $modelClass); |
|
84
|
|
|
} elseif (isset($property['has_many'])) { |
|
85
|
|
|
self::buildHasMany($property, $modelClass); |
|
86
|
|
|
} elseif (isset($property['morphs_to'])) { |
|
87
|
|
|
self::buildPolymorphic($property, $k); |
|
88
|
|
|
} |
|
89
|
|
|
|
|
90
|
|
|
// install validation rule for encrypted properties |
|
91
|
|
|
if (isset($property['encrypted']) && $property['encrypted'] && !isset($property['validate'])) { |
|
92
|
|
|
$property['validate'] = 'encrypt'; |
|
93
|
|
|
} |
|
94
|
|
|
|
|
95
|
|
|
$result[$k] = new Property($property, $k); |
|
96
|
|
|
} |
|
97
|
|
|
|
|
98
|
|
|
// order the properties array by name for consistency |
|
99
|
|
|
// since it is constructed in a random order |
|
100
|
|
|
ksort($result); |
|
101
|
|
|
|
|
102
|
|
|
return new Definition($result); |
|
103
|
|
|
} |
|
104
|
|
|
|
|
105
|
|
|
///////////////////////////////// |
|
106
|
|
|
// Relationship Shortcuts |
|
107
|
|
|
///////////////////////////////// |
|
108
|
|
|
|
|
109
|
|
|
/** |
|
110
|
|
|
* This is added for BC with older versions of pulsar |
|
111
|
|
|
* that only supported belongs to relationships. |
|
112
|
|
|
*/ |
|
113
|
|
|
private static function buildBelongsToLegacy(string $name, array &$property): void |
|
114
|
|
|
{ |
|
115
|
|
|
$property['relation_type'] = Relationship::BELONGS_TO; |
|
116
|
|
|
// in the legacy configuration the default local key was the property name |
|
117
|
|
|
$property['local_key'] = $property['local_key'] ?? $name; |
|
118
|
|
|
|
|
119
|
|
|
// the default foreign key is `id` |
|
120
|
|
|
if (!isset($property['foreign_key'])) { |
|
121
|
|
|
$property['foreign_key'] = Model::DEFAULT_ID_NAME; |
|
122
|
|
|
} |
|
123
|
|
|
} |
|
124
|
|
|
|
|
125
|
|
|
private static function buildBelongsTo(string $name, array &$property, array &$result): void |
|
126
|
|
|
{ |
|
127
|
|
|
$property['relation_type'] = Relationship::BELONGS_TO; |
|
128
|
|
|
$property['relation'] = $property['belongs_to']; |
|
129
|
|
|
$property['persisted'] = false; |
|
130
|
|
|
|
|
131
|
|
|
// the default foreign key is `id` |
|
132
|
|
|
if (!isset($property['foreign_key'])) { |
|
133
|
|
|
$property['foreign_key'] = Model::DEFAULT_ID_NAME; |
|
134
|
|
|
} |
|
135
|
|
|
|
|
136
|
|
|
// the default local key would look like `user_id` |
|
137
|
|
|
// for a property named `user` |
|
138
|
|
|
if (!isset($property['local_key'])) { |
|
139
|
|
|
$property['local_key'] = $name.'_id'; |
|
140
|
|
|
} |
|
141
|
|
|
|
|
142
|
|
|
// when a belongs_to relationship is used then we automatically add a |
|
143
|
|
|
// new property for the ID field which gets persisted to the DB |
|
144
|
|
|
if (!isset($result[$property['local_key']])) { |
|
145
|
|
|
$result[$property['local_key']] = new Property([ |
|
146
|
|
|
'type' => Type::INTEGER, |
|
147
|
|
|
'mutable' => $property['mutable'] ?? Property::MUTABLE, |
|
148
|
|
|
], $property['local_key']); |
|
149
|
|
|
} |
|
150
|
|
|
} |
|
151
|
|
|
|
|
152
|
|
|
private static function buildBelongsToMany(array &$property, string $modelClass): void |
|
153
|
|
|
{ |
|
154
|
|
|
/* @var Model $modelClass */ |
|
155
|
|
|
$property['relation_type'] = Relationship::BELONGS_TO_MANY; |
|
156
|
|
|
$property['relation'] = $property['belongs_to_many']; |
|
157
|
|
|
$property['persisted'] = false; |
|
158
|
|
|
|
|
159
|
|
|
// the default local key would look like `user_id` |
|
160
|
|
|
// for a model named User |
|
161
|
|
|
if (!isset($property['local_key'])) { |
|
162
|
|
|
$inflector = Inflector::get(); |
|
163
|
|
|
$property['local_key'] = strtolower($inflector->underscore($modelClass::modelName())).'_id'; |
|
164
|
|
|
} |
|
165
|
|
|
|
|
166
|
|
|
if (!isset($property['foreign_key'])) { |
|
167
|
|
|
$inflector = Inflector::get(); |
|
168
|
|
|
$property['foreign_key'] = strtolower($inflector->underscore($property['relation']::modelName())).'_id'; |
|
169
|
|
|
} |
|
170
|
|
|
|
|
171
|
|
|
// the default pivot table name looks like |
|
172
|
|
|
// RoleUser for models named Role and User. |
|
173
|
|
|
// the tablename is built from the model names |
|
174
|
|
|
// in alphabetic order. |
|
175
|
|
|
if (!isset($property['pivot_tablename'])) { |
|
176
|
|
|
$names = [ |
|
177
|
|
|
$modelClass::modelName(), |
|
178
|
|
|
$property['relation']::modelName(), |
|
179
|
|
|
]; |
|
180
|
|
|
sort($names); |
|
181
|
|
|
$property['pivot_tablename'] = implode($names); |
|
182
|
|
|
} |
|
183
|
|
|
} |
|
184
|
|
|
|
|
185
|
|
View Code Duplication |
private static function buildHasOne(array &$property, string $modelClass): void |
|
|
|
|
|
|
186
|
|
|
{ |
|
187
|
|
|
/* @var Model $modelClass */ |
|
188
|
|
|
$property['relation_type'] = Relationship::HAS_ONE; |
|
189
|
|
|
$property['relation'] = $property['has_one']; |
|
190
|
|
|
$property['persisted'] = false; |
|
191
|
|
|
|
|
192
|
|
|
// the default foreign key would look like `user_id` |
|
193
|
|
|
// for a model named User |
|
194
|
|
|
if (!isset($property['foreign_key'])) { |
|
195
|
|
|
$inflector = Inflector::get(); |
|
196
|
|
|
$property['foreign_key'] = strtolower($inflector->underscore($modelClass::modelName())).'_id'; |
|
197
|
|
|
} |
|
198
|
|
|
|
|
199
|
|
|
if (!isset($property['local_key'])) { |
|
200
|
|
|
$property['local_key'] = Model::DEFAULT_ID_NAME; |
|
201
|
|
|
} |
|
202
|
|
|
} |
|
203
|
|
|
|
|
204
|
|
View Code Duplication |
private static function buildHasMany(array &$property, string $modelClass): void |
|
|
|
|
|
|
205
|
|
|
{ |
|
206
|
|
|
/* @var Model $modelClass */ |
|
207
|
|
|
$property['relation_type'] = Relationship::HAS_MANY; |
|
208
|
|
|
$property['relation'] = $property['has_many']; |
|
209
|
|
|
$property['persisted'] = false; |
|
210
|
|
|
|
|
211
|
|
|
// the default foreign key would look like |
|
212
|
|
|
// `user_id` for a model named User |
|
213
|
|
|
if (!isset($property['foreign_key'])) { |
|
214
|
|
|
$inflector = Inflector::get(); |
|
215
|
|
|
$property['foreign_key'] = strtolower($inflector->underscore($modelClass::modelName())).'_id'; |
|
216
|
|
|
} |
|
217
|
|
|
|
|
218
|
|
|
// the default local key is `id` |
|
219
|
|
|
if (!isset($property['local_key'])) { |
|
220
|
|
|
$property['local_key'] = Model::DEFAULT_ID_NAME; |
|
221
|
|
|
} |
|
222
|
|
|
} |
|
223
|
|
|
|
|
224
|
|
|
private static function buildPolymorphic(array &$property, string $name): void |
|
225
|
|
|
{ |
|
226
|
|
|
/* @var Model $modelClass */ |
|
227
|
|
|
$property['relation_type'] = Relationship::POLYMORPHIC; |
|
228
|
|
|
$property['persisted'] = false; |
|
229
|
|
|
|
|
230
|
|
|
// the default foreign key is `id` |
|
231
|
|
|
if (!isset($property['foreign_key'])) { |
|
232
|
|
|
$property['foreign_key'] = Model::DEFAULT_ID_NAME; |
|
233
|
|
|
} |
|
234
|
|
|
|
|
235
|
|
|
// the default local key type is the property name |
|
236
|
|
|
if (!isset($property['local_key'])) { |
|
237
|
|
|
$property['local_key'] = $name; |
|
238
|
|
|
} |
|
239
|
|
|
} |
|
240
|
|
|
} |
|
241
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.