BT

arrayDB, a New and Easy PHP ORM

Posted by Mustafa Dokumacı on Oct 04, 2012 |

I've seen many PHP ORM libraries. Most of them make you write a new class for each item you want to keep in the database. Extending this and that for no apparent reason is repetitious and capricious.
Since items have fields of similar data and similar relations, a well-written class can be used for all. If you need a library which makes things easier, this is my approach.

arrayDB ORM library has only 5 classes. You mostly use a singleton of one, while others are used internally, that's all. Caching and keeping cache synchronized with the database is all automated, you don't need to keep track of these.

To start using this library, you simply have to define:

  • Your data model (what items you need to keep, their fields and relations with each other).
  • Your MySQL access.
  • Your cache config.

Defining data model

All data model definitions are written as an array like this:

	$model=array(
	    'user'=>array(
	        'conf'=>array('len'=>7),
	        'fields'=>array(
	            'name'=>array('len'=>50)
	        ),
	        'has_many'=>array(
	            'posts'=>array('type'=>'post', 
'foreign_name'=>'writer')
	        ),
	        'many_to_many'=>array(
	            'liked_posts'=>array('type'=>'post', 
'foreign_name'=>'likers'),
	        ),
	        'self_ref'=>array('friends')
	    ),

	    'post'=>array(
	        'conf'=>array('len'=>10),
	        'fields'=>array(
	            'text'=>array('len'=>200),
	            'view_count'=>array('type'=>'numeric', 'len'=>5)
	            // default field type is text, define if numeric
	        )
	    )
	);

We have 2 items here: User and post.
User has names, many written posts and many liked posts. Posts has texts, view counts and likers.
User also has many users as friends.
With this model, we want to reach posts of a user as $user['posts'] and writers of a post as $post['writer']. This is a one-to-many relation.
We also want to reach liked posts of a user as $user['liked_posts'] and likers of a post as $post['likers']. This is a many-to-many relation.
Finally we want to reach friends of a user as $user['friends']. This is a self-referencing relation.

Defining MySQL access

Defining MySQL access is also writing an array like this:

	$db_config=array(
	    'hostname'=>'localhost', 'database'=>'social', 
'username'=>'root', 'password'=>''
	);
	

Defining cache config

For now, we have three cache types implemented: APC, Memcached and plain text file.
To use APC, this config array is enough:

	$cache_config=array('type'=>'apc');

To use Memcached, you need to give some parameters:

	$cache_config=array('type'=>'memcached', 'host'=>'127.0.0.1', 
'port'=>11211, 'timeout'=>1);

To use plain text files, you need to create a readable and writable directory and provide the absolute path:

	$cache_config=array('type'=>'file', 'path'=>'/tmp/my_project_cache')

There is an optional "prefix" parameter. It is applied to cache keys if given.

Start Using

Now it’s time to use what we defined. We just need these lines to initialize the library.

	DB::init($db_config);
	CACHE::init($cache_config);

	ADB::init($model);
        $adb=ADB::get_instance();

Creating Tables

This is a one time job. We tell the library to create the database tables as required. It does all the hard work for relations and etc.
                 $adb->create_tables();
We need to run this method once when we deploy it to somewhere and never again. It causes data loss if called after inserting data.

Using Items

We have this $adb instance in hand. We will reach all the data through that.

Creating Items

We provide the name of item and an array of data by field names to create an item.

	$uid1=$adb->create('user', array('name'=>'John'));
	$uid2=$adb->create('user', array('name'=>'Marry'));

	$pid1=$adb->create('post', array(
	    'writer'=>$uid1,
	    'text'=>'What a wonderful world!'
	));

	$pid2=$adb->create('post', array(
	    'writer'=>$uid2,
	    'text'=>'Life is beautiful!'
	));

Creating Many-to-many Relations

The first parameter is the name of the item. The second parameter is the local name of the related item. The third parameter is the ID of the item, and the fourth parameter is the ID of the related item.

	$adb->relate('user', 'friends', $uid1, $uid2);

	$adb->relate('user', 'liked_posts', $uid1, $pid1); // self post 
liking :)
	$adb->relate('user', 'liked_posts', $uid1, $pid2);

	$adb->relate('user', 'liked_posts', $uid2, $pid1);

Listing Data

