Working Backwards: New Pronto Data Models
For a long time, I've been wanting to redesign the data models in Pronto. They serve their purpose fairly well, but I've always found myself writing small methods in the model classes that don't really need to be written. They were simply enough that it was a waste of time to even have to write them.
A simple example:
<?php function get_active_users() { return $this->db->get_all("SELECT * FROM users WHERE status='active'"); } ?>
My primary intent for doing this sort of stuff was to keep any SQL out of the controllers that might be requesting data like this. Of course, I ended up with a ton of little methods littering my data models. It wasn't ideal.
I also had a bunch of methods in the base model class that were more-or-less convenience methods to avoid some of these get_whatever() methods, stuff like Model::get_all(), Model::get_by(), etc. It was clear that there was some room for improvement here.
I had been struggling to come up with a very simple and robust API to replace this stuff, but I always got hung up on the details before I'd even get to how the API itself was going to work. I was trying to envision the internals and then move on to the actual API once I had the internals figured out.
So a couple nights ago I tried the reverse. I started with the API, ignoring (for now) how the whole thing would actually work under the hood. I started up vim and just wrote out a bunch of example method calls, just as I would want them to look. I ended up with a fairly small and fluent model API, and it actually proved to be pretty easy to work backwards into the internals.
The big change was adding an auxiliary model class, RecordSelector. This guy builds result sets of zero or more matching records. The matching rows can then be operated on with a few different primitive data manipulation calls (get, set, load, etc.).
Here's a few examples:
<?php // get first 3 users from Romania $users = $m->find("country='Romania'")->order('name')->limit(3)->load(); // set column for one record $m->find('id=12')->set('country', 'United States'); // delete people from Latvia (sorry Latvia) $m->find("country='Latvia'")->delete(); ?>
So far I've been able to rip out a lot of app-level model code, while still keeping the code fairly concise and readable. Win.
I'm hoping to make a new Pronto release soon, but it still might be a few weeks. If you'd like to take a peek, you can grab the latest n' greatest through git:
$ git clone http://zeroflux.org/pronto.git

March 25, 2009 at 9:03 am -0700
Will this do just the update query or a select and then an update?
Maybe you could make different functions for things like this.
Eg:
$m->set(12,'country', 'United States');
March 25, 2009 at 9:34 am -0700
The exception is when entity caching is enabled. If a column is changed, the entire entity cache record needs to be invalidated, and currently they're indexed by PK (id). So a SELECT is used to get the ID before the invalidate() call is made.
I did consider adding set() and get() to the model-level API as you suggest. I may still do so, but I haven't come up with a compelling reason to do so yet. So far it would provide two nice-to-haves though:
- Cleaner syntax when operating on a single record via PK
- No additional SELECT required when invalidating cache records
Thanks for the feedback!
May 14, 2009 at 10:20 am -0700
May 15, 2009 at 7:59 am -0700
Here's a tarball snapshot for you.
http://www.zeroflux.org/proj/pronto-git-20090515.tar.gz
The manual and API docs have not been updated yet, so you'll have to peek at the code to get a feel for the new model stuff. Old-style models will still work though.
The 'user' module does implement the new model API, so that might give you a starting point.
I look forward to your feedback.
May 18, 2009 at 2:59 am -0700
many thanks: I try and then I say you something.