Experiment update: revised Django-like PHP (5.3 compatible) querying interface

After some research, discussion and a bit of hacking together some poorly organized code, I’ve come up with a revised interface from my previous post An experiment: Django Framework-like querying/model interface in PHP.

Now there existed a few problems I was well aware that I wanted to address if I was going to spend any more time on a good, clean interface that promoted rapid development and readable code:

  • It was dependent on a PECL extension that was nearly 4 years old
  • It only worked with PHP 5.2.x (as per above)
  • The actual query-building interface wasn’t pretty and hindered readability

I sat down and revised a good portion of the old code I had put together and came up with something that fixes these problems. I wrote a new collection of classes with PHP 5.3 in mind and it uses a lot of the new language features (especially late static bindings and the __callStatic magic method). Now, because of the use of these new features my code is now dependent on PHP version 5.3 (developed using 5.3.1) — but, I suppose it’s better to code looking to the future rather than being locked into an older version.Thanks to reader Robert P who introduced me to Python’s Storm ORM and how it’s querying interface looked and functioned. After digging my way through its docs, I came up with something that is quite like a hybrid morbid mix of Django and Storm for use in PHP.

Let’s get to it: here’s how my interface has changed.

Basic Querying

I wanted to get away from what I hated the most: an unreadable querying interface that prevented the creation of self-explainable queries. What before was like:

Product::get(<strong>array("make__contains" => "Sony")</strong>);

has now become:

Product::get(<strong>Field::make__contains("Sony")</strong>);

which, using the __callStatic method, allowed me to create an interface that is actually quite readable aloud: “get Products where field make contains “Sony.” This is quite a bit better than using the array() function, where in reality it is completely unnecessary to the interface. This also works when combining expressions:

$q = Product::get(Field::make__contains("Sony"), Field::price__gt(100));
print $q->create_statement();
// output: SELECT id FROM products WHERE (make LIKE '%Sony%' AND price > '100')

Complex Expressions

One of the things I love about Django is the ability to create more complex AND/OR queries using the Q interface and operator overloading (which I was using before, dependent on the PECL operator extension). Storm has this too, but it also offered up an alternative interface using AND() and OR() functions.

I wasn’t able to do this exactly in PHP (“and” and “or” are reserved keywords), but I was able to make something slightly similar:

require_once("Model.class.php");

class Product extends Model { }

$q = Product::get(_AND(Field::make__contains("Sony"), Field::price__gt(100)));
print $q->create_statement();
// output: SELECT id FROM products WHERE make LIKE '%Sony%' AND price > '100'

$q = Product::get(_OR(Field::make__contains("Panasonic"), Field::make__contains("LG")));
print $q->create_statement();
// output: SELECT id FROM products WHERE make LIKE '%Panasonic%' OR make LIKE '%LG%'

Self-Referencing Fields in Expressions

Again, another favourite feature of Django was the ability to use F objects to create expressions that references a table’s own fields. My basic research didn’t show anything like this in Storm, but using the __callStatic method and the same style interface, I was able to create a readable interface for these sorts of queries:

$q = Product::get(Field::price__gt(<strong>Field::cost__times(2)</strong>));
print $q->create_statement();
// output: SELECT id FROM products WHERE price > cost * 2

$q = Product::get(Field::price__gt(Field::cost__times("quantity_sold")));
print $q->create_statement();
// output: SELECT id FROM products WHERE price > cost * quantity_sold

Using [field]__times (or __plus, __minus, __divided_by), complex field-references can be created and is still fairly readable, as for the above example: “get Products where field price is greater than field cost times 2,” and the output should be as expected.

I’m still not entirely happy with this portion of my code at the moment. It is fairly limited in the sorts of queries you can create (for example, it’s not possible to create an expression such as “cost * 2 – price”).

I’m more than happy to offer up what code I have to date. I tried to document as much as possible, but some of the code is admittedly messy and could use a clean-up — which I’m sure I’ll get to at some point or another.

Download/view Expression.class.php — defines how Expressions are created and handled using the Field interface

Download/view ExpressionNode.class.php — slightly revised from the previous code, defines classes for Q and F objects, along with _AND() and _OR() functions for creating more complex expressions.

Download/view Model.class.php — basic class for creating models and queries.

Download/view QuerySet.class.php — defines how filter/exclude/get queries are chained together in a linked list structure.

One thought on “Experiment update: revised Django-like PHP (5.3 compatible) querying interface

  1. […] I have continued with this experiment and created a new querying interface compatible with PHP 5.3 and has no dependent PECL extensions: Experiment update: revised Django-like PHP (5.3 compatible) querying interface. […]

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes:

<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>