Paul M. Jones

Don't listen to the crowd, they say "jump."

Easy Benchmarking with Solar

Comparative benchmarking is tedious work. It's not hard to do, it's just no fun to set up the same scaffold every time you want to figure out how long it takes different pieces of code to execute.

To do a benchmark, essentially you need only four things: a timing mechanism, a loop to run the first case, a loop to run the second case, and a way to show the time taken by each loop.


$loopTimes = 1000;

$before = microtime(true);
for ($i = 0; $i < = $loopTimes; ++$i) {
    // case #1:
    // this code gets run $loopTimes
}
$after = microtime(true);
$case1 = $after - $before;

$before = microtime(true);
for ($i = 0; $i <= $loopTimes; ++$i) {
    // case #2:
    // this code gets run $loopTimes
}
$after = microtime(true);
$case2 = $after - $before;

echo "Case 1 ran $loopTimes in $case1 seconds";
echo "Case 2 ran $loopTimes in $case2 seconds";

This is boring, and repetitive, and essentially the same every time (except for the code cases being benchmarked). This makes it a classic candidate for refactoring to a PHP class.

Enter Solar_Test_Bench. As with Solar_Test, you extend the base class, add your own code cases as bench*() methods, and a runner will execute each of those methods the same number of times and display how long each one took. (It uses Solar_Debug_Timer for the timing mechanism.)

Here's an example of a real benchmark test from Solar to test the speed difference between require_once() and Solar::loadClass().


Solar::loadClass('Solar_Test_Bench');
class Bench_Solar_LoadClass extends Solar_Test_Bench {

    public $file = 'Solar/Sql.php';
    public $class = 'Solar_Sql';

    public function benchLoadClass()
    {
        Solar::loadClass($this->class);
    }

    public function benchRequireOnce()
    {
        require_once $this->file;
    }
}

To run it, we call the benchmark runner to execute each method in the class 1000 times, and get this output:


bash-2.05$ php bench.php Solar_FileExists 1000
name             : diff     : total
__start          : 0.000000 : 0.000000
benchLoadClass   : 0.005793 : 0.005793
benchRequireOnce : 0.034572 : 0.040365
__stop           : 0.000012 : 0.040377
bash-2.05$

You would want to run it a few times to get a better idea of the real speed, but the above is a representative result from my MacBook 2.0GHz. Looking at the "diff" column we can see that Solar::loadClass() is about 5 times faster than using require_once(). (Thanks to Clay Loveless for thinking up this particular benchmark.)

Here's another one:

Currently, Solar::fileExists() uses an fopen() call to see if a file exists on the include_path. (The regular file_exists() and is_readable() PHP calls do not check the include path.) However, Clay Loveless wondered if it would be faster to explode() the include_path and see if the file_exists() on each of those individually.

I won't copy the benchmarking class here; you can see it at Bench_Solar_FileExists. We actually test three different cases; the current fopen() case, and two new versions of using include_path. The results are:


bash-2.05$ php bench.php Solar_LoadClass 1000
name                   : diff     : total
__start                : 0.000000 : 0.000000
benchFopen             : 0.040105 : 0.040105
benchExplodeInclPath   : 0.015582 : 0.055687
benchNoExplodeInclPath : 0.020612 : 0.076299
__stop                 : 0.000004 : 0.076303
bash-2.05$

So the second case, where we explode() the include_path and then check file_exists() on each of the resulting paths, is about 3 times faster than using fopen(). The next release of Solar will use that code instead of the current fopen() code as a result.

(Note: the benchmarking classes are in Subversion only at this point, but will be in the next release of Solar when it comes out.)


Dependency Injection in Solar

Jeff Moore talks about dependency injection in PHP and opines as to how he'd like to see "the current crop of frameworks" adopt the technique. Jeff, your wish is granted! (At least for Solar. ;-)

Because it makes great use of dependecy injection, Solar has a standardized form of generating/retrieving dependency objects using its Solar::dependency() method.

The calling code sends the dependency params to the target object at construction time. The target object then passes those params through Solar::dependency() to retrieve or create the dependency object.

The params may be an object (the dependency object itself), or a string (treated as a registry key for Solar_Registry::get()), or an array (which are treated as params to create a new dependency object internally at the target using Solar::factory()).

UPDATE (2009-03-28): Updating documentation links.


Solar 0.20.0 released

