Make your own service container (php)
Tags: php, laravel, symfony, framework
Published: 2020-04-17 18:33:28

First of all that is nice to know what is the concept of service container :)
"A Service Container (or dependency injection container) is simply a PHP object that manages the instantiation of services (i.e. objects)" 
That is a short definition which i found it in ["symfony.com"](symfony.com)
But let's look at the code ... that is much better (if you want, you can find the project codes in [My Github](https://github.com/azibom/serviceContainer))
```php
.....

class User {
    private $mail;
    public function __construct(Mail $mail)
    {
        $this->mail = $mail;
    }

    public function sendMail($username) {
        $this->mail->sendMail($username);
    }
}

class Mail {
    private $mailSender;
    public function __construct(MailSender $mailSender)
    {
        $this->mailSender = $mailSender;
    }

    public function sendMail($username) {
        $this->mailSender->send($username);
    }
}

class MailSender {
    public function send($username) {
        echo "Email was sent to  ${username} \n";
    }
}

.....
``` 
What is your idea if you want to make an object from user class :/
Maybe you should do somethings like below
```php
$instance = new User(new Mail(new MailSender()));
$instance->sendMail("alex");
```
But what is your idea if you have one object that we call it container and it helps you to make the instance of object really easy ... one thing like this
```php
$container = new Container;
$instance = $container->get('User');
$instance->sendMail("alex");
```
#### So ... let's go to make this theurgic class
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/zwiypmgu3op972uyxtlw.jpeg)
## 1.Create Composer.json File
first of all create one json file like this
```
{
    "name": "php/container",
    "description": "That is a simple container service",
    "type": "library",
    "authors": [
        {
            "name": "azibom",
            "email": "mrsh13610@gmail.com"
        }
    ],
    "require": {
        "psr/container": "^1.0"
    },
    "autoload": {
        "psr-4": {
            "Php\\Container\\": "src/"
        }
    }
}

```
## 2.Create main.php File in the root of your project 
we use it only for testing our container and it's really simple to understand
```php
<?php

use Php\Container\Container;
require __DIR__ . '/vendor/autoload.php';

class User {
    private $mail;
    public function __construct(Mail $mail)
    {
        $this->mail = $mail;
    }

    public function sendMail($username) {
        $this->mail->sendMail($username);
    }
}

class Mail {
    private $mailSender;
    public function __construct(MailSender $mailSender)
    {
        $this->mailSender = $mailSender;
    }

    public function sendMail($username) {
        $this->mailSender->send($username);
    }
}

class MailSender {
    public function send($username) {
        echo "Email was sent to  ${username} \n";
    }
}

$container = new Container;
$instance = $container->get('User');
$instance->sendMail("alex");

$instance = new User(new Mail(new MailSender()));
$instance->sendMail("alex");

```
## 3.Create src folder in the root of the project and fill it like this
```
├── Container.php
└── Exceptions
    ├── ContainerException.php
    └── NotFoundException.php

1 directory, 3 files
```
#### ContainerException.php
```php
<?php

namespace Php\Container\Exceptions;

use Psr\Container\ContainerExceptionInterface;
use Exception;

/**
 * Class could not be instantiated
 */
class ContainerException extends Exception implements ContainerExceptionInterface {}

```
#### NotFoundException.php
```php
<?php

namespace Php\Container\Exceptions;

use Psr\Container\NotFoundExceptionInterface;
use Exception;

/**
 * Class not found
 */
class NotFoundException extends Exception implements NotFoundExceptionInterface {}
```

#### Container.php which is the core of our project :)
```php
<?php

namespace Php\Container;

use Psr\Container\ContainerInterface;
use Php\Container\Exceptions\NotFoundException;
use ReflectionClass;
use ReflectionException;

class Container implements ContainerInterface
{
    private $services = [];

    public function get($id)
    {
        $item = $this->resolve($id);
        if (!($item instanceof ReflectionClass)) {
            return $item;
        }
        return $this->getInstance($item);
    }

    public function has($id)
    {
        try {
            $item = $this->resolve($id);
        } catch (NotFoundException $e) {
            return false;
        }
        if ($item instanceof ReflectionClass) {
            return $item->isInstantiable();
        }
        return isset($item);
    }

    public function set(string $key, $value)
    {
        $this->services[$key] = $value;
        return $this;
    }

    private function resolve($id)
    {
        try {
            $name = $id;
            if (isset($this->services[$id])) {
                $name = $this->services[$id];
                if (is_callable($name)) {
                    return $name();
                }
            }
            return (new ReflectionClass($name));
        } catch (ReflectionException $e) {
            throw new NotFoundException($e->getMessage(), $e->getCode(), $e);
        }
    }

    private function getInstance(ReflectionClass $item)
    {
        $constructor = $item->getConstructor();
        if (is_null($constructor) || $constructor->getNumberOfRequiredParameters() == 0) {
            return $item->newInstance();
        }
        $params = [];
        foreach ($constructor->getParameters() as $param) {
            if ($type = $param->getType()) {
                $params[] = $this->get($type->getName());
            }
        }
        return $item->newInstanceArgs($params);
    }
}
```
## 4.Now you can run it
let's go to the root of your project and run these two commands
```
composer install
```
And then
```
php main.php
```
you should see
```
Email was sent to  alex 
Email was sent to  alex 
```
But how does it works
Two main function in container class are resolve and getInstance.
Resolve trying to make an object from ReflectionClass class ...
but what is this class ["The ReflectionClass class reports information about a class."](https://www.php.net/manual/en/class.reflectionclass.php)
so if you want more information you can check this link.
Now lets look at getInstace method
```php
    private function getInstance(ReflectionClass $item)
    {
        $constructor = $item->getConstructor();
        if (is_null($constructor) || $constructor->getNumberOfRequiredParameters() == 0) {
            return $item->newInstance();
        }
        $params = [];
        foreach ($constructor->getParameters() as $param) {
            if ($type = $param->getType()) {
                $params[] = $this->get($type->getName());
            }
        }
        return $item->newInstanceArgs($params);
    }
```
At the first four lines we get constructor and then check if there is any constructor in the class or not ... but if we find it we do nice thing, we get the params from constructor and then try to make instance of them with get method which make me sure that we will not have any problem if we have a param in constructor which have param it self in its constructor(actually we are trying to make the instance of constructor params recursively) :)
And at the end we use newInstanceArgs for set constructor param and make a new object and then return it :)) 
#### Done
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/lg9z3frsvq0982ywm6fw.png)
I hope you understand how does our simple service container works and if you have any questions feel free to ask them
And if you want you can find this project codes in [My Github](https://github.com/azibom/serviceContainer) 
have a nice time

### sources
[php.net](https://www.php.net/manual/en/class.reflectionclass.php) 
[matthewdaly.co.uk](https://matthewdaly.co.uk/blog/2019/02/02/creating-your-own-dependency-injection-container-in-php/)