├── .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 |
5 |
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 |
--------------------------------------------------------------------------------