Monthly Archives: March 2010

Playing with PHP, CouchDB and ORM

I’m playing with PHP and CouchDB. After writing a class to connect PHP and CouchDB I’ve done a variation of the interface to use CouchDB with PHP in a similar way to ORM. Yes. I know ORM means Object Relational Mapping and CouchDB isn’t a Relational Database but have a look to the following interface:

Imagine you have a CouchDb at localhost:5987 and you have a database called users.

Now let’s build a class called CDB_Users.

class CDB_Users extends Nov_CouchDb_Orm {
    static $_db = 'users';
}

It’s easy to automate the creation of CDB_Users with a script

CDB_Users extends Nov_CouchDb_Orm

class Nov_CouchDb_Orm
{
    static protected $_db;

    /**
     * @param string $key
     * @return Nov_CouchDb
     */
    static public function connect($key)
    {
        $class = get_called_class();
        extract(Nov_CouchDb_Conf::get($key));

        $couchDb = new Nov_CouchDb($host, $port, $protocol, $user, $password);
        return $couchDb->db($class::$_db);
    }
}

A configuration class:

class Nov_CouchDb_Conf
{
    const CDB1 = 'CDB1';

    private static $_conf = array(
        self::CDB1 => array(
            'protocol' => 'http',
            'host'     => 'localhost',
            'port'     => 5984,
            'user'     => null,
            'password' => null
            )
        );

    static function get($key)
    {
        return self::$_conf[$key];
    }
}

Putting all together with the class Nov_CouchDb
we can use:

CDB_Users::connect(Nov_CouchDb_Conf::CDB1)->insert('gonzalo', array('password' => 'g1'));
$data = CDB_Users::connect(Nov_CouchDb_Conf::CDB1)->select('gonzalo')->asArray();
print_r($data);

CDB_Users::connect(Nov_CouchDb_Conf::CDB1)->update('gonzalo', array('password' => 'g2'));
$data = CDB_Users::connect(Nov_CouchDb_Conf::CDB1)->select('gonzalo')->asArray();
print_r($data);

CDB_Users::connect(Nov_CouchDb_Conf::CDB1)->delete('gonzalo');

What do you think? That’s not exactly a ORM. I’m not sure if I will use that interface instead the classic one.

$data = CDB_Users::connect(Nov_CouchDb_Conf::CDB1)->select('gonzalo')->asArray();
// versus
$couchDb = new Nov_CouchDb('localhost', 5984);
$data = $couchDb->db('users')->select('gonzalo')->asArray()
 

Here is the full source code with the examples.

PHP and couchDB

Last days I’ve been playing with couchDB and PHP. There are several ways to use couchDB with PHP. There also are extensions but I want to build a simple library and I will show now the code.

CouchDB has a great RestFul API so I we want to use CouchDB we only need to perform http requests. I have done a small class to do the requests. You can see the code here. It’s a simple class that uses PHP’s curl functions.

I come from relational database world. NoSQL is new for me. Maybe I’m wrong but I want to use INSERT, UPDATE, DELETE and SELECT statements in CouchDB in the same way I use them in Relational database.

The class is focused in the HTTP Document API. There is a great tutorial here that explains the API. Now I’ll show the interface I’ve made to perform the statements with CouchDB.

