├── .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 | styleci passed 5 | easy-container 6 | Latest Stable Version 7 | Total Downloads 8 | License 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 | --------------------------------------------------------------------------------