Passed
Push — master ( 5bf459...a14261 )
by Gabor
27:12
created

ServiceAdapter::get()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 17
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 4

Importance

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