Proposal for a new CiviCRM Architecture: The ORM Layer....Doctrine
This is a follow up to our last post proposing a new architecture for CiviCRM. Much appreciation for everyone's patience. Following from our last post we want to go over the use of Doctrine, a PHP implementation of the Active Record design pattern made popular through Ruby on Rails. The Doctrine Project has done a great job of maintaining detailed documentation and has a lot of features that we believe everyone will find useful when working with CiviCRM objects. We have posted some of our working code for the new ORM and REST API here at git hub.We have given this code set the working name civiBASE.
For those who are not familiar with Active Records and ORM a little background will help. An object relational mapping (ORM) layer translates objects from a programming environment into records in a relational database and vice-a-versa. In our case, we want the ORM to take PHP objects and store them in MySQL and then retrieve them again.
ActiveRecord is a very popular design pattern used to implement ORMs. The biggest advantage of Active Record is that it takes care of most of the database level work (i.e. sql code) for the CRUD operations and for accessing related objects. Such is the case with Doctrine.
For example, let's look at the creation, updating & retrieval of an object as well as accessing a related objects. The following snippet of code shows each of these steps.
Let's suppose that we have a simple User object and that each User object has an Email object. The User object has two attributes: username and password. The Email object has one attribute: address.
First let's create the User object, set some attributes and save it.
$user = new User(); $user->username='foo'; $user->password='bar'; $user->save();
So far so good. We have not had to write SQL. Now let's retrieve this object. For simplicity, let's assume we know the id of the object is 5. In doctrine, and all active record implementations, all objects have a unique id. We will retrieve the object then add an associated Email object.
$user = Doctrine::getTable('User')->find(5);
$user->Email['address'] = 'firstname.lastname@example.org';
Notice a few things. First, we retrieved the User object only using the unique id. Next, even though we had not instantiated an associated Email object for the user object, Doctrine takes care of instantiating the new Email object and setting the address attribute. Finally, notice that the save() command takes care of storing the User object and the Email object. No SQL code, yeah!
This should provide a good flavor for the type of code that we get to write with Doctrine. There is very good documentation online that will step you through getting started and comfortable with the fundamental concepts of Doctrine and Active Record. This can be found at http://www.doctrine-project.org/documentation/manual/1_1/en/introduction. I will not focus on much of that here but highlight two very important features worthy of close attention.
First, Doctrine can go from an existing database and generate the necessary ActiveRecord files for each table taking into account all foreign key relationships, existing fields and data types as well as any cascading relationships and constraints between tables. We took advantage of this feature to get us started. Details of how you do this can be found in the documentation already referenced.
The second, and perhaps more important from a long term perspective, is the files Doctrine uses to represent an object (and as generated automatically from an existing database). Specifically it uses Object, BaseObject and ObjectTable classes.
So for example, for a Contact object we would find:
1. a BaseContact class found in a file called BaseContact.php (BaseObject)
2. a Contact class found in a file called Contact.php (Object)
3. and perhaps a ContactTable class found in a file called ContactTable.php (ObjectTable)
The last one is listed as perhaps because it is not always necessary. The Base class defines the objects, its attributes, its relationships and any special accessing methods. The Object class is used to implement all methods on the object. The ObjectTable class is a utility and captures database level special functions (e.g. for special database retrieval optimizations involving SQL or the DQL, the Doctrine Query Language). It is not always necessary and we do not utilize this class yet in our implementation.
Being a brief introduction I will not continue on much more other than to provide some details of the code found in out CiviCRM Doctrine Git repository.
There are two directories in the repo. One pathed for packages and one simply pathed as doctrine. The doctrine directory should go at the same level as CRM. The packages directory should be added into the packages directory.
Using doctrine requires a bootstrap.php file which sets up connectivity and references the Doctine library. We have set it up so that it pulls the database connectivity info from CiviCRM. It also has pathing to the Doctrine library which it expects to find in the packages directory.
The do_merge.php file is a good example of how we have used doctrine as an API for writing custom functionality. In this case we needed an auto-merge function where we could hand off two contact id's and have them merged into a single contact. This function can be invoked from command line (i.e. php do_merge.php
Lastly, and certainly not least, there is a CRUD.php file. This is a simple REST interface which allows us to access all civicrm objects and perform basic CRUD operations. We will do a separate blog entry for this interface.
In the mean time please poke around and ask questions.