Paul M. Jones

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

Savant version 2.3.1 released

I bungled the Savant 2.3.0 release (c.f. how overload() and __call() bit me in ass on PHP 4), but with the kind forbearance of its users, Savant 2.3.1 will make up for that fouled release. Here are the change notes:

* PHP5 Only: Supports __call overloading as an alias to plugin(). No support for overloading in PHP 4; sorry.

* The form plugin no longer generates layout for hidden elements (thanks Alain Petignat)

* Fixed two errors in the Exception error handler (thanks Jeff Surgeson)

* Fixed trimwhitespace filter to handle preformatted blocks with attributes in the tag (thanks Alain Petignat)

Savant is a template system for PHP that uses PHP itself as the template markup language. Savant has plugins, output filters, customized error handling, and allows you to hook in a compiler object to deal with customized non-PHP template markup. I call it the simple, elegant, and powerful alternative to Smarty.

Speaking of which, John Coggeshall used to think that something like Smarty would be a bad idea (see his comments from 2000, on a PHP3 list, here.

The differences between how I work now and how you propose work be done in the future is staggering -- you are asking me to let a designer that I don't trust to write basic HTML to write advanced control loops and logic blocks using a top-end system which converts this pseudo-language into PHP and THEN processes the PHP Code... FINALLY outputting the page to the user... assuming that designer wrote his code right AND the top-level parsing engine actually parsed it correctly.

(As a side-note, I strongly suggest reading the whole thread, which is about whether or not to embed a template system within PHP, and what the markup should be like. There are arguments for and against PHP-based markup -- the "two languages" and "mini-language" points come up multiple times. There are predictions that the new template language would quickly overlap with PHP and make it necessary for developers to know two languages, the real PHP code and the pseudo-PHP template markup code, for no reason other than to call one of them a "template." Zeev Suraski in particular appears to be clairvoyant on these points.)

This was 2000, of course, and at the height of the dot-com bubble, where anyone who could spell "HTML" was counted as an expert. PHP did not then enjoy the widespread acceptance it does now, so it is easy to understand why Coggeshall was unwilling to let a designer have anything to do with "real code."

Coggeshall recently followed up on that post with new comments here, in which he says he needs to eat his own words, although he doesn't really say why (John, if you're reading, I'd be very happy to hear the specifics :-).

Man, considering I've given Numerous Talks on Smarty, am the author of IntSmarty, and this very web site is completely Smarty-powered, this posting is clearly a case of me shoving my foot deep down my throat. In my defense, it was written 4 years ago.... and I do still agree that there are circumstances when Smarty doesn't make any sense... but still -- well, needless to say its quite funny to read that posting now. Since it's out there, I figure I'd bring it to light myself and laugh before someone else beat me to the punch

So I'll say it, everyone mark it down in your calendars -- I was dead nuts wrong.

Coggeshall's contributions to the PHP world notwithstanding, I would say his conclusion was right in 2000, if for different reasons in 2004. In contrast to John, I believe there is only one reason to even think about using Smarty (or a similar system) and that reason is not exactly all-encompassing. More on that in a moment.

First, here is my foundational question: Why would you have one interpreted language act as the interpreter/compiler for another language just to convert it into the first language? Regardless of whether or not you cache the compiled result (*) like Smarty does, using PHP to "compile" a template into PHP makes no sense to me; if it's going to be converted to PHP anyway, you might as well just use PHP as the markup language to begin with instead of some other markup. Using PHP itself means less to learn, less to debug, more powerful functions, cleaner code, forwards compatibility, and so on. This is how Savant works: you get all the power of model-view separation by using the Savant object to interface with a template script, but the template script itself is in PHP, and that template script can use the Savant plugins for custom tasks (such as generating a form).

For me, there is one reason, and one reason only, to "compile" templates: if the template author is a security risk. I don't mean, "the on-staff template designer can't be trusted to use PHP properly" -- if you are in that position, you have a management and training issue, maybe a personnel issue, and not a template system issue. Frankly, if a designer can learn the complex Smarty loop and section markup, that designer can learn minimalist PHP.

No, I mean if the author is an actual security risk; say, if your application allows its users to edit templates via a web form. Then, and only then, is compiling or converting a limited markup to full PHP a reasonable option. Fortunately, Savant allows for this as well; you can hook in a compiler object to convert markup to PHP. Savant comes with an simple compiler as an example implementation.

None of the above commentary applies to XML/XSLT templating because I know exactly squat about that methodology. Having said that, I am happy to be the malcontent noodge in the PHP templating world, and look forward to hearing you tell me why I am wrong, be it in your own blog (remember to trackback!) or in the comments below. Happy templating, all. :-)

