Completed
Push — master ( 7e3127...a09c74 )
by Gabor
04:27
created

SymfonyAdapter::getNormalizedName()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

Changes 2
Bugs 1 Features 1
Metric Value
c 2
b 1
f 1
dl 0
loc 7
ccs 4
cts 4
cp 1
rs 9.4285
cc 1
eloc 4
nc 1
nop 2
crap 1
1
<?php
2
/**
3
 * WebHemi.
4
 *
5
 * PHP version 5.6
6
 *
7
 * @copyright 2012 - 2016 Gixx-web (http://www.gixx-web.com)
8
 * @license   https://opensource.org/licenses/MIT The MIT License (MIT)
9
 *
10
 * @link      http://www.gixx-web.com
11
 */
12
namespace WebHemi\Adapter\DependencyInjection\Symfony;
13
14
use Symfony\Component\DependencyInjection\ContainerBuilder;
15
use Symfony\Component\DependencyInjection\Definition;
16
use Symfony\Component\DependencyInjection\Reference;
17
use WebHemi\Adapter\DependencyInjection\DependencyInjectionAdapterInterface;
18
use WebHemi\Adapter\Exception\InitException;
19
use WebHemi\Config\ConfigInterface;
20
21
/**
22
 * Class SymfonyAdapter.
23
 */
