Passed
Push — master ( a14261...5f8bab )
by Gabor
05:22
created

ServiceAdapter::get()   B

Complexity

Conditions 5
Paths 12

Size

Total Lines 25
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 5

Importance

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