Inject dependencies via PhpDoc

Last month I attended to Codemotion conference. I was listening to a talk about Java and I saw the “@inject” decorator. I must admit I switched off my mind from the conference and I started to take notes in my notebook. The idea is to implement something similar in PHP. It’s a pity we don’t have real decorators in PHP. I really miss them. We need to use PhpDoc. It’s not the same than real decorators in other programming languages. That’s my prototype. Let’s go.

Imagine this simple class:

class User
{
    private $db;

    public function getInfo($uid)
    {
        $sql = "select * from users where uid=:UID";
        $stmp = $this->db->prepare($sql);
        $stmp->execute(array('UID' => $uid));
        return $stmp->fetchAll();
    }

    public function getDb()
    {
        return $this->db;
    }
}

It doesn’t work. Private property $db must be an instance of PDO object. We can solve it with dependency injection:

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

    public function getInfo($uid)
    {
        $sql = "select * from users where uid=:UID";
        $stmp = $this->db->prepare($sql);
        $stmp->execute(array('UID' => $uid));
        return $stmp->fetchAll();
    }
}

Now it works but we are going to create a simple PDO Wrapper to obtain the PDO connection.

class DbConf
{
    const DB1 = 'db1';

    private static $conf = array(
        self::DB1 => array(
            'dsn'  => 'pgsql:dbname=db;host=localhost',
            'user' => 'gonzalo',
            'pass' => 'password',
        )
    );

    public static function getConf($key)
    {
        return self::$conf[$key];
    }
}

class Db extends PDO
{
    private static $dbInstances = array();

    /**
     * @static
     * @param string $key
     * @return PDO
     */
    static function getDb($key)
    {
        if (!isset($dbInstances[$key])) {
            $dbConf = DbConf::getConf($key);
            $dbInstances[$key] = new PDO($dbConf['dsn'], $dbConf['user'], $dbConf['pass']);
        }
        return $dbInstances[$key];
    }
}

I like to use this kind of classes because I normally work with different databases and I need to use different connection depending on the SQL. It helps me to mock the database connection within the different environments (development, production, QA). Now We can use our simple class:

$user = new User(Db::getDb(DbConf::DB1));
print_r($user->getInfo(4));

The idea is to change the class into something like this:

class User extends DocInject
{
    /**
     * @inject Db::getDb(DbConf::DB1)
     * @var PDO
     */
    private $db;

    public function getInfo($uid)
    {
        $sql = "select * from users where uid=:UID";
        $stmp = $this->db->prepare($sql);
        $stmp->execute(array('UID' => $uid));
        return $stmp->fetchAll();
    }
}

Now we are going to inject the PDO connection to $db private property in the constructor:

class DocInject
{
    public function __construct()
    {
        $reflection = new ReflectionClass($this);
        foreach ($reflection->getProperties() as $property) {
            /** @var ReflectionProperty $property */
            $docComment = $property->getDocComment();
            $docComment = preg_replace('#[ \t]*(?:\/\*\*|\*\/|\*)?[ ]{0,1}(.*)?#', '$1', $docComment);
            $docComment = trim(str_replace('*/', null, $docComment));
            foreach (explode("\n", $docComment) as $item) {
                if (strpos($item, '@inject') !== false) {
                    $inject = trim(str_replace('@inject', null, $item));
                    $value = null;
                    eval("\$value = {$inject};"); // yes, eval. uggly, isnt't?
                    $property->setAccessible(true);
                    $property->setValue($this, $value);
                }
            }
        }
    }
}

If you have read “Clean Code” (if not, you must do it) you noticed that uncle Bob doesn’t like this class. The method is too long, so we are going to refactor a little bit.

class DocInject
{
    public function __construct()
    {
        $reflection = new ReflectionClass($this);
        foreach ($reflection->getProperties() as $property) {
            $this->processProperty($property);
        }
    }

    private function processProperty(ReflectionProperty $property)
    {
        $docComment = $this->cleanPhpDoc($property->getDocComment());
        foreach (explode("\n", $docComment) as $item) {
            if ($this->existsInjectDecorator($item)) {
                $this->performDependencyInjection($property, $item);
            }
        }
    }

