FactoryManager::transferFactoryStatus()   A
last analyzed

Complexity

Conditions 4
Paths 3

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 4

Importance

Changes 2
Bugs 0 Features 1
Metric Value
eloc 4
c 2
b 0
f 1
dl 0
loc 6
ccs 5
cts 5
cp 1
rs 10
cc 4
nc 3
nop 2
crap 4
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Gacela\Container;
6
7
use Closure;
8
use Gacela\Container\Exception\ContainerException;
9
use SplObjectStorage;
10
11
use function count;
12
use function is_array;
13
use function is_callable;
14
use function is_object;
15
16
/**
17
 * Manages factory instances, protected closures, and service extensions.
18
 */
19
final class FactoryManager
20
{
21
    /** @var SplObjectStorage<Closure, mixed> */
22
    private SplObjectStorage $factoryInstances;
23
24
    /** @var SplObjectStorage<Closure, mixed> */
25
    private SplObjectStorage $protectedInstances;
26
27
    private ?string $currentlyExtending = null;
28
29
    /**
30
     * @param array<string, list<Closure>> $instancesToExtend
31
     */
32 89
    public function __construct(
33
        private array $instancesToExtend = [],
34
    ) {
35 89
        $this->factoryInstances = new SplObjectStorage();
36 89
        $this->protectedInstances = new SplObjectStorage();
37
    }
38
39
    /**
40
     * Mark a closure as a factory (always creates new instances).
41
     */
42 5
    public function markAsFactory(Closure $instance): void
43
    {
44 5
        $this->factoryInstances->attach($instance);
45
    }
46
47
    /**
48
     * Mark a closure as protected (won't be invoked by container).
49
     */
50 2
    public function markAsProtected(Closure $instance): void
51
    {
52 2
        $this->protectedInstances->attach($instance);
53
    }
54
55
    /**
56
     * Check if an instance is marked as a factory.
57
     */
58 19
    public function isFactory(mixed $instance): bool
59
    {
60 19
        return $instance instanceof Closure && isset($this->factoryInstances[$instance]);
61
    }
62
63
    /**
64
     * Check if an instance is protected.
65
     */
66 20
    public function isProtected(mixed $instance): bool
67
    {
68 20
        return $instance instanceof Closure && isset($this->protectedInstances[$instance]);
69
    }
70
71
    /**
72
     * Schedule an extension to be applied when the service is set.
73
     */
74 4
    public function scheduleExtension(string $id, Closure $instance): void
75
    {
76 4
        $this->instancesToExtend[$id][] = $instance;
77
    }
78
79
    /**
80
     * Check if there are pending extensions for a service.
81
     */
82 31
    public function hasPendingExtensions(string $id): bool
83
    {
84 31
        return isset($this->instancesToExtend[$id]) && count($this->instancesToExtend[$id]) > 0;
85
    }
86
87
    /**
88
     * Get pending extensions for a service.
89
     *
90
     * @return list<Closure>
91
     */
92 3
    public function getPendingExtensions(string $id): array
93
    {
94 3
        return $this->instancesToExtend[$id] ?? [];
95
    }
96
97
    /**
98
     * Clear pending extensions for a service.
99
     */
100 3
    public function clearPendingExtensions(string $id): void
101
    {
102 3
        unset($this->instancesToExtend[$id]);
103
    }
104
105
    /**
106
     * Set the service ID currently being extended.
107
     */
108 3
    public function setCurrentlyExtending(?string $id): void
109
    {
110 3
        $this->currentlyExtending = $id;
111
    }
112
113
    /**
114
     * Check if we're currently extending a specific service.
115
     */
116 31
    public function isCurrentlyExtending(string $id): bool
117
    {
118 31
        return $this->currentlyExtending === $id;
119
    }
120
121
    /**
122
     * Transfer factory status from one instance to another.
123
     * Used when extending a factory service.
124
     */
125 7
    public function transferFactoryStatus(mixed $from, mixed $to): void
126
    {
127 7
        if ($from instanceof Closure && isset($this->factoryInstances[$from])) {
128 1
            $this->factoryInstances->detach($from);
129 1
            if ($to instanceof Closure) {
130 1
                $this->factoryInstances->attach($to);
131
            }
132
        }
133
    }
134
135
    /**
136
     * Generate an extended instance wrapper.
137
     *
138
     * @psalm-suppress MissingClosureReturnType,MixedAssignment
139
     */
140 8
    public function generateExtendedInstance(Closure $instance, mixed $factory, Container $container): Closure
0 ignored issues
show
Unused Code introduced by
The parameter $container is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

140
    public function generateExtendedInstance(Closure $instance, mixed $factory, /** @scrutinizer ignore-unused */ Container $container): Closure

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
141
    {
142 8
        if (is_callable($factory)) {
143 6
            return static function (Container $c) use ($instance, $factory) {
144 6
                $result = $factory($c);
145
146 6
                return $instance($result, $c) ?? $result;
147 6
            };
148
        }
149
150 4
        if (is_object($factory) || is_array($factory)) {
151 3
            return static fn (Container $c) => $instance($factory, $c) ?? $factory;
152
        }
153
154 1
        throw ContainerException::instanceNotExtendable();
155
    }
156
}
157