views:

197

answers:

2

I've seen various MVC frameworks as well as standalone ORM frameworks for PHP, as well as other ORM questions here; however, most of the questions ask for existing frameworks to get started with, which is not what I'm looking for. (I have also read this SO question but I'm not sure what to make of it, and the answers are vague.)

Instead, I figured I'd learn best by getting my hands dirty and actually writing my own ORM, even a simple one. Except I don't really know how to get started, especially since the code I see in other ORMs is so complicated.

With my PHP 5.2.x (this is important) MVC framework I have a basic custom database abstraction layer, that has:

  • Very simple methods like connect($host, $user, $pass, $base), query($sql, $binds), etc
  • Subclasses for each DBMS that it supports
  • A class (and respective subclasses) to represent SQL result sets

But does not have:

  • Active Record functionality, which I assume is an ORM thing (correct me if I'm wrong)

EDIT: to clarify, I only have a database abstraction layer. I don't have models yet, but when I implement them I want them to be native ORM models (so to speak), hence this question.

I've read up a little about ORM, and from my understanding they provide a means to further abstract data models from the database itself by representing data as nothing more than PHP-based classes/objects; again, correct me if I am wrong or have missed out in any way.

Still, I'd like some simple tips from anyone else who's dabbled more or less with ORM frameworks. Is there anything else I need to take note of, simple example code for me to refer to, or resources I can read? Thanks a lot in advance!

+1  A: 

A simple ORM can be built using __get() and __set() and a couple of custom methods (possibly using __call()), here is a simple pseudo-code:

class ORM
{
  private $table = null;
  private $fields = array();

  function __construct($table)
  {
    $this->table = $table;
  }

  function __get($key)
  {
    return isset($this->fields[$key]) ? $this->fields[$key] : false;
  }

  function __set($key, $value)
  {
    $this->fields[$key] = $value;
  }

  function load($id, $field = 'id')
  {
    // populate $this->fields with SELECT * FROM $this->table WHERE $field = $id;
  }

  function save()
  {
    if (isset($this->fields['id']))
    {
      // UPDATE $this->table SET $this->fields;
    }

    else
    {
      // INSERT INTO $this->table $this->fields;
    }
  }
}

$user = new ORM('user');

$user->name = 'name';
$user->pass = '1337';

$user->save();

This is just a basic example to get you started. You could add further logic using the __call() magic method to fetch results by other fields than id for instance.

Bear in mind that the example I gave doesn't handle relations, that's where various ORM implementations really differ, however I normally don't trust any ORM to handle relations for me since they tend to be way slower and not produce efficient queries.

Alix Axel
Thank you! As for your last point, how do you deal with relations then?
BoltClock
@BoltClock: I don't. I'm not a big fan of ORMs so I usually just write the full SQL queries directly. Read the following question to get some inspiration: http://stackoverflow.com/questions/1336661/simpler-orm-for-php.
Alix Axel
+1  A: 

As this question is rather old, I guess you already have had your try at writing an ORM yourself. Nonetheless, as I wrote a custom ORM two years ago, I would still like to share my experience and ideas.

As said I implemented a custom ORM two years ago and even used it with some success in small to medium sized projects. I integrated it in a rather popular CMS which at that time (and even now) lacks such ORM functionality. Furthermore, back then, popular frameworks like Doctrine didn´t really convince me. Much has changed since then and Doctrine 2 evolved in a solid framework, so, if I now had the choice between implementing my own ORM or using one of the popular frameworks like Doctrine 2 for production use, this would be no question at all - use the existing, stable solutions. BUT: implementing such a framework (in a simple manner) was a very valuable learning exercise and it helped me a lot in working with larger open source ORMs, as you get a better understanding for the pitfalls and difficulties associated with object relational mapping.

It is not too difficult to implement basic ORM functionality, but as soon as mapping of relationships between objects come into play, it gets much, much more difficult/interesting.


How did I get started?

What got me hooked was Martin Fowlers book Patterns of Enterprise Application Architecture. If you want to program your own ORM or even if you are just working with some ORM framework, buy this book. It is one of the most valuable resources that cover many of the basic and advanced techniques regarding the field of object relational mapping. Read up on it, you get many great ideas on the patterns behind a ORM.

Basic Architecture

I decided if I would like to use rather an Active Record approach or some kind of Data Mapper. This decision influences on how the data from the database is mapped to the entity. I decided to implement a simple Data Mapper, the same approach as Doctrine 2 or Hibernate in Java uses. Active Record is the approach of the ORM functionality (if you can call it so) in Zend Framework. Active Record is much simpler then a Data Mapper, but also much more limited. Read up on these patterns and check the mentioned frameworks, you get the difference pretty fast. If you decide to go with a Data Mapper, you should also read up on PHPs reflection API.

Querying

I had the ambitious goal to create my own query language, much like DQL in Doctrine or HQL in Hibernate. I soon abondoned that, as writing a custom SQL parser/lexer seemed way to complicated (and it really is!). What I did was to implement a Query Object, in order to encapsulate the information which table is involved in the query (thats important as you need to map the data from the database to the relevant classes for each table).

Querying for an object in my ORM looked like this:

public function findCountryByUid($countryUid) {
    $queryObject = new QueryObject();
    $queryObject->addSelectFields(new SelectFields('countries', '*'))
            ->addTable(new tx_persistenceTable('countries'))
            ->addWhere('countries.uid = "' . intval($countryUid) . '"');

    $res = $this->findByQuery($queryObject);
    return $res->getSingleResult();
}

Configuration

Normally, you also need to have some kind of configuration format, Hibernate uses XML (among others), Doctrine 2 uses PHP annotations, EZComponents uses PHP arrays in its Persistent Object component as config format. Thats what I used, too, it seemed like a natural choice and the CMS I worked with used the PHP configuration format, too.

With that configuration, you define

  • which table gets mapped to which class
  • what fields should get mapped to the class instance
  • what type the fields of the table have (int, string, etc.)
  • the relations between the entities (e. g. a User class has a reference to a UserGroup class)
  • etc.

And thats the information you use in your Data Mapper to map the DB result to objects.

Implementation

I decided to go with a strong test driven approach, because of the complex nature of writing a custom ORM. TDD or not, writing many, many unit tests is a really good idea on such a project. Apart from that: get your hands dirty and keep Fowlers book close. ;-)


As I said it was really worth the effort, but I wouldn´t want to do it again, much because of the mature frameworks that exist nowadays.

I don´t use my ORM anymore, it worked, but lacked many features, among others: lazy loading, component mapping, transaction support, caching, custom types, prepared statements/parameters etc. And it´s performance wasn´t good enough for using it in large scale projects.

Nonetheless, I hope I could give you some starting points in the field of ORM, if you didn´t knew them already. ;-)

Max
Great overview. You have a valid point about the existence of tried and tested solutions out there, but nevertheless introduced some important ideas I should keep in mind when I dabble with designing my own ORM. I'm glad it was a great learning experience for you too. I'll be sure to look for Fowler's book once I'm out and about. If no new answers come by for a few days, this may get you another +15. Thanks for your time and explanation! :)
BoltClock