We can list all users with posts and their likers in a simple loop:

	foreach ($adb->id_list('user') as $uid) {
	    // load user
	    $user=$adb->load('user', $uid);
	    echo '<h1>' . $user['name'] . '</h1>' . "\n";

	    echo '<h2>Posts: </h2>' . "\n";
	    echo '<ul>' . "\n";

	    foreach ($user['posts'] as $pid) {
	            //load post of user
	            $post=$adb->load('post', $pid);
	            $likers=array();

	            foreach ($post['likers'] as $lid) {
	                // load liker of post
	                $liker=$adb->load('user', $lid);
	                $likers[]=$liker['name'];
	            }
	            $likers=(count($likers)) ? '<br />' . implode(', ', 
$likers) . ' liked.' : '';
	            echo '<li>' . $post['text'] . ' ' . $likers . '</li>' . 
"\n";
	        }

	        echo '</ul>' . "\n";
	    }

Updating Items

We can update any field of any item like this:

	$user1=$adb->load('user', $uid1);
	$user1['name']='Jack';
	// no save method needed, save and keeping cache updated is all 
automated.

If we need to update more than one field at a time, this is the alternative:

	$post1=$adb->load('post', $pid1);
	$post1->update(array('writer'=>$uid2, 'text'=>'Not a wonderful 
world!'));

Removing Relations

It's just same as relating:

	$adb->unrelate('user', 'friends', $uid1, $uid2);

	$adb->unrelate('user', 'liked_posts', $uid1, $pid1);

Removing Items

We can delete items while keeping or removing belongings.

	$adb->delete('user', $uid1);
	// user deleted, posts remain unowned.

	$adb->delete('user', $uid1, true);
	// user and all posts of user deleted.

Querying More

As an example, we want to get the five most-liked posts. All we need is this:

	foreach ($adb->id_list('post', false, 'likers DESC', 5) as $pid) {
	    $post=$adb->load('post', $pid);
	    // do anything with post
	}

Or, if we want to get posts of a user with more than 5 likes, we would do the following:

	$user=$adb->load('user', $uid1);
	foreach ($user->id_list('post', 'view_count>5') as $pid) {
	    $post=$adb->load('post', $pid);
	    // do anything with post
	}

The Main Goal

For a separate post page, our code will be this simple:

	$post=$adb->load('post', $pid1);
	$writer=$adb->load('user', $post['writer']);

	echo $writer['name'] . ' wrote' . "<br />\n";
	echo $post['text'] . "<br />\n";

	$post['view_count']++;
	// yes, increasing view_count is this simple

Are there any queries or cache logics in this code? No the main goal is simplicity.
There are a lot of famous alternatives to use. They are documented better and supported better. This library is not one of them; it is not a well-oiled machine yet. It is the most simple and easy to learn approach, in my opinion. If the goal of ORM libraries is to isolate coder from database logic, this one is the most assertive one among the other new ones . jQuery was the easiest javascript framework and became a standard just because of that. So, an easy to use PHP ORM library has a chance that way.

Here is the library: [arrayDB github]

All ideas and contributions are welcomed.

About the Author

Mustafa Dokumaci is a software developer from Istanbul, Turkey. His education is in Environmental Engineering and Accounting, but he now works for sporcum.com and hipsin.com. Mustafa has been working with PHP, MySQL, Apache, Nginx, Python, CodeIgniter, Magento etc. for the last six years.

Hello stranger!

You need to Register an InfoQ account or to post comments. But there's so much more behind being registered.

Get the most out of the InfoQ experience.

Tell us what you think

Allowed html: a,b,br,blockquote,i,li,pre,u,ul,p

Email me replies to any of my messages in this thread

Support cross database by Milton Loayza

I think ORM should be simple, good job do you have any plans to support cross database? and security for sql injections

Re: Support cross database by mustafa dokumacı

it already escapes all user data to prevent sql injection.
i'm planning to support postgre at first :)

tests? by Massimiliano Arione

Great, but... where is your testing suite? :-|

Re: tests? by Angga Lingga

Great idea :) could you please share the table relation please?

Allowed html: a,b,br,blockquote,i,li,pre,u,ul,p

Email me replies to any of my messages in this thread

Allowed html: a,b,br,blockquote,i,li,pre,u,ul,p

Email me replies to any of my messages in this thread

4 Discuss

Educational Content

General Feedback
Bugs
Advertising
Editorial
InfoQ.com and all content copyright © 2006-2013 C4Media Inc. InfoQ.com hosted at Contegix, the best ISP we've ever worked with.
Privacy policy
BT