Fluent Interfaces Require Fluent Situations

My friend and coworker Mike Naberezny wrote recently of fluent interfaces (a.k.a. object chaining). You should read more about it there, but in the mean time, here’s an example usage provided by Mike for a new customer order with multiple lines and a shipping priority:

$customer->newOrder()
         ->with(6, ‘TAL’)
         ->with(5, ‘HPK’)->skippable()
         ->with(3, ‘LGV’)
         ->priorityRush();

The idea is that the fluent interface makes it very easy to read the resulting code in a way that flows naturally. This is cool, but I want to add a caveat to his examples.

I think, for a fluent interface to be effective, you need situations where you actually have all that information at one time so that you can chain the methods in a fluid way. In the above case, we actually know what the whole order is, what the shipping priority is, etc. But very often (I would say “almost always”) you *don’t* know what the whole customer order is. Instead, you are more likely to iterate through the order lines as the customer adds them, or as you retrieve them from some data source. You might need something more like Mike’s initial contra-example:

$order = $customer->newOrder();
foreach ($line as $item) {
    $order->with($item['number'], $item['type']);
}

That’s not to say that a fluent interface cannot perform in such a way (it can if written to allow for it). My point is that writing class methods in a fluent interface style may not be worth the effort when you don’t often expect to have all the necessary information at once; however, if you *do* expect to have that information, it can be very nice. For example, I could see where a fluent interface could be quite effective in something like Solar_Sql_Select. Currently, you need to do this:

$select = Solar::object('Solar_Sql_Select');
$select->cols('*');
$select->from('table_name');
$select->where('colname = ?', $value);
$select->order('colname DESC');
$result = $select->fetch('all');

But with a fluent interface, one could do it this way (which seems very clean to me at this point):

$select = Solar::object('Solar_Sql_Select');
$select->cols('*')
       ->from('table_name')
       ->where('colname = ?', $value)
       ->order('colname DESC');
$result = $select->fetch('all');

Anybody have thoughts on this particular style of expression?

A Dearth of Blogging Lately

A number of people have emailed me to note that my blog has died since I started at Zend. Very sorry to disappoint those of you who look forward to my missives. 🙁

The main reason for my lack of authorship has been that this blog (with some notable exceptions) has mostly been about whatever PHP project I’m currently working on. Since I’m now working on Zend-internal stuff, none of which can be public right now, there’s not much I am at liberty to write about, especially given that my other public projects are mostly stable in one sense or another. That, and my free time has been in short supply given the holidays, family commitments, etc.

Having said that, look for more in this space over the next few days and weeks; in the mean time; thanks for your patience. 🙂

Hired by Zend

I’ve recently been hired by Zend as a PHP developer for them. Many thanks to Andi, Daniel, Mike, and especially Matthew for their consideration and support throughout the process.

I start on Tuesday, and will be spending three weeks out in California as part of my initial time with them. After that, I’ll be telecommuting from home (in Memphis TN). I’m very excited about this change of employment; Zend are big guns in PHP world, and it’s a big deal to me to be working for them. 🙂

I’m not sure yet as to my specific duties, although I get the impression it will be for internal development (not client work). I don’t know if I’ll be part of the framework development process (although I sure hope so!). Obviously there are non-disclosure issues, so I may not be able to talk about my work there much (although again I hope I will be able to do so, as I think Zend suffers from a little closed-mouthedness in regard to the wider PHP community, and could do with some regular blogging).

What does this mean for Solar, Savant, Yawp, and the rest of my public projects? In a philosophical sense, I don’t know; but in a practical sense, I see no reason why my work on these will suffer or drag in any way, once the first few weeks in California are complete.

So if I’m less responsive to emails than normal for the next 3-4 weeks, that’s why. 🙂

Solar 0.9.0 Released

Solar is a simple object library and application repository for PHP 5. You can view the rather extensive change log here.

This release is a major break to backwards compatibility, mostly because we now use PDO for the SQL database API abstraction (as opposed to the homegrown solution I was using in preparation for PDO). The distribution includes a number of migration documents to help users of prior versions move to the new system.

I want to specifically mention one aspect of the Solar SQL system, the Solar_Sql_Select object. This is probably not new in the PHP world, but it’s the first time I’ve done anything like this, so I want to talk about it.

