• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

overblog / GraphQLBundle / 21415119768

27 Jan 2026 09:35PM UTC coverage: 98.346% (+0.06%) from 98.283%
21415119768

Pull #1232

github

web-flow
Merge b724fd32b into 63c710b33
Pull Request #1232: Bump php version to at least 8.1

4520 of 4596 relevant lines covered (98.35%)

38.7 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

95.15
/src/DependencyInjection/Compiler/ConfigParserPass.php
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\AttributeParser;
10
use Overblog\GraphQLBundle\Config\Parser\GraphQLParser;
11
use Overblog\GraphQLBundle\Config\Parser\ParserInterface;
12
use Overblog\GraphQLBundle\Config\Parser\PreParserInterface;
13
use Overblog\GraphQLBundle\Config\Parser\YamlParser;
14
use Overblog\GraphQLBundle\DependencyInjection\TypesConfiguration;
15
use Overblog\GraphQLBundle\OverblogGraphQLBundle;
16
use ReflectionClass;
17
use ReflectionException;
18
use Symfony\Component\Config\Definition\Exception\ForbiddenOverwriteException;
19
use Symfony\Component\Config\Definition\Processor;
20
use Symfony\Component\Config\Resource\FileResource;
21
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
22
use Symfony\Component\DependencyInjection\ContainerBuilder;
23
use Symfony\Component\Finder\Finder;
24
use Symfony\Component\Finder\SplFileInfo;
25

26
use function array_count_values;
27
use function array_filter;
28
use function array_keys;
29
use function array_map;
30
use function array_merge;
31
use function array_replace_recursive;
32
use function dirname;
33
use function implode;
34
use function is_a;
35
use function is_dir;
36
use function sprintf;
37

