|
1
|
|
|
<?php namespace Limoncello\Flute\Package; |
|
2
|
|
|
|
|
3
|
|
|
use Generator; |
|
4
|
|
|
use Limoncello\Contracts\Application\ApplicationConfigurationInterface as A; |
|
5
|
|
|
use Limoncello\Contracts\Settings\Packages\FluteSettingsInterface; |
|
6
|
|
|
use Limoncello\Flute\Contracts\Schema\SchemaInterface; |
|
7
|
|
|
use Limoncello\Flute\Contracts\Validation\FormRuleSetInterface; |
|
8
|
|
|
use Limoncello\Flute\Contracts\Validation\JsonApiRuleSetInterface; |
|
9
|
|
|
use Limoncello\Flute\Contracts\Validation\QueryRuleSetInterface; |
|
10
|
|
|
use Limoncello\Flute\Validation\Form\Execution\AttributeRulesSerializer; |
|
11
|
|
|
use Limoncello\Flute\Validation\JsonApi\Execution\JsonApiRuleSerializer; |
|
12
|
|
|
use Neomerx\JsonApi\Exceptions\JsonApiException; |
|
13
|
|
|
|
|
14
|
|
|
/** |
|
15
|
|
|
* @package Limoncello\Flute |
|
16
|
|
|
*/ |
|
17
|
|
|
abstract class FluteSettings implements FluteSettingsInterface |
|
18
|
|
|
{ |
|
19
|
|
|
/** |
|
20
|
|
|
* Namespace for string resources. |
|
21
|
|
|
*/ |
|
22
|
|
|
const VALIDATION_NAMESPACE = 'Limoncello.Flute.Validation'; |
|
23
|
|
|
|
|
24
|
|
|
/** |
|
25
|
|
|
* @param string $path |
|
26
|
|
|
* @param string $implementClassName |
|
27
|
|
|
* |
|
28
|
|
|
* @return Generator |
|
29
|
|
|
*/ |
|
30
|
|
|
abstract protected function selectClasses(string $path, string $implementClassName): Generator; |
|
31
|
|
|
|
|
32
|
|
|
/** |
|
33
|
|
|
* @param array $appConfig |
|
34
|
|
|
* |
|
35
|
|
|
* @return array |
|
36
|
|
|
*/ |
|
37
|
28 |
|
final public function get(array $appConfig): array |
|
38
|
|
|
{ |
|
39
|
28 |
|
$defaults = $this->getSettings(); |
|
40
|
|
|
|
|
41
|
28 |
|
$defaults[static::KEY_ROUTES_FOLDER] = $appConfig[A::KEY_ROUTES_FOLDER]; |
|
42
|
28 |
|
$defaults[static::KEY_WEB_CONTROLLERS_FOLDER] = $appConfig[A::KEY_WEB_CONTROLLERS_FOLDER]; |
|
43
|
|
|
|
|
44
|
28 |
|
$apiFolder = $defaults[static::KEY_API_FOLDER] ?? null; |
|
45
|
28 |
|
$valRulesFolder = $defaults[static::KEY_JSON_VALIDATION_RULES_FOLDER] ?? null; |
|
46
|
28 |
|
$jsonCtrlFolder = $defaults[static::KEY_JSON_CONTROLLERS_FOLDER] ?? null; |
|
47
|
28 |
|
$schemesFolder = $defaults[static::KEY_SCHEMAS_FOLDER] ?? null; |
|
48
|
28 |
|
$schemesFileMask = $defaults[static::KEY_SCHEMAS_FILE_MASK] ?? null; |
|
49
|
28 |
|
$jsonValFolder = $defaults[static::KEY_JSON_VALIDATORS_FOLDER] ?? null; |
|
50
|
28 |
|
$jsonValFileMask = $defaults[static::KEY_JSON_VALIDATORS_FILE_MASK] ?? null; |
|
51
|
28 |
|
$formsValFolder = $defaults[static::KEY_FORM_VALIDATORS_FOLDER] ?? null; |
|
52
|
28 |
|
$formsValFileMask = $defaults[static::KEY_FORM_VALIDATORS_FILE_MASK] ?? null; |
|
53
|
28 |
|
$queryValFolder = $defaults[static::KEY_QUERY_VALIDATORS_FOLDER] ?? null; |
|
54
|
28 |
|
$queryValFileMask = $defaults[static::KEY_QUERY_VALIDATORS_FILE_MASK] ?? null; |
|
55
|
|
|
|
|
56
|
28 |
|
assert( |
|
57
|
28 |
|
$apiFolder !== null && empty(glob($apiFolder)) === false, |
|
58
|
28 |
|
"Invalid API folder `$apiFolder`." |
|
59
|
|
|
); |
|
60
|
28 |
|
assert( |
|
61
|
28 |
|
$valRulesFolder !== null && empty(glob($valRulesFolder)) === false, |
|
62
|
28 |
|
"Invalid validation rules folder `$valRulesFolder`." |
|
63
|
|
|
); |
|
64
|
28 |
|
assert( |
|
65
|
28 |
|
$jsonCtrlFolder !== null && empty(glob($jsonCtrlFolder)) === false, |
|
66
|
28 |
|
"Invalid JSON API controllers' folder `$jsonCtrlFolder`." |
|
67
|
|
|
); |
|
68
|
28 |
|
assert( |
|
69
|
28 |
|
$schemesFolder !== null && empty(glob($schemesFolder)) === false, |
|
70
|
28 |
|
"Invalid Schemes folder `$schemesFolder`." |
|
71
|
|
|
); |
|
72
|
28 |
|
assert(empty($schemesFileMask) === false, "Invalid Schemes file mask `$schemesFileMask`."); |
|
73
|
28 |
|
assert( |
|
74
|
28 |
|
$jsonValFolder !== null && empty(glob($jsonValFolder)) === false, |
|
75
|
28 |
|
"Invalid JSON Validators folder `$jsonValFolder`." |
|
76
|
|
|
); |
|
77
|
28 |
|
assert(empty($jsonValFileMask) === false, "Invalid JSON Validators file mask `$jsonValFileMask`."); |
|
78
|
28 |
|
assert( |
|
79
|
28 |
|
$formsValFolder !== null && empty(glob($formsValFolder)) === false, |
|
80
|
28 |
|
"Invalid Forms Validators folder `$formsValFolder`." |
|
81
|
|
|
); |
|
82
|
28 |
|
assert(empty($formsValFileMask) === false, "Invalid Forms Validators file mask `$formsValFileMask`."); |
|
83
|
28 |
|
assert( |
|
84
|
28 |
|
$queryValFolder !== null && empty(glob($queryValFolder)) === false, |
|
85
|
28 |
|
"Invalid Query Validators folder `$queryValFolder`." |
|
86
|
|
|
); |
|
87
|
28 |
|
assert(empty($queryValFileMask) === false, "Invalid Query Validators file mask `$queryValFileMask`."); |
|
88
|
|
|
|
|
89
|
28 |
|
$schemesPath = $schemesFolder . DIRECTORY_SEPARATOR . $schemesFileMask; |
|
90
|
28 |
|
$jsonValidatorsPath = $jsonValFolder . DIRECTORY_SEPARATOR . $jsonValFileMask; |
|
91
|
28 |
|
$formsValidatorsPath = $formsValFolder . DIRECTORY_SEPARATOR . $formsValFileMask; |
|
92
|
28 |
|
$queryValidatorsPath = $queryValFolder . DIRECTORY_SEPARATOR . $queryValFileMask; |
|
93
|
|
|
|
|
94
|
28 |
|
$requireUniqueTypes = $defaults[static::KEY_SCHEMAS_REQUIRE_UNIQUE_TYPES] ?? true; |
|
95
|
|
|
|
|
96
|
28 |
|
$doNotLogExceptions = $defaults[static::KEY_DO_NOT_LOG_EXCEPTIONS_LIST] ?? []; |
|
97
|
28 |
|
unset($defaults[static::KEY_DO_NOT_LOG_EXCEPTIONS_LIST]); |
|
98
|
|
|
|
|
99
|
|
|
return $defaults + [ |
|
100
|
28 |
|
static::KEY_DO_NOT_LOG_EXCEPTIONS_LIST__AS_KEYS => array_flip($doNotLogExceptions), |
|
101
|
|
|
|
|
102
|
28 |
|
static::KEY_MODEL_TO_SCHEME_MAP => $this->createModelToSchemeMap($schemesPath, $requireUniqueTypes), |
|
103
|
|
|
|
|
104
|
28 |
|
static::KEY_JSON_VALIDATION_RULE_SETS_DATA => |
|
105
|
28 |
|
$this->createJsonValidationRulesSetData($jsonValidatorsPath), |
|
106
|
|
|
|
|
107
|
28 |
|
static::KEY_ATTRIBUTE_VALIDATION_RULE_SETS_DATA => |
|
108
|
28 |
|
$this->createValidationAttributeRulesSetData($formsValidatorsPath, $queryValidatorsPath), |
|
109
|
|
|
]; |
|
110
|
|
|
} |
|
111
|
|
|
|
|
112
|
|
|
/** |
|
113
|
|
|
* @return array |
|
114
|
|
|
*/ |
|
115
|
28 |
|
protected function getSettings(): array |
|
116
|
|
|
{ |
|
117
|
28 |
|
$jsonOptions = JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT | JSON_UNESCAPED_SLASHES; |
|
118
|
|
|
|
|
119
|
|
|
return [ |
|
120
|
28 |
|
static::KEY_SCHEMAS_REQUIRE_UNIQUE_TYPES => true, |
|
121
|
28 |
|
static::KEY_SCHEMAS_FILE_MASK => '*.php', |
|
122
|
28 |
|
static::KEY_JSON_VALIDATORS_FILE_MASK => '*.php', |
|
123
|
28 |
|
static::KEY_FORM_VALIDATORS_FILE_MASK => '*.php', |
|
124
|
28 |
|
static::KEY_QUERY_VALIDATORS_FILE_MASK => '*.php', |
|
125
|
28 |
|
static::KEY_THROWABLE_TO_JSON_API_EXCEPTION_CONVERTER => null, |
|
126
|
28 |
|
static::KEY_HTTP_CODE_FOR_UNEXPECTED_THROWABLE => 500, |
|
127
|
28 |
|
static::KEY_DEFAULT_PAGING_SIZE => 20, |
|
128
|
28 |
|
static::KEY_MAX_PAGING_SIZE => 100, |
|
129
|
28 |
|
static::KEY_JSON_ENCODE_OPTIONS => $jsonOptions, |
|
130
|
28 |
|
static::KEY_JSON_ENCODE_DEPTH => 512, |
|
131
|
28 |
|
static::KEY_IS_SHOW_VERSION => false, |
|
132
|
28 |
|
static::KEY_META => null, |
|
133
|
28 |
|
static::KEY_URI_PREFIX => null, |
|
134
|
|
|
|
|
135
|
28 |
|
static::KEY_DO_NOT_LOG_EXCEPTIONS_LIST => [ |
|
136
|
|
|
JsonApiException::class, |
|
137
|
|
|
], |
|
138
|
|
|
]; |
|
139
|
|
|
} |
|
140
|
|
|
|
|
141
|
|
|
/** |
|
142
|
|
|
* @param string $schemesPath |
|
143
|
|
|
* @param bool $requireUniqueTypes |
|
144
|
|
|
* |
|
145
|
|
|
* @return array |
|
146
|
|
|
*/ |
|
147
|
28 |
|
private function createModelToSchemeMap(string $schemesPath, bool $requireUniqueTypes): array |
|
148
|
|
|
{ |
|
149
|
28 |
|
$map = []; |
|
150
|
28 |
|
$types = []; |
|
151
|
28 |
|
foreach ($this->selectClasses($schemesPath, SchemaInterface::class) as $schemeClass) { |
|
152
|
28 |
|
assert( |
|
153
|
28 |
|
is_string($schemeClass) && |
|
154
|
28 |
|
class_exists($schemeClass) && |
|
155
|
28 |
|
array_key_exists(SchemaInterface::class, class_implements($schemeClass)) |
|
156
|
|
|
); |
|
157
|
|
|
/** @var SchemaInterface $schemeClass */ |
|
158
|
28 |
|
$modelClass = $schemeClass::MODEL; |
|
159
|
28 |
|
$resourceType = $schemeClass::TYPE; |
|
160
|
|
|
|
|
161
|
28 |
|
assert(is_string($modelClass) === true && empty($modelClass) === false); |
|
162
|
28 |
|
assert(is_string($resourceType) === true && empty($resourceType) === false); |
|
163
|
|
|
|
|
164
|
|
|
// By default it checks that all Schemes have unique resource types. That's a legit case |
|
165
|
|
|
// to have multiple Schemes for a same resource type however it's more likely that developer |
|
166
|
|
|
// just forgot to set a unique one. If you do need multiple Schemes for a resource feel free |
|
167
|
|
|
// to set to turn off this check. |
|
168
|
28 |
|
assert( |
|
169
|
28 |
|
$requireUniqueTypes === false || array_key_exists($resourceType, $types) === false, |
|
170
|
28 |
|
"Are you sure it's not an error to use resource type `$resourceType` more than once?" |
|
171
|
|
|
); |
|
172
|
28 |
|
$types[$resourceType] = true; |
|
173
|
|
|
|
|
174
|
28 |
|
$map[$modelClass] = $schemeClass; |
|
175
|
|
|
} |
|
176
|
|
|
|
|
177
|
28 |
|
return $map; |
|
178
|
|
|
} |
|
179
|
|
|
|
|
180
|
|
|
/** |
|
181
|
|
|
* @param string $validatorsPath |
|
182
|
|
|
* |
|
183
|
|
|
* @return array |
|
184
|
|
|
*/ |
|
185
|
28 |
|
private function createJsonValidationRulesSetData(string $validatorsPath): array |
|
186
|
|
|
{ |
|
187
|
28 |
|
$serializer = new JsonApiRuleSerializer(); |
|
188
|
28 |
|
foreach ($this->selectClasses($validatorsPath, JsonApiRuleSetInterface::class) as $setClass) { |
|
189
|
|
|
/** @var string $setName */ |
|
190
|
28 |
|
$setName = $setClass; |
|
191
|
28 |
|
assert( |
|
192
|
28 |
|
is_string($setClass) && |
|
193
|
28 |
|
class_exists($setClass) && |
|
194
|
28 |
|
array_key_exists(JsonApiRuleSetInterface::class, class_implements($setClass)) |
|
195
|
|
|
); |
|
196
|
|
|
/** @var JsonApiRuleSetInterface $setClass */ |
|
197
|
28 |
|
$serializer->addResourceRules( |
|
198
|
28 |
|
$setName, |
|
199
|
28 |
|
$setClass::getIdRule(), |
|
200
|
28 |
|
$setClass::getTypeRule(), |
|
201
|
28 |
|
$setClass::getAttributeRules(), |
|
202
|
28 |
|
$setClass::getToOneRelationshipRules(), |
|
203
|
28 |
|
$setClass::getToManyRelationshipRules() |
|
204
|
|
|
); |
|
205
|
|
|
} |
|
206
|
|
|
|
|
207
|
28 |
|
$ruleSetsData = $serializer->getData(); |
|
208
|
|
|
|
|
209
|
28 |
|
return $ruleSetsData; |
|
210
|
|
|
} |
|
211
|
|
|
|
|
212
|
|
|
/** |
|
213
|
|
|
* @param string $formsValPath |
|
214
|
|
|
* @param string $queriesValPath |
|
215
|
|
|
* |
|
216
|
|
|
* @return array |
|
217
|
|
|
*/ |
|
218
|
28 |
|
private function createValidationAttributeRulesSetData(string $formsValPath, string $queriesValPath): array |
|
219
|
|
|
{ |
|
220
|
28 |
|
$serializer = new AttributeRulesSerializer(); |
|
221
|
28 |
View Code Duplication |
foreach ($this->selectClasses($formsValPath, FormRuleSetInterface::class) as $setClass) { |
|
|
|
|
|
|
222
|
|
|
/** @var string $setName */ |
|
223
|
28 |
|
$setName = $setClass; |
|
224
|
28 |
|
assert( |
|
225
|
28 |
|
is_string($setClass) && |
|
226
|
28 |
|
class_exists($setClass) && |
|
227
|
28 |
|
array_key_exists(FormRuleSetInterface::class, class_implements($setClass)) |
|
228
|
|
|
); |
|
229
|
|
|
/** @var FormRuleSetInterface $setClass */ |
|
230
|
28 |
|
$serializer->addResourceRules($setName, $setClass::getAttributeRules()); |
|
231
|
|
|
} |
|
232
|
28 |
View Code Duplication |
foreach ($this->selectClasses($queriesValPath, QueryRuleSetInterface::class) as $setClass) { |
|
|
|
|
|
|
233
|
|
|
/** @var string $setName */ |
|
234
|
28 |
|
$setName = $setClass; |
|
235
|
28 |
|
assert( |
|
236
|
28 |
|
is_string($setClass) && |
|
237
|
28 |
|
class_exists($setClass) && |
|
238
|
28 |
|
array_key_exists(QueryRuleSetInterface::class, class_implements($setClass)) |
|
239
|
|
|
); |
|
240
|
|
|
/** @var QueryRuleSetInterface $setClass */ |
|
241
|
28 |
|
$serializer->addResourceRules($setName, $setClass::getAttributeRules()); |
|
242
|
|
|
} |
|
243
|
|
|
|
|
244
|
28 |
|
$ruleSetsData = $serializer->getData(); |
|
245
|
|
|
|
|
246
|
28 |
|
return $ruleSetsData; |
|
247
|
|
|
} |
|
248
|
|
|
} |
|
249
|
|
|
|
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.