Passed
Push — master ( 67a020...fe0e28 )
by butschster
04:26 queued 17s
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
25 8
    public function __construct(
26
        ScaffolderConfig $config,
27
        protected readonly FilesInterface $files,
28
        protected readonly DirectoriesInterface $dirs,
29
        protected readonly SlugifyInterface $slugify,
30
        protected readonly ConfigDeclaration\TypeAnnotations $typeAnnotations,
31
        protected readonly ConfigDeclaration\TypeHints $typeHints,
32
        protected readonly ConfigDeclaration\Defaults $defaultValues,
33
        protected string $name,
34
        protected ?string $comment = null,
35
        private readonly string $directory = '',
36
        ?string $namespace = null,
37
    ) {
38 8
        parent::__construct($config, $name, $comment, $namespace);
39
    }
40
41 8
    public function create(bool $reverse, string $configName): void
42
    {
43 8
        $this->class->addConstant('CONFIG', $configName)->setPublic();
44
45 8
        $filename = $this->makeConfigFilename($configName);
46 8
        if ($reverse) {
47 3
            if (!$this->files->exists($filename)) {
48
                throw new ScaffolderException(\sprintf("Config filename %s doesn't exist", $filename));
49
            }
50
51 3
            $defaultsFromFile = require $filename;
52 3
            $this->declareGetters($defaultsFromFile);
53
54 3
            $this->class->getProperty('config')->setValue($this->defaultValues->get($defaultsFromFile));
55 5
        } elseif (!$this->files->exists($filename)) {
56 3
            $this->touchConfigFile($filename);
57
        }
58
    }
59
60
    /**
61
     * Declare constant and property.
62
     */
63 8
    public function declare(): void
64
    {
65 8
        $this->namespace->addUse(InjectableConfig::class);
66
67 8
        $this->class->setExtends(InjectableConfig::class);
68 8
        $this->class->setFinal();
69
70 8
        $this->class
71 8
            ->addProperty('config')
72 8
            ->setProtected()
73 8
            ->setType('array')
74 8
            ->setValue([])
75 8
            ->setComment(
76 8
                <<<'DOC'
77
                Default values for the config.
78
                Will be merged with application config in runtime.
79 8
                DOC,
80 8
            );
81
    }
82
83 8
    public function getInstructions(): array
84
    {
85 8
        $configFile = $this->makeConfigFilename(
86 8
            $this->class->getConstant('CONFIG')->getValue()
87 8
        );
88
89 8
        $configFile = \str_replace($this->dirs->get('root'), '', $configFile);
90
91 8
        return [
92 8
            \sprintf('You can now add your config values to the \'<comment>%s</comment>\' file.', $configFile),
93 8
            'Read more about Config Objects in the documentation: https://spiral.dev/docs/framework-config',
94 8
        ];
95
    }
96
97 8
    private function makeConfigFilename(string $filename): string
98
    {
99 8
        return \sprintf('%s%s.php', $this->directory, $filename);
100
    }
101
102 3
    private function touchConfigFile(string $filename): void
103
    {
104 3
        $this->files->touch($filename);
105
106 3
        $file = new FileDeclaration();
107 3
        $file->setComment($this->phpDocSeeReference());
108
109 3
        $this->files->write(
110 3
            $filename,
111 3
            $file->render() . PHP_EOL . (new Dumper())->dump(new Literal('return [];')),
112 3
            FilesInterface::READONLY,
113 3
            true,
114 3
        );
115
    }
116
117 3
    private function phpDocSeeReference(): string
118
    {
119 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

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