PluginProbe
ReCrawler / 1.1.0
ReCrawler v1.1.0
1.1.0 1.0.2 1.0.1 1.0.0 0.3.4 0.3.3 0.1.1 0.1.2 0.1.2.1 0.1.2.2 0.1.3 0.1.4 0.1.5 0.2.0 0.3.0 0.3.1 0.3.2 trunk 0.1.0 0.1.0.1
recrawler / src / Container.php

Container.php in ReCrawler 1.1.0, at src/Container.php

215 lines 5.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php // phpcs:ignoreFile
2 /**
3 * Simple PHP DIC - DI Container in one file for WordPress.
4 * Supports autowiring and allows you to easily use it in your simple PHP applications and
5 * especially convenient for WordPress plugins and themes.
6 *
7 * Author: Andrei Pisarevskii
8 * Author Email: renakdup@gmail.com
9 * Author Site: https://wp-yoda.com/en/
10 *
11 * Version: 1.1.0
12 * Source Code: https://github.com/renakdup/simple-php-dic
13 *
14 * Licence: MIT License
15 */
16
17 declare( strict_types=1 );
18
19 namespace Mihdan\ReCrawler;
20
21 use Closure;
22 use Exception;
23 use ReflectionClass;
24 use ReflectionException;
25 use ReflectionNamedType;
26 use ReflectionParameter;
27
28 use function array_key_exists;
29 use function class_exists;
30 use function is_string;
31
32 class Container {
33 /**
34 * @var mixed[]
35 */
36 protected array $services = [];
37
38 /**
39 * @var mixed[]
40 */
41 protected array $resolved = [];
42
43 /**
44 * @var array<string, ReflectionClass<object>>
45 */
46 protected array $reflection_cache = [];
47
48 public function __construct() {
49 // Auto-register the container
50 $this->resolved = [
51 self::class => $this,
52 ];
53 }
54
55 /**
56 * Set service to the container. Allows to set configurable services
57 * using factory "function () {}" as passed service.
58 *
59 * @param mixed $service
60 */
61 public function set( string $id, $service ): void {
62 $this->services[ $id ] = $service;
63 unset( $this->resolved[ $id ] );
64 unset( $this->reflection_cache[ $id ] );
65 }
66
67 /**
68 * Finds an entry of the container by its identifier and returns it.
69 *
70 * @param string $id Identifier of the entry to look for.
71 *
72 * @return mixed Entry.
73 *
74 * @throws Exception Error while retrieving the entry.
75 * || No entry was found for **this** identifier.
76 */
77 public function get( string $id ) {
78 if ( isset( $this->resolved[ $id ] ) || array_key_exists( $id, $this->resolved ) ) {
79 return $this->resolved[ $id ];
80 }
81
82 $service = $this->resolve( $id );
83
84 $this->resolved[ $id ] = $service;
85
86 return $service;
87 }
88
89 /**
90 * Returns true if the container can return an entry for the given identifier.
91 * Returns false otherwise.
92 *
93 * `has($id)` returning true does not mean that `get($id)` will not throw an exception.
94 * It does however mean that `get($id)` will not throw a `NotFoundExceptionInterface`.
95 *
96 * @param string $id Identifier of the entry to look for.
97 *
98 * @return bool
99 */
100 public function has( string $id ): bool {
101 return array_key_exists( $id, $this->services );
102 }
103
104 /**
105 * Resolves service by its name. It returns a new instance of service every time, but the constructor's
106 * dependencies will not instantiate every time. If dependencies were resolved before
107 * then they will be passed as resolved dependencies.
108 *
109 * @throws Exception
110 */
111 public function make( string $id ): object {
112 if ( ! class_exists( $id ) ) {
113 $message = "Service `{$id}` could not be resolved because class not exist.";
114 throw new Exception( $message );
115 }
116
117 return $this->resolve_class( $id );
118 }
119
120 /**
121 * @return mixed
122 * @throws Exception
123 */
124 protected function resolve( string $id ) {
125 if ( $this->has( $id ) ) {
126 $service = $this->services[ $id ];
127
128 if ( $service instanceof Closure ) {
129 return $service( $this );
130 } elseif ( is_string( $service ) && class_exists( $service ) ) {
131 return $this->resolve_class( $service );
132 }
133
134 return $service;
135 }
136
137 if ( class_exists( $id ) ) {
138 return $this->resolve_class( $id );
139 }
140
141 throw new Exception( "Service `{$id}` not found in the Container." );
142 }
143
144 /**
145 * @param class-string $service
146 *
147 * @return object
148 * @throws Exception
149 */
150 protected function resolve_class( string $service ): object {
151 try {
152 $reflected_class = $this->reflection_cache[ $service ] ?? new ReflectionClass( $service );
153
154 $constructor = $reflected_class->getConstructor();
155
156 if ( ! $constructor ) {
157 return new $service();
158 }
159
160 $params = $constructor->getParameters();
161
162 if ( ! $params ) {
163 return new $service();
164 }
165
166 $resolved_params = $this->resolve_parameters( $params );
167 } catch ( ReflectionException $e ) {
168 throw new Exception(
169 "Service `{$service}` could not be resolved due the reflection issue: `{$e->getMessage()}`"
170 );
171 }
172
173 return new $service( ...$resolved_params );
174 }
175
176 /**
177 * @param ReflectionParameter[] $params
178 *
179 * @return mixed[]
180 * @throws Exception
181 * @throws ReflectionException
182 */
183 protected function resolve_parameters( array $params ): array {
184 $resolved_params = [];
185 foreach ( $params as $param ) {
186 $resolved_params[] = $this->resolve_param( $param );
187 }
188
189 return $resolved_params;
190 }
191
192 /**
193 * @param ReflectionParameter $param
194 *
195 * @return mixed|object
196 * @throws Exception
197 * @throws ReflectionException
198 */
199 protected function resolve_param( ReflectionParameter $param ) {
200 $param_type = $param->getType();
201
202 if ( $param_type instanceof ReflectionNamedType && ! $param_type->isBuiltin() ) {
203 return $this->get( $param_type->getName() );
204 }
205
206 if ( $param->isOptional() ) {
207 return $param->getDefaultValue();
208 }
209
210 // @phpstan-ignore-next-line - Cannot call method getName() on ReflectionClass|null.
211 $message = "Parameter `{$param->getName()}` of `{$param->getDeclaringClass()->getName()}` can't be resolved.";
212 throw new Exception( $message );
213 }
214 }
215