Lookupable Behavior
Posted on 30/8/08 by Felix Geisendörfer
Hey folks,
this is post #11 of my 30 day challenge.
If you ever had to work with a fairly normalized database where for example the statuses of some tables have their own table then you probably know how annoying it can be do keep those status tables in sync with everybody during development. Whenever you create a new status record you need to tell everybody about it. Then you need a good approach to refer to this status in your application and keep your code readable.
So here is what I came up with:
function lookup(&$model, $conditions, $field = 'id', $create = true) {
if (!is_array($conditions)) {
$conditions = array ($model->displayField => $conditions);
}
if (!empty($field)) {
$fieldValue = $model->field($field, $conditions);
} else {
$fieldValue = $model->find($conditions);
}
if ($fieldValue !== false) {
return $fieldValue;
}
if (!$create) {
return false;
}
$model->create($conditions);
if (!$model->save()) {
return false;
}
$conditions[$model->primaryKey] = $model->id;
if (empty($field)) {
return $model->read();
}
return $model->field($field, $conditions);
}
}
The basic idea is to use this behavior with primitive tables that only have an id and a name / title / whatever column. So lets say you have a post_statuses table and you want to assign your current post the status 'Published', things would be as easy as:
This causes the behavior to search your post_statuses table for a record named 'Published'. If it is found then the records id is returned. If it is not found, then the record is created and the new id is returned. This way you never have to worry about keeping your status tables in sync as everything is created on the fly and it is dead simple to expand available status options this way. Of course this method is not going to protect you from typos or people going nuts with adding new statuses, but with a little internal policy on how to use this behavior you should be able to save yourself some time and regain some of that hair you lost because of internet explorer : ).
The latest version of this code along with installation instructions is published in the debuggable scraps repository as always.
Feedback is most welcome! Please leave a comment.
-- Felix Geisendörfer aka the_undefined
You can skip to the end and add a comment.
Andreas: So you want to create a migration for every status you add? That's like 100 migrations for a reasonable sized projects - quite some overhead for what is being accomplished ; ).
Yes, why not? A migration isn't expensive. Ok, it's sure more work than your solution... hmmmm have to think about this ;-)
btw.:
What about the "9am-thing"? Didn't you just loose 50 bucks, it was 9am CET, wasn't it? :)
Andreas: I'm in Atlanta (EST) now so I made an announcement that that will serve as my new reference time zones for the postings 3 days ago ; ).
Another cunning snippet :-)
This method also leads to clearer code by getting rid of a magic number. Might get a bit troublesome if someone decides to change the status string to "Is Published" though...
Well with good database planning the need for such a behavior can be avoided and I would rather aim for that.
The old idea of having everything normalized to the last degree is about as outdated as trying to get all the data via a single complex SQL query
"Whenever you create a new status record you need to tell everybody about it."
The solution will just lead to multiple entries for the same purpose!
If you do not inform your team you will end up with “stored”, “saved”,….
The solution seems to be a way to make a bad approach simply more convenient.
Bill: Symmetry is beauty. If your developers don't get this - you're screwed. We've used this behavior in various projects of various sizes and never had issues with duplicated records.
Tarique Sani: Not everything needs to be normalized. But some things do. This is useful for those cases where normalization makes sense.
I am not sure really why everybody did not find out another, much more convenient aspect about this behaviour: creating tags in a tag system.
With a tag system you typically have to analyze a comma-separated list of tags the user typed in. With the lookupable behavior it's very easy to create a new tag on the fly or find out the id of the tag with the specified name. Makes perfect sense there, also its name "Lookup".
I agree for migrations this behavior can be avoided, but it is so much convenient. Felix did not say you shall use it instead of migrations. Just use it for one or two occasions, where the non-existent database row could severely harm your application. For example, think of the absence of a guest account in your authentication system, if it really needs one.
Really guys, try it out in 2 or 3 projects, you will see for yourself. As Felix said, we never had any issues with it whatsoever and have used it in 8+ projects.
I totally agree that it seems to very convenient and one might use it for something different then status fields.
It just seems for me that this will likely lead to to decisions which should mostly not to be done on a coding level.
“For example, think of the absence of a guest account in your authentication system, if it really needs one.”
Q.e.d.
Part of Software Design is to reduce the risk to get screwed. You might get away with this style as long as your Dev-Team is rather small.
->lookup(‘published’)
Just the hard coded status might give you plenty of “joy” in the future.
I would use it combined with an enum that can be extended at the code level rather than the DB level. This would avoid the whole duplicates issue, allow for changing the string to display (in the DB) very easily without changing all the lookup code and avoid the magic numbers/strings.
This post is too old. We do not allow comments here anymore in order to fight spam. If you have real feedback or questions for the post, please contact us.
If the problem is only to keep all developers databases in sync I'd rather use migrations...
But it looks nice and easy to use anyway. :)