1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
/** |
4
|
|
|
* Author: Nil Portugués Calderó <[email protected]> |
5
|
|
|
* Date: 7/26/15 |
6
|
|
|
* Time: 12:11 PM. |
7
|
|
|
* |
8
|
|
|
* For the full copyright and license information, please view the LICENSE |
9
|
|
|
* file that was distributed with this source code. |
10
|
|
|
*/ |
11
|
|
|
namespace NilPortugues\Api\Mapping; |
12
|
|
|
|
13
|
|
|
use NilPortugues\Api\Mappings\ApiMapping; |
14
|
|
|
use NilPortugues\Api\Mappings\HalMapping; |
15
|
|
|
use NilPortugues\Api\Mappings\JsonApiMapping; |
16
|
|
|
use ReflectionClass; |
17
|
|
|
|
18
|
|
|
/** |
19
|
|
|
* Class MappingFactory. |
20
|
|
|
*/ |
21
|
|
|
class MappingFactory |
22
|
|
|
{ |
23
|
|
|
const CLASS_KEY = 'class'; |
24
|
|
|
const ALIAS_KEY = 'alias'; |
25
|
|
|
const ALIASED_PROPERTIES_KEY = 'aliased_properties'; |
26
|
|
|
const HIDE_PROPERTIES_KEY = 'hide_properties'; |
27
|
|
|
const ID_PROPERTIES_KEY = 'id_properties'; |
28
|
|
|
const URLS_KEY = 'urls'; |
29
|
|
|
const CURIES_KEY = 'curies'; |
30
|
|
|
const RELATIONSHIPS_KEY = 'relationships'; |
31
|
|
|
const SELF_KEY = 'self'; |
32
|
|
|
|
33
|
|
|
/** |
34
|
|
|
* @var array |
35
|
|
|
*/ |
36
|
|
|
protected static $classProperties = []; |
37
|
|
|
|
38
|
|
|
/** |
39
|
|
|
* @param string $className |
40
|
|
|
* |
41
|
|
|
* @throws MappingException |
42
|
|
|
* |
43
|
|
|
* @return Mapping |
44
|
|
|
* |
45
|
|
|
* @since 2.0.0 |
46
|
|
|
*/ |
47
|
|
|
public static function fromClass($className) |
48
|
|
|
{ |
49
|
|
|
/* @var ApiMapping|HalMapping|JsonApiMapping $instance */ |
|
|
|
|
50
|
|
|
$className = '\\'.ltrim($className, '\\'); |
51
|
|
|
if (!class_exists($className, true)) { |
52
|
|
|
throw new MappingException( |
53
|
|
|
\sprintf('Provided class %s could not be loaded.', $className) |
54
|
|
|
); |
55
|
|
|
} |
56
|
|
|
$instance = new $className(); |
57
|
|
|
|
58
|
|
|
if (!in_array(ApiMapping::class, \class_implements($instance, true))) { |
59
|
|
|
throw new MappingException( |
60
|
|
|
\sprintf('Class %s must implement %s.', \ltrim($className, '\\'), ApiMapping::class) |
61
|
|
|
); |
62
|
|
|
} |
63
|
|
|
|
64
|
|
|
$mappedClass = [ |
65
|
|
|
static::CLASS_KEY => $instance->getClass(), |
66
|
|
|
static::ALIAS_KEY => $instance->getAlias(), |
67
|
|
|
static::ALIASED_PROPERTIES_KEY => $instance->getAliasedProperties(), |
68
|
|
|
static::HIDE_PROPERTIES_KEY => $instance->getHideProperties(), |
69
|
|
|
static::ID_PROPERTIES_KEY => $instance->getIdProperties(), |
70
|
|
|
static::URLS_KEY => $instance->getUrls(), |
71
|
|
|
]; |
72
|
|
|
|
73
|
|
|
if (\in_array(HalMapping::class, \class_implements($instance, true))) { |
74
|
|
|
$mappedClass[static::CURIES_KEY] = $instance->getCuries(); |
75
|
|
|
} |
76
|
|
|
|
77
|
|
|
if (\in_array(JsonApiMapping::class, \class_implements($instance, true))) { |
78
|
|
|
$mappedClass[static::RELATIONSHIPS_KEY] = $instance->getRelationships(); |
79
|
|
|
} |
80
|
|
|
|
81
|
|
|
return static::fromArray($mappedClass); |
82
|
|
|
} |
83
|
|
|
|
84
|
|
|
/** |
85
|
|
|
* @param array $mappedClass |
86
|
|
|
* |
87
|
|
|
* @throws MappingException |
88
|
|
|
* |
89
|
|
|
* @return Mapping |
90
|
|
|
*/ |
91
|
|
|
public static function fromArray(array &$mappedClass) |
92
|
|
|
{ |
93
|
|
|
$className = static::getClass($mappedClass); |
94
|
|
|
$resourceUrl = static::getSelfUrl($mappedClass); |
95
|
|
|
$idProperties = static::getIdProperties($mappedClass); |
96
|
|
|
|
97
|
|
|
$mapping = new Mapping($className, $resourceUrl, $idProperties); |
98
|
|
|
$mapping->setClassAlias((empty($mappedClass[static::ALIAS_KEY])) ? $className : $mappedClass[static::ALIAS_KEY]); |
99
|
|
|
|
100
|
|
|
static::setAliasedProperties($mappedClass, $mapping, $className); |
101
|
|
|
static::setHideProperties($mappedClass, $mapping, $className); |
102
|
|
|
static::setRelationships($mappedClass, $mapping, $className); |
103
|
|
|
static::setCuries($mappedClass, $mapping); |
104
|
|
|
static::setProperties($mapping, $className); |
105
|
|
|
|
106
|
|
|
$otherUrls = static::getOtherUrls($mappedClass); |
107
|
|
|
if (!empty($otherUrls)) { |
108
|
|
|
$mapping->setUrls($otherUrls); |
109
|
|
|
} |
110
|
|
|
|
111
|
|
|
return $mapping; |
112
|
|
|
} |
113
|
|
|
|
114
|
|
|
/** |
115
|
|
|
* @param array $mappedClass |
116
|
|
|
* |
117
|
|
|
* @throws MappingException |
118
|
|
|
* |
119
|
|
|
* @return mixed |
120
|
|
|
*/ |
121
|
|
|
protected static function getClass(array &$mappedClass) |
122
|
|
|
{ |
123
|
|
|
if (empty($mappedClass[static::CLASS_KEY])) { |
124
|
|
|
throw new MappingException( |
125
|
|
|
'Could not find "class" property. This is required for class to be mapped' |
126
|
|
|
); |
127
|
|
|
} |
128
|
|
|
|
129
|
|
|
return $mappedClass[static::CLASS_KEY]; |
130
|
|
|
} |
131
|
|
|
|
132
|
|
|
/** |
133
|
|
|
* @param array $mappedClass |
134
|
|
|
* |
135
|
|
|
* @throws MappingException |
136
|
|
|
* |
137
|
|
|
* @return mixed |
138
|
|
|
*/ |
139
|
|
View Code Duplication |
protected static function getSelfUrl(array &$mappedClass) |
|
|
|
|
140
|
|
|
{ |
141
|
|
|
if (empty($mappedClass[static::URLS_KEY][static::SELF_KEY])) { |
142
|
|
|
throw new MappingException( |
143
|
|
|
'Could not find "self" property under "urls". This is required in order to make the resource to be reachable.' |
144
|
|
|
); |
145
|
|
|
} |
146
|
|
|
|
147
|
|
|
return $mappedClass[static::URLS_KEY][static::SELF_KEY]; |
148
|
|
|
} |
149
|
|
|
|
150
|
|
|
/** |
151
|
|
|
* @param array $mappedClass |
152
|
|
|
* |
153
|
|
|
* @return mixed |
154
|
|
|
*/ |
155
|
|
|
protected static function getIdProperties(array &$mappedClass) |
156
|
|
|
{ |
157
|
|
|
return (!empty($mappedClass[static::ID_PROPERTIES_KEY])) ? $mappedClass[static::ID_PROPERTIES_KEY] : []; |
158
|
|
|
} |
159
|
|
|
|
160
|
|
|
/** |
161
|
|
|
* @param array $mappedClass |
162
|
|
|
* @param Mapping $mapping |
163
|
|
|
* @param string $className |
164
|
|
|
* |
165
|
|
|
* @throws MappingException |
166
|
|
|
*/ |
167
|
|
View Code Duplication |
protected static function setAliasedProperties(array &$mappedClass, Mapping $mapping, $className) |
|
|
|
|
168
|
|
|
{ |
169
|
|
|
if (false === empty($mappedClass[static::ALIASED_PROPERTIES_KEY])) { |
170
|
|
|
$mapping->setPropertyNameAliases($mappedClass[static::ALIASED_PROPERTIES_KEY]); |
171
|
|
|
foreach (\array_keys($mapping->getAliasedProperties()) as $propertyName) { |
172
|
|
|
if (false === \in_array($propertyName, static::getClassProperties($className), true)) { |
173
|
|
|
throw new MappingException( |
174
|
|
|
\sprintf( |
175
|
|
|
'Could not alias property %s in class %s because it does not exist.', |
176
|
|
|
$propertyName, |
177
|
|
|
$className |
178
|
|
|
) |
179
|
|
|
); |
180
|
|
|
} |
181
|
|
|
} |
182
|
|
|
} |
183
|
|
|
} |
184
|
|
|
|
185
|
|
|
/** |
186
|
|
|
* Recursive function to get an associative array of class properties by |
187
|
|
|
* property name, including inherited ones from extended classes. |
188
|
|
|
* |
189
|
|
|
* @param string $className Class name |
190
|
|
|
* |
191
|
|
|
* @return array |
192
|
|
|
* |
193
|
|
|
* @link http://php.net/manual/es/reflectionclass.getproperties.php#88405 |
194
|
|
|
*/ |
195
|
|
|
protected static function getClassProperties($className) |
196
|
|
|
{ |
197
|
|
|
if (empty(static::$classProperties[$className])) { |
198
|
|
|
$ref = new ReflectionClass($className); |
199
|
|
|
$properties = []; |
200
|
|
|
foreach ($ref->getProperties() as $prop) { |
201
|
|
|
$f = $prop->getName(); |
202
|
|
|
$properties[$f] = $prop; |
203
|
|
|
} |
204
|
|
|
|
205
|
|
|
if ($parentClass = $ref->getParentClass()) { |
206
|
|
|
$parentPropsArr = static::getClassProperties($parentClass->getName()); |
207
|
|
|
if (\count($parentPropsArr) > 0) { |
208
|
|
|
$properties = \array_merge($parentPropsArr, $properties); |
209
|
|
|
} |
210
|
|
|
} |
211
|
|
|
static::$classProperties[$className] = \array_keys($properties); |
212
|
|
|
} |
213
|
|
|
|
214
|
|
|
return static::$classProperties[$className]; |
215
|
|
|
} |
216
|
|
|
|
217
|
|
|
/** |
218
|
|
|
* @param array $mappedClass |
219
|
|
|
* @param Mapping $mapping |
220
|
|
|
* @param string $className |
221
|
|
|
* |
222
|
|
|
* @throws MappingException |
223
|
|
|
*/ |
224
|
|
View Code Duplication |
protected static function setHideProperties(array &$mappedClass, Mapping $mapping, $className) |
|
|
|
|
225
|
|
|
{ |
226
|
|
|
if (false === empty($mappedClass[static::HIDE_PROPERTIES_KEY])) { |
227
|
|
|
$mapping->setHiddenProperties($mappedClass[static::HIDE_PROPERTIES_KEY]); |
228
|
|
|
foreach ($mapping->getHiddenProperties() as $propertyName) { |
229
|
|
|
if (false === \in_array($propertyName, static::getClassProperties($className), true)) { |
230
|
|
|
throw new MappingException( |
231
|
|
|
\sprintf( |
232
|
|
|
'Could not hide property %s in class %s because it does not exist.', |
233
|
|
|
$propertyName, |
234
|
|
|
$className |
235
|
|
|
) |
236
|
|
|
); |
237
|
|
|
} |
238
|
|
|
} |
239
|
|
|
} |
240
|
|
|
} |
241
|
|
|
|
242
|
|
|
/** |
243
|
|
|
* @param array $mappedClass |
244
|
|
|
* @param Mapping $mapping |
245
|
|
|
* @param string $className |
246
|
|
|
* |
247
|
|
|
* @throws MappingException |
248
|
|
|
*/ |
249
|
|
|
protected static function setRelationships(array &$mappedClass, Mapping $mapping, $className) |
250
|
|
|
{ |
251
|
|
|
if (!empty($mappedClass[static::RELATIONSHIPS_KEY])) { |
252
|
|
|
foreach ($mappedClass[static::RELATIONSHIPS_KEY] as $propertyName => $urls) { |
253
|
|
|
if (false === \in_array($propertyName, static::getClassProperties($className))) { |
254
|
|
|
throw new MappingException( |
255
|
|
|
\sprintf( |
256
|
|
|
'Could not find property %s in class %s because it does not exist.', |
257
|
|
|
$propertyName, |
258
|
|
|
$className |
259
|
|
|
) |
260
|
|
|
); |
261
|
|
|
} |
262
|
|
|
|
263
|
|
|
$mapping->setRelationshipUrls($propertyName, $urls); |
264
|
|
|
} |
265
|
|
|
} |
266
|
|
|
} |
267
|
|
|
|
268
|
|
|
/** |
269
|
|
|
* @param array $mappedClass |
270
|
|
|
* @param Mapping $mapping |
271
|
|
|
*/ |
272
|
|
|
protected static function setCuries(array &$mappedClass, Mapping $mapping) |
273
|
|
|
{ |
274
|
|
|
if (false === empty($mappedClass[static::CURIES_KEY])) { |
275
|
|
|
$mapping->setCuries($mappedClass[static::CURIES_KEY]); |
276
|
|
|
} |
277
|
|
|
} |
278
|
|
|
|
279
|
|
|
/** |
280
|
|
|
* @param Mapping $mapping |
281
|
|
|
* @param string $className |
282
|
|
|
* |
283
|
|
|
* @throws MappingException |
284
|
|
|
*/ |
285
|
|
|
protected static function setProperties(Mapping $mapping, $className) |
286
|
|
|
{ |
287
|
|
|
$mapping->setProperties(static::getClassProperties($className)); |
288
|
|
|
} |
289
|
|
|
|
290
|
|
|
/** |
291
|
|
|
* @param array $mappedClass |
292
|
|
|
* |
293
|
|
|
* @return mixed |
294
|
|
|
*/ |
295
|
|
View Code Duplication |
protected static function getOtherUrls(array $mappedClass) |
|
|
|
|
296
|
|
|
{ |
297
|
|
|
if (!empty($mappedClass[static::URLS_KEY][static::SELF_KEY])) { |
298
|
|
|
unset($mappedClass[static::URLS_KEY][static::SELF_KEY]); |
299
|
|
|
} |
300
|
|
|
|
301
|
|
|
return $mappedClass[static::URLS_KEY]; |
302
|
|
|
} |
303
|
|
|
} |
304
|
|
|
|
Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.
The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.
This check looks for comments that seem to be mostly valid code and reports them.