Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 16 |
|
0.00% |
0 / 4 |
CRAP | |
0.00% |
0 / 1 |
ServiceContainer | |
0.00% |
0 / 16 |
|
0.00% |
0 / 4 |
182 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
6 | |||
getService | |
0.00% |
0 / 10 |
|
0.00% |
0 / 1 |
42 | |||
applyWiring | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
6 | |||
register | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
12 |
1 | <?php |
2 | /** |
3 | * ServiceContainer.php |
4 | * |
5 | * This class provides a simple implementation of a Dependency Injection (DI) container. |
6 | * It allows services to be registered and lazily resolved. Services are registered with |
7 | * a name and a resolver function, which is executed to instantiate the service when |
8 | * needed. The container stores these services and resolves them when requested. |
9 | * |
10 | * This container does not throw exceptions if a service is not found; instead, it |
11 | * returns `null` if a service cannot be resolved. |
12 | * |
13 | * @category Infrastructure |
14 | * @package Codex\Infrastructure |
15 | * @since 0.1.0 |
16 | * @author Doğu Abaris <abaris@null.net> |
17 | * @license https://www.gnu.org/copyleft/gpl.html GPL-2.0-or-later |
18 | * @link https://doc.wikimedia.org/codex/main/ Codex Documentation |
19 | */ |
20 | |
21 | namespace Wikimedia\Codex\Infrastructure; |
22 | |
23 | use InvalidArgumentException; |
24 | use Psr\Log\LoggerInterface; |
25 | use Psr\Log\NullLogger; |
26 | |
27 | /** |
28 | * ServiceContainer |
29 | * |
30 | * The `ServiceContainer` class is responsible for managing Dependency Injection (DI) by registering and |
31 | * resolving services. It allows services to be lazily instantiated only when needed, using callable resolvers |
32 | * or method references. Once a service is resolved, it is stored in memory to avoid redundant instantiation. |
33 | * |
34 | * Key Responsibilities: |
35 | * - Register services using a name and a resolver (callable or method reference). |
36 | * - Resolve services on demand. |
37 | * - Provide methods for monitoring the service resolution process. |
38 | * - Handle and log failed service lookups to avoid redundant error logging. |
39 | * |
40 | * Usage: |
41 | * - Services are registered via the `register()` method. |
42 | * - Services can be retrieved via the `getService()` method. |
43 | * - Handle non-existent services gracefully. |
44 | * |
45 | * @category Infrastructure |
46 | * @package Codex\Infrastructure |
47 | * @since 0.1.0 |
48 | * @author Doğu Abaris <abaris@null.net> |
49 | * @license https://www.gnu.org/copyleft/gpl.html GPL-2.0-or-later |
50 | * @link https://doc.wikimedia.org/codex/main/ Codex Documentation |
51 | */ |
52 | class ServiceContainer { |
53 | |
54 | /** |
55 | * Array of registered services. |
56 | * |
57 | * This array stores the resolver functions for each registered service. |
58 | */ |
59 | private array $services = []; |
60 | |
61 | /** |
62 | * Cache for failed service lookups to avoid repeated error logging. |
63 | */ |
64 | private array $failedServices = []; |
65 | |
66 | /** |
67 | * Logger instance for logging errors and other messages. |
68 | */ |
69 | private LoggerInterface $logger; |
70 | |
71 | /** |
72 | * Constructor for the ServiceContainer. |
73 | * |
74 | * This constructor initializes the service container with an optional logger instance. |
75 | * If no logger is provided, a `NullLogger` will be used by default, which performs no operations. |
76 | * |
77 | * @since 0.1.0 |
78 | * @param LoggerInterface|null $logger An optional logger instance. If null, a `NullLogger` will be used. |
79 | */ |
80 | public function __construct( ?LoggerInterface $logger = null ) { |
81 | $this->logger = $logger ?: new NullLogger(); |
82 | } |
83 | |
84 | /** |
85 | * Resolve a service from the container. |
86 | * |
87 | * @since 0.1.0 |
88 | * @param string $name The name of the service to resolve. |
89 | * @return mixed|null Returns the service instance, or `null` if the service is not found. |
90 | */ |
91 | public function getService( string $name ) { |
92 | // Resolve the service if registered |
93 | if ( isset( $this->services[$name] ) ) { |
94 | $resolver = $this->services[$name]; |
95 | |
96 | if ( is_callable( $resolver ) ) { |
97 | return $resolver( $this ); |
98 | } elseif ( is_array( $resolver ) && method_exists( $resolver[0], $resolver[1] ) ) { |
99 | return call_user_func( $resolver, $this ); |
100 | } |
101 | } |
102 | |
103 | // Handle non-existent service |
104 | if ( !isset( $this->failedServices[$name] ) ) { |
105 | $this->failedServices[$name] = true; |
106 | $this->logger->error( "Service '$name' is not registered in the container." ); |
107 | } |
108 | |
109 | return null; |
110 | } |
111 | |
112 | /** |
113 | * Apply a wiring configuration to register multiple services. |
114 | * |
115 | * @since 0.1.0 |
116 | * @param array $wiring The service wiring configuration. |
117 | * @return void |
118 | */ |
119 | public function applyWiring( array $wiring ): void { |
120 | foreach ( $wiring as $name => $resolver ) { |
121 | $this->register( $name, $resolver ); |
122 | } |
123 | } |
124 | |
125 | /** |
126 | * Register a service in the container. |
127 | * |
128 | * @since 0.1.0 |
129 | * @param string $name The unique name of the service. |
130 | * @param mixed $resolver The function, method reference, or object to return the service instance. |
131 | * @return void |
132 | */ |
133 | public function register( string $name, $resolver ): void { |
134 | if ( !is_callable( $resolver ) && !is_array( $resolver ) ) { |
135 | throw new InvalidArgumentException( 'Service resolver must be a callable or an array.' ); |
136 | } |
137 | |
138 | $this->services[$name] = $resolver; |
139 | } |
140 | } |