DEV Community

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

Make your own service container (php)

Mohammad Reza
Software Engineer at Cafe Bazaar
・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";
    }
}

.....
Enter fullscreen mode Exit fullscreen mode

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");
Enter fullscreen mode Exit fullscreen mode

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");
Enter fullscreen mode Exit fullscreen mode

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/"
        }
    }
}

Enter fullscreen mode Exit fullscreen mode

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");

Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

ContainerException.php

<?php

namespace Php\Container\Exceptions;

use Psr\Container\ContainerExceptionInterface;
use Exception;

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

Enter fullscreen mode Exit fullscreen mode

NotFoundException.php

<?php

namespace Php\Container\Exceptions;

use Psr\Container\NotFoundExceptionInterface;
use Exception;

/**
 * Class not found
 */
class NotFoundException extends Exception implements NotFoundExceptionInterface {}
Enter fullscreen mode Exit fullscreen mode

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);
    }
}
Enter fullscreen mode Exit fullscreen mode

4.Now you can run it

let's go to the root of your project and run these two commands

composer install
Enter fullscreen mode Exit fullscreen mode

And then

php main.php
Enter fullscreen mode Exit fullscreen mode

you should see

Email was sent to  alex 
Email was sent to  alex 
Enter fullscreen mode Exit fullscreen mode

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);
    }
Enter fullscreen mode Exit fullscreen mode

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

Discussion (2)

Collapse
naelsonbrasil profile image
NaelsonBrasil

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!

Collapse
azibom profile image
Mohammad Reza Author

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)