Test Failed
Push — master ( a309d2...0e4b58 )
by butschster
17:34 queued 09:02
created

ConfigDeclaration::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 14
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
eloc 1
dl 0
loc 14
ccs 2
cts 2
cp 1
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 11
crap 1

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
declare(strict_types=1);
4
5
namespace Spiral\Scaffolder\Declaration;
6
7
use Cocur\Slugify\SlugifyInterface;
8
use Doctrine\Inflector\Rules\English\InflectorFactory;
9
use Nette\PhpGenerator\Dumper;
10
use Nette\PhpGenerator\Literal;
11
use Spiral\Boot\DirectoriesInterface;
12
use Spiral\Core\InjectableConfig;
13
use Spiral\Files\FilesInterface;
14
use Spiral\Reactor\FileDeclaration;
15
use Spiral\Reactor\Partial\Method;
16
use Spiral\Scaffolder\Config\ScaffolderConfig;
17
use Spiral\Scaffolder\Exception\ScaffolderException;
18
19
use function Spiral\Scaffolder\defineArrayType;
20
21
class ConfigDeclaration extends AbstractDeclaration implements HasInstructions
22
{
23
    public const TYPE = 'config';
24 7
    private bool $reverse = false;
25
26
    public function __construct(
27
        ScaffolderConfig $config,
28
        protected readonly FilesInterface $files,
29
        protected readonly DirectoriesInterface $dirs,
30
        protected readonly SlugifyInterface $slugify,
31
        protected readonly ConfigDeclaration\TypeAnnotations $typeAnnotations,
32
        protected readonly ConfigDeclaration\TypeHints $typeHints,
33
        protected readonly ConfigDeclaration\Defaults $defaultValues,
34
        protected string $name,
35
        protected ?string $comment = null,
36 7
        private readonly string $directory = '',
37
        ?string $namespace = null,
38
    ) {
39 7
        parent::__construct($config, $name, $comment, $namespace);
40
    }
41 7
42
    public function create(bool $reverse, string $configName): void
43 7
    {
44 7
        $this->reverse = $reverse;
45 3
        $this->class->addConstant('CONFIG', $configName)->setPublic();
46
47
        $filename = $this->makeConfigFilename($configName);
48
        if ($reverse) {
49 3
            if (!$this->files->exists($filename)) {
50 3
                throw new ScaffolderException(\sprintf("Config filename %s doesn't exist", $filename));
51
            }
52 3
53
            $defaultsFromFile = require $filename;
54 4
            $this->declareGetters($defaultsFromFile);
55 2
56
            $this->class->getProperty('config')->setValue($this->defaultValues->get($defaultsFromFile));
57
        } elseif (!$this->files->exists($filename)) {
58
            $this->touchConfigFile($filename);
59
        }
60
    }
61
62
    /**
63 7
     * Declare constant and property.
64
     */
65 7
    public function declare(): void
66
    {
67 7
        $this->namespace->addUse(InjectableConfig::class);
68 7
69
        $this->class->setExtends(InjectableConfig::class);
70 7
        $this->class->setFinal();
71 7
72 7
        $this->class
73 7
            ->addProperty('config')
74 7
            ->setProtected()
75 7
            ->setType('array')
76 7
            ->setValue([])
77
            ->setComment(
78
                <<<'DOC'
79 7
                Default values for the config.
80 7
                Will be merged with application config in runtime.
81
                DOC,
82
            );
83 7
    }
84
85 7
    public function getInstructions(): array
86
    {
87
        $configFile = $this->makeConfigFilename(
88 2
            $this->class->getConstant('CONFIG')->getValue()
89
        );
90 2
91
        $configFile = \str_replace($this->dirs->get('root'), '', $configFile);
92 2
93 2
        return [
94
            \sprintf('You can now add your config values to the \'<comment>%s</comment>\' file.', $configFile),
95 2
            'Read more about Config Objects in the documentation: https://spiral.dev/docs/framework-config',
96 2
        ];
97 2
    }
98 2
99 2
    private function makeConfigFilename(string $filename): string
100 2
    {
101
        return \sprintf('%s%s.php', $this->directory, $filename);
102
    }
103 2
104
    private function touchConfigFile(string $filename): void
105 2
    {
106
        $this->files->touch($filename);
107 2
108
        $file = new FileDeclaration();
109
        $file->setComment($this->phpDocSeeReference());
110 3
111
        $this->files->write(
112 3
            $filename,
113 3
            $file->render() . PHP_EOL . (new Dumper())->dump(new Literal('return [];')),
114
            FilesInterface::READONLY,
115 3
            true,
116 3
        );
117 3
    }
118 3
119
    private function phpDocSeeReference(): string
120 3
    {
121 3
        $namespace = \trim($this->config->classNamespace('config', $this->class->getName()), '\\');
0 ignored issues
show
Bug introduced by
It seems like $this->class->getName() can also be of type null; however, parameter $name of Spiral\Scaffolder\Config...onfig::classNamespace() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

121
        $namespace = \trim($this->config->classNamespace('config', /** @scrutinizer ignore-type */ $this->class->getName()), '\\');
Loading history...
122
123 3
        return \sprintf('@see \%s\%s', $namespace, $this->class->getName());
124 2
    }
125
126
    private function declareGetters(array $defaults): void
127 3
    {
128 3
        $getters = [];
129 3
        $gettersByKey = [];
130
131
        foreach ($defaults as $key => $value) {
132
            $key = (string)$key;
133 3
            $getter = $this->makeGetterName($key);
134 2
            $getters[] = $getter;
135 2
136 2
            $method = $this->class->addMethod($getter)->setPublic();
137
            $method->setBody(\sprintf('return $this->config[\'%s\'];', $key));
138
139
            if (\is_array($value)) {
140
                $gettersByKey[] = ['key' => $key, 'value' => $value];
141 2
            }
142
143
            $returnTypeHint = $this->typeHints->getHint(\gettype($value));
144 2
            if ($returnTypeHint !== null) {
145 2
                $method->setReturnType($returnTypeHint);
146
            }
147
        }
148 2
149 2
        foreach ($gettersByKey as $item) {
150 2
            $method = $this->declareGettersByKey($getters, $item['key'], $item['value']);
151 2
            if ($method !== null) {
152
                $getters[] = $method->getName();
153
            }
154
        }
155 2
    }
156 2
157
    private function declareGettersByKey(array $methodNames, string $key, array $value): ?Method
158
    {
159 2
        //Won't create if there's less than 2 sub-items
160 2
        if (\count($value) < 2) {
161
            return null;
162 2
        }
163 2
164
        $singularKey = $this->singularize($key);
165
        $name = $this->makeGetterName($singularKey);
166
        if (\in_array($name, $methodNames, true)) {
167 2
            $name = $this->makeGetterName($singularKey, 'get', 'by');
168 2
        }
169
170
        //Name conflict, won't merge
171 2
        if (\in_array($name, $methodNames, true)) {
172 2
            return null;
173 2
        }
174
175 2
        $keyType = defineArrayType(\array_keys($value), '-mixed-');
176 2
        $valueType = defineArrayType(\array_values($value), '-mixed-');
177 2
        //We need a fixed structure here
178 2
        if ($keyType === '-mixed-' || $valueType === '-mixed-') {
179
            return null;
180
        }
181 2
182
        //Won't create for associated arrays
183
        if ($this->typeAnnotations->mapType($keyType) === 'int' && \array_is_list($value)) {
184 3
            return null;
185
        }
186 3
187 3
        $method = $this->class->addMethod($name)->setPublic();
188 3
        $method->setBody(\sprintf('return $this->config[\'%s\'][$%s];', $key, $singularKey));
189
        $method->setReturnType($valueType);
190
191 3
        $param = $method->addParameter($singularKey);
192 3
        $paramTypeHint = $this->typeHints->getHint($keyType);
193 3
        if ($paramTypeHint !== null) {
194 2
            $param->setType($paramTypeHint);
195
        }
196
197 3
        return $method;
198
    }
199
200 3
    private function makeGetterName(string $name, string $prefix = 'get', string $postfix = ''): string
201
    {
202 3
        $chunks = [];
203 3
        if (!empty($prefix)) {
204 3
            $chunks[] = $prefix;
205
        }
206
207 2
        $name = $this->slugify->slugify($name, ['lowercase' => false]);
208
        $chunks[] = \count($chunks) !== 0 ? $this->classify($name) : $name;
209 2
        if (!empty($postfix)) {
210 2
            $chunks[] = \ucfirst($postfix);
211 2
        }
212
213
        return \implode('', $chunks);
214
    }
215
216
    private function classify(string $name): string
217
    {
218
        return (new InflectorFactory())
219
            ->build()
220
            ->classify($name);
221
    }
222
223
    private function singularize(string $name): string
224
    {
225
        return (new InflectorFactory())
226
            ->build()
227
            ->singularize($name);
228
    }
229
}
230