Completed
Push — master ( 1a67d2...dfab50 )
by Neomerx
11:35 queued 22s
created

FluteSettings::createModelToSchemeMap()   C

Complexity

Conditions 7
Paths 2

Size

Total Lines 32
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 18
CRAP Score 7

Importance

Changes 0
Metric Value
dl 0
loc 32
ccs 18
cts 18
cp 1
rs 6.7272
c 0
b 0
f 0
cc 7
eloc 18
nc 2
nop 2
crap 7
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\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
18
/**
19
 * @package Limoncello\Flute
20
 */
21
abstract class FluteSettings implements FluteSettingsInterface
22
{
23
    /**
24
     * Namespace for string resources.
25
     */
26
    const GENERIC_NAMESPACE = Messages::RESOURCES_NAMESPACE;
27
28
    /**
29
     * Namespace for string resources.
30
     */
31
    public const VALIDATION_NAMESPACE = 'Limoncello.Flute.Validation';
32
33
    /**
34
     * Default page size.
35
     */
36
    public const DEFAULT_PAGE_SIZE = 10;
37
38
    /**
39
     * Default page size.
40
     */
41
    public const DEFAULT_MAX_PAGE_SIZE = 30;
42
43
    /** Serialized validation data index */
44
    protected const JSON_API_DATA_VALIDATION_RULES_SERIALIZED = 0;
45
46
    /** Serialized validation data index */
47
    protected const JSON_API_QUERIES_VALIDATION_RULES_SERIALIZED = self::JSON_API_DATA_VALIDATION_RULES_SERIALIZED + 1;
48
49
    /**
50
     * @param string $path
51
     * @param string $implementClassName
52
     *
53
     * @return Generator
54
     */
55
    abstract protected function selectClasses(string $path, string $implementClassName): Generator;
56
57
    /**
58
     * @param array $appConfig
59
     *
60
     * @return array
61
     */
62 28
    final public function get(array $appConfig): array
63
    {
64 28
        $defaults = $this->getSettings();
65
66 28
        $defaults[static::KEY_ROUTES_FOLDER]          = $appConfig[A::KEY_ROUTES_FOLDER];
67 28
        $defaults[static::KEY_WEB_CONTROLLERS_FOLDER] = $appConfig[A::KEY_WEB_CONTROLLERS_FOLDER];
68
69 28
        $apiFolder            = $defaults[static::KEY_API_FOLDER] ?? null;
70 28
        $valRulesFolder       = $defaults[static::KEY_JSON_VALIDATION_RULES_FOLDER] ?? null;
71 28
        $jsonCtrlFolder       = $defaults[static::KEY_JSON_CONTROLLERS_FOLDER] ?? null;
72 28
        $schemasFolder        = $defaults[static::KEY_SCHEMAS_FOLDER] ?? null;
73 28
        $schemasFileMask      = $defaults[static::KEY_SCHEMAS_FILE_MASK] ?? null;
74 28
        $jsonDataValFolder    = $defaults[static::KEY_JSON_VALIDATORS_FOLDER] ?? null;
75 28
        $jsonDataValFileMask  = $defaults[static::KEY_JSON_VALIDATORS_FILE_MASK] ?? null;
76 28
        $formsValFolder       = $defaults[static::KEY_FORM_VALIDATORS_FOLDER] ?? null;
77 28
        $formsValFileMask     = $defaults[static::KEY_FORM_VALIDATORS_FILE_MASK] ?? null;
78 28
        $jsonQueryValFolder   = $defaults[static::KEY_QUERY_VALIDATORS_FOLDER] ?? null;
79 28
        $jsonQueryValFileMask = $defaults[static::KEY_QUERY_VALIDATORS_FILE_MASK] ?? null;
80
81 28
        assert(
82 28
            $apiFolder !== null && empty(glob($apiFolder)) === false,
83 28
            "Invalid API folder `$apiFolder`."
84
        );
85 28
        assert(
86 28
            $valRulesFolder !== null && empty(glob($valRulesFolder)) === false,
87 28
            "Invalid validation rules folder `$valRulesFolder`."
88
        );
89 28
        assert(
90 28
            $jsonCtrlFolder !== null && empty(glob($jsonCtrlFolder)) === false,
91 28
            "Invalid JSON API controllers' folder `$jsonCtrlFolder`."
92
        );
93 28
        assert(
94 28
            $schemasFolder !== null && empty(glob($schemasFolder)) === false,
95 28
            "Invalid Schemas folder `$schemasFolder`."
96
        );
97 28
        assert(empty($schemasFileMask) === false, "Invalid Schemas file mask `$schemasFileMask`.");
98 28
        assert(
99 28
            $jsonDataValFolder !== null && empty(glob($jsonDataValFolder)) === false,
100 28
            "Invalid JSON Validators folder `$jsonDataValFolder`."
101
        );
102 28
        assert(empty($jsonDataValFileMask) === false, "Invalid JSON Validators file mask `$jsonDataValFileMask`.");
103 28
        assert(
104 28
            $formsValFolder !== null && empty(glob($formsValFolder)) === false,
105 28
            "Invalid Forms Validators folder `$formsValFolder`."
106
        );
107 28
        assert(empty($formsValFileMask) === false, "Invalid Forms Validators file mask `$formsValFileMask`.");
108 28
        assert(
109 28
            $jsonQueryValFolder !== null && empty(glob($jsonQueryValFolder)) === false,
110 28
            "Invalid Query Validators folder `$jsonQueryValFolder`."
111
        );
112 28
        assert(empty($jsonQueryValFileMask) === false, "Invalid Query Validators file mask `$jsonQueryValFileMask`.");
113
114 28
        $schemasPath         = $schemasFolder . DIRECTORY_SEPARATOR . $schemasFileMask;
115 28
        $jsonDataValPath     = $jsonDataValFolder . DIRECTORY_SEPARATOR . $jsonDataValFileMask;
116 28
        $formsValidatorsPath = $formsValFolder . DIRECTORY_SEPARATOR . $formsValFileMask;
117 28
        $jsonQueryValPath    = $jsonQueryValFolder . DIRECTORY_SEPARATOR . $jsonQueryValFileMask;
118
119 28
        $requireUniqueTypes = $defaults[static::KEY_SCHEMAS_REQUIRE_UNIQUE_TYPES] ?? true;
120
121 28
        $doNotLogExceptions = $defaults[static::KEY_DO_NOT_LOG_EXCEPTIONS_LIST] ?? [];
122 28
        unset($defaults[static::KEY_DO_NOT_LOG_EXCEPTIONS_LIST]);
123
124
        return $defaults + [
125 28
                static::KEY_DO_NOT_LOG_EXCEPTIONS_LIST__AS_KEYS => array_flip($doNotLogExceptions),
126
127 28
                static::KEY_MODEL_TO_SCHEMA_MAP => $this->createModelToSchemaMap($schemasPath, $requireUniqueTypes),
128
129 28
                static::KEY_JSON_VALIDATION_RULE_SETS_DATA =>
130 28
                    $this->serializeJsonValidationRules($jsonDataValPath, $jsonQueryValPath),
131
132 28
                static::KEY_ATTRIBUTE_VALIDATION_RULE_SETS_DATA =>
133 28
                    $this->serializeFormValidationRules($formsValidatorsPath),
134
            ];
135
    }
136
137
    /**
138
     * @return array
139
     */
140 28
    protected function getSettings(): array
141
    {
142 28
        $jsonOptions = JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT | JSON_UNESCAPED_SLASHES;
143
144
        return [
145 28
            static::KEY_SCHEMAS_REQUIRE_UNIQUE_TYPES              => true,
146 28
            static::KEY_SCHEMAS_FILE_MASK                         => '*.php',
147 28
            static::KEY_JSON_VALIDATORS_FILE_MASK                 => '*.php',
148 28
            static::KEY_FORM_VALIDATORS_FILE_MASK                 => '*.php',
149 28
            static::KEY_QUERY_VALIDATORS_FILE_MASK                => '*.php',
150 28
            static::KEY_THROWABLE_TO_JSON_API_EXCEPTION_CONVERTER => null,
151 28
            static::KEY_HTTP_CODE_FOR_UNEXPECTED_THROWABLE        => 500,
152 28
            static::KEY_DEFAULT_PAGING_SIZE                       => static::DEFAULT_PAGE_SIZE,
153 28
            static::KEY_MAX_PAGING_SIZE                           => static::DEFAULT_MAX_PAGE_SIZE,
154 28
            static::KEY_JSON_ENCODE_OPTIONS                       => $jsonOptions,
155 28
            static::KEY_JSON_ENCODE_DEPTH                         => 512,
156 28
            static::KEY_IS_SHOW_VERSION                           => false,
157 28
            static::KEY_META                                      => null,
158 28
            static::KEY_URI_PREFIX                                => null,
159
160 28
            static::KEY_DO_NOT_LOG_EXCEPTIONS_LIST => [
161
                JsonApiException::class,
162
            ],
163
        ];
164
    }
165
166
    /**
167
     * @param string $schemasPath
168
     * @param bool   $requireUniqueTypes
169
     *
170
     * @return array
171
     */
172 28
    private function createModelToSchemaMap(string $schemasPath, bool $requireUniqueTypes): array
173
    {
174 28
        $map   = [];
175 28
        $types = [];
176 28
        foreach ($this->selectClasses($schemasPath, SchemaInterface::class) as $schemaClass) {
177 28
            assert(
178 28
                is_string($schemaClass) &&
179 28
                class_exists($schemaClass) &&
180 28
                array_key_exists(SchemaInterface::class, class_implements($schemaClass))
181
            );
182
            /** @var SchemaInterface $schemaClass */
183 28
            $modelClass   = $schemaClass::MODEL;
184 28
            $resourceType = $schemaClass::TYPE;
185
186 28
            assert(is_string($modelClass) === true && empty($modelClass) === false);
187 28
            assert(is_string($resourceType) === true && empty($resourceType) === false);
188
189
            // By default it checks that all Schemas have unique resource types. That's a legit case
190
            // to have multiple Schemas for a same resource type however it's more likely that developer
191
            // just forgot to set a unique one. If you do need multiple Schemas for a resource feel free
192
            // to set to turn off this check.
193 28
            assert(
194 28
                $requireUniqueTypes === false || array_key_exists($resourceType, $types) === false,
195 28
                "Are you sure it's not an error to use resource type `$resourceType` more than once?"
196
            );
197 28
            $types[$resourceType] = true;
198
199 28
            $map[$modelClass] = $schemaClass;
200
        }
201
202 28
        return $map;
203
    }
204
205
    /**
206
     * @param string $validatorsPath
207
     * @param string $queriesValPath
208
     *
209
     * @return array
210
     */
211 28
    private function serializeJsonValidationRules(string $validatorsPath, string $queriesValPath): array
212
    {
213
        // JSON API data validation rules
214 28
        $dataSerializer = new JsonApiDataRulesSerializer(new BlockSerializer());
215 28
        foreach ($this->selectClasses($validatorsPath, JsonApiDataRulesInterface::class) as $rulesClass) {
216 28
            $dataSerializer->addRulesFromClass($rulesClass);
217
        }
218
219
        // JSON API query validation rules
220 28
        $querySerializer = new JsonApiQueryRulesSerializer(new BlockSerializer());
221
        // Add predefined rules for queries...
222 28
        $querySerializer->addRulesFromClass(DefaultQueryValidationRules::class);
223
        // ... and add user defined ones.
224 28
        foreach ($this->selectClasses($queriesValPath, JsonApiQueryRulesInterface::class) as $rulesClass) {
225 28
            $querySerializer->addRulesFromClass($rulesClass);
226
        }
227
228
        return [
229 28
            static::JSON_API_DATA_VALIDATION_RULES_SERIALIZED    => $dataSerializer->getData(),
230 28
            static::JSON_API_QUERIES_VALIDATION_RULES_SERIALIZED => $querySerializer->getData(),
231
        ];
232
    }
233
234
    /**
235
     * @param string $formsValPath
236
     *
237
     * @return array
238
     */
239 28
    private function serializeFormValidationRules(string $formsValPath): array
240
    {
241 28
        $serializer = new FormRulesSerializer(new BlockSerializer());
242
243 28
        foreach ($this->selectClasses($formsValPath, FormRulesInterface::class) as $rulesClass) {
244 28
            $serializer->addRulesFromClass($rulesClass);
245
        }
246
247 28
        return $serializer->getData();
248
    }
249
250
    // serialization above makes some assumptions about format of returned data
251
    // the methods below help to deal with the data encapsulation
252
253
    /**
254
     * @param array $fluteSettings
255
     *
256
     * @return array
257
     */
258 9
    public static function getJsonDataSerializedRules(array $fluteSettings): array
259
    {
260 9
        $serializedRulesKey = static::KEY_JSON_VALIDATION_RULE_SETS_DATA;
261 9
        $dataSubKey         = static::JSON_API_DATA_VALIDATION_RULES_SERIALIZED;
262
263 9
        return $fluteSettings[$serializedRulesKey][$dataSubKey];
264
    }
265
266
    /**
267
     * @param array $fluteSettings
268
     *
269
     * @return array
270
     */
271 17
    public static function getJsonQuerySerializedRules(array $fluteSettings): array
272
    {
273 17
        $serializedRulesKey = static::KEY_JSON_VALIDATION_RULE_SETS_DATA;
274 17
        $dataSubKey         = static::JSON_API_QUERIES_VALIDATION_RULES_SERIALIZED;
275
276 17
        return $fluteSettings[$serializedRulesKey][$dataSubKey];
277
    }
278
279
    /**
280
     * @param array $fluteSettings
281
     *
282
     * @return array
283
     */
284 1
    public static function getFormSerializedRules(array $fluteSettings): array
285
    {
286 1
        return $fluteSettings[static::KEY_ATTRIBUTE_VALIDATION_RULE_SETS_DATA];
287
    }
288
}
289