Passed
Push — master ( 10d55f...e14a05 )
by Gabor
03:02
created

ServiceAdapter::getNormalizedName()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
eloc 4
nc 2
nop 2
dl 0
loc 8
ccs 5
cts 5
cp 1
crap 2
rs 9.4285
c 0
b 0
f 0
1
<?php
2
/**
3
 * WebHemi.
4
 *
5
 * PHP version 7.1
6
 *
7
 * @copyright 2012 - 2018 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
declare(strict_types = 1);
13
14
namespace WebHemi\DependencyInjection\ServiceAdapter\Symfony;
15
16
use Exception;
17
use Throwable;
18
use InvalidArgumentException;
19
use RuntimeException;
20
use Symfony\Component\DependencyInjection\ContainerBuilder;
21
use Symfony\Component\DependencyInjection\Definition;
22
use Symfony\Component\DependencyInjection\Reference;
23
use WebHemi\Configuration\ServiceInterface as ConfigurationInterface;
24
use WebHemi\DependencyInjection\ServiceInterface;
25
use WebHemi\DependencyInjection\ServiceAdapter\AbstractAdapter;
26
27
/**
28
 * Class ServiceAdapter.
29
 */
30
class ServiceAdapter extends AbstractAdapter
31
{
32
    /**
33
     * @var ContainerBuilder
34
     */
35
    private $container;
36
    /**
37
     * @var int
38
     */
39
    private static $parameterIndex = 0;
40
41
    /**
42
     * ServiceAdapter constructor.
43
     *
44
     * @param ConfigurationInterface $configuration
45
     */
46 16
    public function __construct(ConfigurationInterface $configuration)
47
    {
48 16
        parent::__construct($configuration);
49
50 16
        $this->container = new ContainerBuilder();
51 16
    }
52
53
    /**
54
     * Returns true if the given service is registered.
55
     *
56
     * @param  string $identifier
57
     * @return bool
58
     */
59 1
    public function has(string $identifier) : bool
60
    {
61 1
        return $this->container->has($identifier)
62 1
            || isset($this->serviceLibrary[$identifier])
63 1
            || class_exists($identifier);
64
    }
65
66
    /**
67
     * Gets a service.
68
     *
69
     * @param  null|string $identifier
70
     * @throws RuntimeException
71
     * @return null|object
72
     */
73 15
    public function get(? string $identifier)
74
    {
75 15
        if (is_null($identifier)) {
76 1
            return null;
77
        }
78
79
        // Not registered but valid class name, so register it
80 15
        if (!isset($this->serviceLibrary[$identifier]) && class_exists($identifier)) {
81 7
            $this->registerService($identifier);
82
        }
83
84
        // The service is registered in the library but not in the container, so register it into the container too.
85 15
        if (!$this->container->has($identifier)) {
86 15
            $this->registerServiceToContainer($identifier);
87
        }
88
89
        try {
90 15
            $service = $this->container->get($identifier);
91 14
            $this->serviceLibrary[$identifier][self::SERVICE_INITIALIZED] = true;
92 1
        } catch (Throwable $exception) {
93 1
            throw new RuntimeException(
94 1
                sprintf('There was an issue during creating the object: %s', $exception->getMessage()),
95 1
                1000,
96 1
                $exception
97
            );
98
        }
99
100 14
        return $service;
101
    }
102
103
    /**
104
     * Registers the service into the container.
105
     *
106
     * @param  string $identifier
107
     * @return ServiceAdapter
108
     */
109 15
    private function registerServiceToContainer(string $identifier) : ServiceAdapter
110
    {
111
        // At this point the service must be in the library
112 15
        if (!isset($this->serviceLibrary[$identifier])) {
113 2
            throw new InvalidArgumentException(
114 2
                sprintf('Invalid service name: %s', $identifier),
115 2
                1000
116
            );
117
        }
118
119
        // Create the definition.
120 15
        $definition = new Definition($this->serviceLibrary[$identifier][self::SERVICE_CLASS]);
121 15
        $definition->setShared($this->serviceLibrary[$identifier][self::SERVICE_SHARE]);
122
123
        // Register the service in the container.
124 15
        $service = $this->container->setDefinition($identifier, $definition);
125
126
        // Add arguments.
127 15
        $argumentList = $this->setArgumentListReferences($this->serviceLibrary[$identifier][self::SERVICE_ARGUMENTS]);
128 15
        foreach ($argumentList as $parameter) {
129
            // Create a normalized name for the argument.
130 14
            $serviceClass = $this->serviceLibrary[$identifier][self::SERVICE_CLASS];
131 14
            $normalizedName = $this->getNormalizedName($serviceClass, $parameter);
132 14
            $this->container->setParameter($normalizedName, $parameter);
133 14
            $service->addArgument('%'.$normalizedName.'%');
134
        }
135
136
        // Register method callings.
137 15
        foreach ($this->serviceLibrary[$identifier][self::SERVICE_METHOD_CALL] as $methodCallList) {
138 1
            $method = $methodCallList[0];
139 1
            $argumentList = $this->setArgumentListReferences($methodCallList[1] ?? []);
140 1
            $service->addMethodCall($method, $argumentList);
141
        }
142
143 15
        return $this;
144
    }
145
146
    /**
147
     * Tries to identify referce services in the argument list.
148
     *
149
     * @param  array $argumentList
150
     * @return array
151
     */
152 15
    private function setArgumentListReferences(array $argumentList) : array
153
    {
154 15
        foreach ($argumentList as $key => &$value) {
155
            // Associative array keys marks literal values
156 14
            if (!is_numeric($key)) {
157 8
                continue;
158
            }
159
160
            // Try to get the service. If exists (or can be registered), then it can be referenced too.
161
            try {
162 13
                $this->get($value);
163 13
                $value = new Reference($value);
164 1
            } catch (Exception $e) {
165
                // Not a valid service: no action, go on;
166 13
                continue;
167
            }
168
        }
169
170 15
        return $argumentList;
171
    }
172
173
    /**
174
     * Creates a safe normalized name.
175
     *
176
     * @param  string $className
177
     * @param  mixed  $parameter
178
     * @return string
179
     */
180 14
    private function getNormalizedName(string $className, $parameter) : string
181
    {
182 14
        $parameterName = !is_scalar($parameter) ? self::$parameterIndex++ : $parameter;
183
184 14
        $className = 'C_'.preg_replace('/[^a-z0-9]/', '', strtolower($className));
185 14
        $parameterName = 'A_'.preg_replace('/[^a-z0-9]/', '', strtolower((string) $parameterName));
186
187 14
        return $className.'.'.$parameterName;
188
    }
189
190
    /**
191
     * Register the service object instance.
192
     *
193
     * @param  string $identifier
194
     * @param  object $serviceInstance
195
     * @return ServiceInterface
196
     */
197 13
    public function registerServiceInstance(string $identifier, $serviceInstance) : ServiceInterface
198
    {
199
        // Check if the service is not initialized yet.
200 13
        if (!$this->serviceIsInitialized($identifier)) {
201 13
            $instanceType = gettype($serviceInstance);
202
203
            // Register synthetic services
204 13
            if ('object' !== $instanceType) {
205
                throw new InvalidArgumentException(
206
                    sprintf('The second parameter must be an object instance, %s given.', $instanceType),
207
                    1001
208
                );
209
            }
210
211 13
            $this->container->register($identifier)
212 13
                ->setShared(true)
213 13
                ->setSynthetic(true);
214
215 13
            $this->container->set($identifier, $serviceInstance);
216
217
            // Overwrite any previous settings.
218 13
            $this->serviceLibrary[$identifier] = [
219 13
                self::SERVICE_INITIALIZED => true,
220 13
                self::SERVICE_ARGUMENTS => [],
221 13
                self::SERVICE_METHOD_CALL => [],
222 13
                self::SERVICE_SHARE => true,
223 13
                self::SERVICE_CLASS => get_class($serviceInstance),
224
            ];
225
        }
226
227 13
        return $this;
228
    }
229
}
230