    private function cleanPhpDoc($docComment)
    {
        $docComment = preg_replace('#[ \t]*(?:\/\*\*|\*\/|\*)?[ ]{0,1}(.*)?#', '$1', $docComment);
        $docComment = trim(str_replace('*/', null, $docComment));
        return $docComment;
    }

    private function existsInjectDecorator($item)
    {
        return strpos($item, '@inject') !== false;
    }

    private function performDependencyInjection(ReflectionProperty $property, $item)
    {
        $injectString = $this->removeDecoratorFromPhpDoc($item);
        $value = $this->compileInjectString($injectString);
        $this->injectValueIntoProperty($property, $value);
    }

    private function removeDecoratorFromPhpDoc($item)
    {
        return trim(str_replace('@inject', null, $item));
    }

    private function compileInjectString($injectString)
    {
        $value = null;
        eval("\$value = {$injectString};"); // yes, eval. uggly, isnt't?
        return $value;
    }

    private function injectValueIntoProperty(ReflectionProperty $property, $value)
    {
        $property->setAccessible(true);
        $property->setValue($this, $value);
    }
}

So now we don’t need to pass the new instance of PDO connection in the constructor with DI:

$user = new User();
print_r($user->getInfo(4));

It works but there’s something that I don’t like. We need to extend our User class with DocInject. I like plain classes. Because of that we are going to use the new feature of PHP5.4: traits

Instead of extend our class with DocInject we are going to create:

trait DocInject
{
    public function parseDocInject()
    {
        $reflection = new ReflectionClass($this);
        foreach ($reflection->getProperties() as $property) {
            $this->processProperty($property);
        }
    }

    private function processProperty(ReflectionProperty $property)
    {
        $docComment = $this->cleanPhpDoc($property->getDocComment());
        foreach (explode("\n", $docComment) as $item) {
            if ($this->existsInjectDecorator($item)) {
                $this->performDependencyInjection($property, $item);
            }
        }
    }

    private function cleanPhpDoc($docComment)
    {
        $docComment = preg_replace('#[ \t]*(?:\/\*\*|\*\/|\*)?[ ]{0,1}(.*)?#', '$1', $docComment);
        $docComment = trim(str_replace('*/', null, $docComment));
        return $docComment;
    }

    private function existsInjectDecorator($item)
    {
        return strpos($item, '@inject') !== false;
    }

    private function performDependencyInjection(ReflectionProperty $property, $item)
    {
        $injectString = $this->removeDecoratorFromPhpDoc($item);
        $value = $this->compileInjectString($injectString);
        $this->injectValueIntoProperty($property, $value);
    }

    private function removeDecoratorFromPhpDoc($item)
    {
        return trim(str_replace('@inject', null, $item));
    }

    private function compileInjectString($injectString)
    {
        $value = null;
        eval("\$value = {$injectString};"); // yes, eval. uggly, isnt't?
        return $value;
    }

    private function injectValueIntoProperty(ReflectionProperty $property, $value)
    {
        $property->setAccessible(true);
        $property->setValue($this, $value);
    }
}

And now:

class User
{
    use DocInject;

    public function __construct()
    {
        $this->parseDocInject();
    }

    /**
     * @inject Db::getDb(DbConf::DB1)
     * @var PDO
     */
    private $db;

    public function getInfo($uid)
    {
        $sql = "select * from users where uid=:UID";
        $stmp = $this->db->prepare($sql);
        $stmp->execute(array('UID' => $uid));
        return $stmp->fetchAll();
    }
}

This implementation has a little problem. If our class User needs a constructor we have a problem. As far as I know we cannot use parent::__construct() with a trait. We can solve this problem changing the code a little bit:

class User
{
    use DocInject {parseDocInject as __construct;}

    /**
     * @inject Db::getDb(DbConf::DB1)
     * @var PDO
     */
    private $db;

    public function getInfo($uid)
    {
        $sql = "select * from users where uid=:UID";
        $stmp = $this->db->prepare($sql);
        $stmp->execute(array('UID' => $uid));
        return $stmp->fetchAll();
    }
}