Exactly one month after the last release, I have uploaded Solar version 0.20.0 alpha. There are lots of changes in this release, but the biggest news is that the page-controller now uses method-based actions (as opposed to file-based actions).

You can read the change notes here, but it's a really long list. If you want something a little more readable, with better narrative, you'll find it after the jump below. The new page-controller information is especially good. However, the narrative is quite long, so consider yourself warned. ;-)

Naming and Standards

About a third the change notes relate to naming, vocabulary, and standarization. Across the whole codebase, "drivers" are now called "adapters" in line with the Adapter design pattern. Similarly, I'm trying to use load() and is*() when appropriate, in line with the published coding standards.

Class Name Changes

Solar_User

The Solar_User subclasses have been brought to the top of the library, and their driver classes now have "Adapter" in their names.

  • Solar_User_Access => Solar_Access
  • Solar_User_Access_* => Solar_Access_Adapter_*
  • Solar_User_Auth => Solar_Auth
  • Solar_User_Auth_* => Solar_Auth_Adapter_*
  • Solar_User_Role => Solar_Role
  • Solar_User_Role_* => Solar_Role_*

Solar_Cache

The cache driver classes also now have "Adapter" in their name.

  • Solar_Cache_File => Solar_Cache_Adapter_File
  • Solar_Cache_Memcache => Solar_Cache_Adapter_Memcache

Solar_Sql

The SQL driver classes have been renamed to "Adapter" classes.

  • Solar_Sql_Driver_* => Solar_Sql_Adapter_*

Inherited Config File Values

I have modified Solar_Base so that it looks at the Solar.config.php values for all its parent classes, and inherits them for the current instance. Say for example you have this inheritance hierarchy:



<?php
    class Foo_Base extends Solar_Base {}

    class Foo_Bar extends Foo_Base {}

    class Foo_Bar_Baz extends Foo_Bar {}
?>

Let's also say you have this in your config file:


<?php
    $config['Foo_Base']['zim'] = 'dib';
?>

When you instantiate Foo_Bar_Baz, its $_config will receive array('zim' => 'dib') from the config file, even though there is no 'Foo_Bar_Baz' config file key. This is because Solar_Base now grabs all parent config-file values and merges them automatically into child class config-file values. So unless you override them specifically for the particular child class, the parent values will be used. Parent values are merged top-down, so the immediate parent value takes precedence over grandparent values.

Note that this does not mean that parent class $_config property values are merged when inherited. The inheritance only applies to the values in the config file.

Adapter Configuration

Any class that uses an adapter now passes configuration to that adapter via a 'config' config key entry. For example, it used to be that Solar_Sql took a 'driver' key and passed config to that driver like this:


<?php
    $config['Solar_Sql'] = array(
        'driver' => 'Solar_Sql_Driver_Mysql',
        'host'   => '127.0.0.1',
        'user'   => 'username',
        'pass'   => 'password',
        'name'   => 'database',
    );

?>

Now it uses an 'adapter' key and a 'config' key to indicate options passed to the underlying adapter object:


<?php
    $config['Solar_Sql'] = array(
        'adapter' => 'Solar_Sql_Adapter_Mysql',
        'config'  => array(
            'host'   => '127.0.0.1',
            'user'   => 'username',
            'pass'   => 'password',
            'name'   => 'database',
        ),
    );

?>

Note also that you can leave out the 'config' key entirely, and the master class will use the default configs for the adapter:


<?php
    $config['Solar_Sql'] = array(
        'adapter' => 'Solar_Sql_Adapter_Mysql',
    );

    $config['Solar_Sql_Adapter_Mysql'] = array(
        'host'   => '127.0.0.1',
        'user'   => 'username',
        'pass'   => 'password',
        'name'   => 'database',
    );

?>

Locale Files

It used to be that you could set the non-default location of a locale file using the 'locale' config key in in Solar_Base extended class. This is no longer the case. All locale files are always located in the "Locale/" directory in the class directory.

However, locale strings are now inherited; this means you can set locale keys in a parent class locale file, and the child class will use those values automatically (unless you override them in the child class locale file).

Page-Controller Class

Actions As Methods, Not Files

Previously, page actions were PHP files in the Actions/ directory (e.g., browse.action.php, read.action.php, etc). To map URIs to their actions, and to the parameters they needed, you used $_action_info with the action name as a key, and gave it a string of info paramters. For example, 'read' => 'id' would create $this->_info['id'] for you when the 'read' action was called.

