├── .idea ├── .gitignore ├── PluginMaster-DB.iml ├── misc.xml ├── modules.xml ├── php.xml └── vcs.xml ├── README.md ├── composer.json └── src ├── Base └── Container.php └── DependencyInjectionContainer.php /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /workspace.xml 3 | -------------------------------------------------------------------------------- /.idea/PluginMaster-DB.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/php.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Let's Create PHP Dependency Injection Container and Try to Learn How Laravel Initiate Controller and Method with Dependency and Parameter 2 | 3 | 4 | We are building application with [Laravel](https://laravel.com/) but many of us do not know how [Laravel](https://laravel.com/) Initiate Controller and Method with Dependency and Parameters. 5 | 6 | We declare route like: 7 | 8 | ```php 9 | Route::get('hello', 'TestController@index') 10 | ``` 11 | 12 | 13 | AND Our Controller Like: 14 | 15 | ```php 16 | 17 | namespace App\Http\Controllers\Test; 18 | 19 | 20 | class TestController extends Controller 21 | { 22 | private $requests; 23 | 24 | public function __construct( Request $request) 25 | { 26 | $this->requests = $request; 27 | } 28 | 29 | 30 | /** 31 | * Show the application dashboard. 32 | */ 33 | public function index(TestModel $testModel) 34 | { 35 | return $testModel->get(); 36 | } 37 | 38 | 39 | } 40 | 41 | ``` 42 | 43 | 44 | OR Route Like: 45 | 46 | ```php 47 | Route::get('hello', 'TestController') 48 | ``` 49 | 50 | AND Controller like: 51 | ```php 52 | 53 | namespace App\Http\Controllers\Test; 54 | 55 | 56 | class TestController extends Controller 57 | { 58 | private $requests; 59 | 60 | public function __construct( Request $request) 61 | { 62 | $this->requests = $request; 63 | } 64 | 65 | 66 | /** 67 | * Show the application dashboard. 68 | */ 69 | public function __invoke(TestModel $testModel) 70 | { 71 | return $testModel->get(); 72 | } 73 | 74 | 75 | } 76 | 77 | ``` 78 | 79 | **Now Question:** 80 | 81 | **1.1 How Laravel detect construct's Dependency and inject ?** 82 | 83 | **1.2 And How Laravel initiate class with construct's Dependencies and call the 'index' Method with It's Dependaencies and Parameters?** 84 | 85 | **2.1 We do not pass method name but How Laravel Detect __invoke method as default method?** 86 | 87 | Let's build an dependency injection container to understand previous Questions. 88 | 89 | 90 | ## Why we should know ? 91 | 92 | 1. As [Laravel](https://laravel.com/) Developer , We need to know How Laravel Work. 93 | 2. It will help us to build a new Framework in our own approach. 94 | 3. We can use this container for our large project or Module. 95 | 4. Finally , we will start learning [PHP Reflection](https://www.php.net/manual/en/book.reflection.php) (The Awesome) 96 | 97 | 98 | ## Let's Start 99 | 100 | **Our Steps** 101 | 1. Create a Class 102 | 2. Create a method (make) for initiating a class and injecting constructor's dependencies and parameters 103 | 3. create a method (call) for extracting class & method and injecting dependencies and parameters of method 104 | 105 | ##1. Let's Create a Class 106 | 107 | ```php 108 | class DependencyInjectionContainer 109 | { 110 | 111 | /** 112 | * The container's instance. 113 | * 114 | * @var static 115 | */ 116 | protected static $instance; 117 | 118 | 119 | /** 120 | * the class name with namespace 121 | * 122 | * @var string 123 | */ 124 | protected $callbackClass; 125 | 126 | /** 127 | * the method name of provided class 128 | * 129 | * @var string 130 | */ 131 | protected $callbackMethod; 132 | 133 | /** 134 | * method separator of a class. when pass class and method as string 135 | */ 136 | protected $methodSeparator = '@'; 137 | 138 | /** 139 | * namespace for class. when pass class and method as string 140 | * 141 | * @var string 142 | */ 143 | protected $namespace = "App\\controller\\"; 144 | 145 | 146 | /** 147 | * get Singleton instance of the class 148 | * 149 | * @return static 150 | */ 151 | public static function instance() 152 | { 153 | 154 | } 155 | 156 | /** 157 | * @param $callable -- string class and method name with separator @ 158 | * @param array $parameters 159 | */ 160 | public function call($callable, $parameters = []) 161 | { 162 | 163 | } 164 | 165 | 166 | /** 167 | * separate class and method name 168 | * @param $callback 169 | */ 170 | private function resolveCallback($callback) 171 | { 172 | 173 | } 174 | 175 | /** 176 | * instantiate class with dependency and return class instance 177 | * @param $class - class name 178 | * @param $parameters (optional) -- parameters as array . If constructor need any parameter 179 | */ 180 | public function make($class, $parameters = []) 181 | { 182 | } 183 | 184 | } 185 | ``` 186 | 187 | ##Create a method (make) for initiating a class and injecting constructor's dependencies and parameters 188 | 189 | We are going to use PHP [PHP Reflection](https://www.php.net/manual/en/book.reflection.php). In this section we use PHP [ReflactionClass](https://www.php.net/manual/en/class.reflectionclass.php) object.There are so many methods exist in PHP [ReflactionClass](https://www.php.net/manual/en/class.reflectionclass.php). 190 | In this section we need to make a class instance. 191 | We know that if we make instance of any class , we must pass/inject all dependencies & parameters. 192 | 193 | With PHP [ReflactionClass](https://www.php.net/manual/en/class.reflectionclass.php) we need to detect all dependencies. 194 | 195 | ```php 196 | public function make($class, $parameters = []) 197 | { 198 | $classReflection = new ReflectionClass($class); 199 | $constructorParams = $classReflection->getConstructor()->getParameters(); 200 | } 201 | ``` 202 | 203 | In this above code we collect all parameters for constructor to ```$constructorParams``` variable. 204 | 205 | Then we need to loop with ```$constructorParams``` and detect parameter and dependency. 206 | 207 | ```php 208 | public function make($class, $parameters = []) 209 | { 210 | 211 | $classReflection = new ReflectionClass($class); 212 | $constructorParams = $classReflection->getConstructor()->getParameters(); 213 | $dependencies = []; 214 | 215 | /* 216 | * loop with constructor parameters or dependency 217 | */ 218 | foreach ($constructorParams as $constructorParam) { 219 | 220 | $type = $constructorParam->getType(); 221 | 222 | if ($type && $type instanceof ReflectionNamedType) { 223 | 224 | echo "It is a class and we need to instantiate the class and pass to constructor" 225 | 226 | } else { 227 | 228 | echo "This is a normal parameter and We need to find parameter value from $parameters . If not found value then need to check is this parameter optional or not. If not optional then through error" 229 | 230 | } 231 | 232 | } 233 | } 234 | ``` 235 | Point: 236 | 1. if Parameter is a class, we need to instantiate the class and pass to constructor. 237 | 2. if Parameter is not a class, We need to find parameter value from $parameters . If not found value then need to check is this parameter optional or not. If not optional then through error. 238 | 239 | Lets Finalize the make method: 240 | 241 | ```php 242 | public function make($class, $parameters = []) 243 | { 244 | 245 | $classReflection = new ReflectionClass($class); 246 | $constructorParams = $classReflection->getConstructor()->getParameters(); 247 | $dependencies = []; 248 | 249 | /* 250 | * loop with constructor parameters or dependency 251 | */ 252 | foreach ($constructorParams as $constructorParam) { 253 | 254 | $type = $constructorParam->getType(); 255 | 256 | if ($type && $type instanceof ReflectionNamedType) { 257 | 258 | // make instance of this class : 259 | $paramInstance = $constructorParam->getClass()->newInstance() 260 | 261 | // push to $dependencies array 262 | array_push($dependencies, $paramInstance); 263 | 264 | } else { 265 | 266 | $name = $constructorParam->getName(); // get the name of param 267 | 268 | // check this param value exist in $parameters 269 | if (array_key_exists($name, $parameters)) { // if exist 270 | 271 | // push value to $dependencies sequencially 272 | array_push($dependencies, $parameters[$name]); 273 | 274 | } else { // if not exist 275 | 276 | if (!$constructorParam->isOptional()) { // check if not optional 277 | throw new Exception("Can not resolve parameters"); 278 | } 279 | 280 | } 281 | 282 | } 283 | 284 | } 285 | // finally pass dependancy and param to class instance 286 | return $classReflection->newInstance(...$dependencies); 287 | } 288 | ``` 289 | I explain everything in code . 290 | 291 | OK.... we have completed a Dependency Injection container for class 292 | 293 | ***Let's Create a Singleton Instance*** 294 | 295 | ```php 296 | public static function instance() 297 | { 298 | 299 | if (is_null(static::$instance)) { 300 | static::$instance = new static; 301 | } 302 | 303 | return static::$instance; 304 | } 305 | ``` 306 | 307 | 308 | ***Basic Usage*** 309 | 310 | ```php 311 | $container = DependencyInjectionContainer::instance(); 312 | ``` 313 | 314 | Define a class 315 | ```php 316 | class MyClass 317 | { 318 | private $dependency; 319 | 320 | public function __construct(AnotherClass $dependency) 321 | { 322 | $this->dependency = $dependency; 323 | } 324 | } 325 | ``` 326 | 327 | Instead of using `new MyClass`, use the Container's `make()` method: 328 | 329 | ```php 330 | $instance = $container->make(MyClass::class); 331 | ``` 332 | 333 | The container will automatically instantiate the dependencies, so this is functionally equivalent to: 334 | 335 | ```php 336 | $instance = new MyClass(new AnotherClass()); 337 | ``` 338 | 339 | 340 | ***Practical Example*** 341 | 342 | Here's a more practical example based on the [PHP-DI docs](http://php-di.org/doc/getting-started.html) - separating the mailer functionality from the user registration: 343 | 344 | ```php 345 | class Mailer 346 | { 347 | public function mail($recipient, $content) 348 | { 349 | // Send an email to the recipient 350 | // ... 351 | } 352 | } 353 | ``` 354 | 355 | ```php 356 | class UserManager 357 | { 358 | private $mailer; 359 | 360 | public function __construct(Mailer $mailer) 361 | { 362 | $this->mailer = $mailer; 363 | } 364 | 365 | public function register($email, $password) 366 | { 367 | // Create the user account 368 | 369 | } 370 | } 371 | ``` 372 | 373 | ```php 374 | 375 | 376 | $container = DependencyInjectionContainer::instance(); 377 | 378 | $userManager = $container->make(UserManager::class); 379 | $userManager->register('dave@davejamesmiller.com', 'MySuperSecurePassword!'); 380 | ``` 381 | 382 | ##3. create a method (call) for extracting class & method and injecting dependencies and parameters of method 383 | In this section we are going to understand how Laravel parse controller and method and initiate with dependency/parameters. 384 | 385 | Steps 386 | 1. Separate controller and method 387 | 2. Detect method Dependency / parameters and call method 388 | 3. Instantiate class with our ```make()``` function. 389 | 390 | 391 | **1. Separate controller and method** 392 | 393 | Our class have a property ```$methodSeparator``` as a separator for separating class name and method name. 394 | 395 | ```php 396 | private function resolveCallback($callable) 397 | { 398 | 399 | //separate class and method 400 | $segments = explode($this->methodSeparator, $callable); 401 | 402 | // set class name with namespace 403 | $this->callbackClass = $this->namespace.$segments[0]; 404 | 405 | // set method name . if method name not provided then default method __invoke 406 | $this->callbackMethod = isset($segments[1]) ? $segments[1] : '__invoke'; 407 | 408 | } 409 | ``` 410 | 411 | **2. Detect method Dependency / parameters and call method** 412 | 413 | ```php 414 | public function call($callable, $parameters = []) 415 | { 416 | 417 | // set class name with namespace and method name 418 | $this->resolveCallback($callable); 419 | 420 | // initiate ReflectionMethod with class and method 421 | $methodReflection = new ReflectionMethod($this->callbackClass, $this->callbackMethod); 422 | 423 | // get all dependencies/parameters 424 | $methodParams = $methodReflection->getParameters(); 425 | 426 | $dependencies = []; 427 | 428 | // loop with dependencies/parameters 429 | foreach ($methodParams as $param) { 430 | 431 | $type = $param->getType(); // check type 432 | 433 | if ($type && $type instanceof ReflectionNamedType) { /// if parameter is a class 434 | 435 | $name = $param->getClass()->newInstance(); // create insrance 436 | array_push($dependencies, $name); // push to $dependencies array 437 | 438 | } else { /// Normal parameter 439 | 440 | $name = $param->getName(); 441 | 442 | if (array_key_exists($name, $parameters)) { // check exist in $parameters 443 | 444 | array_push($dependencies, $parameters[$name]); // push to $dependencies array 445 | 446 | } else { // if not exist 447 | 448 | if (!$param->isOptional()) { // check if not optional 449 | throw new Exception("Can not resolve parameters"); 450 | } 451 | } 452 | 453 | } 454 | 455 | } 456 | 457 | // make class instance 458 | $initClass = $this->make($this->callbackClass, $parameters); 459 | 460 | // call method with $dependencies/parameters 461 | return $methodReflection->invoke($initClass, ...$dependencies); 462 | } 463 | ``` 464 | 465 | Now you have a question how laravel collect parameter for calling a function. 466 | 467 | When we declare a route with dynamic parameter like: 468 | 469 | ```php 470 | Route::get('hello/{name}', 'TestController@index') 471 | ``` 472 | 473 | Laravel Routing system collect all parameter and pass to call function. 474 | 475 | ```php 476 | $parameters = ["name" => "AL EMRAN" ]; 477 | $container->call("TestController@index", $parameters) 478 | 479 | ``` 480 | 481 | ***Usage*** 482 | ```php 483 | $container->call('TestController@index'); 484 | $container->call('TestController@show', ['id' => 4]); 485 | 486 | $container->call('TestController'); 487 | $container->call('TestController', ['id' => 4]); 488 | 489 | ``` 490 | 491 | OR 492 | 493 | **example** 494 | 495 | ```php 496 | 497 | class TestController 498 | { 499 | protected $company; 500 | public function __construct(Company $company) 501 | { 502 | $this->company = $company ; 503 | } 504 | 505 | /** 506 | * @param Request $request 507 | */ 508 | public function index(Request $request){ 509 | 510 | $company = $company->get(); 511 | 512 | return view('admin.company', compact('company')); 513 | 514 | } 515 | 516 | } 517 | 518 | ``` 519 | We can use : 520 | ```php 521 | $instance = DependencyInjectionContainer::instance(); 522 | $instance->namespace = "App\\Http\Controllers\\Admin\\"; // change namespace 523 | 524 | $class = $instance->make(CompanyController::class); // make class instance 525 | 526 | $instance->call(["CompanyController", 'index']); // call method 527 | 528 | $instance->call([$class, 'index']); // or--call method 529 | ``` 530 | 531 | 532 | #install package 533 | ```php 534 | composer require emrancu/dependency-injection-container 535 | ``` 536 | 537 | ## Declaimer 538 | I am not an expert in PHP, try to learn. 539 | 540 | Laravel Dependency Injection Container is so vast and also its routing system. I just create this mini-class only for understanding. 541 | 542 | This is my first article, so please comment your valuable opinion or mail me at "emrancu1@gmail.com" 543 | 544 | Inspired from [Here](https://gist.github.com/davejamesmiller/bd857d9b0ac895df7604dd2e63b23afe) 545 | 546 | 547 | [AL EMRAN](http://alemran.me) 548 | 549 | Founder , [Babshaye.com](https://babshaye.com) (A business solution for offline and online) Follow on [Facebook](https://www.facebook.com/babshaye) 550 | 551 | 552 | 553 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "emrancu/php-dependency-injection-container", 3 | "description": "A dependency injection container for php", 4 | "license": "MIT", 5 | "homepage": "https://github.com/emrancu/php-dependency-injection-container", 6 | "support": { 7 | "issues": "https://github.com/emrancu/php-dependency-injection-container/issues", 8 | "source": "https://github.com/emrancu/php-dependency-injection-container" 9 | }, 10 | "authors": [ 11 | { 12 | "name": "AL EMRAN", 13 | "email": "emrancu1@gmail.com" 14 | } 15 | ], 16 | "keywords": [ 17 | "A dependency injection container for php", 18 | "PHP Dependency Injection", 19 | "Laravel Dependency Injection Understanding", 20 | "Initiate Class and method with dependency " 21 | ], 22 | "autoload": { 23 | "classmap": [], 24 | "psr-4": { 25 | "AlEmran\\PHPDependencyInjection\\": "src/" 26 | } 27 | }, 28 | "require": { 29 | "php": ">=7.0.0" 30 | }, 31 | "minimum-stability": "dev", 32 | "prefer-stable": true 33 | 34 | } 35 | 36 | -------------------------------------------------------------------------------- /src/Base/Container.php: -------------------------------------------------------------------------------- 1 | resolveCallback($callable); 84 | 85 | $methodReflection = new ReflectionMethod($this->callbackClass, $this->callbackMethod); 86 | $methodParams = $methodReflection->getParameters(); 87 | 88 | $dependencies = []; 89 | 90 | foreach ($methodParams as $param) { 91 | 92 | $type = $param->getType(); 93 | 94 | if ($type && $type instanceof ReflectionNamedType) { 95 | 96 | $name = $param->getClass()->newInstance(); 97 | array_push($dependencies, $name); 98 | 99 | } else { 100 | 101 | $name = $param->getName(); 102 | 103 | if (array_key_exists($name, $parameters)) { 104 | 105 | array_push($dependencies, $parameters[$name]); 106 | 107 | } else { 108 | 109 | if (!$param->isOptional()) { 110 | throw new Exception("Can not resolve parameters"); 111 | } 112 | } 113 | 114 | } 115 | 116 | } 117 | 118 | if (!is_object($this->callbackClass)) { 119 | $initClass = $this->make($this->callbackClass, $parameters); 120 | } else { 121 | $initClass = $this->callbackClass; 122 | } 123 | 124 | 125 | return $methodReflection->invoke($initClass, ...$dependencies); 126 | } 127 | 128 | 129 | /** 130 | * @param $callable 131 | */ 132 | private function resolveCallback($callable) 133 | { 134 | 135 | if (is_string($callable)) { 136 | 137 | $segments = explode($this->methodSeparator, $callable); 138 | 139 | $this->callbackClass = $this->namespace.$segments[0]; 140 | $this->callbackMethod = isset($segments[1]) ? $segments[1] : '__invoke'; 141 | 142 | } 143 | 144 | if (is_array($callable)) { 145 | 146 | if (is_object($callable[0])) { 147 | $this->callbackClass = $callable[0]; 148 | } 149 | 150 | if (is_string($callable[0])) { 151 | $this->callbackClass = $this->namespace.$callable[0]; 152 | } 153 | 154 | $this->callbackMethod = isset($callable[1]) ? $callable[1] : '__invoke'; 155 | 156 | } 157 | 158 | } 159 | 160 | /** 161 | * @param $class 162 | * @param $parameters 163 | * @return object 164 | * @throws ReflectionException 165 | * @throws Exception 166 | * @throws ReflectionException 167 | */ 168 | public function make($class, $parameters = []) 169 | { 170 | 171 | $classReflection = new ReflectionClass($class); 172 | $constructorParams = $classReflection->getConstructor() ? $classReflection->getConstructor()->getParameters() : []; 173 | $dependencies = []; 174 | 175 | /* 176 | * loop with constructor parameters or dependency 177 | */ 178 | foreach ($constructorParams as $constructorParam) { 179 | 180 | $type = $constructorParam->getType(); 181 | 182 | if ($type && $type instanceof ReflectionNamedType) { 183 | 184 | array_push($dependencies, $constructorParam->getClass()->newInstance()); 185 | 186 | } else { 187 | 188 | $name = $constructorParam->getName(); 189 | 190 | if (!empty($parameters) && array_key_exists($name, $parameters)) { 191 | 192 | array_push($dependencies, $parameters[$name]); 193 | 194 | } else { 195 | 196 | if (!$constructorParam->isOptional()) { 197 | throw new Exception("Can not resolve parameters"); 198 | } 199 | 200 | } 201 | 202 | } 203 | 204 | } 205 | 206 | return $classReflection->newInstance(...$dependencies); 207 | } 208 | 209 | } 210 | --------------------------------------------------------------------------------