Completed
Push — master ( 1ac25e...0210cc )
by Woody
14s
created

ServiceConfig::apply()   B

Complexity

Conditions 6
Paths 32

Size

Total Lines 29
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 17
CRAP Score 6

Importance

Changes 0
Metric Value
dl 0
loc 29
ccs 17
cts 17
cp 1
rs 8.439
c 0
b 0
f 0
cc 6
eloc 11
nc 32
nop 1
crap 6
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 1
    public function __construct(array $config)
17
    {
18 1
        $this->config = $config;
19 1
    }
20
21
    /**
22
     * @param Injector $injector
23
     * @return void
24
     */
25 1
    public function apply(Injector $injector)
26
    {
27
        // Aliases are exactly the same as aliases. Natch.
28 1
        if (isset($this->config['aliases'])) {
29 1
            $this->applyAliases($injector, $this->config['aliases']);
30 1
        }
31
32 1
        if (isset($this->config['delegators'])) {
33
            // Delegators are effectively a chain of prepare() statements.
34 1
            $this->applyDelegators($injector, $this->config['delegators']);
35 1
        }
36
37
        // Factories are exactly the same thing as a delegate.
38 1
        if (isset($this->config['factories'])) {
39 1
            $this->applyDelegates($injector, $this->config['factories']);
40 1
        }
41
42
        // Invokables are references to classes that have no constructor parameters.
43
        // This means nothing in Auryn so we just alias the reference.
44 1
        if (isset($this->config['invokables'])) {
45 1
            $this->applyAliases($injector, $this->config['invokables']);
46 1
        }
47
48
        // Services are already constructed instances of something. To handle this,
49
        // we simply wrap the instance in a callable that returns the instance.
50 1
        if (isset($this->config['services'])) {
51 1
            $this->applyDelegates($injector, $this->kAll($this->config['services']));
52 1
        }
53 1
    }
54
55
    /**
56
     * @param array $services
57
     * @return void
58
     */
59 1
    private function applyAliases(Injector $injector, array $services)
60
    {
61 1
        foreach ($services as $name => $object) {
62 1
            $injector->alias($name, $object);
63 1
        }
64 1
    }
65
66
    /**
67
     * @param array $services
68
     * @return void
69
     */
70 1
    private function applyDelegates(Injector $injector, array $services)
71
    {
72 1
        foreach ($services as $name => $object) {
73 1
            $injector->delegate($name, $object);
74 1
        }
75 1
    }
76
77
    /**
78
     * @param array $delegators
79
     * @return void
80
     */
81 1
    private function applyDelegators(Injector $injector, array $delegators)
82
    {
83
        // https://github.com/rdlowrey/auryn#prepares-and-setter-injection
84 1
        foreach ($delegators as $service => $prepares) {
85 1
            $injector->prepare($service, $this->createDelegator($service, $prepares));
86 1
        }
87 1
    }
88
89
    /**
90
     * Create a chained prepare()
91
     *
92
     * @param string $service
93
     * @param string[] $delegators
94
     * @return callable
95
     */
96 1
    private function createDelegator($service, array $delegators)
97
    {
98
        // Prepare the service by calling each delegator with the result of the previous.
99
        return function ($instance, $injector) use ($service, $delegators) {
100 1
            return array_reduce($delegators, $this->delegatorReducer($injector, $service), $instance);
101 1
        };
102
    }
103
104
    /**
105
     * Create a reducer for a chained prepare()
106
     *
107
     * @param string $service
108
     * @return callable
109
     */
110 1
    private function delegatorReducer(Injector $injector, $service)
111
    {
112
        // https://docs.zendframework.com/zend-expressive/features/container/delegator-factories/
113
        return function ($instance, $delegator) use ($injector, $service) {
114 1
            if (!is_callable($delegator)) {
115 1
                $delegator = $injector->make($delegator);
116 1
            }
117 1
            $callable = $this->k($instance);
118 1
            return $injector->execute($this->curryDelegator($delegator, $service, $callable));
119 1
        };
120
    }
121
122
    /**
123
     * Curry the delegator to only require a container
124
     *
125
     * @param callable $delegator that will be ultimately called
126
     * @param string $service name of service being prepared
127
     * @param callable $callable that returns the instance
128
     * @return callable
129
     */
130 1
    private function curryDelegator(callable $delegator, $service, callable $callable)
131
    {
132
        return static function (ContainerInterface $container) use ($delegator, $service, $callable) {
133 1
            return $delegator($container, $service, $callable);
134 1
        };
135
    }
136
137
    /**
138
     * Returns a function that always returns the same value
139
     *
140
     * Also known as a "kestrel" or "k combinator".
141
     *
142
     * @param mixed $x
143
     * @return callable
144
     */
145 1
    private function k($x)
146
    {
147
        return static function () use ($x) {
148 1
            return $x;
149 1
        };
150
    }
151
152
    /**
153
     * @param array $values
154
     * @return callable[]
155
     */
156 1
    private function kAll(array $values)
157
    {
158 1
        return array_map(
159 1
            function ($x) {
160 1
                return $this->k($x);
161 1
            },
162
            $values
163 1
        );
164
    }
165
}
166