Passed
Push — master ( f7a2cc...fed4ad )
by Gabor
04:19
created

ServiceAdapter::setArgumentListReferences()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 13
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 3

Importance

Changes 0
Metric Value
cc 3
eloc 6
nc 3
nop 1
dl 0
loc 13
ccs 7
cts 7
cp 1
crap 3
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * WebHemi.
4
 *
5
 * PHP version 7.2
6
 *
7
 * @copyright 2012 - 2019 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 30
    public function __construct(ConfigurationInterface $configuration)
47
    {
48 30
        parent::__construct($configuration);
49
50 30
        $this->container = new ContainerBuilder();
51 30
    }
52
53
    /**
54
     * Returns true if the given service is registered.
55
     *
56
     * @param  string $identifier
57
     * @return bool
58
     */
59 2
    public function has(string $identifier) : bool
60
    {
61 2
        return $this->container->has($identifier)
62 2
            || isset($this->serviceLibrary[$identifier])
63 2
            || 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 28
    public function get(? string $identifier)
74
    {
75 28
        if (is_null($identifier)) {
76
            return null;
77
        }
78
79
        // Not registered but valid class name, so register it
80 28
        if (!isset($this->serviceLibrary[$identifier]) && class_exists($identifier)) {
81 12
            $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 28
        if (!$this->container->has($identifier)) {
86 28
            $this->registerServiceToContainer($identifier);
87
        }
88
89
        try {
90 28
            $service = $this->container->get($identifier);
91 26
            $this->serviceLibrary[$identifier][self::SERVICE_INITIALIZED] = true;
92 2
        } catch (Throwable $exception) {
93 2
            throw new RuntimeException(
94 2
                sprintf('There was an issue during creating the object: %s', $exception->getMessage()),
95 2
                1000,
96 2
                $exception
97
            );
98
        }
99
100 26
        return $service;
101
    }
102
103
    /**
104
     * Registers the service into the container.
105
     *
106
     * @param  string $identifier
107
     * @return ServiceAdapter
108
     */
109 28
    private function registerServiceToContainer(string $identifier) : ServiceAdapter
110
    {
111
        // At this point the service must be in the library
112 28
        if (!isset($this->serviceLibrary[$identifier])) {
113
            throw new InvalidArgumentException(
114
                sprintf('Invalid service name: %s', $identifier),
115
                1000
116
            );
117
        }
118
119
        // Create the definition.
120 28
        $definition = new Definition($this->serviceLibrary[$identifier][self::SERVICE_CLASS]);
121 28
        $definition->setShared($this->serviceLibrary[$identifier][self::SERVICE_SHARE]);
122
123
        // Register the service in the container.
124 28
        $service = $this->container->setDefinition($identifier, $definition);
125
126
        // Add arguments.
127 28
        $argumentList = $this->setArgumentListReferences($this->serviceLibrary[$identifier][self::SERVICE_ARGUMENTS]);
128 28
        foreach ($argumentList as $parameter) {
129
            // Create a normalized name for the argument.
130 26
            $serviceClass = $this->serviceLibrary[$identifier][self::SERVICE_CLASS];
131 26
            $normalizedName = $this->getNormalizedName($serviceClass, $parameter);
132 26
            $this->container->setParameter($normalizedName, $parameter);
133 26
            $service->addArgument('%'.$normalizedName.'%');
134
        }
135
136
        // Register method callings.
137 28
        foreach ($this->serviceLibrary[$identifier][self::SERVICE_METHOD_CALL] as $methodCallList) {
138
            $method = $methodCallList[0];
139
            $argumentList = $this->setArgumentListReferences($methodCallList[1] ?? []);
140
            $service->addMethodCall($method, $argumentList);
141
        }
142
143 28
        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 28
    private function setArgumentListReferences(array $argumentList) : array
153
    {
154 28
        foreach ($argumentList as $key => &$value) {
155
            // Associative array keys marks literal values
156 26
            if (!is_numeric($key)) {
157 14
                continue;
158
            }
159
160 24
            $this->get($value);
161 24
            $value = new Reference($value);
162
        }
163
164 28
        return $argumentList;
165
    }
166
167
    /**
168
     * Creates a safe normalized name.
169
     *
170
     * @param  string $className
171
     * @param  mixed  $parameter
172
     * @return string
173
     */
174 26
    private function getNormalizedName(string $className, $parameter) : string
175
    {
176 26
        $parameterName = !is_scalar($parameter) ? self::$parameterIndex++ : $parameter;
177
178 26
        $className = 'C_'.preg_replace('/[^a-z0-9]/', '', strtolower($className));
179 26
        $parameterName = 'A_'.preg_replace('/[^a-z0-9]/', '', strtolower((string) $parameterName));
180
181 26
        return $className.'.'.$parameterName;
182
    }
183
184
    /**
185
     * Register the service object instance.
186
     *
187
     * @param  string $identifier
188
     * @param  object $serviceInstance
189
     * @return ServiceInterface
190
     */
191 26
    public function registerServiceInstance(string $identifier, $serviceInstance) : ServiceInterface
192
    {
193
        // Check if the service is not initialized yet.
194 26
        if (!$this->serviceIsInitialized($identifier)) {
195 26
            $instanceType = gettype($serviceInstance);
196
197
            // Register synthetic services
198 26
            if ('object' !== $instanceType) {
199
                throw new InvalidArgumentException(
200
                    sprintf('The second parameter must be an object instance, %s given.', $instanceType),
201
                    1001
202
                );
203
            }
204
205 26
            $this->container->register($identifier)
206 26
                ->setShared(true)
207 26
                ->setSynthetic(true);
208
209 26
            $this->container->set($identifier, $serviceInstance);
210
211
            // Overwrite any previous settings.
212 26
            $this->serviceLibrary[$identifier] = [
213 26
                self::SERVICE_INITIALIZED => true,
214 26
                self::SERVICE_ARGUMENTS => [],
215 26
                self::SERVICE_METHOD_CALL => [],
216 26
                self::SERVICE_SHARE => true,
217 26
                self::SERVICE_CLASS => get_class($serviceInstance),
218
            ];
219
        }
220
221 26
        return $this;
222
    }
223
}
224