Completed
Push — master ( a7de57...5589b2 )
by Tobias
05:33
created

BazingaGeocoderExtension::loadProviders()   B

Complexity

Conditions 6
Paths 27

Size

Total Lines 33
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 18
CRAP Score 6.2163

Importance

Changes 0
Metric Value
dl 0
loc 33
ccs 18
cts 22
cp 0.8182
rs 8.439
c 0
b 0
f 0
cc 6
eloc 22
nc 27
nop 2
crap 6.2163
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of the BazingaGeocoderBundle package.
7
 * For the full copyright and license information, please view the LICENSE
8
 * file that was distributed with this source code.
9
 *
10
 * @license    MIT License
11
 */
12
13
namespace Bazinga\GeocoderBundle\DependencyInjection;
14
15
use Bazinga\GeocoderBundle\DataCollector\GeocoderDataCollector;
16
use Bazinga\GeocoderBundle\DependencyInjection\Compiler\FactoryValidatorPass;
17
use Bazinga\GeocoderBundle\Plugin\FakeIpPlugin;
18
use Bazinga\GeocoderBundle\Plugin\ProfilingPlugin;
19
use Bazinga\GeocoderBundle\ProviderFactory\PluginProviderFactory;
20
use Bazinga\GeocoderBundle\ProviderFactory\ProviderFactoryInterface;
21
use Geocoder\Plugin\Plugin\CachePlugin;
22
use Geocoder\Plugin\Plugin\LimitPlugin;
23
use Geocoder\Plugin\Plugin\LocalePlugin;
24
use Geocoder\Plugin\Plugin\LoggerPlugin;
25
use Geocoder\Plugin\PluginProvider;
26
use Symfony\Component\Config\Definition\Processor;
27
use Symfony\Component\Config\FileLocator;
28
use Symfony\Component\DependencyInjection\ContainerBuilder;
29
use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException;
30
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;
31
use Symfony\Component\DependencyInjection\Reference;
32
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
33
34
/**
35
 * William Durand <[email protected]>.
36
 */
