Monthly Archives: August 2012

Deployment tip: How to use environ variables to create different environments with PHP

If you use a framework such as Symfony2 this problem is solved for you, but if you aren’t using any framework you probably need to solve it in one way or another. Let me explain it. One typical scenario: production and development. We have one development database and another one for production. Each database has it’s own connection string. Probably we need to hard-code the connection string within the PHP code, but obviously if we are in the development environment we are going to use one connection string and the production one in the production environment. We can solve the problem with exotic tricks in the deployment script. I like to use exactly the same source code in all environments. Exactly the same means exactly the same, so I cannot change the code before pushing it to production. Mainly because normally my production environment usually it’s a cloud, and change the code it’s a mess. What can we do?

The solution that I like for this kind of problems is to use apache’s environ variables. We inject the environ variables in the virtual host configuration:

<VirtualHost *:80>
    ...
    SetEnv GONZALO_ENVIRON development
    ...
</VirtualHost>

Now we can read the environ variable easily with PHP with:

$environ = getenv('GONZALO_ENVIRON');

I’ve seen people who use this trick to avoid to hard-code our database passwords and connection string in the PHP souce code. Using this the developers cannot see the passwords (only sysadmins). I don’t like it, basically because I’m a hybrid between developer and sysadmin and this method is not agile for me, but maybe it can be useful for you.

I will show you a little example using this technique.

<?php
include(__DIR__ . '/../lib/App.php');

$environ = getenv('GONZALO_ENVIRON');
$app = new App($environ);
echo $app->run();

We have two different configuration files one for development:

<?php
return array(
    'ENVIRON' => 'DEVELOPMENT',
    'DB'      => array(
        'MAIN' => array(
            'dsn'      => 'pgsql:host=127.0.0.1;port=5432;dbname=dev_dbname',
            'username' => 'devel_username',
            'password' => 'devel_password',
            'options'  => array(
                PDO::ATTR_ERRMODE            => PDO::ERRMODE_EXCEPTION,
                PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_NAMED
            )
        )
    )
);

And another one for production

return array(
    'ENVIRON' => 'PRODUCTION',
    'DB'      => array(
        'MAIN' => array(
            'dsn'      => 'pgsql:host=xxx.xxx.xxx.xxx;port=5432;dbname=prod_dbname',
            'username' => 'prod_username',
            'password' => 'prod_password',
            'options'  => array(
                PDO::ATTR_ERRMODE            => PDO::ERRMODE_EXCEPTION,
                PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_NAMED
            )
        )
    )
);

Our app library:

class AppException extends Exception{}

class App
{
    private $conf;

    public function __construct($environ)
    {
        $this->conf = $this->getConfFromEnviron($environ);
    }

    private function getConfFromEnviron($environ)
    {
        $filePath = $this->getConfFilePath($environ);
        if (!is_file($filePath)) {
            throw new AppException("File '{$filePath}' not found");
        }
        return require($filePath);
    }

    private function getConfFilePath($environ)
    {
        return __DIR__ . "/../conf/{$environ}.php";
    }

    public function getFromConf($key)
    {
        $out = $this->conf;
        foreach (explode('.', $key) as $item) {
            if (isset($out[$item])) {
                $out = $out[$item];
            } else {
                throw new AppException("Key '{$key}' not found");
            }
        }
        return $out;
    }

    public function run()
    {
        $environ = $this->getFromConf('ENVIRON');
        return "Hello from {$environ}";
    }
}

But probably the best way to explain it is with the tests:

class AppTest extends PHPUnit_Framework_TestCase
{
    public function testEnvironDevelopment()
    {
        $environ = 'development';
        $app     = new App($environ);
        $this->assertEquals('Hello from DEVELOPMENT', $app->run());
    }

    public function testEnvironProduction()
    {
        $environ = 'production';
        $app     = new App($environ);
        $this->assertEquals('Hello from PRODUCTION', $app->run());
    }

    /**
     * @expectedException AppException
     */
    public function testEnvironNotFound()
    {
        $environ = 'xxxxx';
        $app     = new App($environ);
    }