In this release, actions are now methods in the page-controller class, and the parameters for that method are the info mapping. For example, the read action noted above would be "public method actionRead($id = null)". This makes the $_action_info mapping property unnecessary.

To migrate your action files to action methods, create one method for each file, and prefix its method name with the word "action". Then, for each $this->_info key you needed in your action, give the method a parameter. For example, if previously you had an "archive" action file, with info mappings for "year", "month", and "day", you could write a new action method like this:


<?php
public function actionArchive($year = null, $month = null, $day = null)
{
    // ...
}
?>

The page controller maps URI action names with dashes and underscores to studly caps; e.g., "/example-name/" and "/example_name/" become "/exampleName/" internally, which maps to the "actionExampleName()" method.

This also means that once you copy your action files into methods, you can delete the Actions/ directory entirely.

Views

Views are now stored in a singular-name directory, "View/", not a plural-name "Views/". This is in line with the rest of Solar, where directories have singular names.

Views used to be named ".view.php" for full view templates, and ".part.php" for partial view templates. This has changed; the new standard is to name all templates with just a ".php" suffix, and if you have a partial template, you should prefix its name with an underscore. This is identical to the Ruby on Rails convention.

For example, if previously you had "Views/template.view.php" and "Views/partial.part.php", you would rename them "View/template.php" and "View/_partial.php".

Additionally, the page controller defines a series of fallback view-template paths automatically. For a Vendor_App_Example class, the page controller will look first in Vendor/App/Example/View/ for Example-specific views, then Vendor/App/View for views used across all apps. This lets you use standard error views, standard comment views, etc. among all your applications without having to copy and paste them into each application.

The creation of Solar_View instances for view processing has been separated into its own method, _viewInstance(). If you need fine-grained control over how a view instance is created, you can override this method and the page controller will use the returned Solar_View object for rendering your views.

The _viewInstance() method also defines where helper classes are located for you. For a Vendor_App_Example class, it used to that you only got Vendor_App_Example_Helper_* helpers. In this release, the _viewInstance() method defines more locations for helper classes: Vendor_App_Example_Helper for Example-specific helpers, Vendor_App_Helper for helpers across all applications, and Vendor_View_Helper for helpers across the entire Vendor codebase. (Solar_View_Helper remains the final fallback.)

Layouts

Layouts are now stored in a singular-name directory, "Layout/", not a plural-name "Layouts/". This is in line with the rest of Solar, where directories have singular names.

Layouts used to be named ".layout.php" for full layout templates, and ".part.php" for partial layout templates. This has changed; the new standard is to name all templates with just a ".php" suffix, and if you have a partial template, you should prefix its name with an underscore. This is identical to the Ruby on Rails convention.

For example, if previously you had "Layouts/threeCol.layout.php" and "Layouts/auth.part.php", you would rename them "Layout/threeCol.php" and "Layout/_auth.php".

Additionally, the page controller defines a series of fallback layout-template paths automatically. For a Vendor_App_Example class, the page controller will look first in Vendor/App/Example/Layout/ for Example-specific layouts, then Vendor/App/Layout for layouts used across all apps. This lets you override generic layouts for your specific application.

The creation of Solar_View instances for layout processing has been separated into its own method, _layoutInstance(). If you need fine-grained control over how a layout instance is created, you can override this method and the page controller will use the returned Solar_View object for rendering your layouts.

The _layoutInstance() method also defines where helper classes are located for you. For a Vendor_App_Example class, it used to that you only got Vendor_App_Example_Helper_* helpers (which are specific to the Example app, not to layouts). In this release, the _layoutInstance() method defines two different locations for helper classes: Vendor_App_Helper for helpers across all applications, and Vendor_View_Helper for helpers across the entire Vendor codebase. (Solar_View_Helper remains the final fallback.)

Hooks

Previously, there were only three "hooks" in the page controller: _setup() for extended constructor code, _preAction() to run before the first action, and _postAction() to run after the last action (but before view rendering).

Those hooks have been extended and modified to become:

_setup()
after __construct()
_preRun()
before first action
_preAction()
before every action
_postAction()
after every action
_postRun()
after last action

Note that _preAction() and _postAction() have been repurposed; if you currently have code in those methods, you should move it to _preRun() and _postRun().

Finally, there is a separate _render() method that handes view and layout processing; if you need to pre- or post-process rendered output, override that method. For example:


<?php
protected function _render()
{
    $output = parent::_render();
    // run $output through filtering process, then return
    return $output;
}

