├── .editorconfig
├── README.md
├── composer.json
├── pint.json
└── src
├── Container.php
├── ContainerInterface.php
└── ServiceProviderInterface.php
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | indent_style = space
5 | end_of_line = lf
6 | charset = utf-8
7 | trim_trailing_whitespace = true
8 | insert_final_newline = true
9 |
10 | [*.{js,py,css,vue}]
11 | indent_size = 4
12 |
13 | [*.md]
14 | trim_trailing_whitespace = false
15 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
A simple dependency injecting container from laravel.
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 | # Why
12 |
13 | Currently more popular `php` container:
14 |
15 | - [Pimple](https://pimple.symfony.com/)
16 | - [Laravel Container](https://github.com/illuminate/container)
17 | - [Other Dependency-injection Container](https://github.com/ziadoz/awesome-php#dependency-injection)
18 |
19 | `Pimple` is a simple and excellent `php 5.3` container, which is also the most used service container, and the installed capacity of [packagist](https://packagist.org/packages/pimple/pimple) is also up to `1000W+` .But `Pimple` just a simple service container that does not support many features such as:
20 |
21 | ```php
22 | class Cache
23 | {
24 | public function __construct(Config $config){}
25 | }
26 |
27 | class Config
28 | {
29 | }
30 |
31 | // not support
32 | $cache = $container->make('Cache');
33 | ```
34 |
35 | > Pimple Does not support the automatic injection of dependency parameters, when you need to rely on other objects object, you can only instantiate the required parameters.
36 |
37 | `Laravel Container` is the most full-featured service container, including auto-injection, load-loading, alias, TAG, and so so. But the official does not recommend using the component in non-laravel project.
38 |
39 | > If you have noticed the `composer.json` file under that component,You will find that he depends on the [illuminate/contracts](https://github.com/illuminate/contracts) component.([see also](https://github.com/laravel/framework/issues/21435))
40 |
41 | Based on this, [easy-container](https://github.com/godruoyi/easy-container) was born, and the project code relied heavily on [Laravel Container](https://github.com/illuminate/container) :smile: :smile: . You can use it like a `Laravel Container` container.
42 |
43 | # Install
44 |
45 | | SDK Version | PHP Version | Composer Command |
46 | |-------------|-------------|------------------------------------------|
47 | | 3.x | >= 8.1 | `composer require "easy-container:^3.0"` |
48 | | 2.x | >= 7.2 | `composer require "easy-container:^2.1"` |
49 | | 1.x | >= 5.6 | `composer require "easy-container:^2.1"` |
50 |
51 |
52 | # Use
53 |
54 | You can get more help with [container usage](https://laravel.com/docs/5.5/container) at [laravel.com](https://laravel.com).
55 |
56 | Initialize the container.
57 |
58 | ```php
59 | $app = new Godruoyi\Container\Container;
60 | ```
61 |
62 | > The following documents support from [laravel.com](https://laravel.com/docs/5.5/container), reproduced please indicate the source.
63 |
64 | #### Simple Bindings
65 |
66 | We can register a binding using the `bind` method, passing the class or interface name that we wish to register along with a `Closure` that returns an instance of the class:
67 |
68 | ```php
69 | $app->bind('HelpSpot\API', function ($app) {
70 | return new HelpSpot\API($app->make('HttpClient'));
71 | });
72 | ```
73 |
74 | > Note,All anonymous functions accept the service container instance as a parameter.
75 |
76 | #### Binding A Singleton
77 |
78 | The `singleton` method binds a class or interface into the container that should only be resolved one time. Once a singleton binding is resolved, the same object instance will be returned on subsequent calls into the container:
79 |
80 | ```php
81 | $app->singleton('HelpSpot\API', function ($app) {
82 | return new HelpSpot\API($app->make('HttpClient'));
83 | });
84 | ```
85 |
86 | > Each time you call `$app['HelpSpot\API']` will return the same object.
87 |
88 | #### Binding A Singleton
89 |
90 | The `singleton` method binds a class or interface into the container that should only be resolved one time. Once a singleton binding is resolved, the same object instance will be returned on subsequent calls into the container:
91 |
92 | $api = new HelpSpot\API(new HttpClient);
93 |
94 | $app->instance('HelpSpot\API', $api);
95 |
96 | ### Binding Interfaces To Implementations
97 |
98 | A very powerful feature of the service container is its ability to bind an interface to a given implementation. For example, let's assume we have an `EventPusher` interface and a `RedisEventPusher` implementation. Once we have coded our `RedisEventPusher` implementation of this interface, we can register it with the service container like so:
99 |
100 | $app->bind(
101 | 'App\Contracts\EventPusher',
102 | 'App\Services\RedisEventPusher'
103 | );
104 |
105 | This statement tells the container that it should inject the `RedisEventPusher` when a class needs an implementation of `EventPusher`. Now we can type-hint the `EventPusher` interface in a constructor, or any other location where dependencies are injected by the service container:
106 |
107 | use App\Contracts\EventPusher;
108 |
109 | /**
110 | * Create a new instance of the class, which will be injected into the App\Services\RedisEventPusher instance.
111 | *
112 | * @param EventPusher $pusher
113 | * @return void
114 | */
115 | public function __construct(EventPusher $pusher)
116 | {
117 | $this->pusher = $pusher;
118 | }
119 |
120 | ## Resolving
121 |
122 | #### The `make` Method
123 |
124 | You may use the `make` method to resolve a class instance out of the container(regardless of what type of parameter the object needs). The `make` method accepts the name of the class or interface you wish to resolve:
125 |
126 | $api = $app->make('HelpSpot\API');
127 |
128 | The `mark` method is the most important method I think of,You can simply use the "type prompt" way to add dependencies,the container will automatically parse all the parameters you need.
129 |
130 | ```php
131 |
132 | // Automatically parses the dependencies required by the UserController constructor
133 | $userController = $app->make(UserController::class);
134 |
135 | class UserController
136 | {
137 | public function __construct(UserRepository $users, HttpClient $client, $other = 'default')
138 | {
139 | }
140 | }
141 |
142 | ```
143 |
144 | ## PSR-11
145 |
146 | Laravel's service container implements the PSR-11 interface. Therefore, you may type-hint the PSR-11 container interface to obtain an instance of the Laravel container:
147 |
148 | use Psr\Container\ContainerInterface;
149 |
150 | $service = $app->get('Service');
151 |
152 | # LISTEN
153 |
154 | MIT
155 |
156 | # Thanks
157 |
158 | [laravel-china](https://laravel.com)
159 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "godruoyi/easy-container",
3 | "description": "A small PHP 5.3 dependency injection container extended from Laravel container.",
4 | "keywords": ["dependency injection", "container", "ioc", "ioc-container", "laravel container", "laravel"],
5 | "homepage": "https://github.com/godruoyi/easy-container",
6 | "type": "library",
7 | "license": "MIT",
8 | "authors": [{
9 | "name": "godruoyi",
10 | "email": "godruoyi@gmail.com"
11 | }],
12 | "require": {
13 | "php": ">=8.1",
14 | "psr/container": "^2.0"
15 | },
16 | "autoload": {
17 | "psr-4": {
18 | "Godruoyi\\Container\\": "src/"
19 | }
20 | },
21 | "require-dev": {
22 | "phpunit/phpunit": "^10.0"
23 | },
24 | "autoload-dev": {
25 | "psr-4": {
26 | "Tests\\": "tests/"
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/pint.json:
--------------------------------------------------------------------------------
1 | {
2 | "rules": {
3 | "header_comment": {
4 | "header": "This file is part of the godruoyi/easy-container.\n \n(c) Godruoyi \n \nThis source file is subject to the MIT license that is bundled."
5 | },
6 | "no_superfluous_phpdoc_tags": false
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/src/Container.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * This source file is subject to the MIT license that is bundled.
9 | */
10 |
11 | namespace Godruoyi\Container;
12 |
13 | use ArrayAccess;
14 | use Closure;
15 | use Exception;
16 | use InvalidArgumentException;
17 | use ReflectionClass;
18 | use ReflectionException;
19 | use ReflectionFunction;
20 | use ReflectionIntersectionType;
21 | use ReflectionMethod;
22 | use ReflectionNamedType;
23 | use ReflectionParameter;
24 | use ReflectionUnionType;
25 |
26 | class Container implements ArrayAccess, ContainerInterface
27 | {
28 | /**
29 | * The Container instance.
30 | *
31 | * @var static
32 | */
33 | protected static Container $instance;
34 |
35 | /**
36 | * An array of objects that have been resolved in the container.
37 | *
38 | * @var array
39 | */
40 | protected array $resolved = [];
41 |
42 | /**
43 | * Bind array objects in the container.
44 | *
45 | * @var array
46 | */
47 | protected array $bindings = [];
48 |
49 | /**
50 | * An array for instance objects.
51 | *
52 | * @var array
53 | */
54 | protected array $instances = [];
55 |
56 | /**
57 | * Alias array.
58 | *
59 | * for example:
60 | *
61 | * db => \Laravel\DB::class
62 | *
63 | * @var array
64 | */
65 | protected array $aliases = [];
66 |
67 | /**
68 | * An array for object extends array.
69 | *
70 | * @var array
71 | */
72 | protected array $extenders = [];
73 |
74 | /**
75 | * Rebound callback list.
76 | *
77 | * @var array
78 | */
79 | protected array $reboundCallbacks = [];
80 |
81 | /**
82 | * Has bound in this container for grieved abstract.
83 | *
84 | * @param string $abstract
85 | * @return bool
86 | */
87 | public function bound(string $abstract): bool
88 | {
89 | $abstract = $this->normalize($abstract);
90 |
91 | return isset($this->bindings[$abstract]) || isset($this->instances[$abstract]) || $this->isAlias($abstract);
92 | }
93 |
94 | /**
95 | * Set alias for abstract.
96 | *
97 | * @param string $abstract
98 | * @param string $alias
99 | * @return void
100 | *
101 | * @throws Exception
102 | */
103 | public function alias(string $abstract, string $alias): void
104 | {
105 | if ($alias === $abstract) {
106 | throw new Exception("[$abstract] is aliased to itself.");
107 | }
108 |
109 | $this->aliases[$alias] = $this->normalize($abstract);
110 | }
111 |
112 | /**
113 | * {@inheritdoc}
114 | */
115 | public function has(string $id): bool
116 | {
117 | return $this->offsetExists($id);
118 | }
119 |
120 | /**
121 | * {@inheritdoc}
122 | */
123 | public function get(string $id)
124 | {
125 | return $this[$id];
126 | }
127 |
128 | /**
129 | * Bind a class to container.
130 | *
131 | * @param string|array $abstract
132 | * @param Closure|string|null $concrete
133 | * @param bool $shared true set is single instance
134 | * @return void
135 | *
136 | * @throws Exception
137 | */
138 | public function bind($abstract, $concrete = null, bool $shared = false): void
139 | {
140 | $abstract = $this->normalize($abstract);
141 |
142 | $concrete = $this->normalize($concrete);
143 |
144 | if (is_array($abstract)) {
145 | [$abstract, $alias] = $this->extractAlias($abstract);
146 |
147 | $this->alias($abstract, $alias);
148 | }
149 |
150 | $this->dropStaleInstances($abstract);
151 |
152 | if (is_null($concrete)) {
153 | $concrete = $abstract;
154 | }
155 |
156 | if (! $concrete instanceof Closure) {
157 | $concrete = $this->getClosure($abstract, $concrete);
158 | }
159 |
160 | $this->bindings[$abstract] = compact('concrete', 'shared');
161 |
162 | if ($this->resolved($abstract)) {
163 | $this->rebound($abstract);
164 | }
165 | }
166 |
167 | /**
168 | * Register a binding if it hasn't already been registered.
169 | *
170 | * @param string $abstract
171 | * @param Closure|string|null $concrete
172 | * @param bool $shared
173 | * @return void
174 | *
175 | * @throws Exception
176 | */
177 | public function bindIf(string $abstract, $concrete = null, bool $shared = false): void
178 | {
179 | if (! $this->bound($abstract)) {
180 | $this->bind($abstract, $concrete, $shared);
181 | }
182 | }
183 |
184 | /**
185 | * Register a shared binding in the container.
186 | *
187 | * @param string|array $abstract
188 | * @param Closure|string|null $concrete
189 | * @return void
190 | *
191 | * @throws Exception
192 | */
193 | public function singleton($abstract, $concrete = null): void
194 | {
195 | $this->bind($abstract, $concrete, true);
196 | }
197 |
198 | /**
199 | * "Extend" an abstract type in the container.
200 | *
201 | * @param string $abstract
202 | * @param Closure $closure
203 | * @return void
204 | *
205 | * @throws InvalidArgumentException
206 | * @throws Exception
207 | */
208 | public function extend(string $abstract, Closure $closure): void
209 | {
210 | $abstract = $this->normalize($abstract);
211 |
212 | if (isset($this->instances[$abstract])) {
213 | $this->instances[$abstract] = $closure($this->instances[$abstract], $this);
214 |
215 | $this->rebound($abstract);
216 | } else {
217 | $this->extenders[$abstract][] = $closure;
218 | }
219 | }
220 |
221 | /**
222 | * Call the given Closure / class@method and inject its dependencies.
223 | *
224 | * @param callable|string $callback
225 | * @param array $parameters
226 | * @param string|null $defaultMethod
227 | * @return mixed
228 | *
229 | * @throws Exception
230 | */
231 | public function call($callback, array $parameters = [], string $defaultMethod = null): mixed
232 | {
233 | if ($this->isCallableWithAtSign($callback) || $defaultMethod) {
234 | return $this->callClass($callback, $parameters, $defaultMethod);
235 | }
236 |
237 | $dependencies = $this->getMethodDependencies($callback, $parameters);
238 |
239 | return call_user_func_array($callback, $dependencies);
240 | }
241 |
242 | /**
243 | * Register an existing instance as shared in the container.
244 | *
245 | * @param string $abstract
246 | * @param mixed $instance
247 | * @return void
248 | *
249 | * @throws Exception
250 | */
251 | public function instance(string $abstract, mixed $instance): void
252 | {
253 | $abstract = $this->normalize($abstract);
254 |
255 | if (is_array($abstract)) {
256 | [$abstract, $alias] = $this->extractAlias($abstract);
257 |
258 | $this->alias($abstract, $alias);
259 | }
260 |
261 | unset($this->aliases[$abstract]);
262 |
263 | $bound = $this->bound($abstract);
264 |
265 | $this->instances[$abstract] = $instance;
266 |
267 | if ($bound) {
268 | $this->rebound($abstract);
269 | }
270 | }
271 |
272 | /**
273 | * Make a instance.
274 | *
275 | * @param string $abstract
276 | * @param array $parameters
277 | * @return mixed
278 | *
279 | * @throws Exception
280 | */
281 | public function make(string $abstract, array $parameters = []): mixed
282 | {
283 | $abstract = $this->getAlias($this->normalize($abstract));
284 |
285 | if (isset($this->instances[$abstract]) && ! is_null($this->instances[$abstract])) {
286 | return $this->instances[$abstract];
287 | }
288 |
289 | $concrete = $this->getConcrete($abstract);
290 |
291 | if ($this->isBuildable($concrete, $abstract)) {
292 | $object = $this->build($concrete, $parameters);
293 | } else {
294 | $object = $this->make($concrete, $parameters);
295 | }
296 |
297 | foreach ($this->getExtenders($abstract) as $extender) {
298 | $object = $extender($object, $this);
299 | }
300 |
301 | if ($this->isShared($abstract)) {
302 | $this->instances[$abstract] = $object;
303 | }
304 |
305 | $this->resolved[$abstract] = true;
306 |
307 | return $object;
308 | }
309 |
310 | /**
311 | * Build a instance for given class.
312 | *
313 | * @param mixed $concrete
314 | * @param array $parameters
315 | * @return mixed
316 | *
317 | * @throws ReflectionException
318 | * @throws Exception
319 | */
320 | public function build(mixed $concrete, array $parameters = []): mixed
321 | {
322 | if ($concrete instanceof Closure) {
323 | return $concrete($this, $parameters);
324 | }
325 |
326 | $reflector = new ReflectionClass($concrete);
327 |
328 | if (! $reflector->isInstantiable()) {
329 | throw new Exception("Target [$concrete] is not instantiable");
330 | }
331 |
332 | $constructor = $reflector->getConstructor();
333 |
334 | if (is_null($constructor)) {
335 | return new $concrete();
336 | }
337 |
338 | $dependencies = $constructor->getParameters();
339 |
340 | $parameters = $this->keyParametersByArgument(
341 | $dependencies,
342 | $parameters
343 | );
344 |
345 | $instances = $this->getDependencies(
346 | $dependencies,
347 | $parameters
348 | );
349 |
350 | return $reflector->newInstanceArgs($instances);
351 | }
352 |
353 | /**
354 | * Check abstract has resolved.
355 | *
356 | * @param string $abstract
357 | * @return bool
358 | */
359 | public function resolved(string $abstract): bool
360 | {
361 | $abstract = $this->normalize($abstract);
362 |
363 | if ($this->isAlias($abstract)) {
364 | $abstract = $this->getAlias($abstract);
365 | }
366 |
367 | return isset($this->resolved[$abstract]) || isset($this->instances[$abstract]);
368 | }
369 |
370 | /**
371 | * Determine if the given string is in Class@method syntax.
372 | *
373 | * @param mixed $callback
374 | * @return bool
375 | */
376 | protected function isCallableWithAtSign(mixed $callback): bool
377 | {
378 | return is_string($callback) && str_contains($callback, '@');
379 | }
380 |
381 | /**
382 | * Get all dependencies for a given method.
383 | *
384 | * @param callable|string $callback
385 | * @param array $parameters
386 | * @return array
387 | *
388 | * @throws ReflectionException
389 | * @throws Exception
390 | */
391 | protected function getMethodDependencies(mixed $callback, array $parameters = []): array
392 | {
393 | $dependencies = [];
394 |
395 | foreach ($this->getCallReflector($callback)->getParameters() as $parameter) {
396 | $this->addDependencyForCallParameter($parameter, $parameters, $dependencies);
397 | }
398 |
399 | return array_merge($dependencies, $parameters);
400 | }
401 |
402 | /**
403 | * Get the proper reflection instance for the given callback.
404 | *
405 | * @param callable|string $callback
406 | * @return ReflectionMethod|ReflectionFunction
407 | *
408 | * @throws ReflectionException
409 | */
410 | protected function getCallReflector(mixed $callback): ReflectionMethod|ReflectionFunction
411 | {
412 | if (is_string($callback) && str_contains($callback, '::')) {
413 | $callback = explode('::', $callback);
414 | }
415 |
416 | if (is_array($callback)) {
417 | return new ReflectionMethod($callback[0], $callback[1]);
418 | }
419 |
420 | return new ReflectionFunction($callback);
421 | }
422 |
423 | /**
424 | * Get the dependency for the given call parameter.
425 | *
426 | * @param ReflectionParameter $parameter
427 | * @param array $parameters
428 | * @param array $dependencies
429 | * @return void
430 | *
431 | * @throws Exception
432 | */
433 | protected function addDependencyForCallParameter(ReflectionParameter $parameter, array &$parameters, array &$dependencies): void
434 | {
435 | if (array_key_exists($parameter->name, $parameters)) {
436 | $dependencies[] = $parameters[$parameter->name];
437 |
438 | unset($parameters[$parameter->name]);
439 | } elseif ($this->getParameterClass($parameter)) {
440 | $dependencies[] = $this->make($this->getParameterClass($parameter)->getName());
441 | } elseif ($parameter->isDefaultValueAvailable()) {
442 | $dependencies[] = $parameter->getDefaultValue();
443 | }
444 | }
445 |
446 | /**
447 | * Compatible with PHP 5.3.
448 | *
449 | * @param ReflectionParameter $p
450 | * @return ReflectionIntersectionType|ReflectionNamedType|ReflectionUnionType|null
451 | */
452 | protected function getParameterClass(ReflectionParameter $p)
453 | {
454 | return $p->getType();
455 | }
456 |
457 | /**
458 | * Call a string reference to a class using Class@method syntax.
459 | *
460 | * @param string $target
461 | * @param array $parameters
462 | * @param string|null $defaultMethod
463 | * @return mixed
464 | *
465 | * @throws Exception
466 | */
467 | protected function callClass(string $target, array $parameters = [], string $defaultMethod = null): mixed
468 | {
469 | $segments = explode('@', $target);
470 |
471 | // If the listener has an @ sign, we will assume it is being used to delimit
472 | // the class name from the handle method name. This allows for handlers
473 | // to run multiple handler methods in a single class for convenience.
474 | $method = count($segments) == 2 ? $segments[1] : $defaultMethod;
475 |
476 | if (is_null($method)) {
477 | throw new Exception('Method not provided.');
478 | }
479 |
480 | return $this->call([$this->make($segments[0]), $method], $parameters);
481 | }
482 |
483 | /**
484 | * Normalized service name.
485 | *
486 | * @param mixed $service
487 | * @return mixed
488 | */
489 | protected function normalize(mixed $service): mixed
490 | {
491 | return is_string($service) ? ltrim($service, '\\') : $service;
492 | }
493 |
494 | /**
495 | * Check name has a alias in container.
496 | *
497 | * @param string $name
498 | * @return bool
499 | */
500 | public function isAlias(string $name): bool
501 | {
502 | return isset($this->aliases[$this->normalize($name)]);
503 | }
504 |
505 | /**
506 | * Get alias.
507 | *
508 | * @param array $alias
509 | * @return array
510 | */
511 | protected function extractAlias(array $alias): array
512 | {
513 | return [key($alias), current($alias)];
514 | }
515 |
516 | /**
517 | * Delete instance in container.
518 | *
519 | * @param string $abstract
520 | * @return void
521 | */
522 | protected function dropStaleInstances(string $abstract): void
523 | {
524 | unset($this->instances[$abstract], $this->aliases[$abstract]);
525 | }
526 |
527 | /**
528 | * @param string $abstract
529 | * @param string $concrete
530 | * @return Closure
531 | */
532 | protected function getClosure(string $abstract, string $concrete): Closure
533 | {
534 | return function ($container, array $parameters = []) use ($abstract, $concrete) {
535 | $method = ($abstract == $concrete) ? 'build' : 'make';
536 |
537 | return $container->$method($concrete, $parameters);
538 | };
539 | }
540 |
541 | /**
542 | * Get alias for given abstract if available.
543 | *
544 | * @param string $abstract
545 | * @return string
546 | */
547 | public function getAlias(string $abstract): string
548 | {
549 | if (! isset($this->aliases[$abstract])) {
550 | return $abstract;
551 | }
552 |
553 | return $this->getAlias($this->aliases[$abstract]);
554 | }
555 |
556 | /**
557 | * Rebind abstract to container.
558 | *
559 | * @param string $abstract
560 | * @return void
561 | *
562 | * @throws Exception
563 | */
564 | protected function rebound(string $abstract): void
565 | {
566 | $instance = $this->make($abstract);
567 |
568 | foreach ($this->getReboundCallbacks($abstract) as $callback) {
569 | call_user_func($callback, $this, $instance);
570 | }
571 | }
572 |
573 | /**
574 | * Get abstract extender.
575 | *
576 | * @param string $abstract
577 | * @return array
578 | */
579 | protected function getExtenders(string $abstract): array
580 | {
581 | if (isset($this->extenders[$abstract])) {
582 | return $this->extenders[$abstract];
583 | }
584 |
585 | return [];
586 | }
587 |
588 | /**
589 | * Check abstract is shared in container.
590 | *
591 | * @param string $abstract
592 | * @return bool
593 | */
594 | protected function isShared(string $abstract): bool
595 | {
596 | $abstract = $this->normalize($abstract);
597 |
598 | if (isset($this->instances[$abstract])) {
599 | return true;
600 | }
601 |
602 | if (! isset($this->bindings[$abstract]['shared'])) {
603 | return false;
604 | }
605 |
606 | return $this->bindings[$abstract]['shared'] === true;
607 | }
608 |
609 | /**
610 | * Get abstract type in container.
611 | *
612 | * @param string $abstract
613 | * @return mixed
614 | */
615 | public function getConcrete(string $abstract): mixed
616 | {
617 | if (! isset($this->bindings[$abstract])) {
618 | return $abstract;
619 | }
620 |
621 | return $this->bindings[$abstract]['concrete'];
622 | }
623 |
624 | /**
625 | * Check abstract and concrete has buildable.
626 | *
627 | * @param mixed $concrete
628 | * @param string $abstract
629 | * @return bool
630 | */
631 | public function isBuildable(mixed $concrete, string $abstract): bool
632 | {
633 | return $concrete === $abstract || $concrete instanceof Closure;
634 | }
635 |
636 | /**
637 | * Get Rebound Callbacks.
638 | *
639 | * @param string $abstract
640 | * @return array
641 | */
642 | public function getReboundCallbacks(string $abstract): array
643 | {
644 | if (isset($this->reboundCallbacks[$abstract])) {
645 | return $this->reboundCallbacks[$abstract];
646 | }
647 |
648 | return [];
649 | }
650 |
651 | /**
652 | * $app->build('Some\Class', [params..]).
653 | *
654 | * @param array $dependencies
655 | * @param array $parameters
656 | * @return array
657 | */
658 | public function keyParametersByArgument(array $dependencies, array $parameters): array
659 | {
660 | foreach ($parameters as $key => $value) {
661 | if (is_numeric($key)) {
662 | unset($parameters[$key]);
663 |
664 | $parameters[$dependencies[$key]->name] = $value;
665 | }
666 | }
667 |
668 | return $parameters;
669 | }
670 |
671 | /**
672 | * Get dependencies.
673 | *
674 | * @param array $dependencies
675 | * @param array $parameters
676 | * @return array
677 | *
678 | * @throws Exception
679 | */
680 | protected function getDependencies(array $dependencies, array $parameters): array
681 | {
682 | $dependenciesArr = [];
683 |
684 | foreach ($dependencies as $parameter) {
685 | $dependency = $this->getParameterClass($parameter);
686 |
687 | if (array_key_exists($parameter->name, $parameters)) {
688 | $dependenciesArr[] = $parameters[$parameter->name];
689 | } elseif (is_null($dependency)) {
690 | $dependenciesArr[] = $this->resolveNonClass($parameter);
691 | } else {
692 | $dependenciesArr[] = $this->resolveClass($parameter);
693 | }
694 | }
695 |
696 | return $dependenciesArr;
697 | }
698 |
699 | /**
700 | * Resolve no class construct.
701 | *
702 | * @param ReflectionParameter $parameter
703 | * @return mixed
704 | *
705 | * @throws Exception
706 | */
707 | protected function resolveNonClass(ReflectionParameter $parameter): mixed
708 | {
709 | if ($parameter->isDefaultValueAvailable()) {
710 | return $parameter->getDefaultValue();
711 | }
712 |
713 | $message = "Unresolvable dependency resolving [$parameter] in class {$parameter->getDeclaringClass()->getName()}";
714 |
715 | throw new Exception($message);
716 | }
717 |
718 | /**
719 | * Resolve Reflection Parameter.
720 | *
721 | * @param ReflectionParameter $parameter
722 | * @return mixed
723 | *
724 | * @throws ReflectionException
725 | */
726 | protected function resolveClass(ReflectionParameter $parameter): mixed
727 | {
728 | try {
729 | return $this->make($this->getParameterClass($parameter)->getName());
730 | } catch (Exception $e) {
731 | if ($parameter->isOptional()) {
732 | return $parameter->getDefaultValue();
733 | }
734 |
735 | throw $e;
736 | }
737 | }
738 |
739 | /**
740 | * Determine if a given offset exists.
741 | *
742 | * @param string $offset
743 | * @return bool
744 | */
745 | public function offsetExists(mixed $offset): bool
746 | {
747 | return $this->bound($offset);
748 | }
749 |
750 | /**
751 | * Get the value at a given offset.
752 | *
753 | * @param string $offset
754 | * @return mixed
755 | *
756 | * @throws Exception
757 | */
758 | public function offsetGet($offset): mixed
759 | {
760 | return $this->make($offset);
761 | }
762 |
763 | /**
764 | * Set the value at a given offset.
765 | *
766 | * @param string $offset
767 | * @param mixed $value
768 | * @return void
769 | *
770 | * @throws Exception
771 | */
772 | public function offsetSet(mixed $offset, mixed $value): void
773 | {
774 | // If the value is not a Closure, we will make it one. This simply gives
775 | // more "drop-in" replacement functionality for the Pimple which this
776 | // container's simplest functions are base modeled and built after.
777 | if (! $value instanceof Closure) {
778 | $value = function () use ($value) {
779 | return $value;
780 | };
781 | }
782 |
783 | $this->bind($offset, $value);
784 | }
785 |
786 | /**
787 | * Unset the value at a given offset.
788 | *
789 | * @param string $offset
790 | * @return void
791 | */
792 | public function offsetUnset(mixed $offset): void
793 | {
794 | $key = $this->normalize($offset);
795 |
796 | unset($this->bindings[$key], $this->instances[$key], $this->resolved[$key]);
797 | }
798 |
799 | /**
800 | * Dynamically access container services.
801 | *
802 | * @param string $key
803 | * @return mixed
804 | */
805 | public function __get(string $key)
806 | {
807 | return $this[$key];
808 | }
809 |
810 | /**
811 | * Dynamically set container services.
812 | *
813 | * @param string $key
814 | * @param mixed $value
815 | * @return void
816 | */
817 | public function __set(string $key, mixed $value)
818 | {
819 | $this[$key] = $value;
820 | }
821 | }
822 |
--------------------------------------------------------------------------------
/src/ContainerInterface.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * This source file is subject to the MIT license that is bundled.
9 | */
10 |
11 | namespace Godruoyi\Container;
12 |
13 | use Closure;
14 | use InvalidArgumentException;
15 | use Psr\Container\ContainerInterface as BaseContainerInterface;
16 |
17 | interface ContainerInterface extends BaseContainerInterface
18 | {
19 | /**
20 | * Determine if the given abstract type has been bound.
21 | *
22 | * @param string $abstract
23 | * @return bool
24 | */
25 | public function bound(string $abstract): bool;
26 |
27 | /**
28 | * Alias a type to a different name.
29 | *
30 | * @param string $abstract
31 | * @param string $alias
32 | * @return void
33 | */
34 | public function alias(string $abstract, string $alias): void;
35 |
36 | /**
37 | * Register a binding with the container.
38 | *
39 | * @param string|array $abstract
40 | * @param Closure|string|null $concrete
41 | * @param bool $shared
42 | * @return void
43 | */
44 | public function bind($abstract, $concrete = null, bool $shared = false): void;
45 |
46 | /**
47 | * Register a binding if it hasn't already been registered.
48 | *
49 | * @param string $abstract
50 | * @param Closure|string|null $concrete
51 | * @param bool $shared
52 | * @return void
53 | */
54 | public function bindIf(string $abstract, mixed $concrete = null, bool $shared = false): void;
55 |
56 | /**
57 | * Register a shared binding in the container.
58 | *
59 | * @param string|array $abstract
60 | * @param Closure|string|null $concrete
61 | * @return void
62 | */
63 | public function singleton($abstract, $concrete = null): void;
64 |
65 | /**
66 | * "Extend" an abstract type in the container.
67 | *
68 | * @param string $abstract
69 | * @param Closure $closure
70 | * @return void
71 | *
72 | * @throws InvalidArgumentException
73 | */
74 | public function extend(string $abstract, Closure $closure): void;
75 |
76 | /**
77 | * Register an existing instance as shared in the container.
78 | *
79 | * @param string $abstract
80 | * @param mixed $instance
81 | * @return void
82 | */
83 | public function instance(string $abstract, mixed $instance): void;
84 |
85 | /**
86 | * Resolve the given type from the container.
87 | *
88 | * @param string $abstract
89 | * @param array $parameters
90 | * @return mixed
91 | */
92 | public function make(string $abstract, array $parameters = []): mixed;
93 |
94 | /**
95 | * Call the given Closure / class@method and inject its dependencies.
96 | *
97 | * @param callable|string $callback
98 | * @param array $parameters
99 | * @param string|null $defaultMethod
100 | * @return mixed
101 | */
102 | public function call($callback, array $parameters = [], string $defaultMethod = null): mixed;
103 |
104 | /**
105 | * Determine if the given abstract type has been resolved.
106 | *
107 | * @param string $abstract
108 | * @return bool
109 | */
110 | public function resolved(string $abstract): bool;
111 | }
112 |
--------------------------------------------------------------------------------
/src/ServiceProviderInterface.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * This source file is subject to the MIT license that is bundled.
9 | */
10 |
11 | namespace Godruoyi\Container;
12 |
13 | /**
14 | * service provider interface.
15 | *
16 | * @author Godruoyi
17 | */
18 | interface ServiceProviderInterface
19 | {
20 | /**
21 | * Registers services on the given container.
22 | *
23 | * This method should only be used to configure services and parameters.
24 | * It should not get services.
25 | *
26 | * @param Container $container A container instance
27 | */
28 | public function register(ContainerInterface $container);
29 | }
30 |
--------------------------------------------------------------------------------