    public function testGetConf()
    {
        $environ = 'development';
        $app     = new App($environ);

        $this->assertEquals('DEVELOPMENT', $app->getFromConf('ENVIRON'));
        $this->assertEquals('devel_username', $app->getFromConf('DB.MAIN.username'));
        $this->assertEquals('devel_password', $app->getFromConf('DB.MAIN.password'));
    }

    /**
     * @expectedException AppException
     */
    public function testGetConfWithKeyNotFound()
    {
        $environ = 'development';
        $app     = new App($environ);

        $app->getFromConf('DB.MAIN.xxx');
    }
}

You can see the whole code at github

Encode our PHP code into spaces and new line characters

According to one my last blog post with an exotic usage of dynamic includes with PHP we will keep on with something still more exotic. The idea is to create clean code. And what is cleaner than the blank?

We will encode our PHP code into spaces and new line characters (\n). The idea is simple. We will translate each character of our script into (n) spaces where (n) is the hexadecimal representation of it’s ASCII code.

function decodeText($encodedText) {
    $out = array();
    foreach (explode("\n", $encodedText) as $line) {
        $lineOut = array();
        foreach (explode("\t", $line) as $item) {
            $lineOut[] = chr(strlen($item));
        }
        $out[] = implode(null, $lineOut);
    }
    return implode("\n", $out);
}

function encodeText($text) {
    $out = array();
    foreach (explode("\n", $text) as $line) {
        $lineOut = array();
        foreach (str_split($line) as $item) {
            $lineOut[] = str_repeat(" ", ord($item));
        }
        $out[] = implode("\t", $lineOut);
    }
    return implode("\n", $out);
}

And now we will create two functions to include our files and parse them with PHP. One function to include from raw input and another to include files.

function includeFromFile($file)
{
    $raw = file_get_contents($file);
    includeFromRaw($raw);
}

function includeFromRaw($rawText)
{
    $decodedCode = decodeText($rawText);
    $tmpfname = tempnam("/tmp", "blank");
    $handle = fopen($tmpfname, "w");
    fwrite($handle, $decodedCode);
    fclose($handle);
    ob_start(function($buffer) use ($tmpfname, $decodedCode) {
        return str_replace($tmpfname, "<pre>" . htmlentities($decodedCode). "</pre>", $buffer);
    });
    include $tmpfname;
    ob_end_flush();
    unlink($tmpfname);
}

Now we can use:

$text = encodeText('<?php echo "Gonzalo";' . "\n" . ' print_r(array(1,2,311)); ?>');
includeFromRaw($text);

Or if we save our code with the name “Blank.clean”

includeFromFile("Blank.clean");

Now if you are brave enough you can write code in our new blankAndSpaces programming language, instead of PHP :)

Building a simple API proxy server with PHP

This days I’m playing with Backbone and using public API as a source. The Web Browser has one horrible feature: It don’t allow to fetch any external resource to our host due to the cross-origin restriction. For example if we have a server at localhost we cannot perform one AJAX request to another host different than localhost. Nowadays there is a header to allow it: Access-Control-Allow-Origin. The problem is that the remote server must set up this header. For example I was playing with github’s API and github doesn’t have this header. If the server is my server, is pretty straightforward to put this header but obviously I’m not the sysadmin of github, so I cannot do it. What the solution? One possible solution is, for example, create a proxy server at localhost with PHP. With PHP we can use any remote API with curl (I wrote about it here and here for example). It’s not difficult, but I asked myself: Can we create a dummy proxy server with PHP to handle any request to localhost and redirects to the real server, Instead of create one proxy for each request?. Let’s start. Problably there is one open source solution (tell me if you know it) but I’m on holidays and I want to code a little bit (I now, it looks insane but that’s me :) ).

The idea is:

...
$proxy->register('github', 'https://api.github.com');
...

And when I type:

http://localhost/github/users/gonzalo123

and create a proxy to :

https://api.github.com/users/gonzalo123

The request method is also important. If we create a POST request to localhost we want a POST request to github too.

This time we’re not going to reinvent the wheel, so we will use symfony componets so we will use composer to start our project:

We create a conposer.json file with the dependencies:

{
    "require": {
        "symfony/class-loader":"dev-master",
        "symfony/http-foundation":"dev-master"
    }
}

Now

php composer.phar install

And we can start coding. The script will look like this:

register('github', 'https://api.github.com');
$proxy->run();