?>


No More Loners!

Read and heed the latest from Clay Loveless: Stop Writing Loner Applications.

After nearly a decade in web application development, I continue to be dumbfounded by how many applications are built and released by the open-source community that apparently believe that they'll be the only complex, user-database application I'll will want to run.

...

In nearly every case, these loners have been built with no apparent concern for the fact that the person or company involved may already have a database of users, or that one of the other applications may be installed alongside it. Hard to believe, but true.

The result is that the guys in the trenches trying to put together a cohesive whole have to spend a great deal of their cycles writing and maintaining "bridge code" to allow site visitors to register and authenticate against all of the chosen applications at once. ...

Right on, Clay. Authentication and user-profile support in particular are a real burden to bear. I've tried very hard to make my apps (such as YaWiki) authentication-system agnostic, and the user-info related classes in Solar are read-only for exactly that reason.

One of the nice things about Solar-based authentication is that it is adapter based: you can change the backend from an LDAP server to an email server, or to a .htaccess file, and you never need to change your application code. (PEAR Auth also provides decent tools for this if you need PHP4 support.)

I have one other major criticism of most public code: classes are not generally namespaced properly. It's as if project managers think theirs is the only "User" or "Member" class in the world. All classes need, at a minimum, a project-name or vendor-name prefix. (Consistency in naming is a related issue, but I'll save that for another time.)

This is one thing the Zend Framework gets right, although it was against their will for a very long time. (All Zend classes are prefixed with "Zend_" so they automatically deconflict with all other userland classes.) That's one thing I'm glad I kept yammering about while I was at Zend; it took months to get the managers to accept that as a naming convention, and even then it was only through the combined efforts of numerous developers to push it through.


Quick Hits: Savant Forum, Akismet

Two quick hits: first, by popular request, I've started a web-forum for Savant at http://phpsavant.com/forum; it uses Vanilla. Second, because of a recent spate of spam attacks on my blog comments (1000 or more per day), I've activated Akismet; in 24 hours it has captured 3900 spam comments and trackbacks. Makes life a lot easier, although it'd be nice if that was the default spam-catcher for Wordpress.


Solar 0.19.0 released

(Solar, the simple object library and application repository for PHP5, is both a library and framework for developing web applications in PHP5.)

The single biggest change in this release is in the license; we have moved from LGPL to New BSD.

The main additions in this release are project-support related.

  • The Solar_Docs_Apiref class will read an entire Solar-style file hierarchy and parse the inline documentation into an array; you can then build a writer of your own to take that array and generate documentation files from it. (This is how I build the API reference documentation for the Solar web site.)
  • The related Solar_Docs_Phpdoc class will parse a PHPDoc comment block into its summary, narrative, and technical portions. While nowhere near as robust or full-featured as PHPDocumentor, I think the internals of Solar_Docs_Phpdoc are much easier to understand. It doesn't support inline tags, but most of the important block tags are recognized and parsed appropriately (@var, @param, @return, @throws, etc).
  • Solar_Docs_Apiref also makes use of the new Solar_Class_Map, which parses a Solar-style file hierarchy and returns an array of class-name keys with file-name values.
  • There is a new Solar_Log class, with adapters for file-based logging (good for production), echo-based logging (good for development), and multiple log support.

There's one big change in Solar_View: instead of custom helper locations being defined by paths, they are now defined by class names. This means your custom helpers no longer need to be named 'Solar_View_Helper_*'; you can now name them as is proper for their location in the file system. For example, if your helper classes are in the "Vendor/App/Helper/*" directory, you now name each helper class "Vendor_App_Helper_*". This makes helper class names consistent with the rest of Solar.

In line with this, you no longer use Solar_View::addHelperPath(),
getHelperPath(), or setHelperPath(); instead, you addHelperClass(),
getHelperClass(), and setHelperClass(). The class-based methods work
just the same as the path-based methods, except you specify class
name prefixes instead of path prefixes. For example, if you used to
"addHelperPath('Vendor/App/Helper')", you would now "addHelperClass
('Vendor_App_Helper')". Then helpers of that class will be used
before the Solar_View_Helper classes are used.

Also in line with this, the Solar_View 'helper_path' config key has
been renamed 'helper_class', and a new Solar_Controller_Page config
key 'helper_class' has been added so that page controllers know where
helpers are (this is to support view helpers attached to layouts).

