Paul M. Jones

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

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.

* WARNING: This is a public development release, and is not yet stable.

* Translations: Jean-Eric Laurent has contributed fr_FR locale files for
all existing classes.  Thanks, JEL!  :-)

* Documentation: added HelloWorld app to the docs/ folder per note from
James Kilbride

* Solar:

    * The environment() setup now uses an improved routine to unset
    variables set from global sources when register_globals is on (per
    Stefan Esser and Richard Heyes)

    * The environment() setup no longer unsets $_REQUEST (although you
    still shouldn't use it if you can reasonably avoid it).

* Solar_Base:

    * If a 'locale' config key is not set, auto-generate one based on
    the class name. For example, 'Solar_Example_Class' maps to
    'Solar/Example/Class/Locale/'.

* Solar_Form:

    * Made addValidate() and addFilter() public.

    * In validate(), the 'validate' key is forced to an array; this
    allows you to use just the Solar_Valid method name, and the default
    VALID_* message will be used as the feedback text.

    * The populate() method is now free of the eval() calls copied from
    HTML_QuickForm; we now recursively populate the $submitted property
    with the _populate() method.

    * The values() method is now free of the eval() calls copied from
    HTML_QuickForm; we now recursively build the returned values using
    the _values() method.

    * In the addValidate() method, the $message parameter is now
    optional; if not present, the method uses the generic VALID_* locale
    string for the validation.

* Solar_Form_Load_Xml:

    * Applied patch from Matther Weier O'Phinney to add XML format doc
    comments inline; also fixes a bug where $this was addressed instead
    of $filter.

* Solar_Sql:

    * WARNING: This may be the last release of Solar with the current
    RDBMS API abstraction; a future release should incorporate PDO as
    the underlying abstraction layer.

    * The quote() method now recursively quotes all values in arrays.

    * Added a new select() method in anticipation of Solar_Sql_Select
    class.

* Solar_Sql_Driver_*:

    * Added new select() and limitSelect() methods in anticipation of
    new Solar_Sql_Select class. The new limitSelect() method will
    provide much better offset emulation in, e.g., MS-SQL.

* Solar_Sql_Driver_Mssql:

    * Fixed bug in createSequence() method where $start was in
    single-quotes (changed to double-quotes to interpolate the variable
    properly)

* Solar_Sql_Locale:

    * Added new OP_CREATE, LABEL_OP, and VALID_WORD translation keys

    * Swapped VALID_BLANK and VALID_NOTBLANK translation strings (they
    were backwards)

* Solar_Valid:

    * Added ipv4() method to validate IPv4 addresses; also added test
    file.

    * Added locale() method to validate locale code formats; also added
    test file.

    * Added word() method to validate against word characters (regex
    "w" type); also added test file.

    * The nonZero() method now honors the $blank parameter.

* Solar_Template:

    * Updated to use most-recent releases of Savant3 (beta 2) and
    Savant3_Plugin_Form (devel 0.2.1)

    * Updated all Solar/App/*/views/* to use the new Savant3 code

* Solar_User_Auth:

    * Moved driver instantiation to start() instead of __construct();
    this should allow custom drivers better integration into the start()
    process (e.g., single sign-on systems and HTTP-based auth).  This
    eliminates the __construct() method as well.

* Solar_User_Role_Sql:

    * Fixed double-concat bug when generating the WHERE clause

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. :-)


Savant2 version 2.4.1

This is a minor bugfix release (I was using func_get_args() as a function parameter in one place). Also, I've added a new method "eprint()" as an alternative to "_()" for escaping and printing. You can see the change notes and the download and installation pages for more information.


Interview Errata

I wrote previously that Marcus Whitney had interviewed me for Pro-PHP podcast; well, it is online at last! :-) Thanks again, Marcus, for the opportunity, and don't worry about that high-pitched whine -- I guess that from me talking so fast the whole time (I had no idea I did so). It was a great experience for me. :-)

I misspoke a couple of times, and I want to correct those statements here, as well as provide links to some people, projects, and sources I mentioned.

Regarding the PEAR proposal for Savant, I said it ended up at -3; in fact, it ended at +2 (but needed a +5). You can read many of the comments here, but there are a lot more in the PEAR-DEV mailing list archives in several different threads.

I mentioned Matthew Weier O'Phinney near the end, around the discussion of Solar collaboration; he runs the PHP-based Cgiapp project. Also regarding Solar, I failed to mention Clay Loveless, who has provided hosting, services, and moral support for Solar.

I think that's it; if anyone else notices inaccuracies, or just has questions in general, leave them in the comments here or at Pro-PHP and I'll do my best to answer them. :-)



Solar 0.7.0-devel released

After too-long a hiatus, I've released Solar 0.7.0; you can get it from our PEAR channel server here. (Solar is a simple object library and application repository for PHP5; it is similar to PEAR and Horde, and might be described as a library-like framework for MVC development.)

The interim was filled with lots of improvements, not the least of which are some new classes and fixes from Matthew Weier O'Phinney. His work on the forms portions has been helpful and enlightening; I think we may now have a true MVC contender against HTML_QuickForm when combined with the improved form plugin for Savant3.

The change notes are quite long; you can read them below.

* WARNING: This is a public development release, and is not yet stable.

* Added new class, Solar_Filter, to filter data.  This is a companion to
Solar_Valid and is currently used mostly for prefiltering submitted form
values.  Thanks, Matthew Weier O'Phinney.

* Added new class, Solar_Form_Loader, to allow loading of form elements
from external sources.  Currently has only one method, fromXml(), to load
elements from a SimpleXML file.  Thanks, Matthew Weier O'Phinney.

* Solar:

  * New protected method environment() performs some standardized
  environment setup at Solar::start() time.  Solar now
  automatically...

    * Unsets all registered global variables if 'register_globals'
    is on.

    * Unsets $_REQUEST (you should use Solar::get(), ::post(), and
    ::cookie() for security reasons).

    * Dispels magic quotes from get/post/cookie/files/server
    superglobals if 'magic_quotes_gpc' is on; handles Sybase quotes
    as well as slashed quotes.

    * Turns off 'magic_quotes_runtime' and 'magic_quotes_sybase' so
    that SQL sources do not quote values on retrieval.

  * Solar::start() no longer sets error_reporting and display_errors.
   Per note from Matthew Weier O'Phinney.

  * Fixed bug where start() was calling __solar('start') instead of
  solar('start') on shared objects (the latter is correct).

  * Added locale strings for 'VALID_*' keys to support default
  validation messages in Solar_Sql and Solar_Form.

  * You can now specify an alternate config sources as the only
  parameter to calling Solar::start().  Pass an array to populate
  $config directly, an object to populate from the object properties,
  or a string to indicate an arbitrary path to a config file.  The
  default remains to load $config from the file defined by the
  SOLAR_CONFIG_PATH constant.  Per discussion with Matthew Weier
  O'Phinney.

* Solar_App_*:

  * View scripts now use the new Savant3 auto-escaping function
  and the new Solar_Template plugin locale() for localized strings.

* Solar_Base:

  * Constructor now allows passing of a string as the $config value,
  which is subsequently treated as a path to a php-array config file
  (this means you can have a config file specifically for a class
  instead of having to pass an array).

* Solar_Filter:

  * The alphanumeric() method is deprecated; use alnum() instead, it's
  easier to type. The alphanumeric() method will be removed after the
  0.7.0 release.

* Solar_Form:

  * BC BREAK:  Made $config protected like every other Solar class.
  Config values are now populated through to the new $attribs
  property, which stores attributes for the form tag itself.  If you
  were using $config to retrive form-tag attributes, you should now
  use $attribs instead.  Updated Solar_App_* views to reflect this
  change.

  * Added prefiltering of submitted values; use addFilter() method, or
  add a 'filter' key to the element array when using setElement().
  Thanks, Matthew Weier O'Phinney.

  * The populate() and validate() methods now take an optional
  paramter; if that param is an array, it is used for the $submitted
  property.  This allows you to pick the value population source
  instead of having to choose only from $_GET and $_POST.  Thanks,
  Matthew Weier O'Phinney.

  * Added reset() method to restore form to its originally-configured
  state.

  * Added load() method to load attributes and elements (with filters

  and validations) from an external source.  Per discussions with
  Matthew Weier O'Phinney.

  * When no message is given for validation, attempt to get a default
  validation message from Solar/Locale/en_US.php based on the method
  used for validation (c.f. the new VALID_* translation keys).

* Solar_Sql:

  * In quote(), changed is_double() to is_float() to match with column
  pseudo-type name.

* Solar_Super:

  * No longer uses the magicStripSlashes() filter when values are
  fetched, because the Solar environment setup now takes care of
  slashes at start() time.

* Solar_Template:

  * Updated to Savant3 alpha2 and converted all Solar_App_* view
  scripts to use its new auto-escaping functions. See change notes at
  http://phpsavant.com/yawiki/index.php?area=Savant3.  Of note, the
  'form' plugin will change in the next release, and the 'scrub'
  plugin will disappear in the next release.

  * Added new 'Plugin/' directory with new 'locale' plugin to help
  with locale transalations (you can now use $this->locale('KEY')
  instead of Solar::locale('Solar', 'KEY')).  All Solar_App_* view
  scripts now use this plugin.

  * Adds the 'Solar/Template/Plugin/' directory to the top of the
  configured resource_path automatically.

* Solar_Valid:

  * The alphanumeric() method is deprecated; use alnum() instead, it's
  easier to type. The alphanumeric() method will be removed after the
  0.7.0 release.

Harry Potter III: The Prisoner of Azkaban

What a wonderful series. I've said it before, but I'll say it again: if this is what kids are clamoring to read, then I have hope for the future. Not only are the morals good, but the storylines are preparing the readers for other literary works, including not only Greek and Roman mythology, but also such things as C. S. Lewis' "Narnia" and Tolkien's "Lord of the Rings."

Near the end of the third Potter book, Dumbledore (so much more than just an "old-man-as-mentor" archetype described by Jospeh Campbell) counsels Harry after Harry has mercifully spared a traitorous informer against the Potter family: "This is magic at its deepest, its most impenetrable, Harry. But trust me ... the time may come when you will be very glad you saved [his] life." Anyone who reads that, who has also read the Lord of the Rings, will recognize immediately the similarities to the conversation between Gandalf and Frodo at the door to Moria about Gollum. Kids who read Harry Potter are thus ready to be introduced to Lord of the Rings, and will recognize the motif in the same way.

The Potter books are simply stunning; I can't believe I waited this long to read them. The movies don't do them justice by half.

Incidentally, I have a guess about the Harry/Hermione relationship; I think it will mirror the Luke/Leia relationship. But who knows? ;-)