The examples assumes there are CouchDB host at localhost:5987. Also as I’ve said before the class focused only in HTTP Document API so first of all I create a new database called users using CouchDB’s web interface (http://localhost:5984/_utils/). Now let’s start:

INSERT

$couchDb = new Nov_CouchDb('localhost', 5984);
$couchDb->db('users')->insert('gonzalo', array('password' => "g1"));

SELECT

$data = $couchDb->db('users')->select('gonzalo')->asArray();
print_r($data);

UPDATE

$couchDb->db('users')->update('gonzalo', array('password' => 'g2'));

DELETE

$out = $couchDb->db('users')->delete('gonzalo')->asArray();
print_r($out);

Basically Nov_CouchDb has a constructor that stores connection information into private member variables:

class Nov_CouchDb
{
    private $_protocol;
    private $_host;
    private $_port;
    private $_user;
    private $_password;

    public function __construct($host, $port=Nov_Http::DEF_PORT , $protocol=Nov_Http::HTTP, $user = null, $password=null)
    {
        $this->_host     = $host;
        $this->_port     = $port;
        $this->_protocol = $protocol;
        $this->_user     = $user;
        $this->_password = $password;
    }
}

There are also a public function called db. This function stores the database and returns the instance of the class (I like chainable functions)

...
    private $_db;
    /**
     * @param string $db
     * @return Nov_CouchDb
     */
    public function db($db)
    {
        $this->_db = $db;
        return $this;
    }
...

And finally the main functions:

SELECT

public function select($key)
{
    try {
        $out = Nov_Http::connect($this->_host, $this->_port, $this->_protocol)
            ->setCredentials($this->_user, $this->_password)
            ->doGet("{$this->_db}/{$key}");
    } catch (Nov_Http_Exception $e) {
        $this->_manageExceptions($e);
    }
    return new Nov_CouchDb_Resulset($out);
}

INSERT

    /**
     * @param string $key
     * @param array $values
     * @return Nov_CouchDb_Resulset
     */
    public function insert($key, $values)
    {
        try {
            $out = Nov_Http::connect($this->_host, $this->_port, $this->_protocol)
                ->setCredentials($this->_user, $this->_password)
                ->setHeaders(array('Content-Type' =>  'application/json'))
                ->doPut("{$this->_db}/{$key}", json_encode($values));
        } catch (Nov_Http_Exception $e) {
            $this->_manageExceptions($e);
        }
        return new Nov_CouchDb_Resulset($out);
    }

UPDATE

    /**
     * @param string $key
     * @param array $values
     * @return Nov_CouchDb_Resulset
     */
    public function update($key, $values)
    {
        try {
            $http = Nov_Http::connect($this->_host, $this->_port, $this->_protocol)
                ->setCredentials($this->_user, $this->_password);
            $out = $http->doGet("{$this->_db}/{$key}");
            $reg = json_decode($out);
            $out = $http->setHeaders(array('Content-Type' =>  'application/json'))
                ->doPut("{$this->_db}/{$key}", json_encode($reg));
        } catch (Nov_Http_Exception $e) {
            $this->_manageExceptions($e);
        }
        return new Nov_CouchDb_Resulset($out);
    }

DELETE

    /**
     * @param string $key
     * @return Nov_CouchDb_Resulset
     */
    public function delete($key)
    {
        try {
            $http = Nov_Http::connect($this->_host, $this->_port, $this->_protocol)
                ->setCredentials($this->_user, $this->_password);
            $out = $http->doGet("{$this->_db}/{$key}");
            $reg = json_decode($out);
            $out = $http->doDelete("{$this->_db}/{$key}", array('rev' => $reg->_rev));
        } catch (Nov_Http_Exception $e) {
            $this->_manageExceptions($e);
        }
        return new Nov_CouchDb_Resulset($out);
    }

insert, update, delete and select returns an instance of Nov_CouchDb_Resulset. CouchDb API returns a json string. But sometimes I want a PHP array or maybe an object. I use Nov_CouchDb_Resulset to perform those transformations:

// Different outputs
$data = $couchDb->db('users')->select('dummy')->asArray();
print_r($data);

$data = $couchDb->db('users')->select('dummy')->asObject();
print_r($data);

$data = $couchDb->db('users')->select('dummy')->asJson();
print_r($data);

EXCEPTIONS

CouuchDB API is a RestFul API so it uses HTTP exceptions when something wrong happens. I’ve done three exceptions. One generic and other two with the most common exception in relational databases: NoDataFound and DupValOnIndex. this is because I want do things like the following ones:

try {
    $couchDb->db('users')->update('gonzalo', array('password' => 'g2'));
} catch (Nov_CouchDb_Exception_NoDataFound $e) {
    echo "No data found \n";
}

$couchDb->db('users')->insert('gonzalo1', array('password' => "g1"));
try {
    $couchDb->db('users')->insert('gonzalo1', array('password' => "g1"));
} catch (Nov_CouchDb_Exception_DupValOnIndex $e) {
    echo "Dup Val On Index \n";
}
try {
    $couchDb->db('users')->delete('gonzalo');
} catch (Nov_CouchDb_Exception_NoDataFound $e) {
    echo "No data found \n";
}

The source code is available on google code here

Follow

Get every new post delivered to your Inbox.

Join 869 other followers