TransformerFactory::__construct()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 2
Code Lines 0

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 0
c 1
b 0
f 0
dl 0
loc 2
rs 10
cc 1
nc 1
nop 1
1
<?php
2
3
/**
4
 * @author     Oleh Boiko <[email protected]>
5
 *
6
 * @site       https://mackrais.com
7
 *
8
 * @copyright  2014-2025 MackRais
9
 *
10
 * @date: 15.03.25
11
 */
12
13
declare(strict_types=1);
14
15
namespace MackRais\PropertyTransform;
16
17
use MackRais\PropertyTransform\Exception\InvalidArgumentException;
18
use MackRais\PropertyTransform\Exception\NotCallableException;
19
use Psr\Container\ContainerInterface;
20
21
class TransformerFactory
22
{
23
    private array $transformerCache = [];
24
25
    public function __construct(private readonly ContainerInterface $container)
26
    {
27
    }
28
29
    public function create(object|string|array $transformer): callable
30
    {
31
        $transformerId = $this->getCallableKey($transformer);
32
33
        if (!isset($this->transformerCache[$transformerId])) {
34
            $this->transformerCache[$transformerId] = function (...$params) use ($transformer) {
35
                if ($transformer instanceof \Closure) {
0 ignored issues
show
introduced by
$transformer is never a sub-type of Closure.
Loading history...
36
                    return $transformer(...$params);
37
                }
38
39
                if (\is_array($transformer)) {
0 ignored issues
show
introduced by
The condition is_array($transformer) is always true.
Loading history...
40
                    [$class, $method] = $transformer;
41
                    $object = \is_object($class) ? $class : $this->container->get($class);
42
                    if (null === $object) {
43
                        $object = new $class();
44
                    }
45
                    /** @var object $object */
46
                    $reflection = new \ReflectionMethod($object, $method);
47
                    $dependencies = [];
48
                    $paramsQueue = $params;
49
50
                    foreach ($reflection->getParameters() as $parameter) {
51
                        $type = $parameter->getType();
52
                        if (!$type instanceof \ReflectionNamedType) {
53
                            continue;
54
                        }
55
56
                        if (!$type->isBuiltin()) {
57
                            $dependencies[] = $this->container->get($type->getName());
58
                        } elseif (!empty($paramsQueue)) {
59
                            $dependencies[] = array_shift($paramsQueue);
60
                        }
61
                    }
62
63
                    return $reflection->invoke($object, ...$dependencies);
64
                }
65
66
                if (!\is_callable($transformer)) {
67
                    throw new NotCallableException('Provided transformer is not callable.');
68
                }
69
70
                return $transformer(...$params);
71
            };
72
        }
73
74
        return $this->transformerCache[$transformerId];
75
    }
76
77
    private function getCallableKey(object|string|array $transformer): string
78
    {
79
        if (\is_string($transformer)) {
0 ignored issues
show
introduced by
The condition is_string($transformer) is always false.
Loading history...
80
            return $transformer;
81
        }
82
83
        if (\is_array($transformer)) {
0 ignored issues
show
introduced by
The condition is_array($transformer) is always true.
Loading history...
84
            if (2 !== \count($transformer)) {
85
                throw new InvalidArgumentException('Array transformer must have exactly two elements: [object/class, method].');
86
            }
87
88
            [$objectOrClass, $method] = $transformer;
89
90
            if (!\is_string($method)) {
91
                throw new InvalidArgumentException('The second element of the array transformer must be a method name (string).');
92
            }
93
94
            if (\is_object($objectOrClass)) {
95
                return spl_object_hash($objectOrClass).'::'.$method;
96
            }
97
98
            if (\is_string($objectOrClass) && class_exists($objectOrClass)) {
99
                return $objectOrClass.'::'.$method;
100
            }
101
102
            throw new InvalidArgumentException('The first element of the array transformer must be an object or a valid class name.');
103
        }
104
105
        return spl_object_hash($transformer);
106
    }
107
}
108