--

(*) Yes, I know that the Smarty folks don't call it a "cache" -- but the fact remains that they store the compiled template on disk for faster future access. That sure sounds like a cache to me. They refer to it as distinct from their "output cache" but that's another issue -- why use a template-system cache that is separate from the rest of your application? Again, makes no sense to me; caching seems more like an application-level task, not a template-system task.


DB_Table and Oracle Support

I want to make DB_Table work with everything, everywhere, all the time, the same way, so that you don't have to use extra function t0 massage your SQL queries and the data you use in them; that way, you can make your DB_Table-based application completely portable without having to think about it too much. This is necessarily a lowest-common-denominator approach, and so far it seems to have worked passably well. The biggest problem, until yesterday, was that PostgreSQL won't allow you to have a table with indentical field names and index names (easy solution: append index names with "_index" automatically).

However, Alex Höbart informs me (among other things) that Oracle has a strict limit on the length of table, field, index, and sequence names: 30 characters, maximum. This is a problem, becuase many other databases seem to allow at least twice that length (e.g.: MySQL allows 64 chars, Microsoft SQL allows 128, and SQLite has no limitation).

If it turns out that at least one other RDBMS has a similar limitation, then I can in good conscience make that limitation universal in DB_Table. If not, I will be faced with the hard choice of either dropping Oracle support (awful!) or at least adding a "strict check" flag so that DB_Table classes will know to use the 30-character limit (not as awful, but still bad, and not really what I want to do).

Anybody out there know if the Frontbase and PostgreSQL have 30-character (or similar) length limits for names of tables, indexes, fields, and sequences?

Update (11:01): It appears PostgreSQL has a default limit of 31 chars.


A cousin, of sorts, to DB_Table?

It seems I am not alone in thinking it is wise to embed database table column information in the PHP class that will interface with that table. Harry Fuecks, in his blog entry titled A Development Infrastructure for PHP, points to work by Tony Marston.

Check out Tony's notes about his business class field properties and compare with the DB_Table schema setup documentation. They are obviously very similar, at least in example and documentation. The both do automated validation as well, and have "hints" for form-building embedded in the class.

Nice to know someone is on the same path as me (although Tony is obviously **much** further ahead :-).


DB_Table goes "beta" in new 0.22.0 release

I just released DB_Table 0.22.0 beta. This is the first new release since July 7, and the first beta release. It has been pretty well tested against MySQL and PostgreSQL, but should work with Oracle, MS-SQL, and SQLite as well. I'd be very happy if MS-SQL or Oracle users could provide feedback on how well DB_Table works on those systems; please email me if you would like to provide a report.

The ideas behind DB_Table are simple: embed your column definitions and baseline queries within the PHP class. Then you can call select('myQuery') and get your results, or call insert($myColumns) to insert a new row, and so on. Becuase the class knows the column definitions, it can validate the new data before even connecting to the database; you can extend the validation method to cover all kinds of data. This also lets you automatically create a form based on the column definitions with HTML_QuickForm. DB_Table automates table creation; if a table does not exist in the database when you call a DB_Table object, DB_Table can create it right then, along with indexes -- all because the column definitions are embedded in the class.

DB_Table also provides a poor-man's version of data type abstraction. This element is somewhat controversial. We all know that different database systems keep date and time data in different formats, and it becomes an ... interesting ... task to create portable SQL statements that will work properly against those different systems. Metabase and its cousin MDB do this by providing translation functions to convert the native RDBMS format to a common format on retrieval and insert/update, but they are not exactly fully automated.

The DB_Table abstraction method (such as it is) instead forces the database to store the date and time as fixed-length strings in ISO-standard format. While this is going to give most database administrators a case of the screaming rants, it is a very workable solution for low- and mid-range applications where portability, not 100% optimization and efficiency among multiple systems, is the paramount concern. This is particularly good for those of us who write applications intended for distribution and need to know for sure that the app will work on a wide range of databases (with minimal effort). For example, YaWiki uses DB_Table to great effect.

