1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace Overblog\GraphQLBundle\DependencyInjection; |
4
|
|
|
|
5
|
|
|
use Overblog\GraphQLBundle\Config\Parser\GraphQLParser; |
6
|
|
|
use Overblog\GraphQLBundle\Config\Parser\XmlParser; |
7
|
|
|
use Overblog\GraphQLBundle\Config\Parser\YamlParser; |
8
|
|
|
use Overblog\GraphQLBundle\OverblogGraphQLBundle; |
9
|
|
|
use Symfony\Component\Config\Definition\Exception\ForbiddenOverwriteException; |
10
|
|
|
use Symfony\Component\Config\Resource\FileResource; |
11
|
|
|
use Symfony\Component\DependencyInjection\ContainerBuilder; |
12
|
|
|
use Symfony\Component\Finder\Finder; |
13
|
|
|
use Symfony\Component\Finder\SplFileInfo; |
14
|
|
|
use Symfony\Component\HttpKernel\DependencyInjection\Extension; |
15
|
|
|
|
16
|
|
|
class OverblogGraphQLTypesExtension extends Extension |
17
|
|
|
{ |
18
|
|
|
const SUPPORTED_TYPES_EXTENSIONS = ['yaml' => '{yaml,yml}', 'xml' => 'xml', 'graphql' => '{graphql,graphqls}']; |
19
|
|
|
|
20
|
|
|
const PARSERS = [ |
21
|
|
|
'yaml' => YamlParser::class, |
22
|
|
|
'xml' => XmlParser::class, |
23
|
|
|
'graphql' => GraphQLParser::class, |
24
|
|
|
]; |
25
|
|
|
|
26
|
|
|
private static $defaultDefaultConfig = [ |
27
|
|
|
'definitions' => [ |
28
|
|
|
'mappings' => [ |
29
|
|
|
'auto_discover' => [ |
30
|
|
|
'root_dir' => true, |
31
|
|
|
'bundles' => true, |
32
|
|
|
], |
33
|
|
|
'types' => [], |
34
|
|
|
], |
35
|
|
|
], |
36
|
|
|
]; |
37
|
|
|
|
38
|
|
|
private $treatedFiles = []; |
39
|
|
|
|
40
|
|
|
const DEFAULT_TYPES_SUFFIX = '.types'; |
41
|
|
|
|
42
|
35 |
|
public function load(array $configs, ContainerBuilder $container) |
43
|
|
|
{ |
44
|
35 |
|
$configs = array_filter($configs); |
45
|
|
|
//$configs = array_filter($configs); |
|
|
|
|
46
|
34 |
|
if (count($configs) > 1) { |
47
|
34 |
|
throw new \InvalidArgumentException('Configs type should never contain more than one config to deal with inheritance.'); |
48
|
34 |
|
} |
49
|
|
|
$configuration = $this->getConfiguration($configs, $container); |
50
|
29 |
|
$config = $this->processConfiguration($configuration, $configs); |
|
|
|
|
51
|
29 |
|
|
52
|
|
|
$container->setParameter($this->getAlias().'.config', $config); |
53
|
30 |
|
} |
54
|
|
|
|
55
|
30 |
|
public function containerPrependExtensionConfig(array $configs, ContainerBuilder $container) |
56
|
|
|
{ |
57
|
30 |
|
$typesMappings = $this->mappingConfig($configs, $container); |
58
|
30 |
|
// reset treated files |
59
|
|
|
$this->treatedFiles = []; |
60
|
30 |
|
$typesMappings = call_user_func_array('array_merge', $typesMappings); |
61
|
30 |
|
$typeConfigs = []; |
62
|
|
|
// treats mappings |
63
|
28 |
|
foreach ($typesMappings as $params) { |
64
|
|
|
$typeConfigs = array_merge($typeConfigs, $this->parseTypeConfigFiles($params['type'], $params['files'], $container)); |
65
|
|
|
} |
66
|
|
|
|
67
|
|
|
$this->checkTypesDuplication($typeConfigs); |
68
|
|
|
// flatten config is a requirement to support inheritance |
69
|
|
|
$flattenTypeConfig = call_user_func_array('array_merge', $typeConfigs); |
70
|
30 |
|
|
71
|
|
|
$container->prependExtensionConfig($this->getAlias(), $flattenTypeConfig); |
72
|
30 |
|
} |
73
|
30 |
|
|
74
|
30 |
|
/** |
75
|
1 |
|
* @param $type |
76
|
|
|
* @param SplFileInfo[] $files |
77
|
|
|
* @param ContainerBuilder $container |
78
|
30 |
|
* |
79
|
28 |
|
* @return array |
80
|
28 |
|
*/ |
81
|
|
|
private function parseTypeConfigFiles($type, $files, ContainerBuilder $container) |
82
|
28 |
|
{ |
83
|
|
|
$config = []; |
84
|
35 |
|
foreach ($files as $file) { |
85
|
|
|
$fileRealPath = $file->getRealPath(); |
86
|
35 |
|
if (isset($this->treatedFiles[$fileRealPath])) { |
87
|
35 |
|
continue; |
88
|
35 |
|
} |
89
|
35 |
|
|
90
|
35 |
|
$config[] = call_user_func(self::PARSERS[$type].'::parse', $file, $container); |
91
|
1 |
|
$this->treatedFiles[$file->getRealPath()] = true; |
92
|
1 |
|
} |
93
|
1 |
|
|
94
|
|
|
return $config; |
95
|
|
|
} |
96
|
34 |
|
|
97
|
|
|
private function checkTypesDuplication(array $typeConfigs) |
98
|
30 |
|
{ |
99
|
|
|
$types = call_user_func_array('array_merge', array_map('array_keys', $typeConfigs)); |
100
|
|
|
$duplications = array_keys(array_filter(array_count_values($types), function ($count) { |
101
|
30 |
|
return $count > 1; |
102
|
|
|
})); |
103
|
30 |
|
if (!empty($duplications)) { |
104
|
30 |
|
throw new ForbiddenOverwriteException(sprintf( |
105
|
|
|
'Types (%s) cannot be overwritten. See inheritance doc section for more details.', |
106
|
|
|
implode(', ', array_map('json_encode', $duplications)) |
107
|
30 |
|
)); |
108
|
1 |
|
} |
109
|
|
|
} |
110
|
30 |
|
|
111
|
3 |
|
private function mappingConfig(array $config, ContainerBuilder $container) |
112
|
3 |
|
{ |
113
|
|
|
// use default value if needed |
114
|
|
|
$config = array_replace_recursive(self::$defaultDefaultConfig, $config); |
115
|
27 |
|
|
116
|
27 |
|
$mappingConfig = $config['definitions']['mappings']; |
117
|
|
|
$typesMappings = $mappingConfig['types']; |
118
|
|
|
|
119
|
|
|
// app only config files (yml or xml or graphql) |
120
|
|
|
if ($mappingConfig['auto_discover']['root_dir'] && $container->hasParameter('kernel.root_dir')) { |
121
|
|
|
$typesMappings[] = ['dir' => $container->getParameter('kernel.root_dir').'/config/graphql', 'types' => null]; |
122
|
30 |
|
} |
123
|
|
|
if ($mappingConfig['auto_discover']['bundles']) { |
124
|
30 |
|
$mappingFromBundles = $this->mappingFromBundles($container); |
125
|
|
|
$typesMappings = array_merge($typesMappings, $mappingFromBundles); |
126
|
|
|
} else { |
127
|
30 |
|
// enabled only for this bundle |
128
|
|
|
$typesMappings[] = [ |
129
|
30 |
|
'dir' => $this->bundleDir(OverblogGraphQLBundle::class).'/Resources/config/graphql', |
130
|
30 |
|
'types' => ['yaml'], |
131
|
30 |
|
]; |
132
|
30 |
|
} |
133
|
30 |
|
|
134
|
|
|
// from config |
135
|
30 |
|
$typesMappings = $this->detectFilesFromTypesMappings($typesMappings, $container); |
136
|
30 |
|
|
137
|
30 |
|
return $typesMappings; |
138
|
|
|
} |
139
|
|
|
|
140
|
|
|
private function detectFilesFromTypesMappings(array $typesMappings, ContainerBuilder $container) |
141
|
3 |
|
{ |
142
|
|
|
return array_filter(array_map( |
143
|
3 |
|
function (array $typeMapping) use ($container) { |
144
|
3 |
|
$suffix = isset($typeMapping['suffix']) ? $typeMapping['suffix'] : ''; |
145
|
|
|
$types = isset($typeMapping['types']) ? $typeMapping['types'] : null; |
146
|
|
|
$params = $this->detectFilesByTypes($container, $typeMapping['dir'], $suffix, $types); |
147
|
3 |
|
|
148
|
1 |
|
return $params; |
149
|
|
|
}, |
150
|
|
|
$typesMappings |
151
|
1 |
|
)); |
152
|
|
|
} |
153
|
|
|
|
154
|
3 |
|
private function mappingFromBundles(ContainerBuilder $container) |
155
|
|
|
{ |
156
|
|
|
$typesMappings = []; |
157
|
30 |
|
$bundles = $container->getParameter('kernel.bundles'); |
158
|
|
|
|
159
|
|
|
// auto detect from bundle |
160
|
30 |
|
foreach ($bundles as $name => $class) { |
161
|
30 |
|
$bundleDir = $this->bundleDir($class); |
162
|
1 |
|
|
163
|
|
|
// only config files (yml or xml) |
164
|
30 |
|
$typesMappings[] = ['dir' => $bundleDir.'/Resources/config/graphql', 'types' => null]; |
165
|
|
|
} |
166
|
30 |
|
|
167
|
|
|
return $typesMappings; |
168
|
30 |
|
} |
169
|
30 |
|
|
170
|
|
|
private function detectFilesByTypes(ContainerBuilder $container, $path, $suffix, array $types = null) |
171
|
30 |
|
{ |
172
|
30 |
|
// add the closest existing directory as a resource |
173
|
|
|
$resource = $path; |
174
|
30 |
|
while (!is_dir($resource)) { |
175
|
1 |
|
$resource = dirname($resource); |
176
|
1 |
|
} |
177
|
|
|
$container->addResource(new FileResource($resource)); |
178
|
30 |
|
|
179
|
30 |
|
$stopOnFirstTypeMatching = empty($types); |
180
|
30 |
|
|
181
|
30 |
|
$types = $stopOnFirstTypeMatching ? array_keys(self::SUPPORTED_TYPES_EXTENSIONS) : $types; |
182
|
|
|
$files = []; |
183
|
30 |
|
|
184
|
30 |
|
foreach ($types as $type) { |
185
|
|
|
$finder = Finder::create(); |
186
|
|
|
try { |
187
|
|
|
$finder->files()->in($path)->name(sprintf('*%s.%s', $suffix, self::SUPPORTED_TYPES_EXTENSIONS[$type])); |
188
|
|
|
} catch (\InvalidArgumentException $e) { |
189
|
30 |
|
continue; |
190
|
|
|
} |
191
|
|
|
if ($finder->count() > 0) { |
192
|
28 |
|
$files[] = [ |
193
|
|
|
'type' => $type, |
194
|
28 |
|
'files' => $finder, |
195
|
28 |
|
]; |
196
|
|
|
if ($stopOnFirstTypeMatching) { |
197
|
28 |
|
break; |
198
|
|
|
} |
199
|
|
|
} |
200
|
29 |
|
} |
201
|
|
|
|
202
|
29 |
|
return $files; |
203
|
|
|
} |
204
|
|
|
|
205
|
29 |
|
private function bundleDir($bundleClass) |
206
|
|
|
{ |
207
|
29 |
|
$bundle = new \ReflectionClass($bundleClass); |
208
|
|
|
$bundleDir = dirname($bundle->getFileName()); |
209
|
|
|
|
210
|
34 |
|
return $bundleDir; |
211
|
|
|
} |
212
|
34 |
|
|
213
|
|
|
public function getAliasPrefix() |
214
|
|
|
{ |
215
|
|
|
return 'overblog_graphql'; |
216
|
|
|
} |
217
|
|
|
|
218
|
|
|
public function getAlias() |
219
|
|
|
{ |
220
|
|
|
return $this->getAliasPrefix().'_types'; |
221
|
|
|
} |
222
|
|
|
|
223
|
|
|
public function getConfiguration(array $config, ContainerBuilder $container) |
224
|
|
|
{ |
225
|
|
|
return new TypesConfiguration(); |
226
|
|
|
} |
227
|
|
|
} |
228
|
|
|
|
Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.
The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.
This check looks for comments that seem to be mostly valid code and reports them.