Passed
Pull Request — master (#806)
by Maxim
19:17
created

ConfigDeclaration::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 12
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
eloc 1
c 0
b 0
f 0
dl 0
loc 12
ccs 2
cts 2
cp 1
rs 10
cc 1
nc 1
nop 9
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\Literal;
10
use Nette\PhpGenerator\Dumper;
11
use Spiral\Core\InjectableConfig;
12
use Spiral\Files\FilesInterface;
13
use Spiral\Reactor\FileDeclaration;
14
use Spiral\Reactor\Partial\Method;
15
use Spiral\Scaffolder\Config\ScaffolderConfig;
16
use Spiral\Scaffolder\Exception\ScaffolderException;
17
18
use function Spiral\Scaffolder\defineArrayType;
19
use function Spiral\Scaffolder\isAssociativeArray;
20
21
class ConfigDeclaration extends AbstractDeclaration
22
{
23
    public const TYPE = 'config';
24
25 6
    public function __construct(
26
        ScaffolderConfig $config,
27
        private readonly FilesInterface $files,
28
        private readonly SlugifyInterface $slugify,
29
        private readonly ConfigDeclaration\TypeAnnotations $typeAnnotations,
30
        private readonly ConfigDeclaration\TypeHints $typeHints,
31
        private readonly ConfigDeclaration\Defaults $defaultValues,
32
        string $name,
33
        ?string $comment = null,
34
        private readonly string $directory = ''
35
    ) {
36 6
        parent::__construct($config, $name, $comment);
37
    }
38
39 6
    public function create(bool $reverse, string $configName): void
40
    {
41 6
        $this->class->addConstant('CONFIG', $configName)->setPublic();
42
43 6
        $filename = $this->makeConfigFilename($configName);
44 6
        if ($reverse) {
45 3
            if (!$this->files->exists($filename)) {
46
                throw new ScaffolderException(\sprintf("Config filename %s doesn't exist", $filename));
47
            }
48
49 3
            $defaultsFromFile = require $filename;
50 3
            $this->declareGetters($defaultsFromFile);
51
52 3
            $this->class->getProperty('config')->setValue($this->defaultValues->get($defaultsFromFile));
53
        } else {
54 3
            if (!$this->files->exists($filename)) {
55 2
                $this->touchConfigFile($filename);
56
            }
57
        }
58
    }
59
60
    /**
61
     * Declare constant and property.
62
     */
63 6
    public function declare(): void
64
    {
65 6
        $this->namespace->addUse(InjectableConfig::class);
66
67 6
        $this->class->setExtends(InjectableConfig::class);
68
69 6
        $this->class
70 6
            ->addProperty('config')
71 6
            ->setProtected()
72 6
            ->setType('array')
73 6
            ->setValue([])
74 6
            ->setComment('@internal For internal usage. Will be hydrated in the constructor.');
75
    }
76
77 6
    private function makeConfigFilename(string $filename): string
78
    {
79 6
        return \sprintf('%s%s.php', $this->directory, $filename);
80
    }
81
82 2
    private function touchConfigFile(string $filename): void
83
    {
84 2
        $this->files->touch($filename);
85
86 2
        $file = new FileDeclaration();
87 2
        $file->setComment($this->phpDocSeeReference());
88
89 2
        $this->files->write(
90
            $filename,
91 2
            $file->render() . PHP_EOL . (new Dumper())->dump(new Literal('return [];')),
92
            FilesInterface::READONLY,
93
            true
94
        );
95
    }
96
97 2
    private function phpDocSeeReference(): string
98
    {
99 2
        $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

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