You can read the full DB_Table documentation here.


Belmont Club

Wretchard at Belmont Club is a great writer and analyst. Read this post right away.

It is interesting to compare Marine preparations to assault Fallujah in April 2004 with those apparently under way today, just months later. The Marine methods of April would have been instantly familiar to any military historian: hammer and anvil, seizure of key terrain; feint and attack. Today, many of the military objectives in the developing siege of the terrorist stronghold are abstract. They consist of developing a network of informers in the city; of setting up a functioning wireless network; of getting close enough for smaller US units to deploy their line-of-sight controlled UAV and UGV units to create a seamless operational and tactical environment to wage "swarm" warfare; of getting artillery and mortar units close enough to play hopscotch over everything the network decides to engage. To the traditional methods of warfare the Americans were adding a whole new plane which only they could inhabit.

It looks like large organizations can adopt strategies to beat a small, dispersed, loosely connected foe. Thank goodness the leadership had the willingness to let go of traditional tactics and lear new ones on-the-fly.


Adam Smith and Charles Darwin

This brief entry from Cafe Hayek about the Scottish Enlightenment is, ah, enlightening, especially when read in the context of emergent behaviors.

About Stewart's intellectual biography of Adam Smith, Gould has this to say: "[Darwin] imbibed the basic belief of the Scottish economists that theories of overall social structure must begin by analyzing the unconstrained actions of individuals ... The theory of natural selection is a creative transfer to biology of Adam Smith's basic argument for a rational economy: the balance and order of nature does not arise from a higher, external (divine) control, or from the existence of laws operating directly upon the whole, but from struggle among individuals for their own benefits."

Read the whole thing; it's short and sweet. (I know a family of Scots, and let me tell you, the intelligence and creative thinking displayed by Smith and Adams has not dwindled over the intervening generations.)


Savant: Revoking the 2.3.0 release

I am revoking the Savant 2.3.0 release from yesterday. The file will still be available, but I can no longer vouch that it will work everywhere for everyone, and I cannot promise that the new function provided therein ("calling of plugins as native methods") will be in a future Savant 2.x release. I recommend reverting to 2.2.0, particularly for PHP5 users.

For 2.3.0, the only major change was that you could call plugins as native Savant functions through the magic of overload() and __call(). The use of __call() turned out to be unwise, as it exposed a serious compatibility issue between PHP4 and PHP5.

I'm a big believer in reporting failure as well as success; it's important to let others learn from your mistakes. It's embarrassing, but necessary. This is one of those times, I'm afraid. The use of __call() turned out to be a serious problem. Yes, overload() is marked as EXPERIMENTAL in the PHP4 documentation, and brother, do they mean it.

Read on for more information about how __call() bit me in the ass, and why aiming for simultaneous compatibility on PHP4 and PHP5 can be unexpectedly difficult when trying to do "neat stuff."

__call() in PHP4 and PHP5

The first problem is that the arguments for __call() are different in PHP4 and PHP5 and are enforced differently.

In PHP4, you need three arguments: __call($method, $params, &$return). $method is the method called, $params is an array of parameters passed to that method, and &$return is a reference to the variable into which the method call should return a value. The "real" return value of __call() is true (indicating that the call succeeded) or false (which will trigger an error that $method does not exist). You can leave off the &$return parameter if you like, and PHP4 will not complain.

In PHP5, you need exactly two arguments: __call($method, $params), both of which act the same as in PHP4. If you have a third argument, PHP5 will issue a parsing error (which means the script will not load). Under PHP5, the return value of __call() is passed the way you would expect with a normal method, which is why you don't need the &$return argument.

