Completed
Pull Request — master (#6)
by Woody
01:15
created

ServiceConfig::apply()   D

Complexity

Conditions 8
Paths 128

Size

Total Lines 39
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 23
CRAP Score 8

Importance

Changes 0
Metric Value
dl 0
loc 39
ccs 23
cts 23
cp 1
rs 4.6666
c 0
b 0
f 0
cc 8
eloc 15
nc 128
nop 1
crap 8
1
<?php
2
3
namespace Northwoods\Container\Config;
4
5
use Auryn\Injector;
6
use Northwoods\Container\InjectorConfig;
7
use Psr\Container\ContainerInterface;
8
9
class ServiceConfig implements InjectorConfig
10
{
11
    /**
12
     * @var array
13
     */
14
    private $config;
15
16
    /**
17
     * @var bool
18
     */
19
    private $sharedByDefault = true;
20
21
    /**
22
     * @var array
23
     */
24
    private $shared = [];
25
26 4
    public function __construct(array $config)
27
    {
28 4
        $this->config = $config;
29 4
    }
30
31
    /**
32
     * @param Injector $injector
33
     * @return void
34
     */
35 4
    public function apply(Injector $injector)
36
    {
37
        // Enable or disable sharing all services by default.
38 4
        if (isset($this->config['shared_by_default'])) {
39 2
            $this->sharedByDefault = (bool) $this->config['shared_by_default'];
40 2
        }
41
42
        // Overload specific services to be shared.
43 4
        if (isset($this->config['shared'])) {
44 2
            $this->shared = $this->config['shared'];
45 2
        }
46
47
        // Aliases are exactly the same as aliases. Natch.
48 4
        if (isset($this->config['aliases'])) {
49 4
            $this->applyAliases($injector, $this->config['aliases']);
50 4
        }
51
52
        // Delegators are effectively a chain of prepare() statements.
53 4
        if (isset($this->config['delegators'])) {
54 4
            $this->applyDelegators($injector, $this->config['delegators']);
55 4
        }
56
57
        // Factories are exactly the same thing as a delegate.
58 4
        if (isset($this->config['factories'])) {
59 4
            $this->applyDelegates($injector, $this->config['factories']);
60 4
        }
61
62
        // Invokables are references to classes that have no constructor parameters.
63
        // This means nothing in Auryn so we just alias the reference.
64 4
        if (isset($this->config['invokables'])) {
65 4
            $this->applyAliases($injector, $this->config['invokables']);
66 4
        }
67
68
        // Services are already constructed instances of something. To handle this,
69
        // we simply wrap the instance in a callable that returns the instance.
70 4
        if (isset($this->config['services'])) {
71 4
            $this->applyDelegates($injector, $this->kAll($this->config['services']));
72 4
        }
73 4
    }
74
75
    /**
76
     * @param string $name
77
     * @return bool
78
     */
79 4
    private function isShared($name)
80
    {
81 4
        if (isset($this->shared[$name])) {
82 2
            return (bool) $this->shared[$name];
83
        }
84
85 4
        return $this->sharedByDefault;
86
    }
87
88
    /**
89
     * @param array $services
90
     * @return void
91
     */
92 4 View Code Duplication
    private function applyAliases(Injector $injector, array $services)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
93
    {
94 4
        foreach ($services as $name => $object) {
95 4
            $injector->alias($name, $object);
96 4
            if ($this->isShared($name)) {
97 2
                $injector->share($name);
98 2
            }
99 4
        }
100 4
    }
101
102
    /**
103
     * @param array $services
104
     * @return void
105
     */
106 4 View Code Duplication
    private function applyDelegates(Injector $injector, array $services)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
107
    {
108 4
        foreach ($services as $name => $object) {
109 4
            $injector->delegate($name, $object);
110 4
            if ($this->isShared($name)) {
111 3
                $injector->share($name);
112 3
            }
113 4
        }
114 4
    }
115
116
    /**
117
     * @param array $delegators
118
     * @return void
119
     */
120 4
    private function applyDelegators(Injector $injector, array $delegators)
121
    {
122
        // https://github.com/rdlowrey/auryn#prepares-and-setter-injection
123 4
        foreach ($delegators as $service => $prepares) {
124 4
            $injector->prepare($service, $this->createDelegator($service, $prepares));
125 4
            if ($this->isShared($service)) {
126 2
                $injector->share($service);
127 2
            }
128 4
        }
129 4
    }
130
131
    /**
132
     * Create a chained prepare()
133
     *
134
     * @param string $service
135
     * @param string[] $delegators
136
     * @return callable
137
     */
138 4
    private function createDelegator($service, array $delegators)
139
    {
140
        // Prepare the service by calling each delegator with the result of the previous.
141
        return function ($instance, $injector) use ($service, $delegators) {
142 4
            return array_reduce($delegators, $this->delegatorReducer($injector, $service), $instance);
143 4
        };
144
    }
145
146
    /**
147
     * Create a reducer for a chained prepare()
148
     *
149
     * @param string $service
150
     * @return callable
151
     */
152 4
    private function delegatorReducer(Injector $injector, $service)
153
    {
154
        // https://docs.zendframework.com/zend-expressive/features/container/delegator-factories/
155
        return function ($instance, $delegator) use ($injector, $service) {
156 4
            if (!is_callable($delegator)) {
157 4
                $delegator = $injector->make($delegator);
158 4
            }
159 4
            $callable = $this->k($instance);
160 4
            return $injector->execute($this->curryDelegator($delegator, $service, $callable));
161 4
        };
162
    }
163
164
    /**
165
     * Curry the delegator to only require a container
166
     *
167
     * @param callable $delegator that will be ultimately called
168
     * @param string $service name of service being prepared
169
     * @param callable $callable that returns the instance
170
     * @return callable
171
     */
172 4
    private function curryDelegator(callable $delegator, $service, callable $callable)
173
    {
174
        return static function (ContainerInterface $container) use ($delegator, $service, $callable) {
175 4
            return $delegator($container, $service, $callable);
176 4
        };
177
    }
178
179
    /**
180
     * Returns a function that always returns the same value
181
     *
182
     * Also known as a "kestrel" or "k combinator".
183
     *
184
     * @param mixed $x
185
     * @return callable
186
     */
187 4
    private function k($x)
188
    {
189
        return static function () use ($x) {
190 4
            return $x;
191 4
        };
192
    }
193
194
    /**
195
     * @param array $values
196
     * @return callable[]
197
     */
198 4
    private function kAll(array $values)
199
    {
200 4
        return array_map(
201 4
            function ($x) {
202 4
                return $this->k($x);
203 4
            },
204
            $values
205 4
        );
206
    }
207
}
208