Passed
Pull Request — master (#856)
by Maxim
06:25
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 7
    public function __construct(
25
        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 7
        parent::__construct($config, $name, $comment, $namespace);
37
    }
38
39 7
    public function create(bool $reverse, string $configName): void
40
    {
41 7
        $this->class->addConstant('CONFIG', $configName)->setPublic();
42
43 7
        $filename = $this->makeConfigFilename($configName);
44 7
        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 4
            if (!$this->files->exists($filename)) {
55 2
                $this->touchConfigFile($filename);
56
            }
57
        }
58
    }
59
60
    /**
61
     * Declare constant and property.
62
     */
63 7
    public function declare(): void
64
    {
65 7
        $this->namespace->addUse(InjectableConfig::class);
66
67 7
        $this->class->setExtends(InjectableConfig::class);
68
69 7
        $this->class
70 7
            ->addProperty('config')
71 7
            ->setProtected()
72 7
            ->setType('array')
73 7
            ->setValue([])
74 7
            ->setComment('@internal For internal usage. Will be hydrated in the constructor.');
75
    }
76
77 7
    private function makeConfigFilename(string $filename): string
78
    {
79 7
        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 3
    private function declareGetters(array $defaults): void
105
    {
106 3
        $getters = [];
107 3
        $gettersByKey = [];
108
109 3
        foreach ($defaults as $key => $value) {
110 3
            $key = (string)$key;
111 3
            $getter = $this->makeGetterName($key);
112 3
            $getters[] = $getter;
113
114 3
            $method = $this->class->addMethod($getter)->setPublic();
115 3
            $method->setBody(\sprintf('return $this->config[\'%s\'];', $key));
116 3
            $method->setComment(\sprintf('@return %s', $this->typeAnnotations->getAnnotation($value)));
117
118 3
            if (\is_array($value)) {
119 2
                $gettersByKey[] = ['key' => $key, 'value' => $value];
120
            }
121
122 3
            $returnTypeHint = $this->typeHints->getHint(\gettype($value));
123 3
            if ($returnTypeHint !== null) {
124 3
                $method->setReturnType($returnTypeHint);
125
            }
126
        }
127
128 3
        foreach ($gettersByKey as $item) {
129 2
            $method = $this->declareGettersByKey($getters, $item['key'], $item['value']);
130 2
            if ($method !== null) {
131 2
                $getters[] = $method->getName();
132
            }
133
        }
134
    }
135
136 2
    private function declareGettersByKey(array $methodNames, string $key, array $value): ?Method
137
    {
138
        //Won't create if there's less than 2 sub-items
139 2
        if (\count($value) < 2) {
140 2
            return null;
141
        }
142
143 2
        $singularKey = $this->singularize($key);
144 2
        $name = $this->makeGetterName($singularKey);
145 2
        if (\in_array($name, $methodNames, true)) {
146 2
            $name = $this->makeGetterName($singularKey, 'get', 'by');
147
        }
148
149
        //Name conflict, won't merge
150 2
        if (\in_array($name, $methodNames, true)) {
151 2
            return null;
152
        }
153
154 2
        $keyType = defineArrayType(\array_keys($value), '-mixed-');
155 2
        $valueType = defineArrayType(\array_values($value), '-mixed-');
156
        //We need a fixed structure here
157 2
        if ($keyType === '-mixed-' || $valueType === '-mixed-') {
158 2
            return null;
159
        }
160
161
        //Won't create for associated arrays
162 2
        if ($this->typeAnnotations->mapType($keyType) === 'int' && \array_is_list($value)) {
163 2
            return null;
164
        }
165
166 2
        $method = $this->class->addMethod($name)->setPublic();
167 2
        $method->setBody(\sprintf('return $this->config[\'%s\'][$%s];', $key, $singularKey));
168 2
        $method->setReturnType($valueType);
169 2
        $method->setComment([
170 2
            \sprintf('@param %s %s', $this->typeAnnotations->mapType($keyType), $singularKey),
171 2
            \sprintf('@return %s', $this->typeAnnotations->getAnnotation(array_values($value)[0])),
172 2
        ]);
173
174 2
        $param = $method->addParameter($singularKey);
175 2
        $paramTypeHint = $this->typeHints->getHint($keyType);
176 2
        if ($paramTypeHint !== null) {
177 2
            $param->setType($paramTypeHint);
178
        }
179
180 2
        return $method;
181
    }
182
183 3
    private function makeGetterName(string $name, string $prefix = 'get', string $postfix = ''): string
184
    {
185 3
        $chunks = [];
186 3
        if (!empty($prefix)) {
187 3
            $chunks[] = $prefix;
188
        }
189
190 3
        $name = $this->slugify->slugify($name, ['lowercase' => false]);
191 3
        $chunks[] = \count($chunks) !== 0 ? $this->classify($name) : $name;
192 3
        if (!empty($postfix)) {
193 2
            $chunks[] = \ucfirst($postfix);
194
        }
195
196 3
        return \implode('', $chunks);
197
    }
198
199 3
    private function classify(string $name): string
200
    {
201 3
        return ( new InflectorFactory() )
202 3
            ->build()
203 3
            ->classify($name);
204
    }
205
206 2
    private function singularize(string $name): string
207
    {
208 2
        return ( new InflectorFactory() )
209 2
            ->build()
210 2
            ->singularize($name);
211
    }
212
}
213