Completed
Push — develop ( dcfc04...3d10a6 )
by Neomerx
04:33
created

FluteSettings::createSchemaMapping()   A

Complexity

Conditions 5
Paths 2

Size

Total Lines 29

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 15
CRAP Score 5

Importance

Changes 0
Metric Value
dl 0
loc 29
ccs 15
cts 15
cp 1
rs 9.1448
c 0
b 0
f 0
cc 5
nc 2
nop 2
crap 5
1
<?php namespace Limoncello\Flute\Package;
2
3
use Limoncello\Common\Reflection\ClassIsTrait;
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\FormRulesInterface;
8
use Limoncello\Flute\Contracts\Validation\JsonApiDataRulesInterface;
9
use Limoncello\Flute\Contracts\Validation\JsonApiQueryRulesInterface;
10
use Limoncello\Flute\L10n\Messages;
11
use Limoncello\Flute\Validation\Form\Execution\FormRulesSerializer;
12
use Limoncello\Flute\Validation\JsonApi\Execution\JsonApiDataRulesSerializer;
13
use Limoncello\Flute\Validation\JsonApi\Execution\JsonApiQueryRulesSerializer;
14
use Limoncello\Flute\Validation\JsonApi\Rules\DefaultQueryValidationRules;
15
use Limoncello\Validation\Execution\BlockSerializer;
16
use Neomerx\JsonApi\Exceptions\JsonApiException;
17
use ReflectionException;
18
19
/**
20
 * @package Limoncello\Flute
21
 */