One of the problems with database portability is that you can’t depend on a LIMIT to work the same way across database backends; indeed, it may not even exist as such. Different DB abstraction packages use different methods to support LIMIT emulation, either by rewriting the SELECT statement, or by supporting only the portions of a LIMIT clause available to the particular backend.

What Solar_Sql_Select does is let you programmatically build a SELECT statement, and it keeps the clause portions separated internally. It then combines those portions in a manner specific to the database backend driver, putting the appropriate LIMIT clauses in the right place. Here’s a quick example; let’s build this SELECT statement.

SELECT id, date, type, name
FROM example
WHERE
    date >= '2005-01-01' AND
    date <= '2005-01-31' AND
    type IN('a','b','c')
ORDER BY id
LIMIT 10,50

(The LIMIT in this case is to grab 10 rows starting at row 50.)

The equivalent Solar_Sql_Select code is:



<?php
$select = Solar::object("Solar_Sql_Select");

// the basic columns
$table = 'example';
$cols = array('id', 'date', 'type', 'name');

$select->from($table, $cols);

// WHERE clauses for the date
$select->where('date >= ?', '2005-01-01');

$select->where('date <= ?', '2005-01-31');

// WHERE clause for the type
$types = array('a', 'b', 'c');

$select->where('type IN(?)', $types);

// ORDER and LIMIT
$select->order('id');
$select->limit(10, 50);


$statement = $select->fetch('statement');
?>

Some notes:

  • You can use $select->fetch() to retrieve the ‘statement’ as built, or actual results using ‘all’, ‘col’, ‘row’, ‘PDOStatement’, etc.
  • In this example, quoting happens on-the-fly, and quoting an array returns a comma-separated string of the individually-quoted array values. However, you can also use named placeholders (:start_date, :end_date, :type_list) and then use $select->bind() to bind data to those placeholders all at once. All hail the glory of PDO. 🙂

In the above Solar-based PHP code, when MySQL is the driver, the $statement contents will look something like the initial example. However, when using the Microsoft SQL driver (which does not support LIMIT, only TOP), the resulting SELECT looks something like this:

SELECT *
FROM (
    SELECT TOP 10 *
    FROM (
        SELECT TOP 60 id, date, type, name
        FROM example
        WHERE
            date >= '2005-01-01' AND
            date <= '2005-01-31' AND
            type IN('a','b','c')
        ) AS solar_limit_rev
        ORDER BY id DESC
    ) AS solar_limit
ORDER BY id ASC

Which looks like a mess, but these guys seem to think it works. The point is that by keeping the SELECT clauses separate until you build the statement, you can manipulate the individual pieces with great precision for better portability.

Another thing about Solar_Sql_Select is that paging is built in. If you wanted to grab page 5, where pages are 10 rows each, you can do this:


<?php
// instead of $select->limit(10,50) ...
$select->paging(10); // 10 rows per page

$select->limitPage(5); // limit to page 5
?>

Also, row-and-page counting is built in.


<?php

// instead of $select->fetch() ...
$total = $select->countPages();

/*
$total = array(
    'count' => number_of_rows,
    'pages' => number_of_pages
);
*/
?>

You can see more extensive Solar_Sql_Select docs here (although they are not "real" docs, just migration examples).

Yawp 1.2.0 Released

Yawp is “yet another web programming” foundation for PHP4 (it works in PHP5, too, but is not E_STRICT compliant). It composes a number of PEAR classes into a single encapsulating class so that you can concentrate on writing business logic, not instantiating your support objects. Yawp is a single PHP file, and uses a single .ini file for its configuration.

Among other things, Yawp lets you define “hook” scripts to execute at certain times. For example, a “start” hook runs every time you call Yawp::start(), a “stop” hook executes every time you call Yawp::stop(), and so on.

One of the issues with a “start” hook is that it runs only after the internal Yawp objects have been instantiated (DB, Auth, Log, etc). Sometimes, however, a developer will want to run scripts **before** the objects are created, perhaps in order to replace the standard Yawp objects with objects of his own. That kind of functionality was not available in Yawp 1.x and earlier without a lot of extra work.

The guys at Babel.com.au got tired of this, and one of their developers (Justin Randell) sent me a major patch to add a new kind of hook, the “prep” hook, which is now available in the new Yawp 1.2.0 release.

