Passed
Push — master ( c776c7...570285 )
by Kirill
04:05
created

ConfigDeclaration::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 22
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 9
c 1
b 0
f 0
nc 1
nop 10
dl 0
loc 22
rs 9.9666

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
<?php
2
3
/**
4
 * Spiral Framework. Scaffolder
5
 *
6
 * @license MIT
7
 * @author  Valentin V (vvval)
8
 */
9
10
declare(strict_types=1);
11
12
namespace Spiral\Scaffolder\Declaration;
13
14
use Cocur\Slugify\SlugifyInterface;
15
use Doctrine\Inflector\Rules\English\InflectorFactory;
16
use Spiral\Core\InjectableConfig;
17
use Spiral\Files\FilesInterface;
18
use Spiral\Reactor\ClassDeclaration;
19
use Spiral\Reactor\DependedInterface;
20
use Spiral\Reactor\FileDeclaration;
21
use Spiral\Reactor\Partial\Method;
22
use Spiral\Reactor\Partial\Source;
23
use Spiral\Scaffolder\Config\ScaffolderConfig;
24
use Spiral\Scaffolder\Exception\ScaffolderException;
25
26
use function Spiral\Scaffolder\defineArrayType;
27
use function Spiral\Scaffolder\isAssociativeArray;
28
29
class ConfigDeclaration extends ClassDeclaration implements DependedInterface
30
{
31
    /** @var ScaffolderConfig */
32
    private $config;
33
34
    /** @var FilesInterface */
35
    private $files;
36
37
    /** @var SlugifyInterface */
38
    private $slugify;
39
40
    /** @var ConfigDeclaration\TypeAnnotations */
41
    private $typeAnnotations;
42
43
    /** @var ConfigDeclaration\TypeHints */
44
    private $typeHints;
45
46
    /** @var ConfigDeclaration\Defaults */
47
    private $defaultValues;
48
49
    /** @var string */
50
    private $configName;
51
52
    /** @var string */
53
    private $directory;
54
55
    /**
56
     * @param ScaffolderConfig                  $config
57
     * @param FilesInterface                    $files
58
     * @param SlugifyInterface                  $slugify
59
     * @param ConfigDeclaration\TypeAnnotations $typeAnnotations
60
     * @param ConfigDeclaration\TypeHints       $typeHints
61
     * @param ConfigDeclaration\Defaults        $defaultValues
62
     * @param string                            $configName
63
     * @param string                            $name
64
     * @param string                            $comment
65
     * @param string                            $directory
66
     */
67
    public function __construct(
68
        ScaffolderConfig $config,
69
        FilesInterface $files,
70
        SlugifyInterface $slugify,
71
        ConfigDeclaration\TypeAnnotations $typeAnnotations,
72
        ConfigDeclaration\TypeHints $typeHints,
73
        ConfigDeclaration\Defaults $defaultValues,
74
        string $configName,
75
        string $name,
76
        string $comment = '',
77
        string $directory = ''
78
    ) {
79
        parent::__construct($name, 'InjectableConfig', [], $comment);
80
81
        $this->config = $config;
82
        $this->files = $files;
83
        $this->slugify = $slugify;
84
        $this->typeAnnotations = $typeAnnotations;
85
        $this->typeHints = $typeHints;
86
        $this->defaultValues = $defaultValues;
87
        $this->directory = $directory;
88
        $this->configName = $configName;
89
    }
90
91
    /**
92
     * {@inheritdoc}
93
     */
94
    public function getDependencies(): array
95
    {
96
        return [InjectableConfig::class => null];
97
    }
98
99
    /**
100
     * @param bool $reverse
101
     */
102
    public function create(bool $reverse): void
103
    {
104
        $filename = $this->makeConfigFilename($this->configName);
105
        if ($reverse) {
106
            if (!$this->files->exists($filename)) {
107
                throw new ScaffolderException("Config filename $filename doesn't exist");
108
            }
109
110
            $defaultsFromFile = require $filename;
111
            $this->declareGetters($defaultsFromFile);
112
            $this->declareStructure($this->configName, $this->defaultValues->get($defaultsFromFile));
113
        } else {
114
            if (!$this->files->exists($filename)) {
115
                $this->touchConfigFile($filename);
116
            }
117
118
            $this->declareStructure($this->configName, []);
119
        }
120
    }
121
122
    /**
123
     * @param string $filename
124
     * @return string
125
     */
126
    private function makeConfigFilename(string $filename): string
127
    {
128
        return "{$this->directory}{$filename}.php";
129
    }
130
131
    /**
132
     * @param string $filename
133
     */
134
    private function touchConfigFile(string $filename): void
135
    {
136
        $this->files->touch($filename);
137
138
        $file = new FileDeclaration();
139
        $file->setDirectives('strict_types=1');
140
        $file->setComment($this->phpDocSeeReference());
141
        $file->addElement(new Source(['', 'return [];']));
142
        $file->render();
143
144
        $this->files->write(
145
            $filename,
146
            $file->render(),
147
            FilesInterface::READONLY,
148
            true
149
        );
150
    }
151
152
    /**
153
     * @return string
154
     */
155
    private function phpDocSeeReference(): string
156
    {
157
        $namespace = trim($this->config->classNamespace('config', $this->getName()), '\\');
158
159
        return "@see \\$namespace\\{$this->getName()}";
160
    }
161
162
    /**
163
     * @param array $defaults
164
     * @return double[]|float[]
165
     */
166
    private function declareGetters(array $defaults): array
167
    {
168
        $output = [];
169
        $getters = [];
170
        $gettersByKey = [];
171
172
        foreach ($defaults as $key => $value) {
173
            $key = (string)$key;
174
            $getter = $this->makeGetterName($key);
175
            $getters[] = $getter;
176
177
            $method = $this->method($getter)->setPublic();
178
            $method->setSource("return \$this->config['$key'];");
179
            $method->setComment("@return {$this->typeAnnotations->getAnnotation($value)}");
180
181
            if (is_array($value)) {
182
                $gettersByKey[] = compact('key', 'value');
183
            }
184
185
            $returnTypeHint = $this->typeHints->getHint(gettype($value));
186
            if ($returnTypeHint !== null) {
187
                $method->setReturn($returnTypeHint);
188
            }
189
        }
190
191
        foreach ($gettersByKey as $item) {
192
            $method = $this->declareGettersByKey($getters, $item['key'], $item['value']);
193
            if ($method !== null) {
194
                $getters[] = $method->getName();
195
            }
196
        }
197
198
        return $output;
199
    }
200
201
    /**
202
     * @param array  $methodNames
203
     * @param string $key
204
     * @param array  $value
205
     * @return Method|null
206
     */
207
    private function declareGettersByKey(array $methodNames, string $key, array $value): ?Method
208
    {
209
        //Won't create if there's less than 2 sub-items
210
        if (count($value) < 2) {
211
            return null;
212
        }
213
214
        $singularKey = $this->singularize($key);
215
        $name = $this->makeGetterName($singularKey);
216
        if (in_array($name, $methodNames, true)) {
217
            $name = $this->makeGetterName($singularKey, 'get', 'by');
218
        }
219
220
        //Name conflict, won't merge
221
        if (in_array($name, $methodNames, true)) {
222
            return null;
223
        }
224
225
        $keyType = defineArrayType(array_keys($value), '-mixed-');
226
        $valueType = defineArrayType(array_values($value), '-mixed-');
227
        //We need a fixed structure here
228
        if ($keyType === '-mixed-' || $valueType === '-mixed-') {
229
            return null;
230
        }
231
232
        //Won't create for associated arrays
233
        if ($this->typeAnnotations->mapType($keyType) === 'int' && !isAssociativeArray($value)) {
234
            return null;
235
        }
236
237
        $method = $this->method($name)->setPublic();
238
        $method->setSource("return \$this->config['$key'][\$$singularKey];");
239
        $method->setReturn($valueType);
240
        $method->setComment([
241
            "@param {$this->typeAnnotations->mapType($keyType)} $singularKey",
242
            "@return {$this->typeAnnotations->getAnnotation(array_values($value)[0])}"
243
        ]);
244
245
        $param = $method->parameter($singularKey);
246
        $paramTypeHint = $this->typeHints->getHint($keyType);
247
        if ($paramTypeHint !== null) {
248
            $param->setType($paramTypeHint);
249
        }
250
251
        return $method;
252
    }
253
254
    /**
255
     * @param string $name
256
     * @param string $prefix
257
     * @param string $postfix
258
     * @return string
259
     */
260
    private function makeGetterName(string $name, string $prefix = 'get', string $postfix = ''): string
261
    {
262
        $chunks = [];
263
        if (!empty($prefix)) {
264
            $chunks[] = $prefix;
265
        }
266
267
        $name = $this->slugify->slugify($name, ['lowercase' => false]);
268
        $chunks[] = count($chunks) !== 0 ? $this->classify($name) : $name;
269
        if (!empty($postfix)) {
270
            $chunks[] = ucfirst($postfix);
271
        }
272
273
        return implode('', $chunks);
274
    }
275
276
    /**
277
     * Declare constant and property.
278
     *
279
     * @param string $configName
280
     * @param array  $defaults
281
     */
282
    private function declareStructure(string $configName, array $defaults): void
283
    {
284
        $this->constant('CONFIG')->setPublic()->setValue($configName);
285
        $this->property('config')->setProtected()->setDefaultValue($defaults)
286
            ->setComment('@internal For internal usage. Will be hydrated in the constructor.');
287
    }
288
289
    /**
290
     * @param string $name
291
     * @return string
292
     */
293
    private function classify(string $name): string
294
    {
295
        return ( new InflectorFactory() )
296
            ->build()
297
            ->classify($name);
298
    }
299
300
    /**
301
     * @param string $name
302
     * @return string
303
     */
304
    private function singularize(string $name): string
305
    {
306
        return ( new InflectorFactory() )
307
            ->build()
308
            ->singularize($name);
309
    }
310
}
311