Finally, Solar_Test_Suite usage has changed a bit. Instead of
setting a 'sub' key to specify that you want to run a sub-series of
class tests, you specify that same value when calling
Solar_Test_Suite::run($sub).

Future Plans

The next release will concentrate on feature requests, and (sorry!)
another class reorganization so as to be more consistent with pattern-
based names, and to reduce the depth of some parts of the hierarchy.

None of the reorganizational changes should require more than a
simple search-and-replace on the existing code base when complete.
For example, Solar_Sql_Driver will become Solar_Sql_Adapter,
Solar_Cache_File will become Solar_Cache_Adapter_File, and so on.

The User subclasses (Auth, Role, Access) will come to the top of the
hierarchy, so Solar_User_Auth will become Solar_Auth, and the
Solar_User_Auth_Driver files will become Solar_Auth_Adapter files.

There are other change ideas too; you can see those in the todo file
here.


Database access problems

Right after I posted last, there was a database hiccup at my hoster. They've reset it now. Sorry for the site access delays.


"Do Your Best" Is Not A Plan

My buddy Bolivar Shagnasty described a problem he was having with his manager (Dirk Karkles). At their annual review, Dirk wasn't presenting any measurable goals for Bolivar's performance. "I just want you to do your best," said Dirk. "As long as you do your best, I'll be satisfied." Bolivar wasn't happy with this statement, but was unsure why. After all, shouldn't everyone try to do his best?

Bolivar is an excellent worker, but Dirk Karkles would always find something unsatisfactory with Bolivar's performance. "Is this really your best?" he would ask. "Well, yes," Bolivar would say, "but I can tell you're not happy with it. What level of performance on this task would make you happy?"

Dirk's answer was, "I just want you to do your best. I don't want to limit you by setting goals for you. For example, if I set a goal for you of '30', and you're capable of doing '50', then I've cheated you of '20' by setting too low a goal. You don't want me to cheat you of your best performance, do you?"

* * *

As personal philosophy, "do your best" is hard to beat, but as a management plan, it sucks. A manager cannot externally measure when you've attained "your best" in anything but the most strictly-defined activties (like a "personal best" in sports). The developer manager who uses "do your best" as goal direction is being lazy by avoiding the hard work of defining his expectations, and is setting his workers up for failure.

Here are some reasons "do your best" is a poor management plan:

  • It's not measurable.
  • A worker's "best" is a contingent target; it depends on how you marshal your limited resources (time, energy, attention, etc). If you do your absolute top best on one task, it reduces the amount of resources available for another task, so you have not done your "best" on everything. This means there is always something for the manager to point at and say "you're not doing your best."
  • Even assuming that it is a legitimate goal, there is no way to exceed the expectation of "your best." This means doing your absolute top best at all times is only "breaking even" as far as the manager is concerned. This is demoralizing to a worker; every single time he misses even a small thing, he gets picked-at for not doing his best. This leads to lower performance, since the goal of "your best" can never be truly met at all times.
  • It is lazy and greedy on the part of the manager. His job is to meet measurable goals, not to wring workers dry. The line about "then I've cheated you of '20'" above is an example of this.

Does this mean that all management expectations must be measurably defined explicitly in advance? Of course not; there is necessarily a lot of give-and-take in the implicit relationships and expectations of a particular organizational culture. But these implicit expectations are behavioral and interpersonal guides, not management goals. Goals by definition must be measurable; that way, you know when you've achieved them.

The lesson for managers here is: you need to do the hard work of defining your expectations in a measurable way; if you can't do that, don't be surprised if your workers are performing at the level you want. "Do your best" is philosophy, not planning.


Developers and Managers

Shortly, I will post the first of what I expect to be an occasional series of observations on management practice in the development world. Although the series is not PHP-specific, it will be in the context of developer-managers, so I'm categorizing it under both PHP and management.

I hold an undergraduate business degree, have contributed to various management papers and studies, have been an entrepreneur, have rejoiced under good management and suffered under bad management, and have been both a good manager and bad manager. I think these are sufficient qualifications for my opinion having some basis in reality.

To protect the guilty (including myself ;-) I've invented a composite character I'll call "Dirk Karkles" to act the part of the poor or misguided manager; any resemblance to persons living or dead is purely coincidental. Similarly, I'll be using the composite character "Bolivar Shagnasty" from time to time as the developer or other target of Dirk's policies and practices.

My catchphrase for this series is "Don't be a Dirk." :-)