“Prep” hooks run almost immediately in the Yawp::start() process: right after the configuration file text is loaded, and before the first internal Yawp object is created. Thereafter, each internal Yawp object creation routine checks to see if it has already been created by a prep hook, and skips creation if so. This allows a great deal of control over the Yawp internal objects. (One note of caution: if you override a default object like Auth, make sure your overriding API matches the API expected by the Yawp static convenience methods, otherwise those convenience methods will break.)

In other news, the Yawp::getObject() method had a bug in it where, if a requested object did not exist, there was no return statement. This bug was not an issue in PHP 4.3.x and earlier, but in PHP 4.4.x (when the internals of the engine were changed somewhat) that behavior causes an error stating “only variables may be returned by reference.” The fix was simple: if the object doesn’t exist, return null (instead of having no return statement at all).

Solar 0.8.0 Released

(UPDATE: Fixed the broken link to Solar. Thanks, Justin Patrin.)

After *entirely* too long, I’ve packaged and released Solar version 0.8.0 (devel). I had wanted to release it two weeks ago, but other events demanded priority.

There are lots of changes, so be sure to read the change notes before upgrading. One thing to pay attention to in particular is that I’ve updated the bundled Savant3 installation to beta 2, and updated the Savant3_Plugin_Form installation to 0.2.1 (dev). You can read the various Savant3 change notes at the Savant site.

You’ll find the 0.8.0 release notes for Solar below; be warned, it’s quite long.

Continue reading

Benchmarking call_user_func_array()

Andreas Korthaus wrote the Savant mailing list recently with some interesting benchmark numbers on Savant3 and the output escaping routines. He wrote up a quick template script (in PHP of course) and used the Savant eprint() method 300+ times to escape output (which itself uses only htmlspecialchars() by default). Then he ran the script; the time to run was …

Result: 161 ms

After that I looked at the cachegrind listings to find out
where some noticable time is lost. Looks like escaping costs a
lot of time, so I changed my templates not to use output
escaping:

Result: 12 ms!!!

No, I did not forget a digit 😉

So it seems the way escaping is implemented, hurts performance
very much!

That very dramatic difference in running times (14x!) looked mostly due to the fact that he wasn’t using htmlspecialchars() on the echoed output. Then he changed all the “echo” to “echo htmlspecialchars()” and claimed only a 1.5ms difference. Followup emails from his continued benchmarking noted the slow-points in Savant were related primarily to call_user_func_array() calling the escaping functions, although the output escaping itself also adds running time.

All this piqued my interest to see the speed difference between using native functions, user-defined functions, objects, variable-functions, and the call_user_func[_array]() functions. For an experiment, I wrote a user-defined function …

// mimics htmlentities() as a user-defined function
function html($value)
{
	return htmlentities($value);
}

… and a user-defined class:

class example {
	// mimics htmlentites() as a method
	function html($value)
	{
		return htmlentities($value);
	}
}

Then I wrote a script to make 100,000 calls to each of these:

  • htmlentities($value)
  • html($value)
  • $func($value) where $func = ‘html’
  • $object->html($value) where $object = new example()
  • $object->$func($value)
  • call_user_func($func, $value)
  • call_user_func(array($object, $func), $value)
  • call_user_func_array($func, array($value))
  • call_user_func_array(array($object, $func), array($value))

One set of representative results from my Mac Mini (PHP 5.1rc1), in seconds:

