Test Failed
Push — master ( 539796...4ba9a1 )
by Gabor
04:03
created

ServiceAdapter::has()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 3

Importance

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