Proposal for PEAR::Structures_DataGrid column formatter

September 3rd, 2005. Tagged: PEAR

This posting outlines a proposal for an addition to the PEAR package Structures_DataGrid. More specifically it concerns the formatter method of the Structures_DataGrid_Column class.

First off, great package! Many thanks to the authors Andrew Nagy [Wishlist] and Olivier Guilyardi!

I was trying to use the package to create a listing of database data (a list of users actually) , with a link to delete a record next to each data row. So I needed a custom column that doesn't exist in in the database and in order to create it I needed to use a callback function as a formatter of the column. Following the examples in the documentation I did:

<?php
$column =& new Structures_DataGrid_Column('&nbsp;');
$column->formatter = 'getDeleteLink(link_text=Delete)';
?>

This is a strange syntax to define a callback function, with the brackets and the way parameters are given. But I can live with it, having in mind all the benefits I gain from using the package.

Next, I wanted the getDeleteLink function to be part of my User class, the same class that contained the listing() method, which is where I create an use the DataGrid object. So I tried something along the lines of

<?php
array($this, 'getDeleteLink')
?>

This is the usual way to use the callback pseudo type in PHP when you've in a class whose method you want to call back. It didn't work, but I found another solution. If the callback method is static, I can use the syntax to call the method statically:

<?php
$column =& new Structures_DataGrid_Column('&nbsp;');
$column->formatter = 'User::getDeleteLink(link_text=Delete)';
?>

This is still not so intuitive way of defining a callback function but it worked and this is the important thing.

OK, then for my one of the columns I wanted to call back htmlentities(), just in case someone really wants the username "I'm an <script>var XSSGuy;</script>" ;) I tried quite a few way to make it work, but I couldn't. In the end, the formatter() method always adds the current record as a parameter to the function being called back and htmlentities() won't accept an array. I guess I could wrap htmlentities() in another static method in my class and call the new method, but seemed like such a waste. In addition I saw a @todo comment that says the formatter() needs a revision, so I went ahead.

What I ended up having is much shorter measured in lines of code that the original implementation. Also it is more flexible and intuitive, following the usual way a callback function is defined.

Also with my implementation I could use the array($this, 'method') syntax.

A step back. What are we looking for in the formatter method? It should give the ability to use:

  • A function with no parameters.
  • A function with one parameter which is the value of the current column of the data row (the most common use probably)
  • A function that accepts the whole data row (record) as a parameter
  • A function that accepts the whole data row as a parameter plus any number and type of other parameters

OK, that's for the parameters. Now for the function itself. It could be:

  • A stand-alone, old-procedural-school function
  • A static method of a class
  • A non-static method of a class

And finally we should be able to call the function inside and outside of a class.

Follows a list of valid ways to define a formatter function with my implementation.

<?php
// This will call myFunction passing the
// current column record as a parameter
array('myFunction')
// This is also the syntax to call a function
// without any parameters.
// It will happen in case the column is not mapped
// to a table column
// (the second parameter of the
// Structures_DataGrid_Column constructor)
// To be exact, this second use will call the
// callback function with FALSE parameter
// PHP allows passing parameters to functions
// that don't expect any

// This will pass the current record
// as an array with key 'record'
// The key 'record' is backwards-compatible
// with the way it's currently working
array('myFunction', true)


// This allows for any number if parameters to be
// passed in addition to the
// current record, which is still
// identified by its 'record' key
array('myFunction',
        array(
            'param1' => 'value1',
            'param2' => 'value2'
        )
     )


// For the call from within a class or
// outside of it, this example shows all valid calls
class myClass {
    
    function blah(){}
    
    function dg(){
        $column =& new Structures_DataGrid_Column('&nbsp;');
        
        // static
        $column->formatter(
            array(
                array('myClass', 'blah')
            )
        );
        
        // non-static, using $this instance
        $column->formatter(
            array(
                array($this, 'blah')
            )
        );
    
    }
}

// non-static using an instance of the class, outside of it
$column =& new Structures_DataGrid_Column('&nbsp;');
$myInstance = new MyClass();

$column->formatter = array(array($myInstance, 'blah'));
?>

The callback function is always defined as an array, this is in case the authors of the package are committed to the API and doesn't want to change it. Currently the formatter is a string, so a simple check is_array() can serve is a switch between the old and the new implementation.

Finally, here's the actual implementation of the formatter() method. It may be disappointingly short, after this whole long posting ;)

<?php
class Structures_DataGrid_Column
{
// ...
    function formatter($record)
    {
        
        if (empty($this->formatter[0])) {
            return false;
        }
        
        $parameters = false;
        if (empty($this->formatter[1])) {
            if (!empty($this->fieldName)){
                $parameters = $record[$this->fieldName];
            }
        } else {
            $parameters = array('record' => $record);
            if (!is_bool($this->formatter[1])) {
                $parameters = array_merge(array('record' => $record), $this->formatter[1]);
            }
        }
        
        return call_user_func($this->formatter[0], $parameters);
    }

...
}
?>

Tell your friends about this post: Facebook, Twitter, Google+

Sorry, comments disabled and hidden due to excessive spam. Working on restoring the existing comments...

Meanwhile, hit me up on twitter @stoyanstefanov