Extra fields for Zend_Db_Table

by Naneau

Zend_Db_Table is a very, VERY handy class for working with databases. It has come up in numerous posts on this blog. But I’ve always felt that for a complete model implementation there was something missing. For me, the greatest advantage of defining my models away from the rest of the code is that you can put a lot of your domain logic in a single place.

For instance, if you have users in your database, you may have information like their first name, last name, title, etc. To output just their “name”, as a single entity, you have to combine those fields into a single value. For instance, in my case, this would be Mr. Maurice J. Fonk. It makes perfect sense to have a function for your rows that can do that, so you don’t have to do it in your controllers.

So you end up writing a function getName() for your custom row classes. In your views you would then call $row->getName(), to get the full, combined name for a user. But if you wanted just their last name, you would have to call $row->lastname, because it’s a field, not a method. I feel that this is a bit confusing, especially if you have to let others work with them. In my opinion both should be either methods or fields.

Playing around with that idea I came up with the following extensions for Zend_Db_Table and Zend_Db_Table_Row.

Naneau_Db_Table:

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
<?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();

    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
    }
}

Naneau_Db_Table_Row

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
<?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
        }
    }
}

As you can see, you can add a “setup” method to your Zend_Db_Table extensions, that can add those ‘extra fields’:

1
2
3
4
5
6
7
8
9
protected function setup() {
    $this->addExtraField('extraField', array($this, 'extraFieldFunc'));
    //add an extra field referring to method extraFieldFunc for this object
}

public function extraFieldFunc($row, $what) {
    return 'extra field for id: ' . $row->id;
    //assuming there's a field "id" for this row
}

As you can see I define the logic for it in the Zend_Db_Table class itself, because doing it in the rows would lead to considerable overhead. I wanted just a single definition, away from the rows themselves. This kind of approach has the benefit of creating consistent rows that template designers might find easier to work with. The disadvantage of course is that you need a ’setup’ method and some kind of table definition, which Zend_Db_Table tries to avoid. Also, you can not set the values of the extra fields directly, it’s just a one way process.