You can see already why this would cause trouble: any __call() definition that works in PHP5 will limit its PHP4 equivalent to never giving a return value (because you can't have the &$return argument in PHP5). Similarly, any fully-functional PHP4 __call() method cannot possibly work under PHP5 (because the &$return argument will cause the PHP5 parser o choke).

__call() in PHP4.3.2 and 4.3.8

When I originally tested the __call() method in 4.3.2, it would successfully set &$return to an object. However, in 4.3.8, it would not set &$return to an object (scalar or array came back OK, but no objects).

Possible Solutions

First, I tried having alternate class definitions in the same file, and picking the definition based on phpversion(). Did not work. PHP5 chokes when parsing the file with a "bad" __call() definition, even if its inside the PHP4 portion of the if() block.

Then I tried extending a base _Savant2 class into Savant2 class proper with an include at the end of the Savant2.php file; that is, if ($php4) include 'Savant2v4.php'; if ($php5) include 'Savant2v5.php'. Each of the version-specific files worked in their respective environments, and I thought I had a successful solution.

However, further testing showed that PHP 4.3.8 would not set &$return to an object (necessary in Savant as it reports plugin errors using the Savant2_Error class). To my recollection, it had worked properly in 4.3.2.

For me, this was the final straw; if __call() didn't work consistently even under 0.0.1 versions of PHP4, then it was not good for production use.

Whose Fault?

Mine, of course. First, I used overload() which is clearly marked as experimental. Then, I tested it thoroughly, but on only one version of PHP (4.3.2) and figured it would be forwards compatible (wrong!). These two errors, combined, resulted in a broken release under some versions of PHP. My goal for Savant is that it run everywhere, and this release failed to meet that goal; to boot, it broke severely under PHP5, which userbase is only growing.

I tried to fix the release, but I fear there is no elegant solution. Rather than excuse my bad planning through documentation (i.e., "Savant works this way under PHP4 but it's not quite right, and under PHP5 it works this other way, so look out!") I decided to pull the release. It's only been out 24 hours, and the added functionality is convenient, not critical. (Still embarrassing and irresponsible to have released it like that in the first place, though.)

So the blame lies with me; I'm sorry if I broke your app with this release. Having said that, oh how I wish the __call() method was identical in both PHP4 and PHP5.



Savant version 2.3.0 now available

I just released Savant version 2.3.0.

Normally, if you want to use a template plugin, you call the plugin() method like so:

<?php $this->plugin('myPlug', $arg1, $arg2); ?>

Through the magic of the overload() function in PHP and the __call() magic method, you can call plugins as if they were native Savant2 methods.

<?php $this->myPlug($arg1, $arg2); ?>

This should make for a little less typing and a little easier reading. For example, to generate a form with Savant2 using the 'form' plugin, you can now do something like this:

<?php
//
// start the form
$this->form('start');
//
// add a text field and label
$this->form('text', 'myTextField', 'default value',
    'Text field label:');
//
// add save and reset buttons as a group
$this->form('group', 'start', 'Group label:');
$this->form('submit', 'op', 'Save');
$this->form('reset', 'op', 'Reset');
$this->form('group', 'end');
//
// end the form
$this->form('end');
?>

(Indicentally, the 'form' plugin is pretty powerful; it support all standard field elements, handles layout, and generates valid XHTML with divs, labels, fieldsets, and tables. Check out the documentation for the 'form' plugin here.)

Savant is an object-oriented template system for PHP. Savant does not compile templates; instead, you write your templates as regular PHP scripts, so there's no new markup to learn. As such, Savant is a simple, elegant, and powerful alternative to Smarty.

UPDATE: (19:24 central time) There appears to be a problem with PHP5 and the __call() method in Savant 2.3.0. Users of PHP will want to test before using 2.3.0. I am working on a patch so that __call() works in Savant2 for both PHP4 and PHP5 transparently, and will blog about the solution at that time. Thanks for your patience.


Yawp 1.0.4 Released, and Yawp2

With this release, sessions can be configured to not auto-start by adding a [Yawp] group 'session_start = false' directive and commenting out the [Auth] group, per request by David Glenn. Download the new version from phpyawp.com.

Also, I'm beginning work on Yawp2, which should be infinitely more configurable using a plugin architecture (suggested via proof-of-concept code from Ian Eure). Instead of being limited to the standard Yawp object set (Auth, Benchmark_Timer, Cache_Lite, DB, Log, and Var_Dump) you will be able to plug in any PEAR object and lazy-load it on first call, as well as autoload objects at Yawp::start() time (the current behavior). There will be support for convenience methods through the core Yawp object so that you can use method calls to a plugin without having to load the plugin yourself.

The idea behind Yawp is that you always need a certain set of single objects in your app: a database connection, a cache, authentication, a logger, and so on. Yawp instantiates, aggregates, and encapsulates those objects away from the global space so you can concentrate on the "real" part of your program. Does that mean Yawp is an example of aspect-oriented programming?