A simple unit test

    public function testSimple()
    {
        $user = new User();
        $this->assertTrue(count($user->getInfo(4)) > 0);
    }

If we use different DbConf file for each environment we can easily use one database or another without changing any line of code.

And that’s all. What do you think?

(Files available as gist here and here)

Reflection over PHPDoc with PHP

I want to parse PHPDoc code. Let me explain a little bit what I want to do. Imagine a dummy function documented with PHPDoc:

class Foo
{
    /**
     * @param String $param1
     * @param String $param2
     * @return String
     */
    public function dummy($param1, $param2)
    {
        return "Hello World";
    }
}

PHP has a great reflection API, but as at least in the current PHP version (as far as I know) we only can get the PHPDoc as a string, without parse it. I need to get the parameters and the type of them with reflection. Parameters are the easy part:

$class = new ReflectionClass($obj);
$reflect = $class->getMethod($function);
foreach ($reflect->getParameters() as $i => $param) {
}

But the type is different. PHP is a loosely/weak typed, dynamic language, because of that we need to parse the PHPDoc string to get the “real” type of the variables. I put “real” quoted because if we set a variable as a String within PHPDoc, we can use an Integer at runtime. We can cast variables but only with complex types (classes), and not with primary types as String or Integer. I’ve read it will be available on PHP’s next releases, but now we need to use exotic tricks (as the trick I will show now).

I’m not a master in regular expressions (in fact I hate them), so it looks like a hard work for me, but I realized that Zend Framework does something similar when it builds a XMLRPC server. I don’t use ZF in this project, and I don’t want to include the whole library only for parsing the PHPDoc. But there’s a great thing. ZF is an open source library, really well coded and documented. Because of that I dive into the ZF code, looking for the functionality that I need and adapt it.

function processPHPDoc(ReflectionMethod $reflect)
{
    $phpDoc = array('params' => array(), 'return' => null);
    $docComment = $reflect->getDocComment();
    if (trim($docComment) == '') {
        return null;
    }
    $docComment = preg_replace('#[ \t]*(?:\/\*\*|\*\/|\*)?[ ]{0,1}(.*)?#', '$1', $docComment);
    $docComment = ltrim($docComment, "\r\n");
    $parsedDocComment = $docComment;
    $lineNumber = $firstBlandLineEncountered = 0;
    while (($newlinePos = strpos($parsedDocComment, "\n")) !== false) {
        $lineNumber++;
        $line = substr($parsedDocComment, 0, $newlinePos);

        $matches = array();
        if ((strpos($line, '@') === 0) && (preg_match('#^(@\w+.*?)(\n)(?:@|\r?\n|$)#s', $parsedDocComment, $matches))) {
            $tagDocblockLine = $matches[1];
            $matches2 = array();

            if (!preg_match('#^@(\w+)(\s|$)#', $tagDocblockLine, $matches2)) {
                break;
            }
            $matches3 = array();
            if (!preg_match('#^@(\w+)\s+([\w|\\\]+)(?:\s+(\$\S+))?(?:\s+(.*))?#s', $tagDocblockLine, $matches3)) {
                break;
            }
            if ($matches3[1] != 'param') {
                if (strtolower($matches3[1]) == 'return') {
                    $phpDoc['return'] = array('type' => $matches3[2]);
                }
            } else {
                $phpDoc['params'][] = array('name' => $matches3[3], 'type' => $matches3[2]);
            }

            $parsedDocComment = str_replace($matches[1] . $matches[2], '', $parsedDocComment);
        }
    }
    return $phpDoc;
}

Our processPHPDoc function takes as argument $reflect (ReflectionMethod) and returns an array

Array
(
    [params] => Array
        (
            [0] => Array
                (
                    [name] => $param1
                    [type] => String
                )

            [1] => Array
                (
                    [name] => $param2
                    [type] => String
                )
        )

    [return] => Array
        (
            [type] => String
        )
)

And that was exactly what I needed. Open Source is great.