Scrutinizer GitHub App not installed

We could not synchronize checks via GitHub's checks API since Scrutinizer's GitHub App is not installed for this repository.

Install GitHub App

Passed
Pull Request — master (#730)
by Mathieu
07:51 queued 25s
created

ConfigParserPass::detectFilesFromTypesMappings()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 10
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 1

Importance

Changes 0
Metric Value
eloc 6
c 0
b 0
f 0
dl 0
loc 10
ccs 7
cts 7
cp 1
rs 10
cc 1
nc 1
nop 2
crap 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Overblog\GraphQLBundle\DependencyInjection\Compiler;
6
7
use InvalidArgumentException;
8
use Overblog\GraphQLBundle\Config\Parser\AnnotationParser;
9
use Overblog\GraphQLBundle\Config\Parser\GraphQLParser;
10
use Overblog\GraphQLBundle\Config\Parser\PreParserInterface;
11
use Overblog\GraphQLBundle\Config\Parser\XmlParser;
12
use Overblog\GraphQLBundle\Config\Parser\YamlParser;
13
use Overblog\GraphQLBundle\DependencyInjection\TypesConfiguration;
14
use Overblog\GraphQLBundle\OverblogGraphQLBundle;
15
use ReflectionClass;
16
use ReflectionException;
17
use Symfony\Component\Config\Definition\Exception\ForbiddenOverwriteException;
18
use Symfony\Component\Config\Definition\Processor;
19
use Symfony\Component\Config\Resource\FileResource;
20
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
21
use Symfony\Component\DependencyInjection\ContainerBuilder;
22
use Symfony\Component\Finder\Finder;
23
use Symfony\Component\Finder\SplFileInfo;
24
use function array_count_values;
25
use function array_filter;
26
use function array_keys;
27
use function array_map;
28
use function array_merge;
29
use function array_replace_recursive;
30
use function call_user_func;
31
use function dirname;
32
use function implode;
33
use function is_a;
34
use function is_dir;
35
use function sprintf;
36
37
class ConfigParserPass implements CompilerPassInterface
38
{
39
    public const SUPPORTED_TYPES_EXTENSIONS = [
40
        'yaml' => '{yaml,yml}',
41
        'xml' => 'xml',
42
        'graphql' => '{graphql,graphqls}',
43
        'annotation' => 'php',
44
    ];
45
46
    public const PARSERS = [
47
        'yaml' => YamlParser::class,
48
        'xml' => XmlParser::class,
49
        'graphql' => GraphQLParser::class,
50
        'annotation' => AnnotationParser::class,
51
    ];
52
53
    private static array $defaultDefaultConfig = [
54
        'definitions' => [
55
            'mappings' => [
56
                'auto_discover' => [
57
                    'root_dir' => true,
58
                    'bundles' => true,
59
                ],
60
                'types' => [],
61
            ],
62
        ],
63
    ];
64
65
    private array $treatedFiles = [];
66
    private array $preTreatedFiles = [];
67
68
    public const DEFAULT_TYPES_SUFFIX = '.types';
69
70 39
    public function process(ContainerBuilder $container): void
71
    {
72 39
        $config = $this->processConfiguration([$this->getConfigs($container)]);
73 36
        $container->setParameter($this->getAlias().'.config', $config);
74 36
    }
75
76 47
    public function processConfiguration(array $configs): array
77
    {
78 47
        return (new Processor())->processConfiguration(new TypesConfiguration(), $configs);
79
    }
80
81 39
    private function getConfigs(ContainerBuilder $container): array
82
    {
83 39
        $config = $container->getParameterBag()->resolveValue($container->getParameter('overblog_graphql.config'));
84 39
        $container->getParameterBag()->remove('overblog_graphql.config');
85 39
        $container->setParameter($this->getAlias().'.classes_map', []);
86 39
        $typesMappings = $this->mappingConfig($config, $container);
87
        // reset treated files
88 39
        $this->treatedFiles = [];
89 39
        $typesMappings = array_merge(...$typesMappings);
90 39
        $typeConfigs = [];
91
92
        // treats mappings
93
        // Pre-parse all files
94 39
        AnnotationParser::reset();
95 39
        $typesNeedPreParsing = $this->typesNeedPreParsing();
96 39
        foreach ($typesMappings as $params) {
97 39
            if ($typesNeedPreParsing[$params['type']]) {
98 1
                $this->parseTypeConfigFiles($params['type'], $params['files'], $container, $config, true);
99
            }
100
        }
101
102
        // Parse all files and get related config
103 39
        foreach ($typesMappings as $params) {
104 39
            $typeConfigs = array_merge($typeConfigs, $this->parseTypeConfigFiles($params['type'], $params['files'], $container, $config));
105
        }
106
107 37
        $this->checkTypesDuplication($typeConfigs);
108
        // flatten config is a requirement to support inheritance
109 37
        $flattenTypeConfig = array_merge(...$typeConfigs);
110
111 37
        return $flattenTypeConfig;
112
    }
113
114 39
    private function typesNeedPreParsing(): array
115
    {
116 39
        $needPreParsing = [];
117 39
        foreach (self::PARSERS as $type => $className) {
118 39
            $needPreParsing[$type] = is_a($className, PreParserInterface::class, true);
119
        }
120
121 39
        return $needPreParsing;
122
    }
123
124
    /**
125
     * @param SplFileInfo[] $files
126
     */
127 39
    private function parseTypeConfigFiles(string $type, iterable $files, ContainerBuilder $container, array $configs, bool $preParse = false): array
128
    {
129 39
        if ($preParse) {
130 1
            $method = 'preParse';
131 1
            $treatedFiles = &$this->preTreatedFiles;
132
        } else {
133 39
            $method = 'parse';
134 39
            $treatedFiles = &$this->treatedFiles;
135
        }
136
137 39
        $config = [];
138 39
        foreach ($files as $file) {
139 39
            $fileRealPath = $file->getRealPath();
140 39
            if (isset($treatedFiles[$fileRealPath])) {
141 1
                continue;
142
            }
143
144 39
            $config[] = call_user_func([self::PARSERS[$type], $method], $file, $container, $configs);
145 37
            $treatedFiles[$file->getRealPath()] = true;
146
        }
147
148 37
        return $config;
149
    }
150
151 37
    private function checkTypesDuplication(array $typeConfigs): void
152
    {
153 37
        $types = array_merge(...array_map('array_keys', $typeConfigs));
154 37
        $duplications = array_keys(array_filter(array_count_values($types), function ($count) {
155 37
            return $count > 1;
156 37
        }));
157 37
        if (!empty($duplications)) {
158
            throw new ForbiddenOverwriteException(sprintf(
159
                'Types (%s) cannot be overwritten. See inheritance doc section for more details.',
160
                implode(', ', array_map('json_encode', $duplications))
161
            ));
162
        }
163 37
    }
164
165 39
    private function mappingConfig(array $config, ContainerBuilder $container): array
166
    {
167
        // use default value if needed
168 39
        $config = array_replace_recursive(self::$defaultDefaultConfig, $config);
169
170 39
        $mappingConfig = $config['definitions']['mappings'];
171 39
        $typesMappings = $mappingConfig['types'];
172
173
        // app only config files (yml or xml or graphql)
174 39
        if ($mappingConfig['auto_discover']['root_dir'] && $container->hasParameter('kernel.root_dir')) {
175 1
            $typesMappings[] = ['dir' => $container->getParameter('kernel.root_dir').'/config/graphql', 'types' => null];
176
        }
177 39
        if ($mappingConfig['auto_discover']['bundles']) {
178 4
            $mappingFromBundles = $this->mappingFromBundles($container);
179 4
            $typesMappings = array_merge($typesMappings, $mappingFromBundles);
180
        } else {
181
            // enabled only for this bundle
182 35
            $typesMappings[] = [
183 35
                'dir' => $this->bundleDir(OverblogGraphQLBundle::class).'/Resources/config/graphql',
184
                'types' => ['yaml'],
185
            ];
186
        }
187
188
        // from config
189 39
        $typesMappings = $this->detectFilesFromTypesMappings($typesMappings, $container);
190
191 39
        return $typesMappings;
192
    }
193
194 39
    private function detectFilesFromTypesMappings(array $typesMappings, ContainerBuilder $container): array
195
    {
196 39
        return array_filter(array_map(
197 39
            function (array $typeMapping) use ($container) {
198 39
                $suffix = $typeMapping['suffix'] ?? '';
199 39
                $types = $typeMapping['types'] ?? null;
200
201 39
                return $this->detectFilesByTypes($container, $typeMapping['dir'], $suffix, $types);
202
            },
203 39
            $typesMappings
204
        ));
205
    }
206
207 4
    private function mappingFromBundles(ContainerBuilder $container): array
208
    {
209 4
        $typesMappings = [];
210 4
        $bundles = $container->getParameter('kernel.bundles');
211
212
        // auto detect from bundle
213 4
        foreach ($bundles as $name => $class) {
214 1
            $bundleDir = $this->bundleDir($class);
215
216
            // only config files (yml or xml)
217 1
            $typesMappings[] = ['dir' => $bundleDir.'/Resources/config/graphql', 'types' => null];
218
        }
219
220 4
        return $typesMappings;
221
    }
222
223 39
    private function detectFilesByTypes(ContainerBuilder $container, string $path, string $suffix, array $types = null): array
224
    {
225
        // add the closest existing directory as a resource
226 39
        $resource = $path;
227 39
        while (!is_dir($resource)) {
228 1
            $resource = dirname($resource);
229
        }
230 39
        $container->addResource(new FileResource($resource));
231
232 39
        $stopOnFirstTypeMatching = empty($types);
233
234 39
        $types = $stopOnFirstTypeMatching ? array_keys(self::SUPPORTED_TYPES_EXTENSIONS) : $types;
235 39
        $files = [];
236
237 39
        foreach ($types as $type) {
238 39
            $finder = Finder::create();
239
            try {
240 39
                $finder->files()->in($path)->name(sprintf('*%s.%s', $suffix, self::SUPPORTED_TYPES_EXTENSIONS[$type]));
241 1
            } catch (InvalidArgumentException $e) {
242 1
                continue;
243
            }
244 39
            if ($finder->count() > 0) {
245 39
                $files[] = [
246 39
                    'type' => $type,
247 39
                    'files' => $finder,
248
                ];
249 39
                if ($stopOnFirstTypeMatching) {
250 1
                    break;
251
                }
252
            }
253
        }
254
255 39
        return $files;
256
    }
257
258
    /**
259
     * @throws ReflectionException
260
     */
261 36
    private function bundleDir(string $bundleClass): string
262
    {
263 36
        $bundle = new ReflectionClass($bundleClass); // @phpstan-ignore-line
264
265 36
        return dirname($bundle->getFileName());
266
    }
267
268 39
    private function getAliasPrefix(): string
269
    {
270 39
        return 'overblog_graphql';
271
    }
272
273 39
    private function getAlias(): string
274
    {
275 39
        return $this->getAliasPrefix().'_types';
276
    }
277
}
278