Passed
Pull Request — master (#68)
by Witold
01:56
created

MonologServiceFactory::createService()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6

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 10
c 0
b 0
f 0
cc 1
nc 1
nop 1
crap 1
1
<?php
2
/**
3
 * @author Evgeny Shpilevsky <[email protected]>
4
 */
5
6
namespace EnliteMonolog\Service;
7
8
use Closure;
9
use Exception;
10
use Interop\Container\ContainerInterface;
11
use Interop\Container\Exception\ContainerException;
12
use Interop\Container\Exception\NotFoundException;
13
use InvalidArgumentException;
14
use Laminas\ServiceManager\Factory\FactoryInterface;
15
use Monolog\Handler\FormattableHandlerInterface;
16
use Monolog\Handler\HandlerInterface;
17
use Monolog\Logger;
18
use Monolog\Formatter\FormatterInterface;
19
use ReflectionClass;
20
use ReflectionException;
21
use RuntimeException;
22
use Laminas\ServiceManager\ServiceLocatorInterface;
23
24
class MonologServiceFactory implements FactoryInterface
25
{
26
    /**
27
     * {@inheritdoc}
28
     * @throws NotFoundException
29
     * @throws RuntimeException
30
     * @throws ContainerException
31
     * @throws ReflectionException
32
     */
33 3
    public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
34
    {
35
        /** @var MonologOptions $options */
36 3
        $options = $container->get('EnliteMonologOptions');
37 3
        return $this->createLogger($container, $options);
38
    }
39
40
    /**
41
     * @param ServiceLocatorInterface|ContainerInterface $container
42
     * @param MonologOptions $options
43
     * @return Logger
44
     * @throws NotFoundException
45
     * @throws RuntimeException
46
     * @throws ContainerException
47
     * @throws ReflectionException
48
     */
49 4
    public function createLogger($container, MonologOptions $options)
50
    {
51 4
        $logger = new Logger($options->getName());
52
53 4
        $handlers = array_reverse($options->getHandlers());
54 4
        foreach ($handlers as $handler) {
55 3
            $logger->pushHandler($this->createHandler($container, $options, $handler));
56
        }
57
58 4
        foreach ($options->getProcessors() as $processor) {
59 1
            $logger->pushProcessor($this->createProcessor($container, $processor));
60
        }
61
62 4
        return $logger;
63
    }
64
65
    /**
66
     * @param ServiceLocatorInterface|ContainerInterface $container
67
     * @param MonologOptions $options
68
     * @param string|array $handler
69
     * @return HandlerInterface
70
     * @throws RuntimeException
71
     * @throws NotFoundException
72
     * @throws ContainerException
73
     * @throws ReflectionException
74
     *
75
     */
76 16
    public function createHandler($container, MonologOptions $options, $handler)
77
    {
78 16
        if (is_string($handler) && $container->has($handler)) {
79 2
            return $container->get($handler);
80
        }
81
82 15
        if (!isset($handler['name'])) {
83 1
            throw new RuntimeException('Cannot create logger handler');
84
        }
85
86 14
        $handlerClassName = $handler['name'];
87
88 14
        if (!class_exists($handlerClassName)) {
89 1
            throw new RuntimeException('Cannot create logger handler (' . $handlerClassName . ')');
90
        }
91
92 13
        $arguments = array_key_exists('args', $handler) ? $handler['args'] : array();
93
94 13
        if (!is_array($arguments)) {
95 1
            throw new RuntimeException('Arguments of handler(' . $handlerClassName . ') must be array');
96
        }
97
98 12
        if (isset($arguments['handler'])) {
99 1
            foreach ($options->getHandlers() as $key => $option) {
100 1
                if ($arguments['handler'] == $key) {
101 1
                    $arguments['handler'] = $this->createHandler($container, $options, $option);
102 1
                    break;
103
                }
104
            }
105
        }
106
107
        try {
108
            /** @var HandlerInterface $instance */
109 12
            $instance = $this->createInstanceFromArguments($handlerClassName, $arguments);
110 1
        } catch (InvalidArgumentException $exception) {
111 1
            throw new RuntimeException(sprintf(
112 1
                'Handler(%s) has an invalid argument configuration',
113
                $handlerClassName
114 1
            ), 0, $exception);
115
        }
116
117 11
        if (isset($handler['formatter'])) {
118 2
            if ($instance instanceof FormattableHandlerInterface
119 1
                || !defined('Monolog\Logger::API')
120 2
                || \Monolog\Logger::API === 1) {
121 1
                $formatter = $this->createFormatter($container, $handler['formatter']);
122 1
                $instance->setFormatter($formatter);
123
            } else {
124 1
                throw new RuntimeException(sprintf(
125 1
                    'Handler(%s) doesn\'t implements %s to attach formatter.',
126
                    $handlerClassName,
127 1
                    FormattableHandlerInterface::class
128
                ));
129
            }
130
        }
131
132 10
        return $instance;
133
    }
134
135
    /**
136
     * @param ContainerInterface $container
137
     * @param string|array $formatter
138
     * @return FormatterInterface
139
     * @throws NotFoundException
140
     * @throws ContainerException
141
     * @throws RuntimeException
142
     * @throws ReflectionException
143
     */
144 12
    public function createFormatter($container, $formatter)
145
    {
146 12
        if (is_string($formatter) && $container->has($formatter)) {
147 1
            return $container->get($formatter);
148
        }
149
150 11
        if (!isset($formatter['name'])) {
151 1
            throw new RuntimeException('Cannot create logger formatter');
152
        }
153
154 10
        $formatterClassName = $formatter['name'];
155
156 10
        if (!class_exists($formatterClassName)) {
157 1
            throw new RuntimeException('Cannot create logger formatter (' . $formatterClassName . ')');
158
        }
159
160 9
        $arguments = array_key_exists('args', $formatter) ? $formatter['args'] : array();
161
162 9
        if (!is_array($arguments)) {
163 1
            throw new RuntimeException('Arguments of formatter(' . $formatterClassName . ') must be array');
164
        }
165
166
        try {
167
            /** @var FormatterInterface $instance */
168 8
            $instance = $this->createInstanceFromArguments($formatterClassName, $arguments);
169 3
        } catch (InvalidArgumentException $exception) {
170 3
            throw new RuntimeException(sprintf(
171 3
                'Formatter(%s) has an invalid argument configuration',
172
                $formatterClassName
173 3
            ), 0, $exception);
174
        }
175
176 5
        return $instance;
177
    }
178
179
    /**
180
     * @param ContainerInterface $container
181
     * @param $processor
182
     * @return Closure
183
     * @throws NotFoundException
184
     * @throws ContainerException
185
     * @throws RuntimeException
186
     * @throws ReflectionException
187
     */
188 13
    public function createProcessor($container, $processor)
189
    {
190 13
        if ($processor instanceof Closure) {
191 1
            return $processor;
192
        }
193
194 12
        if (is_string($processor)) {
195
            try {
196 5
                $instance = $container->get($processor);
197 3
            } catch (Exception $ex) {
198 3
                $instance = null;
199
            }
200
201 5
            if ($instance && is_callable($instance)) {
202 1
                return $instance;
203
            }
204
205 4
            $processor = new $processor();
206
207 4
            if (is_callable($processor)) {
208 2
                return $processor;
209
            }
210
        }
211
212 9
        if (is_array($processor)) {
213 7
            if (!isset($processor['name'])) {
214 1
                throw new RuntimeException('Cannot create logger processor');
215
            }
216
217 6
            $processorClassName = $processor['name'];
218
219 6
            if (!class_exists($processorClassName)) {
220 1
                throw new RuntimeException('Cannot create logger processor (' . $processorClassName . ')');
221
            }
222
223 5
            $arguments = array_key_exists('args', $processor) ? $processor['args'] : array();
224
225 5
            if (!is_array($arguments)) {
226 1
                throw new RuntimeException('Arguments of processor (' . $processorClassName . ') must be array');
227
            }
228
229
            try {
230 4
                $instance = $this->createInstanceFromArguments($processorClassName, $arguments);
231 1
            } catch (InvalidArgumentException $exception) {
232 1
                throw new RuntimeException(sprintf(
233 1
                    'Processor(%s) has an invalid argument configuration',
234
                    $processorClassName
235 1
                ), 0, $exception);
236
            }
237
238 3
            if (is_callable($instance)) {
239 3
                return $instance;
240
            }
241
        }
242
243 2
        throw new RuntimeException(
244 2
            'Unknown processor type, must be a Closure, array or the FQCN of an invokable class'
245
        );
246
    }
247
248
    /**
249
     * Handles the constructor arguments and if they're named, just sort them to fit constructor ordering.
250
     *
251
     * @param string $className
252
     * @param array $arguments
253
     *
254
     * @return object
255
     * @throws InvalidArgumentException If given arguments are not valid for provided className constructor.
256
     * @throws ReflectionException
257
     */
258 23
    private function createInstanceFromArguments($className, array $arguments)
259
    {
260 23
        $reflection = new ReflectionClass($className);
261 23
        $constructor = $reflection->getConstructor();
262
263
        // There is no or at least a non-accessible constructor for provided class name,
264
        // therefore there is no need to handle arguments anyway
265 23
        if ($constructor === null) {
266 1
            return $reflection->newInstanceArgs($arguments);
267
        }
268
269 22
        if (!$constructor->isPublic()) {
270 1
            throw new InvalidArgumentException(sprintf(
271 1
                '%s::__construct is not accessible',
272
                $className
273
            ));
274
        }
275
276 21
        $requiredArgsCount = $constructor->getNumberOfRequiredParameters();
277 21
        $argumentCount = count($arguments);
278
279 21
        if ($requiredArgsCount > $argumentCount) {
280 2
            throw new InvalidArgumentException(sprintf(
281 2
                '%s::__construct() requires at least %d arguments; %d given',
282
                $className,
283
                $requiredArgsCount,
284
                $argumentCount
285
            ));
286
        }
287
288
        // Arguments supposed to be ordered
289 19
        if (isset($arguments[0])) {
290 2
            return $reflection->newInstanceArgs($arguments);
291
        }
292
293 17
        $parameters = array();
294
295 17
        foreach ($constructor->getParameters() as $parameter) {
296 17
            $parameterName = $parameter->getName();
297
298 17
            if (array_key_exists($parameterName, $arguments)) {
299 6
                $parameters[$parameter->getPosition()] = $arguments[$parameterName];
300 6
                continue;
301
            }
302
303 15
            if (!$parameter->isOptional()) {
304 2
                throw new InvalidArgumentException(sprintf(
305 2
                    'Missing at least one required parameters `%s`',
306
                    $parameterName
307
                ));
308
            }
309
310 13
            $parameters[$parameter->getPosition()] = $parameter->getDefaultValue();
311
        }
312
313 15
        return $reflection->newInstanceArgs($parameters);
314
    }
315
}
316