24
class SymfonyAdapter implements DependencyInjectionAdapterInterface
25
{
26
    /** @var ContainerBuilder */
27
    private $container;
28
    /** @var array */
29
    private $configuration;
30
    /** @var array */
31
    private $servicesToDefine = [];
32
    /** @var array */
33
    private $instantiatedSharedServices = [];
34
    /** @var int */
35
    private static $parameterIndex = 0;
36
37
    /**
38
     * DependencyInjectionAdapterInterface constructor.
39
     *
40
     * @param ConfigInterface $configuration
41
     */
42 5
    public function __construct(ConfigInterface $configuration)
43
    {
44 5
        $this->container = new ContainerBuilder();
45 5
        $this->configuration = $configuration->toArray();
46
47 5
        $this->initContainer();
48 5
    }
49
50
    /**
51
     * Initializes the DI container from the config.
52
     */
53 5
    private function initContainer()
54
    {
55
        // Collect the name information about the services to be registered
56 5
        foreach ($this->configuration as $alias => $setupData) {
57 4
            if (isset($setupData[self::SERVICE_CLASS])) {
58 4
                $serviceClass = $setupData[self::SERVICE_CLASS];
59 4
            } else {
60 2
                $serviceClass = $alias;
61
            }
62
63 4
            $this->servicesToDefine[$alias] = $serviceClass;
64 5
        }
65
66 5
        foreach ($this->servicesToDefine as $alias => $serviceClass) {
67 4
            $this->registerService($alias, $serviceClass);
68 5
        }
69 5
    }
70
71
    /**
72
     * Register the service.
73
     *
74
     * @param string $identifier
75
     * @param string $serviceClass
76
     */
77 4
    public function registerService($identifier, $serviceClass)
78 1
    {
79
        // Do nothing if the service has been already registered with the same alias.
80
        // It is allowed to register the same service multiple times with different aliases.
81 4
        if ($this->has($identifier)) {
82 2
            return;
83
        }
84
85
        // Init settings.
86
        $setUpData = [
87 4
            self::SERVICE_CLASS       => $serviceClass,
88 4
            self::SERVICE_ARGUMENTS   => [],
89 4
            self::SERVICE_METHOD_CALL => [],
90
            // By default the Symfony DI shares all services. In WebHemi by default nothing is shared.
91 4
            self::SERVICE_SHARE       => false,
92 4
        ];
93
        // Override settings from the configuration if exists.
94 4
        if (isset($this->configuration[$identifier])) {
95 4
            $setUpData = array_merge($setUpData, $this->configuration[$identifier]);
96 4
        }
97
98
        // Create the definition.
99 4
        $definition = new Definition($serviceClass);
100
101 4
        $sharedService = (bool) $setUpData[self::SERVICE_SHARE];
102 4
        $definition->setShared($sharedService);
103
104
        // Register the service.
105 4
        $service = $this->container->setDefinition($identifier, $definition);
106 4
        if ($sharedService) {
107 3
            $this->instantiatedSharedServices[$service->getClass()] = false;
108 3
        }
109
110
        // Add arguments.
111 4
        foreach ((array) $setUpData[self::SERVICE_ARGUMENTS] as $parameter) {
112 2
            $this->setServiceArgument($service, $parameter);
113 4
        }
114
115
        // Register method callings.
116 4
        foreach ((array) $setUpData[self::SERVICE_METHOD_CALL] as $method => $parameterList) {
117 2
            $this->addMethodCall($service, $method, $parameterList);
118 4
        }
119 4
    }
120
121
    /**
122
     * Adds a method call for the service. It will be triggered as soon as the service had been initialized.
123
     *
124
     * @param Definition $service
125
     * @param string     $method
126
     * @param array      $parameterList
127
     */
128 2
    private function addMethodCall(Definition $service, $method, $parameterList = [])
129
    {
130
        // Check the parameter list for reference services
131 2
        foreach ($parameterList as &$parameter) {
132 2
            $parameter = $this->getReferenceServiceIfAvailable($parameter);
133 2
        }
134
135 2
        $service->addMethodCall($method, $parameterList);
136 2
    }
137
138
    /**
139
     * If possible create register the parameter as a service and give it back as a reference.
140
     *
141
     * @param mixed $classOrServiceName
142
     *
143
     * @return mixed|Reference
144
     */
145 3
    private function getReferenceServiceIfAvailable($classOrServiceName)
146
    {
147 3
        $reference = $classOrServiceName;
148
149
        // Check string parameter if it is a valid service or class name.
150 3
        if (!is_string($classOrServiceName)) {
151 2
            return $reference;
152
        }
153
154 3
        if (isset($this->servicesToDefine[$classOrServiceName])) {
155
            // The parameter is defined as a service but it is not yet registered; alias is given.
156 2
            $this->registerService($classOrServiceName, $this->servicesToDefine[$classOrServiceName]);
157 3
        } elseif (in_array($classOrServiceName, $this->servicesToDefine)) {
158
            // The parameter is defined as a service but it is not yet registered; real class is given.
159 1
            $referenceAlias = array_search($classOrServiceName, $this->servicesToDefine);
160 1
            $this->registerService($referenceAlias, $this->servicesToDefine[$referenceAlias]);
161 1
            $classOrServiceName = $referenceAlias;
162 3
        } elseif (class_exists($classOrServiceName)) {
163
            // The parameter is not a service, but it is a class that can be instantiated. e.g.: DateTime::class
164 1
            $this->container->register($classOrServiceName, $classOrServiceName);
165 1
        }
166
167 3
        if ($this->has($classOrServiceName)) {
168 2
            $reference = new Reference($classOrServiceName);
169 2
        }
170
171 3
        return $reference;
172
    }
173
174
    /**
175
     * Creates a safe normalized name.
176
     *
177
     * @param $className
178
     * @param $argumentName
179
     *
180
     * @return string
181
     */
182 3
    private function getNormalizedName($className, $argumentName)
183
    {
184 3
        $className = 'C_'.preg_replace('/[^a-z0-9]/', '', strtolower($className));
185 3
        $argumentName = 'A_'.preg_replace('/[^a-z0-9]/', '', strtolower($argumentName));
186
187 3
        return $className.'.'.$argumentName;
188
    }
189
190
    /**
191
     * Gets a service. It also tries to register the one without arguments which not yet registered.
192
     *
193
     * @param string $identifier
194
     *
195
     * @return object
196
     */
197 2
    public function get($identifier)
198
    {
199 2
        if (!$this->container->has($identifier) && class_exists($identifier)) {
200 1
            $this->registerService($identifier, $identifier);
201 1
        }
202
203 2
        $service = $this->container->get($identifier);
204 2
        $serviceClass = get_class($service);
205
206 2
        if (isset($this->instantiatedSharedServices[$serviceClass])) {
207 2
            $this->instantiatedSharedServices[$serviceClass] = true;
208 2
        }
209
210 2
        return $service;
211
    }
212
213
    /**
214
     * Returns true if the given service is defined.
215
     *
216
     * @param string $identifier
217
     *
218
     * @return bool
219
     */
220 4
    public function has($identifier)
221
    {
222 4
        return $this->container->has($identifier);
223
    }
224
225
    /**
226
     * Sets service argument.
227
     *
228
     * @param string|Definition $service
229
     * @param mixed             $parameter
230
     *
231
     * @throws InitException
232
     *
233
     * @return DependencyInjectionAdapterInterface
234
     */
235 3
    public function setServiceArgument($service, $parameter)
236
    {
237 3
        if (!$service instanceof Definition) {
238 1
            $service = $this->container->getDefinition($service);
239 1
        }
240
241 3
        $parameterName = $parameter;
242 3
        $serviceClass = $service->getClass();
243
244 3
        if (isset($this->instantiatedSharedServices[$serviceClass])
245 3
            && $this->instantiatedSharedServices[$serviceClass] === true
246 3
        ) {
247 1
            throw new InitException('Cannot add argument to an already initialized service.');
248
        }
249
250
251 3
        if (!is_scalar($parameterName)) {
252 1
            $parameterName = self::$parameterIndex++;
253 1
        }
254
255
        // Create a normalized name for the argument.
256 3
        $normalizedName = $this->getNormalizedName($serviceClass, $parameterName);
257
258
        // Check if the parameter is a service.
259 3
        $parameter = $this->getReferenceServiceIfAvailable($parameter);
260
261 3
        $this->container->setParameter($normalizedName, $parameter);
262 3
        $service->addArgument('%'.$normalizedName.'%');
263
264 3
        return $this;
265
    }
266
}
267