37
class BazingaGeocoderExtension extends Extension
38
{
39 24
    public function load(array $configs, ContainerBuilder $container)
40
    {
41 24
        $processor = new Processor();
42 24
        $configuration = $this->getConfiguration($configs, $container);
43 24
        $config = $processor->processConfiguration($configuration, $configs);
0 ignored issues
show
Bug introduced by
It seems like $configuration defined by $this->getConfiguration($configs, $container) on line 42 can be null; however, Symfony\Component\Config...:processConfiguration() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
44
45 24
        $loader = new YamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
46 24
        $loader->load('services.yml');
47
48 24
        if (true === $config['profiling']['enabled']) {
49 1
            $loader->load('profiling.yml');
50
        }
51
52 24
        if ($config['fake_ip']['enabled']) {
53
            $definition = $container->getDefinition(FakeIpPlugin::class);
54
            $definition->replaceArgument(1, $config['fake_ip']['ip']);
55
        } else {
56 24
            $container->removeDefinition(FakeIpPlugin::class);
57
        }
58
59 24
        $this->loadProviders($container, $config);
60 24
    }
61
62 24
    private function loadProviders(ContainerBuilder $container, array $config)
63
    {
64 24
        foreach ($config['providers'] as $providerName => $providerConfig) {
65
            try {
66 23
                $factoryService = $container->getDefinition($providerConfig['factory']);
67 23
                $factoryClass = $factoryService->getClass() ?: $providerConfig['factory'];
68 23
                if (!$this->implementsPoviderFactory($factoryClass)) {
69
                    throw new \LogicException(sprintf('Provider factory "%s" must implement ProviderFactoryInterface', $providerConfig['factory']));
70
                }
71
                // See if any option has a service reference
72 23
                $providerConfig['options'] = $this->findReferences($providerConfig['options']);
73 23
                $factoryClass::validate($providerConfig['options'], $providerName);
74
            } catch (ServiceNotFoundException $e) {
75
                // Assert: We are using a custom factory. If invalid config, it will be caught in FactoryValidatorPass
76
                $providerConfig['options'] = $this->findReferences($providerConfig['options']);
77
                FactoryValidatorPass::addFactoryServiceId($providerConfig['factory']);
78
            }
79
80 23
            $serviceId = 'bazinga_geocoder.provider.'.$providerName;
81 23
            $plugins = $this->configureProviderPlugins($container, $providerConfig, $serviceId);
82
83 23
            $def = $container->register($serviceId, PluginProvider::class)
84 23
                ->setFactory([PluginProviderFactory::class, 'createPluginProvider'])
85 23
                ->addArgument($plugins)
86 23
                ->addArgument(new Reference($providerConfig['factory']))
87 23
                ->addArgument($providerConfig['options']);
88
89 23
            $def->addTag('bazinga_geocoder.provider');
90 23
            foreach ($providerConfig['aliases'] as $alias) {
91 23
                $container->setAlias($alias, $serviceId);
92
            }
93
        }
94 24
    }
95
96
    /**
97
     * Configure plugins for a client.
98
     *
99
     * @param ContainerBuilder $container
100
     * @param array            $config
101
     * @param string           $providerServiceId
102
     *
103
     * @return array
104
     */
105 23
    public function configureProviderPlugins(ContainerBuilder $container, array $config, string $providerServiceId): array
106
    {
107 23
        $plugins = [];
108 23
        foreach ($config['plugins'] as $plugin) {
109
            $plugins[] = $plugin['id'];
110
        }
111
112 23
        if (isset($config['cache']) || isset($config['cache_lifetime'])) {
113 1
            if (null === $cacheServiceId = $config['cache']) {
114
                if (!$container->has('app.cache')) {
115
                    throw new \LogicException('You need to specify a service for cache.');
116
                }
117
                $cacheServiceId = 'app.cache';
118
            }
119 1
            $plugins[] = $providerServiceId.'.cache';
120 1
            $container->register($providerServiceId.'.cache', CachePlugin::class)
121 1
                ->setPublic(false)
122 1
                ->setArguments([new Reference($cacheServiceId), (int) $config['cache_lifetime']]);
123
        }
124
125 23 View Code Duplication
        if (isset($config['limit'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
126
            $plugins[] = $providerServiceId.'.limit';
127
            $container->register($providerServiceId.'.limit', LimitPlugin::class)
128
                ->setPublic(false)
129
                ->setArguments([(int) $config['limit']]);
130
        }
131
132 23 View Code Duplication
        if (isset($config['locale'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
133
            $plugins[] = $providerServiceId.'.locale';
134
            $container->register($providerServiceId.'.locale', LocalePlugin::class)
135
                ->setPublic(false)
136
                ->setArguments([$config['locale']]);
137
        }
138
139 23 View Code Duplication
        if (isset($config['logger'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
140
            $plugins[] = $providerServiceId.'.logger';
141
            $container->register($providerServiceId.'.logger', LoggerPlugin::class)
142
                ->setPublic(false)
143
                ->setArguments([new Reference($config['logger'])]);
144
        }
145
146 23
        if ($container->has(FakeIpPlugin::class)) {
147
            $plugins[] = FakeIpPlugin::class;
148
        }
149
150 23
        if ($container->has(GeocoderDataCollector::class)) {
151
            $plugins[] = $providerServiceId.'.profiler';
152
            $container->register($providerServiceId.'.profiler', ProfilingPlugin::class)
153
                ->setPublic(false)
154
                ->setArguments([substr($providerServiceId, strlen('bazinga_geocoder.provider.'))])
155
                ->addTag('bazinga_geocoder.profiling_plugin');
156
        }
157
158 23
        return array_map(function (string $id) {
159 1
            return new Reference($id);
160 23
        }, $plugins);
161
    }
162
163
    /**
164
     * {@inheritdoc}
165
     */
166 24
    public function getConfiguration(array $config, ContainerBuilder $container)
167
    {
168 24
        return new Configuration($container->getParameter('kernel.debug'));
169
    }
170
171
    /**
172
     * @param array $options
173
     *
174
     * @return array
175
     */
176 23
    private function findReferences(array $options): array
177
    {
178 23
        foreach ($options as $key => $value) {
179 18
            if (is_array($value)) {
180 1
                $options[$key] = $this->findReferences($value);
181 18
            } elseif ('_service' === substr((string) $key, -8) || 0 === strpos((string) $value, '@') || 'service' === $key) {
182 18
                $options[$key] = new Reference(ltrim($value, '@'));
183
            }
184
        }
185
186 23
        return $options;
187
    }
188
189
    /**
190
     * @param mixed $factoryClass
191
     *
192
     * @return bool
193
     */
194 23
    private function implementsPoviderFactory($factoryClass): bool
195
    {
196 23
        if (false === $interfaces = class_implements($factoryClass)) {
197
            return false;
198
        }
199
200 23
        return in_array(ProviderFactoryInterface::class, $interfaces);
201
    }
202
}
203