Comunidad de diseño web y desarrollo en internet online

POO: Inyección de dependencias en Laravel (III)

Este es el tercer tutorial de la serie sobre inyección de dependencias. Antes de comenzar asegúrate de haber leído el primero y el segundo tutorial de esta nueva entrega.

En la POO generalmente usamos el constructor de una clase para definir sus dependencias. Ejemplo:

Código :

class MysqlDatabase {

}

class UserRepo {
    /**
     * @var MysqlDatabase $db
     */
    protected $db;

    public function __construct(MysqlDatabase $db)
    {
        $this->db = $db;
    }
}

class PostRepo {
    /**
     * @var MysqlDatabase $db
     */
    protected $db;

    public function __construct(MysqlDatabase $db)
    {
        $this->db = $db;
    }
}

class PostsController {

    protected $users;
    protected $posts;

    public function __construct(UserRepo $users, PostRepo $posts)
    {
        $this->users = $users;
        $this->posts = $posts;
    }

}


UseRepo y PostRepo dependen de MysqlDatabase, y PostsController depende de UserRepo y PostRepo. Sólo para crear un PostsController tendríamos que hacer esto:

Código :

$db = new MysqlDatabase();
$users = new UserRepo($db);
$posts = new PostRepo($db);
$controller = new PostsController($users, $posts);
var_dump($controller);


¿Mucho trabajo, no? Bueno, uno de los features que más me gusta de Laravel es que puede instanciar un objeto por nosotros, creando todas las dependencias que hagan falta, como les mostré en este artículo.

Inyección de dependencias en Laravel


Laravel usa las clases Reflection de PHP en su contenedor para conocer qué dependencias tiene cada clase y las crea una a una, recursivamente (como un árbol de dependencias). Aunque parezca algo super complicado es tan fácil que voy a crearles un ejemplo en tan sólo cinco minutos…

Otros 30 minutos después (Los programadores somos demasiado optimistas con los tiempos…)

Veamos el código y debajo los ejemplos:

Código :

class Factory {

    /**
     * Store the alias between interfaces/parent classes and concrete/child classes
     * @var array
     */
    protected $alias = array();

    /**
     * Bind an alias to a real class name. i.e.: UserRepoInterface to UserRepo.
     * Can be use to replace parent classes with child classes.
     * (Be aware both classes would need the same interface).
     * @param $alias
     * @param $name
     */
    public function bind($alias, $name)
    {
        $this->alias[$alias] = $name;
    }

    /**
     * Get the real name used for a class, as indicated by the bind method above
     * Example: getRealName('UserRepoInterface') should return 'UserRepo'
     * @param $name
     * @return mixed
     */
    public function getRealName($name)
    {
        if (isset ($this->alias[$name]))
        {
            $name = $this->alias[$name];
        }

        return $name;
    }

    /**
     * Build a new object determines and instantiate its dependencies automatically
     * @param $name
     * @return object
     */
    public function build($name)
    {
        try
        {
            $name = $this->getRealName($name);

            $reflection = new ReflectionClass($name);

            if ( ! $reflection->isInstantiable())
            {
                throw new Exception($name . " is not instantiable");
            }

            $constructor = $reflection->getConstructor();

            if (is_null($constructor))
            {
                return new $name;
            }

            $parameters = $constructor->getParameters();

            $args = array();

            foreach ($parameters as $parameter)
            {
                $args[] = $this->build($parameter->getClass()->getName());
            }

            return $reflection->newInstanceArgs($args);
        }
        catch(Exception $e)
        {
            exit('Error trying to build "' . $name . '": ' . $e->getMessage());
        }
    }

}


Ahora para crear un PostsController sólo hacemos esto:

Código :

$factory = new Factory;

$posts = $factory->build('PostsController');

var_dump($posts);


Vean de nuevo cómo la nueva clase Factory instancia por nosotros el controlador y sus dependencias y las dependencias de las dependencias: PostController -> UserRepo -> MysqlDatabase, etc.

Pero la mejor práctica es no atar una implementación a una clase concreta sino usar interfaces, veamos esto último. Primero las clases e interfaces “dummies” para crear el ejemplo:

Código :

interface DatabaseInterface {

}

class MysqlDatabase implements DatabaseInterface {

}

interface UserRepoInterface {

}

interface PostRepoInterface {

}

class UserRepo implements UserRepoInterface {
    /**
     * @var DatabaseInterface $db
     */
    protected $db;

    public function __construct(DatabaseInterface $db)
    {
        $this->db = $db;
    }
}

class PostRepo implements PostRepoInterface {
    /**
     * @var DatabaseInterface $db
     */
    protected $db;

    public function __construct(DatabaseInterface $db)
    {
        $this->db = $db;
    }
}

class PostsController {

    protected $users;
    protected $posts;

    public function __construct(UserRepoInterface $users, PostRepoInterface $posts)
    {
        $this->users = $users;
        $this->posts = $posts;
    }

}


Noten que PostsController ahora necesita "UserRepoInterface" en vez de "UserRepo" y UserRepo necesita de DatabaseInterface en vez de MysqlDatabase, esto hace nuestras clases mucho más flexibles (podríamos tener diferentes repositorios y bases de datos), pero si ahora procedemos como antes:

Código :

$factory = new Factory;

$controller = $factory->build('PostsController');
var_dump($controller);


¡Boom! Error trying to build "UserRepoInterface": UserRepoInterface is not instantiable ¿Por qué? No se puede instanciar una interface, y la clase Factory no sabe qué hacer. Acá sí necesita instrucciones adicionales pero es muy fácil:

Código :

$factory = new Factory;

$factory->bind('DatabaseInterface', 'MysqlDatabase');
$factory->bind('UserRepoInterface', 'UserRepo');
$factory->bind('PostRepoInterface', 'PostRepo');

$controller = $factory->build('PostsController');

var_dump($controller);


Laravel hace esto, al instanciar controladores usa el IoC que permite instanciar de manera automática todas las dependencias. Similar a lo que programamos aquí.

Por último quiero aclarar que mi clase es sólo con fines educativos, haría falta integrarla con el Container del artículo pasado, crear Unit Tests, etc. Mi intención en este tutorial es como el mago que revela los trucos, al entender cómo funcionan los frameworks por dentro se hacen mejores programadores y la próxima vez que alguien les hable de “Dependency injection” y “Automatic resolution” no se sentirán intimidados.

¿Te gustó el tutorial? Dale me gusta, compartelo y sígueme en Twitter. ¡Nos vemos en el próximo! Saludos.

¿Sabes SQL? ¿No-SQL? Aprende MySQL, PostgreSQL, MongoDB, Redis y más con el Curso Profesional de Bases de Datos que empieza el martes, en vivo.

Publica tu comentario

o puedes...

¿Estás registrado en Cristalab y quieres
publicar tu URL y avatar?

¿No estás registrado aún pero quieres hacerlo antes de publicar tu comentario?

Registrate