Completed
Pull Request — master (#4)
by Woody
01:58
created

ServiceConfig::curryDelegator()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

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