Modified fields for Zend_Db_Table
by Naneau
Earlier today, I wrote a little extension to Zend_Db_Table that allows for “custom” fields to be added to it’s rows. When it was done some other ideas came floating up. Why wouldn’t I make my life even easier, by creating fields that don’t output their values directly, but in some meaningfully modified way.
Why would I want to do that, you ask? Well, let’s assume you have a database definition that stores a created_on value for every row. It contains a UNIX timestamp. Like most people, I don’t know what time it is by looking at a very long number (at the time of writing it is 11784070556). But what if I could make my created_on field not contain a timestamp, but a Zend_Date object.
Internally it should still work with the timestamp, as it is the “pure” form that computers like. Another field type that may need modification is that for passwords. In that case you need a one way modification. You want to retrieve a encrypted password, but set a plain text one. You only need modification (encryption) while setting it.
Now, I’ll admit that this idea is a little more far-fetched and less useful as the one described in the previous post. Most of this could be done in various custom row classes and domain logic in the table class. Yet I like to play around and decided to go ahead anyway, so I needed some kind of “modifier” for fields. Something a little like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | interface Naneau_Db_Table_Modifier { /** * get the modified value * @param mixed $value * @param Naneau_Db_Table_Row $row * @return mixed */ public function modify($value, $row); /** * get the original value * @param mixed $value * @param Naneau_Db_Table_Row $row * @return void */ public function unModify($value, $row); } |
I modified my earlier extensions to check for the existence of such a modifier and call the relevant methods when getting and setting a row field. They now are:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 | <?php require_once 'Zend/Db/Table/Abstract.php'; require_once 'Naneau/Db/Table/Row.php'; class Naneau_Db_Table extends Zend_Db_Table_Abstract { /** * array of extra fields * * @var array */ private $_extraFields = array(); /** * modifiers * * @var array */ private $_modifiers = array(); public function __construct(array $config = array()) { if (!isset($config['rowClass'])) { $config['rowClass'] = 'Naneau_Db_Table_Row'; } //boeh! parent::__construct($config); if (method_exists($this, 'setup')) { $this->setup(); } } /** * add an extra field to the row definition * * @param string $name * @param string|array $fieldFunc */ protected function addExtraField($name, $fieldFunc) { $this->_extraFields[$name] = $fieldFunc; } /** * get an extra field * * @param Naneau_Db_Row $row * @param string $what * @return mixed * @throws Zend_Db_Table_Row_Exception */ public function getExtraField($row, $what) { if (!array_key_exists($what, $this->_extraFields)) { //field doesn't exist in extra fields require_once 'Zend/Db/Table/Row/Exception.php'; throw new Zend_Db_Table_Row_Exception("Specified column \"$what\" is not in the row"); } $func = $this->_extraFields[$what]; //the function $args = array($row, $what); //the arguments (an copy of this row is given to the function to work with) if (is_array($func)) { //method of some class or object return call_user_method_array($func[1], $func[0], $args); } else { //plain function return call_user_func_array($func, $args); } //return the result of a function } /** * get a modified field value, if a modifier has been registered * * @param Naneau_Db_Table_Row $row * @param string $what * @param mixed $value * @return mixed */ public function getModifiedValue($row, $what, $value) { if (array_key_exists($what, $this->_modifiers)) { $modifier = $this->_modifiers[$what]; return $modifier->modify($value, $row); } return $value; } /** * get an unModified value, if a modifier has been registered * * @param Naneau_Db_Table_Row $row * @param string $what * @param mixed $value * @return mixed */ public function getUnModifiedValue($row, $what, $value) { if (array_key_exists($what, $this->_modifiers)) { $modifier = $this->_modifiers[$what]; $value = $modifier->unModify($value, $row); } return $value; } /** * add a modifier to the model * * @param string $field * @param Naneau_Db_Table_Modifier $modifier */ protected function addModifier($field, $modifier) { $this->_modifiers[$field] = $modifier; } } |
and
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | <?php require_once 'Zend/Db/Table/Row.php'; class Naneau_Db_Table_Row extends Zend_Db_Table_Row_Abstract { /** * overridden __get * * @param string $what * @return mixed * @throws Zend_Db_Table_Row_Exception */ public function __get($what) { try { $return = parent::__get($what); //it exists as a 'normal' field } catch (Zend_Db_Table_Row_Exception $e) { //field doesn't exist as 'normal' field $return = $this->_getTable()->getExtraField($this, $what); //try to get it from the extra fields in the table definition } return $this->_getTable()->getModifiedValue($this, $what, $return); //return modified value (if it can be modified, that is) } public function __set($what, $value) { $value = $this->_getTable()->getUnModifiedValue($this, $what, $value); parent::__set($what, $value); } } |
You can set modified fields in the same “setup” method that you can use for the “extra” fields.
1 2 3 4 | protected function setup() { Zend_Loader::loadClass('Naneau_Db_Table_Modifier_Date'); $this->addModifier('timestamp', new Naneau_Db_Table_Modifier_Date()); } |
Where the date modifier is:
1 2 3 4 5 6 7 8 9 | class Naneau_Db_Table_Modifier_Date implements Naneau_Db_Table_Modifier { public function modify($timestamp, $row) { return new Zend_Date($timestamp, null, Zend_Registry::get('locale')); } public function unModify($date, $row) { return $date->get(); } } |
After you have registered a date modifier for a field, you can work with it as if it were a Zend_Date object:
1 2 3 | //fetch a db row echo $row->created_on; //outputs something like: 6-apr-2007 6:20:00 |
At the moment it really is just a modifier. You can get and set fields it as if they were objects, but you can’t call methods on them changing their internal state, which you could then save. I’m thinking up a solution for that, using some kind of object store/cache. Also please note that the toArray methods of both Row and Rowset still return “internal” values.
And that’s a lot of code for a single post. But as this is more of an experiment than anything else, I decided to just post it inline, and not as an attachment. I’d love comments on this. Preferably comments on why it’s evil and shouldn’t be done this way, because for me this is just an attempt at learning.
Comments
very nice. i try to get something similar to work.
eg. i have a DB field “changedate” TIMESTAMP (ON UPDATE CURRENT_TIMESTAMP).
when zend_db_table selects this field, i would like to get UNIX_TIMESTAMP(changedate) in the SELECT.
i tried to rewrite $_metadata and $_cols to get an zend_db_expr in to it, but it seems to be ignored.
it would be so nice if a simple $bar=$foo->find(1234)->current()->toArray(); echo $bar['changedate']; would display a unix timestamp in case of the mysql timestamp.
any idea ?
Rufinus
Exactly what I was looking for. I need to store a string in database but access it as it was an array. Good for me I decided to look whether somebody already did something similar. Thank’s a lot!
I’ve got same problem as Rufinus …
In ZF 1.x was possible to change $this->_cols but seems that with 1.5.x it’s ignored …
Does anyone has a workaround ?