Test Failed
Pull Request — master (#856)
by Maxim
06:34
created

ConfigDeclaration::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 13
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 13
ccs 2
cts 2
cp 1
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 10
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
20
class ConfigDeclaration extends AbstractDeclaration
21
{
22
    public const TYPE = 'config';
23
24
    public function __construct(
25 6
        ScaffolderConfig $config,
26
        protected readonly FilesInterface $files,
27
        protected readonly SlugifyInterface $slugify,
28
        protected readonly ConfigDeclaration\TypeAnnotations $typeAnnotations,
29
        protected readonly ConfigDeclaration\TypeHints $typeHints,
30
        protected readonly ConfigDeclaration\Defaults $defaultValues,
31
        protected string $name,
32
        protected ?string $comment = null,
33
        private readonly string $directory = '',
34
        ?string $namespace = null
35
    ) {
36 6
        parent::__construct($config, $name, $comment, $namespace);
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 2
            $filename,
91 2
            $file->render() . PHP_EOL . (new Dumper())->dump(new Literal('return [];')),
92 2
            FilesInterface::READONLY,
93 2
            true
94 2
        );
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): void
108
    {
109 3
        $getters = [];
110 3
        $gettersByKey = [];
111 3
112
        foreach ($defaults as $key => $value) {
113 3
            $key = (string)$key;
114 3
            $getter = $this->makeGetterName($key);
115 3
            $getters[] = $getter;
116 3
117
            $method = $this->class->addMethod($getter)->setPublic();
118 3
            $method->setBody(\sprintf('return $this->config[\'%s\'];', $key));
119 3
            $method->setComment(\sprintf('@return %s', $this->typeAnnotations->getAnnotation($value)));
120 3
121
            if (\is_array($value)) {
122 3
                $gettersByKey[] = ['key' => $key, 'value' => $value];
123 2
            }
124
125
            $returnTypeHint = $this->typeHints->getHint(\gettype($value));
126 3
            if ($returnTypeHint !== null) {
127 3
                $method->setReturnType($returnTypeHint);
128 3
            }
129
        }
130
131
        foreach ($gettersByKey as $item) {
132 3
            $method = $this->declareGettersByKey($getters, $item['key'], $item['value']);
133 2
            if ($method !== null) {
134 2
                $getters[] = $method->getName();
135 2
            }
136
        }
137
    }
138
139 3
    private function declareGettersByKey(array $methodNames, string $key, array $value): ?Method
140
    {
141
        //Won't create if there's less than 2 sub-items
142 2
        if (\count($value) < 2) {
143
            return null;
144
        }
145 2
146 2
        $singularKey = $this->singularize($key);
147
        $name = $this->makeGetterName($singularKey);
148
        if (\in_array($name, $methodNames, true)) {
149 2
            $name = $this->makeGetterName($singularKey, 'get', 'by');
150 2
        }
151 2
152 2
        //Name conflict, won't merge
153
        if (\in_array($name, $methodNames, true)) {
154
            return null;
155
        }
156 2
157 2
        $keyType = defineArrayType(\array_keys($value), '-mixed-');
158
        $valueType = defineArrayType(\array_values($value), '-mixed-');
159
        //We need a fixed structure here
160 2
        if ($keyType === '-mixed-' || $valueType === '-mixed-') {
161 2
            return null;
162
        }
163 2
164 2
        //Won't create for associated arrays
165
        if ($this->typeAnnotations->mapType($keyType) === 'int' && \array_is_list($value)) {
166
            return null;
167
        }
168 2
169 2
        $method = $this->class->addMethod($name)->setPublic();
170
        $method->setBody(\sprintf('return $this->config[\'%s\'][$%s];', $key, $singularKey));
171
        $method->setReturnType($valueType);
172 2
        $method->setComment([
173 2
            \sprintf('@param %s %s', $this->typeAnnotations->mapType($keyType), $singularKey),
174 2
            \sprintf('@return %s', $this->typeAnnotations->getAnnotation(array_values($value)[0])),
175 2
        ]);
176 2
177 2
        $param = $method->addParameter($singularKey);
178 2
        $paramTypeHint = $this->typeHints->getHint($keyType);
179
        if ($paramTypeHint !== null) {
180 2
            $param->setType($paramTypeHint);
181 2
        }
182 2
183 2
        return $method;
184
    }
185
186 2
    private function makeGetterName(string $name, string $prefix = 'get', string $postfix = ''): string
187
    {
188
        $chunks = [];
189 3
        if (!empty($prefix)) {
190
            $chunks[] = $prefix;
191 3
        }
192 3
193 3
        $name = $this->slugify->slugify($name, ['lowercase' => false]);
194
        $chunks[] = \count($chunks) !== 0 ? $this->classify($name) : $name;
195
        if (!empty($postfix)) {
196 3
            $chunks[] = \ucfirst($postfix);
197 3
        }
198 3
199 2
        return \implode('', $chunks);
200
    }
201
202 3
    private function classify(string $name): string
203
    {
204
        return ( new InflectorFactory() )
205 3
            ->build()
206
            ->classify($name);
207 3
    }
208 3
209 3
    private function singularize(string $name): string
210
    {
211
        return ( new InflectorFactory() )
212 2
            ->build()
213
            ->singularize($name);
214 2
    }
215
}
216