loading...
Cover image for Make your own service container (php)

Make your own service container (php)

azibom profile image Mohammad Reza ・4 min read

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"
But let's look at the code ... that is much better (if you want, you can find the project codes in My Github)

.....

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

$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

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

So ... let's go to make this theurgic class

Alt Text

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

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

namespace Php\Container\Exceptions;

use Psr\Container\ContainerExceptionInterface;
use Exception;

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

NotFoundException.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

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."
so if you want more information you can check this link.
Now lets look at getInstace method

    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
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
have a nice time

sources

php.net
matthewdaly.co.uk

Posted on by:

azibom profile

Mohammad Reza

@azibom

azibom ... bom ... bom

Discussion

markdown guide
 

Do you have any tutorial of how to create and understand the way best analogy of service providers? Most late i will learn reading this it article, for just now thank so much!

 

I personally look at the project in a big picture and seprate it to the diffrents bundles in my mind for example for example when you have a ecommerce project you can consider the order as a bundle and make a service provider for it and register all of your services which are related to the order in it (I use this strategy in the laravel for example but some frameworks like symfony have this bundles already)