38
final class ConfigParserPass implements CompilerPassInterface
39
{
40
    /**
41
     * @var array<string, string>
42
     */
43
    public const SUPPORTED_TYPES_EXTENSIONS = [
44
        'yaml' => '{yaml,yml}',
45
        'graphql' => '{graphql,graphqls}',
46
        'annotation' => 'php',
47
        'attribute' => 'php',
48
    ];
49

50
    /**
51
     * @var array<string, class-string<ParserInterface>>
52
     */
53
    public const PARSERS = [
54
        'yaml' => YamlParser::class,
55
        'graphql' => GraphQLParser::class,
56
        'annotation' => AnnotationParser::class,
57
        'attribute' => AttributeParser::class,
58
    ];
59

60
    private static array $defaultDefaultConfig = [
61
        'definitions' => [
62
            'mappings' => [
63
                'auto_discover' => [
64
                    'root_dir' => true,
65
                    'bundles' => true,
66
                    'built_in' => true,
67
                ],
68
                'types' => [],
69
            ],
70
        ],
71
    ];
72

73
    private array $treatedFiles = [];
74
    private array $preTreatedFiles = [];
75

76
    public const DEFAULT_TYPES_SUFFIX = '.types';
77

78
    public function process(ContainerBuilder $container): void
79
    {
80
        $config = $this->processConfiguration([$this->getConfigs($container)]);
45✔
81
        $container->setParameter($this->getAlias().'.config', $config);
43✔
82
    }
83

84
    public function processConfiguration(array $configs): array
85
    {
86
        return (new Processor())->processConfiguration(new TypesConfiguration(), $configs);
54✔
87
    }
88

89
    private function getConfigs(ContainerBuilder $container): array
90
    {
91
        $config = $container->getParameterBag()->resolveValue($container->getParameter('overblog_graphql.config'));
45✔
92
        $container->getParameterBag()->remove('overblog_graphql.config');
45✔
93
        $container->setParameter($this->getAlias().'.classes_map', []);
45✔
94
        $container->setParameter($this->getAlias().'.interfaces_map', []);
45✔
95

96
        $typesMappings = $this->mappingConfig($config, $container);
45✔
97
        // reset treated files
98
        $this->treatedFiles = [];
45✔
99
        $typesMappings = array_merge(...$typesMappings);
45✔
100
        $typeConfigs = [];
45✔
101

102
        // treats mappings
103
        // Pre-parse all files
104
        AnnotationParser::reset($config);
45✔
105
        AttributeParser::reset($config);
45✔
106
        $typesNeedPreParsing = $this->typesNeedPreParsing();
45✔
107
        foreach ($typesMappings as $params) {
45✔
108
            if ($typesNeedPreParsing[$params['type']]) {
44✔
109
                $this->parseTypeConfigFiles($params['type'], $params['files'], $container, $config, true);
2✔
110
            }
111
        }
112

113
        // Parse all files and get related config
114
        foreach ($typesMappings as $params) {
45✔
115
            $typeConfigs = array_merge($typeConfigs, $this->parseTypeConfigFiles($params['type'], $params['files'], $container, $config));
44✔
116
        }
117

118
        $this->checkTypesDuplication($typeConfigs);
44✔
119
        // flatten config is a requirement to support inheritance
120
        $flattenTypeConfig = array_merge(...$typeConfigs);
44✔
121

122
        AnnotationParser::finalize($container);
44✔
123
        AttributeParser::finalize($container);
44✔
124

125
        return $flattenTypeConfig;
44✔
126
    }
127

128
    private function typesNeedPreParsing(): array
129
    {
130
        $needPreParsing = [];
45✔
131
        foreach (self::PARSERS as $type => $className) {
45✔
132
            $needPreParsing[$type] = is_a($className, PreParserInterface::class, true);
45✔
133
        }
134

135
        return $needPreParsing;
45✔
136
    }
137

138
    /**
139
     * @param SplFileInfo[] $files
140
     */
141
    private function parseTypeConfigFiles(string $type, iterable $files, ContainerBuilder $container, array $configs, bool $preParse = false): array
142
    {
143
        if ($preParse) {
44✔
144
            $method = 'preParse';
2✔
145
            $treatedFiles = &$this->preTreatedFiles;
2✔
146
        } else {
147
            $method = 'parse';
44✔
148
            $treatedFiles = &$this->treatedFiles;
44✔
149
        }
150

151
        $config = [];
44✔
152
        foreach ($files as $file) {
44✔
153
            $fileRealPath = $file->getRealPath();
44✔
154
            if (isset($treatedFiles[$fileRealPath])) {
44✔
155
                continue;
1✔
156
            }
157

158
            $config[] = [self::PARSERS[$type], $method]($file, $container, $configs);
44✔
159
            $treatedFiles[$file->getRealPath()] = true;
43✔
160
        }
161

162
        return $config;
43✔
163
    }
164

165
    private function checkTypesDuplication(array $typeConfigs): void
166
    {
167
        $types = array_merge(...array_map('array_keys', $typeConfigs));
44✔
168
        $duplications = array_keys(array_filter(array_count_values($types), fn ($count) => $count > 1));
44✔
169
        if (!empty($duplications)) {
44✔
170
            throw new ForbiddenOverwriteException(sprintf(
×
171
                'Types (%s) cannot be overwritten. See inheritance doc section for more details.',
×
172
                implode(', ', array_map('json_encode', $duplications))
×
173
            ));
×
174
        }
175
    }
176

177
    private function mappingConfig(array $config, ContainerBuilder $container): array
178
    {
179
        // use default value if needed
180
        $config = array_replace_recursive(self::$defaultDefaultConfig, $config);
45✔
181

182
        $mappingConfig = $config['definitions']['mappings'];
45✔
183
        $typesMappings = $mappingConfig['types'];
45✔
184

185
        // app only config files (yml or xml or graphql)
186
        if ($mappingConfig['auto_discover']['root_dir'] && $container->hasParameter('kernel.root_dir')) {
45✔
187
            // @phpstan-ignore-next-line
188
            $typesMappings[] = ['dir' => $container->getParameter('kernel.root_dir').'/config/graphql', 'types' => null];
×
189
        }
190
        if ($mappingConfig['auto_discover']['bundles']) {
45✔
191
            $mappingFromBundles = $this->mappingFromBundles($container);
3✔
192
            $typesMappings = array_merge($typesMappings, $mappingFromBundles);
3✔
193
        }
194
        if ($mappingConfig['auto_discover']['built_in']) {
45✔
195
            $typesMappings[] = [
44✔
196
                'dir' => $this->bundleDir(OverblogGraphQLBundle::class).'/Resources/config/graphql',
44✔
197
                'types' => ['yaml'],
44✔
198
            ];
44✔
199
        }
200

201
        // from config
202
        $typesMappings = $this->detectFilesFromTypesMappings($typesMappings, $container);
45✔
203

204
        return $typesMappings;
45✔
205
    }
206

207
    private function detectFilesFromTypesMappings(array $typesMappings, ContainerBuilder $container): array
208
    {
209
        return array_filter(array_map(
45✔
210
            function (array $typeMapping) use ($container) {
45✔
211
                $suffix = $typeMapping['suffix'] ?? '';
44✔
212
                $types = $typeMapping['types'] ?? null;
44✔
213

214
                return $this->detectFilesByTypes($container, $typeMapping['dir'], $suffix, $types);
44✔
215
            },
45✔
216
            $typesMappings
45✔
217
        ));
45✔
218
    }
219

220
    private function mappingFromBundles(ContainerBuilder $container): array
221
    {
222
        $typesMappings = [];
3✔
223

224
        /** @var array<string, class-string> $bundles */
225
        $bundles = $container->getParameter('kernel.bundles');
3✔
226

227
        // auto detect from bundle
228
        foreach ($bundles as $class) {
3✔
229
            // skip this bundle
230
            if (OverblogGraphQLBundle::class === $class) {
1✔
231
                continue;
1✔
232
            }
233

234
            $bundleDir = $this->bundleDir($class);
1✔
235

236
            // only config files (yml)
237
            $typesMappings[] = ['dir' => $bundleDir.'/Resources/config/graphql', 'types' => null];
1✔
238
        }
239

240
        return $typesMappings;
3✔
241
    }
242

243
    private function detectFilesByTypes(ContainerBuilder $container, string $path, string $suffix, ?array $types = null): array
244
    {
245
        // add the closest existing directory as a resource
246
        $resource = $path;
44✔
247
        while (!is_dir($resource)) {
44✔
248
            $resource = dirname($resource);
1✔
249
        }
250
        $container->addResource(new FileResource($resource));
44✔
251

252
        $stopOnFirstTypeMatching = empty($types);
44✔
253

254
        $types = $stopOnFirstTypeMatching ? array_keys(self::SUPPORTED_TYPES_EXTENSIONS) : $types;
44✔
255
        $files = [];
44✔
256

257
        foreach ($types as $type) {
44✔
258
            $finder = Finder::create();
44✔
259
            try {
260
                $finder->files()->in($path)->name(sprintf('*%s.%s', $suffix, self::SUPPORTED_TYPES_EXTENSIONS[$type]));
44✔
261
            } catch (InvalidArgumentException $e) {
1✔
262
                continue;
1✔
263
            }
264
            if ($finder->count() > 0) {
44✔
265
                $files[] = [
44✔
266
                    'type' => $type,
44✔
267
                    'files' => $finder,
44✔
268
                ];
44✔
269
                if ($stopOnFirstTypeMatching) {
44✔
270
                    break;
1✔
271
                }
272
            }
273
        }
274

275
        return $files;
44✔
276
    }
277

278
    /**
279
     * @throws ReflectionException
280
     */
281
    private function bundleDir(string $bundleClass): string
282
    {
283
        $bundle = new ReflectionClass($bundleClass); // @phpstan-ignore-line
44✔
284

285
        return dirname($bundle->getFileName());
44✔
286
    }
287

288
    private function getAliasPrefix(): string
289
    {
290
        return 'overblog_graphql';
45✔
291
    }
292

293
    private function getAlias(): string
294
    {
295
        return $this->getAliasPrefix().'_types';
45✔
296
    }
297
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc