I'm moving! This site is being phased out in preference to my new site at ptom.net. Existing content will be reworked and reformatted over yonder (and some of it retired), with lots of new additions and updates. Go check it out!
paul@PaulTomlinson.net
Information Technology Leadership
(& Congenial Geek)

Torpor PHP ORM

local links

home resume technology contact me bio blog music photography writing

external links [+]

 

Overview

Torpor is an Object-Relational Mapping (ORM) framework and persistence layer abstraction for PHP, characterized by the following features:

Using schema-validated XML configuration (which can optionally be automatically produced using the included tools) to describe a data repository, Torpor acts as an intelligent factory to create objects corresponding to data structures, complete with appropriate mutators and convenient shortcuts for deep loading (retrieval of related objects). A wide variety of criteria can be used for object searching, applied not only to the desired data, but any class of data to which it refers, or which refers to it, automatically associated by the data engine and with tightly controlled shortest-path selection routines (well formed exclusive ANSI joins, in the case of SQL). Whole collection bulk operations (multiple object retrieval within a single transaction) and database-side pagination are also provided.

Background

Working with a contract web application company to reorganize their development in 2009, it became apparent that the code base presented a significant challenge as constituted. Though generally well-disciplined object oriented code, it showed signs of growth and maturation throughout its lifetime that had not been refactored into existing classes or patterns, and which had not yet been aggregated into a single "best-of-breed" (with a database in similar condition). Without this foundation, development staff were spending up to 80% of their time working on CRUD operation containers and dependent object loading in manually coded pseudo-ORM patterns rather than on the business logic which relied on them.

I took it upon myself on the side to find an appropriate remedy that could be fit into the environment with maximal effect and minimal disruption. Existing PHP ORM solutions all had critical flaws which ruled them out for integration with an existing code base: either relying on convoluted and unwieldy inheritance models in order to apply business logic, or no inherent support for deep loading, or completely non-performant memory hogs, etc. None of them approached the combined level of sophistication and elegance needed based on the patterns of implementation present in the existing models in the code base, nor were they likely to play nice side-by-side with it.

Based on those development patterns I outlined the usage styles which would be most compatible with what the code was evolving toward, and which would mitigate the issues which most frequently hampered that evolution. With that template in mind I distilled the level of detail required to achieve a proof of concept, and built from there into an object model similar to Active Record in appearance and use, but supporting a level of abstraction that decouples logical and functional database definitions and allows for progressively finer levels of control where desired. With the addition of enterprise-level distributed caching support the framework became a major value-add to the organization. The end result aims to be the most effective way to work with the objects natively in PHP, and the most efficient means of interacting with the database (and cache) under the hood. Fully abstracted configuration makes environment maintenance (dev, test, stage, prod), database mutation, and access control an inherent part of the platform.

Rather than bill the work as a one-off for a single company, which would increase their cost and limit my ability to showcase the code, I created it as open source under the relatively unrestrictive MIT license.

All told it was a fun project, and it was nice to play with code.

Implementation

Torpor takes advantage of several features of PHP syntax that do not apply well to other languages. These are often described as "magical" built-in functions which are invoked as a last line of defense before crying fowl: for example, __call as a method prototype will be invoked (if defined) on an object instance when the method which has been requested cannot be found, passing the name of the attempted method and its arguments as the input to this method. Similar features apply to __get and __set operations for member variable accessors and mutators not available in the current scope, and a globally defined __autoload function responsible for finding a class that has been requested but not yet defined in the stack. Plugging into the __autoload function allows for classes corresponding to recognized database descriptions to be defined on-the-fly. Using this as the standard means of producing such classes also allows for extremely simply extension from the implementing developer perspective: since the default mechanism of automatic class generation is to provide a no-content extension (new class in name only) to intelligent primitives, the developer is free to provide that same extension with content of their choosing using the exact same naming conventions. This makes navigating to and employing the right object definition when inheritance is in play extremely straightforward.

Using these methods liberally, and treating the Torpor instance (singleton or otherwise) as the repository of all knowledge and gateway to the database in this cross-cutting endeavor, objects receiving intelligently formatted requests or commands can pass that information up the chain in order to determine whether such a thing is possible in the given context and what configuration values may govern the behavior. Dynamic object factories, rather than a generated library of static class definitions, help contain concepts and controls to a central location. Altering database definitions or features immediately and automatically applies to the entire code base (and can also be made effectively transparent: logical and structural definitions are largely decoupled).

This implementation model also provides strict control over styles of using PHP object code that typically follow a more wild-west tradition. All mutator access (even those performed invisibly via __get and __set) goes through strict validation of data members, object relationships, and values. Loose typing of values is still supported (as are loose naming conventions), but only insofar as those values can be evaluated and cast to match the constraints of the data members as specified in the database description (including character and code page encoding, numeric precision, field length, etc.) - which is appropriate, since that's where they're going to end up eventually anyway.

Effective use of exceptions and warnings are provided throughout, and the code passes the strictest of PHP syntax and convention validations. Reliance on extensions is kept to an absolute minimum, making use of only those required for managing the XML configuration and interaction with the requested database, and which are all available as part of the default build. Version 5.3.0 conventions, though they would be hugely valuable, have been eschewed in favor of more conventional means in order to target versions as low as 5.1.0 instead.

Details on how to deploy and make use of Torpor can be found in the User Guide linked to below.

Links

Valid XHTML 1.0 Strict Firefox 3