22
abstract class FluteSettings implements FluteSettingsInterface
23
{
24
    use ClassIsTrait;
25
26
    /**
27
     * Namespace for string resources.
28
     */
29
    const GENERIC_NAMESPACE = Messages::RESOURCES_NAMESPACE;
30
31
    /**
32
     * Namespace for string resources.
33
     */
34
    public const VALIDATION_NAMESPACE = 'Limoncello.Flute.Validation';
35
36
    /**
37
     * Default page size.
38
     */
39
    public const DEFAULT_PAGE_SIZE = 10;
40
41
    /**
42
     * Default page size.
43
     */
44
    public const DEFAULT_MAX_PAGE_SIZE = 30;
45
46
    /**
47
     * Default JSON API version.
48
     */
49
    public const DEFAULT_JSON_API_VERSION = '1.0';
50
51
    /** Serialized validation data index */
52
    protected const JSON_API_DATA_VALIDATION_RULES_SERIALIZED = 0;
53
54
    /** Serialized validation data index */
55
    protected const JSON_API_QUERIES_VALIDATION_RULES_SERIALIZED = self::JSON_API_DATA_VALIDATION_RULES_SERIALIZED + 1;
56
57
    /**
58
     * @param array $appConfig
59
     *
60
     * @return array
61
     *
62
     * @throws ReflectionException
63
     */
64 29
    final public function get(array $appConfig): array
65
    {
66 29
        $defaults = $this->getSettings();
67
68 29
        $defaults[static::KEY_ROUTES_FOLDER]          = $appConfig[A::KEY_ROUTES_FOLDER];
69 29
        $defaults[static::KEY_WEB_CONTROLLERS_FOLDER] = $appConfig[A::KEY_WEB_CONTROLLERS_FOLDER];
70
71 29
        $apiFolder            = $defaults[static::KEY_API_FOLDER] ?? null;
72 29
        $valRulesFolder       = $defaults[static::KEY_JSON_VALIDATION_RULES_FOLDER] ?? null;
73 29
        $jsonCtrlFolder       = $defaults[static::KEY_JSON_CONTROLLERS_FOLDER] ?? null;
74 29
        $schemasFolder        = $defaults[static::KEY_SCHEMAS_FOLDER] ?? null;
75 29
        $schemasFileMask      = $defaults[static::KEY_SCHEMAS_FILE_MASK] ?? null;
76 29
        $jsonDataValFolder    = $defaults[static::KEY_JSON_VALIDATORS_FOLDER] ?? null;
77 29
        $jsonDataValFileMask  = $defaults[static::KEY_JSON_VALIDATORS_FILE_MASK] ?? null;
78 29
        $formsValFolder       = $defaults[static::KEY_FORM_VALIDATORS_FOLDER] ?? null;
79 29
        $formsValFileMask     = $defaults[static::KEY_FORM_VALIDATORS_FILE_MASK] ?? null;
80 29
        $jsonQueryValFolder   = $defaults[static::KEY_QUERY_VALIDATORS_FOLDER] ?? null;
81 29
        $jsonQueryValFileMask = $defaults[static::KEY_QUERY_VALIDATORS_FILE_MASK] ?? null;
82
83 29
        assert(
84 29
            $apiFolder !== null && empty(glob($apiFolder)) === false,
85 29
            "Invalid API folder `$apiFolder`."
86
        );
87 29
        assert(
88 29
            $valRulesFolder !== null && empty(glob($valRulesFolder)) === false,
89 29
            "Invalid validation rules folder `$valRulesFolder`."
90
        );
91 29
        assert(
92 29
            $jsonCtrlFolder !== null && empty(glob($jsonCtrlFolder)) === false,
93 29
            "Invalid JSON API controllers' folder `$jsonCtrlFolder`."
94
        );
95 29
        assert(
96 29
            $schemasFolder !== null && empty(glob($schemasFolder)) === false,
97 29
            "Invalid Schemas folder `$schemasFolder`."
98
        );
99 29
        assert(empty($schemasFileMask) === false, "Invalid Schemas file mask `$schemasFileMask`.");
100 29
        assert(
101 29
            $jsonDataValFolder !== null && empty(glob($jsonDataValFolder)) === false,
102 29
            "Invalid JSON Validators folder `$jsonDataValFolder`."
103
        );
104 29
        assert(empty($jsonDataValFileMask) === false, "Invalid JSON Validators file mask `$jsonDataValFileMask`.");
105 29
        assert(
106 29
            $formsValFolder !== null && empty(glob($formsValFolder)) === false,
107 29
            "Invalid Forms Validators folder `$formsValFolder`."
108
        );
109 29
        assert(empty($formsValFileMask) === false, "Invalid Forms Validators file mask `$formsValFileMask`.");
110 29
        assert(
111 29
            $jsonQueryValFolder !== null && empty(glob($jsonQueryValFolder)) === false,
112 29
            "Invalid Query Validators folder `$jsonQueryValFolder`."
113
        );
114 29
        assert(empty($jsonQueryValFileMask) === false, "Invalid Query Validators file mask `$jsonQueryValFileMask`.");
115
116 29
        $schemasPath         = $schemasFolder . DIRECTORY_SEPARATOR . $schemasFileMask;
117 29
        $jsonDataValPath     = $jsonDataValFolder . DIRECTORY_SEPARATOR . $jsonDataValFileMask;
118 29
        $formsValidatorsPath = $formsValFolder . DIRECTORY_SEPARATOR . $formsValFileMask;
119 29
        $jsonQueryValPath    = $jsonQueryValFolder . DIRECTORY_SEPARATOR . $jsonQueryValFileMask;
120
121 29
        $requireUniqueTypes = $defaults[static::KEY_SCHEMAS_REQUIRE_UNIQUE_TYPES] ?? true;
122
123 29
        $doNotLogExceptions = $defaults[static::KEY_DO_NOT_LOG_EXCEPTIONS_LIST] ?? [];
124 29
        unset($defaults[static::KEY_DO_NOT_LOG_EXCEPTIONS_LIST]);
125
126 29
        [$modelToSchemaMap, $typeToSchemaMap] = $this->createSchemaMapping($schemasPath, $requireUniqueTypes);
0 ignored issues
show
Bug introduced by
The variable $modelToSchemaMap does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
Bug introduced by
The variable $typeToSchemaMap does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
127
128
        return $defaults + [
129 29
                static::KEY_DO_NOT_LOG_EXCEPTIONS_LIST__AS_KEYS => array_flip($doNotLogExceptions),
130
131 29
                static::KEY_MODEL_TO_SCHEMA_MAP => $modelToSchemaMap,
132 29
                static::KEY_TYPE_TO_SCHEMA_MAP  => $typeToSchemaMap,
133
134
                static::KEY_JSON_VALIDATION_RULE_SETS_DATA =>
135
                    $this->serializeJsonValidationRules($jsonDataValPath, $jsonQueryValPath),
136
137
                static::KEY_ATTRIBUTE_VALIDATION_RULE_SETS_DATA =>
138
                    $this->serializeFormValidationRules($formsValidatorsPath),
139
            ];
140
    }
141
142
    /**
143
     * @return array
144
     */
145 29
    protected function getSettings(): array
146
    {
147 29
        $jsonOptions = JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT | JSON_UNESCAPED_SLASHES;
148
149
        return [
150 29
            static::KEY_SCHEMAS_REQUIRE_UNIQUE_TYPES              => true,
151 29
            static::KEY_SCHEMAS_FILE_MASK                         => '*.php',
152 29
            static::KEY_JSON_VALIDATORS_FILE_MASK                 => '*.php',
153 29
            static::KEY_FORM_VALIDATORS_FILE_MASK                 => '*.php',
154 29
            static::KEY_QUERY_VALIDATORS_FILE_MASK                => '*.php',
155 29
            static::KEY_THROWABLE_TO_JSON_API_EXCEPTION_CONVERTER => null,
156 29
            static::KEY_HTTP_CODE_FOR_UNEXPECTED_THROWABLE        => 500,
157 29
            static::KEY_DEFAULT_PAGING_SIZE                       => static::DEFAULT_PAGE_SIZE,
158 29
            static::KEY_MAX_PAGING_SIZE                           => static::DEFAULT_MAX_PAGE_SIZE,
159 29
            static::KEY_JSON_ENCODE_OPTIONS                       => $jsonOptions,
160 29
            static::KEY_JSON_ENCODE_DEPTH                         => 512,
161 29
            static::KEY_IS_SHOW_VERSION                           => false,
162 29
            static::KEY_META                                      => null,
163 29
            static::KEY_URI_PREFIX                                => '',
164
165 29
            static::KEY_DO_NOT_LOG_EXCEPTIONS_LIST => [
166
                JsonApiException::class,
167
            ],
168
        ];
169
    }
170
171
    /**
172
     * @param string $schemasPath
173
     * @param bool   $requireUniqueTypes
174
     *
175
     * @return array
176
     *
177
     * @throws ReflectionException
178
     */
179 29
    private function createSchemaMapping(string $schemasPath, bool $requireUniqueTypes): array
180
    {
181 29
        $modelMap = [];
182 29
        $typeMap  = [];
183 29
        foreach ($this->selectClasses($schemasPath, SchemaInterface::class) as $schemaClass) {
184 29
            assert(static::classImplements($schemaClass, SchemaInterface::class));
185
186
            /** @var SchemaInterface $schemaClass */
187 29
            $modelClass   = $schemaClass::MODEL;
188 29
            $resourceType = $schemaClass::TYPE;
189
190 29
            assert(is_string($modelClass) === true && empty($modelClass) === false);
191 29
            assert(is_string($resourceType) === true && empty($resourceType) === false);
192
193
            // By default it checks that all Schemas have unique resource types. That's a legit case
194
            // to have multiple Schemas for a same resource type however it's more likely that developer
195
            // just forgot to set a unique one. If you do need multiple Schemas for a resource feel free
196
            // to set to turn off this check.
197 29
            assert(
198 29
                $requireUniqueTypes === false || array_key_exists($resourceType, $typeMap) === false,
199 29
                "Are you sure it's not an error to use resource type `$resourceType` more than once?"
200
            );
201 29
            $typeMap[$resourceType] = $schemaClass;
202
203 29
            $modelMap[$modelClass] = $schemaClass;
204
        }
205
206 29
        return [$modelMap, $typeMap];
207
    }
208
209
    /**
210
     * @param string $rulesPath
211
     * @param string $queriesValPath
212
     *
213
     * @return array
214
     *
215
     * @throws ReflectionException
216
     */
217
    private function serializeJsonValidationRules(string $rulesPath, string $queriesValPath): array
218
    {
219
        // JSON API data validation rules
220
        $dataSerializer = new JsonApiDataRulesSerializer(new BlockSerializer());
221
        foreach ($this->selectClasses($rulesPath, JsonApiDataRulesInterface::class) as $rulesClass) {
222
            $dataSerializer->addRulesFromClass($rulesClass);
223
        }
224
225
        // JSON API query validation rules
226
        $querySerializer = new JsonApiQueryRulesSerializer(new BlockSerializer());
227
        // Add predefined rules for queries...
228
        $querySerializer->addRulesFromClass(DefaultQueryValidationRules::class);
229
        // ... and add user defined ones.
230
        foreach ($this->selectClasses($queriesValPath, JsonApiQueryRulesInterface::class) as $rulesClass) {
231
            $querySerializer->addRulesFromClass($rulesClass);
232
        }
233
234
        return [
235
            static::JSON_API_DATA_VALIDATION_RULES_SERIALIZED    => $dataSerializer->getData(),
236
            static::JSON_API_QUERIES_VALIDATION_RULES_SERIALIZED => $querySerializer->getData(),
237
        ];
238
    }
239
240
    /**
241
     * @param string $formsValPath
242
     *
243
     * @return array
244
     *
245
     * @throws ReflectionException
246
     */
247
    private function serializeFormValidationRules(string $formsValPath): array
248
    {
249
        $serializer = new FormRulesSerializer(new BlockSerializer());
250
251
        foreach ($this->selectClasses($formsValPath, FormRulesInterface::class) as $rulesClass) {
252
            $serializer->addRulesFromClass($rulesClass);
253
        }
254
255
        return $serializer->getData();
256
    }
257
258
    // serialization above makes some assumptions about format of returned data
259
    // the methods below help to deal with the data encapsulation
260
261
    /**
262
     * @param array $fluteSettings
263
     *
264
     * @return array
265
     */
266
    public static function getJsonDataSerializedRules(array $fluteSettings): array
267
    {
268
        $serializedRulesKey = static::KEY_JSON_VALIDATION_RULE_SETS_DATA;
269
        $dataSubKey         = static::JSON_API_DATA_VALIDATION_RULES_SERIALIZED;
270
271
        return $fluteSettings[$serializedRulesKey][$dataSubKey];
272
    }
273
274
    /**
275
     * @param array $fluteSettings
276
     *
277
     * @return array
278
     */
279
    public static function getJsonQuerySerializedRules(array $fluteSettings): array
280
    {
281
        $serializedRulesKey = static::KEY_JSON_VALIDATION_RULE_SETS_DATA;
282
        $dataSubKey         = static::JSON_API_QUERIES_VALIDATION_RULES_SERIALIZED;
283
284
        return $fluteSettings[$serializedRulesKey][$dataSubKey];
285
    }
286
287
    /**
288
     * @param array $fluteSettings
289
     *
290
     * @return array
291
     */
292
    public static function getFormSerializedRules(array $fluteSettings): array
293
    {
294
        return $fluteSettings[static::KEY_ATTRIBUTE_VALIDATION_RULE_SETS_DATA];
295
    }
296
}
297