Overview
Torpor is an Object-Relational Mapping (ORM) framework and persistence layer abstraction for PHP, characterized by the following features:
- Ease of installation, configuration, and extension
- Light-weight object factories and on-the-fly class definition
- Just-in-time fetching and efficient bulk operations
- Intermediate read/write-through caching at thread, session, or distributed network levels
- Related object factories, deep loading, and recursion-safe dependency cascading for publishing
-
Plug-in architecture and interfaces for database abstraction with out-of-the-box support for:
- MySQL
- Microsoft SQL Server
- Oracle
- SQLite
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
-
Source Code
Torpor's subversion repository and release downloads as maintained via Google's project hosting.
-
User's Guide
Full documentation for deploying and extending Torpor, wrapped in a self-contained wiki via the excellent TiddlyWiki; this makes for a slightly larger initial download since it's everything at once, but makes for a very usable document even offline.
-
Blog References
References to Torpor and its evolution as posted on the blog; note that this is a simple search, and may find other textual references to the project from tangentially related personal posts in addition to the more professional development-centric content.