Completed
Push — master ( 338bba...3a4b59 )
by mcfog
03:00
created

Configurator::config()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 4.128

Importance

Changes 0
Metric Value
eloc 4
dl 0
loc 7
ccs 4
cts 5
cp 0.8
rs 10
c 0
b 0
f 0
cc 4
nc 3
nop 3
crap 4.128
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Lit\Air;
6
7
use Lit\Air\Psr\Container;
8
use Lit\Air\Psr\ContainerException;
9
use Lit\Air\Recipe\BuilderRecipe;
10
use Lit\Air\Recipe\Decorator\AbstractRecipeDecorator;
11
use Lit\Air\Recipe\Decorator\CallbackDecorator;
12
use Lit\Air\Recipe\Decorator\SingletonDecorator;
13
use Lit\Air\Recipe\FixedValueRecipe;
14
use Lit\Air\Recipe\RecipeInterface;
15
16
class Configurator
17
{
18
    protected static $decorators = [
19
        'callback' => CallbackDecorator::class,
20
        'singleton' => SingletonDecorator::class,
21
    ];
22
23 1
    public static function config(Container $container, array $config, bool $force = true): void
24
    {
25 1
        foreach ($config as $key => $value) {
26 1
            if (!$force && $container->has($key)) {
27
                continue;
28
            }
29 1
            self::write($container, $key, $value);
30
        }
31 1
    }
32
33 6
    public static function convertToRecipe($value): RecipeInterface
34
    {
35 6
        if (is_object($value) && $value instanceof RecipeInterface) {
36 2
            return $value;
37
        }
38
39 5
        if (is_callable($value)) {
40 1
            return (new BuilderRecipe($value))->singleton();
41
        }
42
43 4
        if (is_array($value) && array_key_exists(0, $value) && isset($value['$'])) {
44 1
            return self::makeRecipe($value);
45
        }
46
47 3
        return Container::value($value);
48
    }
49
50 1
    public static function singleton(string $classname, array $extra = []): array
51
    {
52 1
        return self::decorateSingleton(self::instance($classname, $extra));
53
    }
54
55 1
    public static function decorateSingleton(array $config): array
56
    {
57 1
        $config['decorator'] = $config['decorator'] ?? [];
58 1
        $config['decorator']['singleton'] = true;
59
60 1
        return $config;
61
    }
62
63 1
    public static function decorateCallback(array $config, callable $callback): array
64
    {
65 1
        $config['decorator'] = $config['decorator'] ?? [];
66 1
        $config['decorator']['callback'] = $callback;
67
68 1
        return $config;
69
    }
70
71 1
    public static function provideParameter(array $extra = []): array
72
    {
73
        return [
74 1
            '$' => 'autowire',
75
            null,
76 1
            $extra,
77
        ];
78
    }
79
80 1
    public static function produce(?string $classname, array $extra = []): array
81
    {
82
        return [
83 1
            '$' => 'autowire',
84 1
            $classname,
85 1
            $extra,
86
        ];
87
    }
88
89 1
    public static function instance(string $classname, array $extra = []): array
90
    {
91
        return [
92 1
            '$' => 'instance',
93 1
            $classname,
94 1
            $extra,
95
        ];
96
    }
97
98 1
    public static function alias(string... $key): array
99
    {
100
        return [
101 1
            '$' => 'alias',
102 1
            self::join(...$key),
103
        ];
104
    }
105
106 1
    public static function builder(callable $builder, array $extra = []): array
107
    {
108
        return [
109 1
            '$' => 'builder',
110 1
            $builder,
111 1
            $extra
112
        ];
113
    }
114
115 2
    public static function value($value): array
116
    {
117
        return [
118 2
            '$' => 'value',
119 2
            $value,
120
        ];
121
    }
122
123 1
    protected static function write(Container $container, $key, $value)
124
    {
125 1
        if (is_scalar($value) || is_resource($value)) {
126 1
            $container->set($key, $value);
127 1
            return;
128
        }
129
130 1
        if (substr($key, -2) === '::'
131 1
            && class_exists(substr($key, 0, -2))
132
        ) {
133 1
            $container->set($key, self::convertArray($value));
134 1
            return;
135
        }
136
137 1
        $recipe = self::convertToRecipe($value);
138
139 1
        if ($recipe instanceof FixedValueRecipe) {
140 1
            $container->set($key, $recipe->getValue());
141
        } else {
142 1
            $container->flush($key);
143 1
            $container->define($key, $recipe);
144
        }
145 1
    }
146
147
    /**
148
     * @param array $value
149
     * @return array
150
     */
151 1
    protected static function convertArray(array $value): array
152
    {
153 1
        $result = [];
154 1
        foreach ($value as $k => $v) {
155 1
            $result[$k] = self::convertToRecipe($v);
156 1
            if ($result[$k] instanceof FixedValueRecipe) {
157 1
                $result[$k] = $result[$k]->getValue();
158
            }
159
        }
160
161 1
        return $result;
162
    }
163
164 1
    protected static function makeRecipe(array $value): RecipeInterface
165
    {
166 1
        $type = $value['$'];
167 1
        unset($value['$']);
168
169 1
        if (array_key_exists($type, [
170 1
            'alias' => 1,
171
            'autowire' => 1,
172
            'instance' => 1,
173
            'builder' => 1,
174
            'value' => 1,
175
        ])) {
176 1
            $valueDecorator = $value['decorator'] ?? null;
177 1
            unset($value['decorator']);
178
179 1
            $builder = [Container::class, $type];
180 1
            assert(is_callable($builder));
181
            /**
182
             * @var RecipeInterface $recipe
183
             */
184 1
            $recipe = call_user_func_array($builder, $value);
185
186 1
            if ($valueDecorator) {
187 1
                $recipe = self::wrapRecipeWithDecorators($valueDecorator, $recipe);
188
            }
189
190 1
            return $recipe;
191
        }
192
193
        throw new ContainerException("cannot understand given recipe");
194
    }
195
196
    /**
197
     * @param array $decorators
198
     * @param RecipeInterface $recipe
199
     * @return RecipeInterface
200
     */
201 1
    public static function wrapRecipeWithDecorators(array $decorators, RecipeInterface $recipe): RecipeInterface
202
    {
203 1
        foreach ($decorators as $name => $option) {
204 1
            if (isset(self::$decorators[$name])) {
205
                $decorateFn = [self::$decorators[$name], 'decorate'];
206
                assert(is_callable($decorateFn));
207
                $recipe = call_user_func($decorateFn, $recipe);
208 1
            } elseif (is_subclass_of($name, AbstractRecipeDecorator::class)) {
209 1
                $decorateFn = [$name, 'decorate'];
210 1
                assert(is_callable($decorateFn));
211 1
                $recipe = call_user_func($decorateFn, $recipe);
212
            } else {
213
                throw new ContainerException("cannot understand recipe decorator [$name]");
214
            }
215
216 1
            assert($recipe instanceof AbstractRecipeDecorator);
217 1
            if (!empty($option)) {
218 1
                $recipe->setOption($option);
219
            }
220
        }
221
222 1
        return $recipe;
223
    }
224
225 1
    public static function join(string... $args): string
226
    {
227 1
        return implode('::', $args);
228
    }
229
}
230