Passed
Pull Request — master (#856)
by Maxim
06:25
created

ConfigDeclaration::declareGetters()   A

Complexity

Conditions 6
Paths 15

Size

Total Lines 28
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 19
CRAP Score 6

Importance

Changes 0
Metric Value
eloc 18
dl 0
loc 28
ccs 19
cts 19
cp 1
rs 9.0444
c 0
b 0
f 0
cc 6
nc 15
nop 1
crap 6
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