PluginProbe
Double Opt-In for Contact Form 7 – Secure, GDPR-Compliant Email Verification / 5.5.0
Double Opt-In for Contact Form 7 – Secure, GDPR-Compliant Email Verification v5.5.0
5.5.0 5.4.0 5.3.2 5.3.1 5.1.6 5.1.5 trunk 2.1.5 2.11 2.12 2.13 2.15 3.0.0 3.0.1 3.0.2 3.0.3 3.0.5 3.0.51 3.0.60 3.0.61 3.0.62 3.0.70 3.0.71 3.0.72 3.1.0 All 34 releases
double-opt-in / src / Container / Container.php

Container.php in Double Opt-In for Contact Form 7 – Secure, GDPR-Compliant Email Verification 5.5.0, at src/Container/Container.php

319 lines 7.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Dependency Injection Container
4 *
5 * @package Forge12\DoubleOptIn\Container
6 * @since 4.0.0
7 */
8
9 namespace Forge12\DoubleOptIn\Container;
10
11 use Forge12\Shared\LoggerInterface;
12
13 if ( ! defined( 'ABSPATH' ) ) {
14 exit;
15 }
16
17 /**
18 * Class Container
19 *
20 * @internal
21 *
22 * Concrete PSR-11 compatible DI container with auto-wiring. Addons MUST
23 * depend on {@see ContainerInterface}, not on this class. Use of the
24 * concrete class (e.g. casting to Container to access non-interface
25 * methods like {@see Container::singleton()}) is supported only for
26 * license providers inside the Pro bundle during the migration window
27 * and will become unsupported after that window closes.
28 */
29 class Container implements ContainerInterface {
30
31 /**
32 * Registered bindings.
33 *
34 * @var array<string, array{concrete: string|callable, shared: bool}>
35 */
36 private array $bindings = array();
37
38 /**
39 * Resolved singleton instances.
40 *
41 * @var array<string, mixed>
42 */
43 private array $instances = array();
44
45 /**
46 * Factory callables for lazy loading.
47 *
48 * @var array<string, callable>
49 */
50 private array $factories = array();
51
52 /**
53 * Registered service providers.
54 *
55 * @var ServiceProviderInterface[]
56 */
57 private array $providers = array();
58
59 /**
60 * Logger instance for debugging.
61 *
62 * @var LoggerInterface|null
63 */
64 private ?LoggerInterface $logger = null;
65
66 /**
67 * Singleton instance.
68 *
69 * @var Container|null
70 */
71 private static ?Container $instance = null;
72
73 /**
74 * Get the singleton container instance.
75 *
76 * Used for backward compatibility during migration.
77 *
78 * @return Container
79 */
80 public static function getInstance(): Container {
81 if ( self::$instance === null ) {
82 self::$instance = new self();
83 }
84 return self::$instance;
85 }
86
87 /**
88 * Reset the singleton instance.
89 *
90 * Useful for testing.
91 *
92 * @return void
93 */
94 public static function resetInstance(): void {
95 self::$instance = null;
96 }
97
98 /**
99 * Set the logger instance.
100 *
101 * @param LoggerInterface $logger The logger.
102 *
103 * @return void
104 */
105 public function setLogger( LoggerInterface $logger ): void {
106 $this->logger = $logger;
107 }
108
109 /**
110 * Register a binding in the container.
111 *
112 * @param string $abstract The abstract type (interface or class name).
113 * @param string|callable|null $concrete The concrete implementation.
114 * @param bool $shared Whether to share the instance (singleton).
115 *
116 * @return void
117 */
118 public function bind( string $abstract, $concrete = null, bool $shared = false ): void {
119 $concrete = $concrete ?? $abstract;
120
121 $this->bindings[ $abstract ] = array(
122 'concrete' => $concrete,
123 'shared' => $shared,
124 );
125
126 $this->log(
127 'Binding registered',
128 array(
129 'abstract' => $abstract,
130 'shared' => $shared,
131 )
132 );
133 }
134
135 /**
136 * Register a singleton binding.
137 *
138 * @param string $abstract The abstract type.
139 * @param string|callable|null $concrete The concrete implementation.
140 *
141 * @return void
142 */
143 public function singleton( string $abstract, $concrete = null ): void {
144 $this->bind( $abstract, $concrete, true );
145 }
146
147 /**
148 * Register a factory for lazy loading.
149 *
150 * @param string $abstract The abstract type.
151 * @param callable $factory The factory callable.
152 *
153 * @return void
154 */
155 public function factory( string $abstract, callable $factory ): void {
156 $this->factories[ $abstract ] = $factory;
157 $this->log( 'Factory registered', array( 'abstract' => $abstract ) );
158 }
159
160 /**
161 * Register an existing instance in the container.
162 *
163 * @param string $abstract The abstract type.
164 * @param mixed $instance The instance.
165 *
166 * @return void
167 */
168 public function instance( string $abstract, $instance ): void {
169 $this->instances[ $abstract ] = $instance;
170 $this->log( 'Instance registered', array( 'abstract' => $abstract ) );
171 }
172
173 /**
174 * Register a service provider.
175 *
176 * @param ServiceProviderInterface $provider The service provider.
177 *
178 * @return void
179 */
180 public function addProvider( ServiceProviderInterface $provider ): void {
181 $this->providers[] = $provider;
182 $provider->register( $this );
183 $this->log( 'Provider registered', array( 'provider' => get_class( $provider ) ) );
184 }
185
186 /**
187 * Boot all registered providers.
188 *
189 * @return void
190 */
191 public function boot(): void {
192 foreach ( $this->providers as $provider ) {
193 if ( $provider instanceof BootableProviderInterface ) {
194 $provider->boot( $this );
195 $this->log( 'Provider booted', array( 'provider' => get_class( $provider ) ) );
196 }
197 }
198 }
199
200 /**
201 * {@inheritdoc}
202 */
203 public function get( string $id ) {
204 // Return existing instance if available
205 if ( isset( $this->instances[ $id ] ) ) {
206 return $this->instances[ $id ];
207 }
208
209 // Use factory if available
210 if ( isset( $this->factories[ $id ] ) ) {
211 $instance = ( $this->factories[ $id ] )( $this );
212 $this->instances[ $id ] = $instance;
213 return $instance;
214 }
215
216 // Check bindings
217 if ( isset( $this->bindings[ $id ] ) ) {
218 $binding = $this->bindings[ $id ];
219 $concrete = $binding['concrete'];
220
221 if ( $binding['shared'] && isset( $this->instances[ $id ] ) ) {
222 return $this->instances[ $id ];
223 }
224
225 $instance = is_callable( $concrete )
226 ? $concrete( $this )
227 : $this->resolve( $concrete );
228
229 if ( $binding['shared'] ) {
230 $this->instances[ $id ] = $instance;
231 }
232
233 return $instance;
234 }
235
236 // Auto-wire if class exists
237 if ( class_exists( $id ) ) {
238 return $this->resolve( $id );
239 }
240
241 throw new NotFoundException( "No binding found for: {$id}" );
242 }
243
244 /**
245 * {@inheritdoc}
246 */
247 public function has( string $id ): bool {
248 return isset( $this->bindings[ $id ] )
249 || isset( $this->instances[ $id ] )
250 || isset( $this->factories[ $id ] )
251 || class_exists( $id );
252 }
253
254 /**
255 * Resolve a class with automatic dependency injection.
256 *
257 * @param string $class The class name.
258 *
259 * @return object The resolved instance.
260 * @throws NotFoundException If dependencies cannot be resolved.
261 */
262 private function resolve( string $class ): object {
263 $reflector = new \ReflectionClass( $class );
264
265 if ( ! $reflector->isInstantiable() ) {
266 throw new NotFoundException( "Class {$class} is not instantiable" );
267 }
268
269 $constructor = $reflector->getConstructor();
270
271 if ( $constructor === null ) {
272 return new $class();
273 }
274
275 $dependencies = array();
276 foreach ( $constructor->getParameters() as $param ) {
277 $type = $param->getType();
278
279 if ( $type === null || $type->isBuiltin() ) {
280 if ( $param->isDefaultValueAvailable() ) {
281 $dependencies[] = $param->getDefaultValue();
282 } else {
283 throw new NotFoundException(
284 "Cannot resolve parameter {$param->getName()} for {$class}"
285 );
286 }
287 } else {
288 $typeName = $type instanceof \ReflectionNamedType ? $type->getName() : (string) $type;
289 $dependencies[] = $this->get( $typeName );
290 }
291 }
292
293 return $reflector->newInstanceArgs( $dependencies );
294 }
295
296 /**
297 * Log a debug message.
298 *
299 * @param string $message The message.
300 * @param array $context The context.
301 *
302 * @return void
303 */
304 private function log( string $message, array $context = array() ): void {
305 if ( $this->logger ) {
306 $this->logger->debug(
307 $message,
308 array_merge(
309 array(
310 'plugin' => 'double-opt-in',
311 'component' => 'container',
312 ),
313 $context
314 )
315 );
316 }
317 }
318 }
319