name            : diff     : description
native          : 0.614219 : htmlentities($value)
literal_func    : 0.745537 : html($value)
variable_func   : 0.826048 : $func($value)
literal_method  : 0.957708 : $object->html($value)
variable_method : 0.840837 : $object->$func($value)
call_func       : 1.006599 : call_user_func($func, $value)
call_object     : 1.193323 : call_user_func((array($object, $func), $value)
cufa_func       : 1.232891 : call_user_func_array($func, array($value))
cufa_object     : 1.309725 : call_user_func_array((array($object, $func), array($value)

Here’s another one:

name            : diff     : total
native          : 0.772733 : 0.772733
literal_func    : 0.737210 : 1.509943
variable_func   : 0.807990 : 2.317933
literal_method  : 0.820931 : 3.138864
variable_method : 0.821516 : 3.960380
call_func       : 1.006845 : 4.967225
call_object     : 1.210223 : 6.177448
cufa_func       : 1.114493 : 7.291941
cufa_object     : 1.307970 : 8.599911

And another, just for good measure:

name            : diff     : total
native          : 0.613295 : 0.613295
literal_func    : 0.733299 : 1.346594
variable_func   : 0.815782 : 2.162376
literal_method  : 0.965143 : 3.127519
variable_method : 0.842771 : 3.970290
call_func       : 1.023640 : 4.993930
call_object     : 1.221747 : 6.215677
cufa_func       : 1.104610 : 7.320287
cufa_object     : 1.449468 : 8.769755

So native calls to htmlentities() are twice as fast as doing effectively the same thing via call_user_func() with an object method, and using call_user_func_array() is 10-20% slower than using call_user_func(). I hadn’t realized the differences in speed of execution would be so distinct; of course, I never really thought about it before, either. Clearly PHP has to do a lot more work behind the scenes to map the variables to objects and parameters when using call_user_func_array().

You can download the very simple func_speed.php script here.

Um, you’ll need Solar for it (sorry about that, I used the debug-timer object for the testing).

Stumping for Solar

Joe Stump writes a good MVC overview at OnLamp.com in this article. He’s kind enough to mention my Solar project in one of the opening paragraphs:

As of this writing, there are very few true MVC frameworks written in PHP. In fact, there is only one that I know of, Solar, that is entirely pure PHP 5 code. Another one out there is Cake, which is trying to be the “Ruby on Rails of PHP.” I, personally, have a few problems with both of these frameworks. Both Solar and Cake fail to leverage existing code in PEAR, Smarty, etc. Cake appears a bit disorganized at the moment. Finally, Solar is the work of mostly a single person (not that Paul isn’t a great coder or person, but there is only a single gatekeeper at the time of this writing). These may not be issues that concern you, and if they don’t concern you, by all means check these two out.

I’m thankful for the mention, but I want to nitpick a bit. 😉

That Solar does not use PEAR: Solar does not use PEAR code, because most PEAR code (especially for the parts that Solar needs) are not E_STRICT compliant for PHP5. Because Solar aims for strict PHP5, reuse is not really an option.

Regarding Smarty reuse: Solar uses the Savant3 template system instead of Smarty. Of course, I wrote Savant too, so this may not count so much in Joe’s eyes. 😉

That Solar is the work of one person: Well, this part is true in the way he means it; I’m the founder, and pretty much the only author on the project (see below for caveats). I take a lot of direction and input from Solar users and commenters, but only insofar as their opinions match the foundational concepts of Solar to begin with.

The benefit of the “one person” model, at least in the beginning, is that the project has evidence of conceptual integrity (as outlined by Fred Brooks in The Mythical Man Month).

The drawback is that one person can only do so much work over time. If other people want to help, I’m all for it. However, my experience has been that it takes people a long time to contribute new features to a project, usually (1) after they have used and picked it apart over an extended period, and (2) when it has solved other problems for them, but fails to solve an additional related problem. (Bug reports are a different beast. 😉

Solar, as a public project, has only been around since February 2005. Six months of an in-development project is not really long enough for major contributions to arrive en masse (9 to 12 is usually more like it). I’m very happy for the help I have received so far, though:

  • Clay hosts the site and acts as sounding board
  • Matthew authored Solar_Filter and Solar_Form_Load_Xml
  • Jean-Eric is the first translation contributor
  • Travis as the “loyal opposition” to simplicity and brevity 😉
  • Everyone else on mailing list 🙂

[UPDATE] When I say above that Solar doesn’t reuse PEAR code, I mean the libraries. Solar is installed and upgraded using the PEAR installer, and is distributed via a PEAR channel. The entire PHP world has Greg Beaver to thank for recent and spectacular developments on that front. 🙂

Savant3 version 3.0.0 beta 2

On the heels of yesterday’s Savant2 release, I have a Savant3 beta 2 release today; you can view the change log on the main page. With any luck, this will be the last beta release of Savant3; if I get no bug reports, I’ll mark it stable (at last!).

In addition, I’ve been working on the newly separated Form plugin package; look for a release of that in the next few days, with .phpt-based unit tests included.

And then I get to start documentation on everything. 🙂