foreach($proxy->getHeaders() as $header) {
    header($header);
}
echo $proxy->getContent();

As we can see we can register as many servers as we want. In this example we only register github. The application only has two classes:
RestProxy, who extracts the information from the request object and calls to the real server through CurlWrapper.

<?php
namespace RestProxy;

class RestProxy
{
    private $request;
    private $curl;
    private $map;

    private $content;
    private $headers;

    public function __construct(\Symfony\Component\HttpFoundation\Request $request, CurlWrapper $curl)
    {
        $this->request  = $request;
        $this->curl = $curl;
    }

    public function register($name, $url)
    {
        $this->map[$name] = $url;
    }

    public function run()
    {
        foreach ($this->map as $name => $mapUrl) {
            return $this->dispatch($name, $mapUrl);
        }
    }

    private function dispatch($name, $mapUrl)
    {
        $url = $this->request->getPathInfo();
        if (strpos($url, $name) == 1) {
            $url         = $mapUrl . str_replace("/{$name}", NULL, $url);
            $queryString = $this->request->getQueryString();

            switch ($this->request->getMethod()) {
                case 'GET':
                    $this->content = $this->curl->doGet($url, $queryString);
                    break;
                case 'POST':
                    $this->content = $this->curl->doPost($url, $queryString);
                    break;
                case 'DELETE':
                    $this->content = $this->curl->doDelete($url, $queryString);
                    break;
                case 'PUT':
                    $this->content = $this->curl->doPut($url, $queryString);
                    break;
            }
            $this->headers = $this->curl->getHeaders();
        }
    }

    public function getHeaders()
    {
        return $this->headers;
    }

    public function getContent()
    {
        return $this->content;
    }
}

The RestProxy receive two instances in the constructor via dependency injection (CurlWrapper and Request). This architecture helps a lot in the tests, because we can mock both instances. Very helpfully when building RestProxy.

The RestProxy is registerd within packaist so we can install it using composer installer:

First install componser

curl -s https://getcomposer.org/installer | php

and create a new project:

php composer.phar create-project gonzalo123/rest-proxy proxy

If we are using PHP5.4 (if not, what are you waiting for?) we can run the build-in server

cd proxy
php -S localhost:8888 -t www/

Now we only need to open a web browser and type:

http://localhost:8888/github/users/gonzalo123

The library is very minimal (it’s enough for my experiment) and it does’t allow authorization.

Of course full code is available in github.

Book review: CouchDB and PHP Web Development

Finally the new Book “CouchDB and PHP Web Development” written by Tim Juravich is ready an in my hands. It was my first experience as technical reviewer. The author contacted me by email and the editor sent me book chapters to review. Basically I gave my opinion, I test the code and I hunt for bugs. It was a great experience. Now is really cool to see the book in my hands.

As a general rule I don’t like the Beginner’s books. They normally fill various chapters with trivial things. Things that you can easily find within the project homepage, or you also can get thousands of articles in the Internet. Maybe the first 3 chapters are trivial things about NoSQL databases, how to set up a PHP environments, and we also can read about how to set up a github account. OK this book is not one exception, but it has something “different”. The author builds one application (a twitter clone) using PHP as backend languaje, CouchDB for the storage and Twitter’s Bootstrap for the frontend. It covers all the process from the beginning (setting up the environment) to the end (deploy the application to the cloud). The book is very interactive. It’s crowed by “Time for action” sections where the reader can build the application at the same time than he is reading. And to top it all the author also encourages the user to commit each change to one git repository.

My Conclusions
In my humble opinion is a good book, even with its trivial chapters (probably they are mandatory in this kind of books). If you read the book and you follow the all the “Time for action” sections you will have great picture about one real web application development process is and it also covers very well all CouchDB related things. Another good point for the author is the usage of newest front end technologies such as Twitter’s Bootstrap. Maybe I miss a bit the usage of TDD in the development process of the application but either way if fulfills its mission quite well.

Contact with me.
As I said before it was my first experience as a technical reviewer. It was a great experience for me. I really enjoyed a lot reading and commenting chapters. If you are writing a book and you need one reviewer, feel free to contact with me.

Follow

Get every new post delivered to your Inbox.

Join 972 other followers