Paul M. Jones

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

Solar 0.18.0 released, and more about testing

I released Solar version 0.18.0 over the weekend. The biggest change is in the testing methodology (yes, again ;-).

A couple of weeks ago I had an IM-chat with Matthew about testing, and he related the merits of a PHPUnit-style approach, versus the PHPT approach I have blogged about previously.

After having used both, I can say that the procedural PHPT testing style is easier to approach for someone new to testing, and is great for smaller or standalone projects, but it doesn't seem to "scale up" well for larger class structures. The object-oriented testing style used by PHPUnit and SimpleTest requires more inital setup work, but "scales up" much better in larger projects.

I used PHPUnit while working at Zend, but frankly it seemed kind of magical to me; I just didn't get the way it worked internally, especially setting up test suites. PHPUnit is a robust and highly configurable piece of software, but it does seem a bit impenetrable. This may be due to the fact that PHPUnit is ideologically derived from similar Java projects, and I am just not a Java kind of guy.

So in a spirit of meet-in-the-middle, I coded up a pair of Solar-based unit-test classes to take over from Solar_Test_Assert and .phpt, then converted a couple of existing .phpt tests to the new technique. It worked like a champ. After adding a Solar_Test_Suite class to scan a base directory for test cases and generate TAP-compliant output, I converted all the remaining .phpt tests to the new object-oriented testing style. The new test suites run much faster, and it's easier to extend an abstract test-class than it is to copy and modify a bunch of .phpt files.

You can see some code examples here:

The test-runner is very easy to use. If you want to run all tests, just issue "php run.php" from that directory and it will recursively run everything in the Test/* directory. If you want to run a sub-series of tests, pass a class name as the only parameter; e.g., to run the Solar_Uri tests, issue "php run.php Solar_Uri".

Test-runner output is TAP-compliant (which should make Mike Lively happy ;-). Among other things, this means it should be easy (in theory) to hook in to apache-test. TAP output looks something like the following:

bash-2.05$ php run.php Solar_Uri
1..21
ok 1 - Test_Solar_Uri::test__construct
ok 2 - Test_Solar_Uri::test_config
ok 3 - Test_Solar_Uri::testSet
ok 4 - Test_Solar_Uri::testFetch
ok 5 - Test_Solar_Uri::testQuick
ok 6 - Test_Solar_Uri::testSetQuery
ok 7 - Test_Solar_Uri::testSetPath
ok 8 - Test_Solar_Uri_Action::testFetch
ok 9 - Test_Solar_Uri_Action::testQuick
ok 10 - Test_Solar_Uri_Action::test__construct
ok 11 - Test_Solar_Uri_Action::test_config
ok 12 - Test_Solar_Uri_Action::testSet
ok 13 - Test_Solar_Uri_Action::testSetQuery
ok 14 - Test_Solar_Uri_Action::testSetPath
ok 15 - Test_Solar_Uri_Public::testFetch
ok 16 - Test_Solar_Uri_Public::testQuick
ok 17 - Test_Solar_Uri_Public::test__construct
ok 18 - Test_Solar_Uri_Public::test_config
ok 19 - Test_Solar_Uri_Public::testSet
ok 20 - Test_Solar_Uri_Public::testSetQuery
ok 21 - Test_Solar_Uri_Public::testSetPath
21/21 tests, 0 seconds
0 fail, 0 todo, 0 skip, 21 pass
bash-2.05$

There's also a test-maker script that generates a skeleton test class, with a test method for each public method in the target class. Just issue "php make.php My_Class_Name".

And of course, if you write your libraries to Solar standards (which are essentially PEAR standards with a couple of additional points) then you too can use the Solar_Test tools to make your testing life much easier.

Update (11 July 2006): Updated links to point to new SVN directories. Thanks, Clay Loveless.


PHPBlox from Zend?

I just read this article from PHP architect about QEDWiki, and at the end they mention something called PHPBlox. A quick Google search reveals this .swf presentation at Zend Devzone. Anybody know if this is supposed to be a replacement for the Zend IDE, or how it relates to the Zend Framework?


ASP-Style Programming in PHP?

An old buddy of mine wants to start using PHP, but the problem is that he's been working with Microsoft technologies for so long, he doesn't get "the PHP way". His background, for many years, has been with ASP.NET, COM, and those sorts of things.

As it turns out, there's a PHP framework out there that maps well to "the ASP.NET way": Prado.

http://www.pradosoft.com/

They're at a 3.0 release as of a week ago, and won the Zend PHP5 competition last year, so that should speak to the quality of the software. From the page about PRADO we can see they base their concepts on a component model, so it's very like Apache Tapestry, Borland Delphi, and (for the guy who asked me originally) Microsoft ASP:

Most PHP frameworks are trying to establish a loose standard of organizing PHP programming, most preferably the MVC (model-view-controller) model. It is difficult to compare PRADO with these frameworks because they have different focuses. What we can say is, PRADO is more like a high-level language built upon PHP while those MVC frameworks stand for the best programming practices. Both aim to help developers to rapidly complete Web application development. The advantage of PRADO is its rich set of prebuilt powerful components and extreme reusability of the PRADO code, while the advantage of the MVC frameworks is the complete separation of model, view and controller, which greatly facilitates team integration.

I can see that PRADO is very much *not* my style; I don't know a lot of open-source scripter types (Perl/PHP/Python/Ruby) who would really go for this, as it seems over-architected to me. This is not meant as a critical point against PRADO, because it obviously fills the needs for those who like that style of programming. Personally, I don't really want another language on top of PHP (this relates back to why I started Savant; i.e., as a response to Smarty). But if you're used to Microsoft or Java, I can see how this would be an easy way to get on board in the open-source world and start using PHP with your previous non-PHP habits.

Which brings me to a philosophical point: is there a better phrase than "the PHP way" to describe the way we expect PHP apps to be built, or some sort of metaphor that encapsulates the concepts related to "the PHP way"? Not just MVC, because PRADO appears to do that, but some other way of describing succinctly how a PHP app "ought" to be. Maybe there isn't one single description.

Having said that, I think these template notes from the WACT project might be useful hints; see also here. My bet is that the greater majority of PHP/Perl/Python/Ruby programmers are "imperative" guys than "declarative" guys, and maybe that plays into what a proper metaphor might be. Prado/ASP.NET are clearly more declarative, and Savant/Solar/etc are clearly more imperative. Perhaps Less Code is related to the metaphor we want.

I'd be interested to hear comments and feedback, if for no other reason than to compare and contrast the different approaches.

UPDATE (2006-05-04): Interesting analysis from here; note that it is originally from Dec 2005.


Instant Forms from Tables with Solar

I was talking with Matthew this morning, and he asked if I was setting up a methodology to create forms using table definitions. My answer was "yes!".

Here's an example: 2 lines to start Solar, 2 lines to connect to an existing Solar_Sql_Table, 2 lines to create a Solar_Form using the table columns, and 2 lines to display it with Solar_View using the associated form helper. (The "Solar_Model_Nodes" table is part of the existing Solar_Content domain model.)

<?php
// prelims
require 'Solar.php';
Solar::start();

// sql and table connection
Solar::register('sql', 'Solar_Sql');
$table = Solar::factory('Solar_Model_Nodes');

// get a form and load elements from the table
$form = Solar::factory('Solar_Form');
$form->load('Solar_Form_Load_Table', $table);

// display the form
$view = Solar::factory('Solar_View');
echo $view->form($form);

// done!
Solar::stop();
?>

This creates an element for each column and a form-level validation based on the column datatype. Number and string columns get a properly-sized "text" element, CLOBs get a "textarea", and booleans get a checkbox. If the column has its own validation of "inList" or "inKeys", it gets a "select" element of the allowed values. Soon, when I add Ajax-y widgets, date/time/timestamp columns will get date-picker elements.

The $form->load() call can use 'Solar_Form_Load_Xml' too, if you want to define forms using XML. (I don't, but Matthew liked the idea and contributed the code.)

If you only want certain columns, you can pass a list of them as the third param to $form->load():

$cols = array('email', 'subj', 'body');
$form->load('Solar_Form_Load_Table', $table, $cols);

Now, this is not all sweetness and light; the magic requires that you do the work of definining your columns in the Solar_Sql_Table class. Lucky for us, that's pretty easy. More on that in another post.


Solar 0.17.0 Released

This release of Solar includes a minor BC break in how Solar_Sql_Table lets you set up column validation. It also standardizes how validation routines get feedback messages. The change notes are:

* Solar_Sql_Table

    * When specifying a 'valid' key for a column, you now get only
      one validation; if you need more than one validation routine,
      use the 'multiple' method.  Also, you can now specify the
      validation message key you want to use, instead of it being
      forced to 'VALID_COLNAME' (where COLNAME is the column name).
      You can still use a string as the 'valid' key value for simple
      validations.  These changes per discussion with Jeff Surgeson.

    * Uses more Solar_Valid validations internally now

* Solar_Form_Load_Table

    * Modified loading logic to use the new Solar_Sql_Table::$_col
      'valid' key value structure.

* Solar_Valid

    * New method feedback() standardizes Solar_Sql_Table and Solar_Form
      calls where a validation message is returned when data validation
      fails; this is the opposite of all other Solar_Valid methods, in
      that an empty return value indicated success, not failure.

    * New method range() checks a value against min and max range

    * New method rangeLength() checks the length of a value against
      min and max lengths

    * Renamed method inScope() to scope()

Automating Release Tasks

I noted earlier today that I've made 5 releases of Solar in 7 days. (Clearly I'm a fan of "release early, release often." ;-) Obviously, I have the Solar-talk subscribers to thank for pointing out bugs and and making functionality requests; these are what drive the need for a release ... thanks, guys. :-)

But what I want to talk about in this entry is the release process itself. With the help of Greg Beaver (indirectly) and Clay Loveless (directly), Solar now has a moderate-length PHP script that handles almost all aspects of the release process automatically. Usage is at the command line; issue "php release.php" for a test run, or "php release.php commit" for a full release-and-commit cycle.

With any luck, the lessons I've learned here will be of use to someone else; with more luck, perhaps someone else will see possible improvements and mention them here. Read on for a narrative of how the script came to be.

First Iteration

The first person I have to thank here is Greg Beaver and his great work on the PEAR packaging and installation tools. Without his stewardship and maintenenance of this wonderful toolset, the Solar release process would be much more difficult than it is.

Even with the PEAR installer, though, building a package XML release file by hand is a terribly tedious exercise. So the second person I have to thank is Clay Loveless: some months ago, he wrote up the first iteration of the auto-packager for Solar using PEAR_PackageFileManager. I added on a bit to it at that time so that we could keep release information (authors, change notes, etc) in a separate location.

The point of keeping the relase info in a separate location was so we could avoid having to modify the packaging script for every release. The script looks for an "info/" directory; in that directory, there is a sub-directory for every release number, and contained therein are a number text and comma-separated values files. The packaging script then uses those files to generate the "header" portions of the PEAR pacakge file. You can see an example "info/" directory here.

Second Iteration

The PEAR_PackageFileManager has a great auto-replacement utility built in. It can look through the source code at installationpackage time and replace certain keywords, such as "@package_version@", with the proper value for that particular installation. (UPDATE: [19:33 CST] Greg Beaver pointed out that addGlobalReplacement() does replace at package-time. Once again, Greg proves he has thought of all the right things. Thanks man.)

But there's a small problem with that for non-PEAR users. Because the replacements happen at installation-time, not at package-time, it means the pearball still has the keywords in place, not the actual values. For methods like Solar::apiVersion() which depend on the automated replacement of "@pacakge_version@" with the correct version number, this is a functionality break for them.

It turns out there's an easy, if somewhat brute-force, solution: copy the original source to a temporary location, replace keywords yourself, and then have PEAR package that new codebase with the keywords pre-replaced. You can see this portion of the code in release.php at the comment noted "prepare package directory".

We copy the original source ("src/") to a packaging directory ("pkg/") and str_replace() keywords in the new "pkg/" directory. When PEAR_PackageFileManager comes into play, it works on the "pkg/" directory instead of the original source. Note also that the pre-replacements happen in documentation files as well (more on that in a bit).

The end result of this is a pearball/tarball that is equally useful to PEAR and non-PEAR installation processes.

Third (and Current) Iteration

So that takes care of the packaging itself. The last issues to handle are mostly administrative. On a full commit cycle, we need to check a few things before rolling the release:

  • Set Subversion keywords on all files
  • Update the wiki files in "docs/apiref/" (these are built with a custom class that applies the PHP5 Reflection API to each class in Solar; more on that in another post)
  • Make sure all pending changes have been committed to Subversion

If those pre-flight checks pass, we then need to actually build the package (see iterations 1 and 2 above). After that:

  • Tag the release using "svn copy"
  • Commit the tagged release to Subversion
  • Extract the documentation files for updating the Solar website

The release.php script splits this into three task sets.

First, the administrative tasks are handled at the top of the script under "status check". This sets keywords, builds docs, etc., and then makes sure that there weren't any changes as a result. If these automated tasks generated changes (as noted by "svn status" returning any ouput at all), the script terminates with a warning to handle outstanding issues.

Second, after the package is built, near the end of the script, we issue a series of "svn" commands to tag the release and commit to the repository.

Finally, we unzip a copy of the pearball and pull out a copy of the docs; these are re-zipped in a separate tarball for upload to the Solar site. We use the packaged docs instead of the source docs so we don't get all the .svn files mixed in with them.

Manual Tasks

There are still some manual tasks to accomplish in a release, but they're easy: upload the pearball to the channel server, upload the docs to the website ... oh, and write an entry about the release on my blog. ;-)


Solar 0.16.1 released

This is a minor bugfix and enhancement release to the Solar library and framework for PHP5. The biggest changes are:

  • Packaging script now properly keeps Solar/App/Public in the main
    library directory (instead of in the data directory) ... reported
    by StR.
  • Solar_View_Helper_Form now uses a 'text' element when an
    unrecognized element type is requested
  • Solar_Controller_Page::_forward() now sets $this->_view properly (previously, was
    not replacing dashes with slashes)

Let's see, that's 5 releases in 7 days; not too shabby. I think it's time to blog about the release process.


Solar 0.16.0 released

Version 0.16.0 of the simple object library and application repository for PHP5 was released yesterday, mostly because I needed to make a minor schema change to one of the "content" tables. Seems the word "position" is reserved in some databases, and I was using that as a column name; the migration script in this release renames it to "pos". There are other minor changes to the Solar_Sql class as well (including more documentation :-).


Solar 0.15.2 released

Just a quick note: fixed two bugs (one in the Solar_Controller_Page::_redirect() method, and one in Solar_App_Bookmarks "user" action for RSS feeds) and released 0.15.2, available now.

Solar is a simple object library and application repository for PHP. It acts as a framework for application development in PHP5, and runs cleanly under E_ALL | E_STRICT error reporting levels.


Solar 0.15.0 alpha released

Yesterday, I uploaded the first non-development (!) release of Solar, version 0.15.0 alpha. I know, "alpha" is only slightly less change-prone than "devel", but it's a move forward and it makes me happy. :-)

You can see the change notes here, but the major updates are:

  • Solar_Uri has been refactored to be much easier to use, from 16 methods down to 5, with better "action" and "public" support, and it works with mod_rewrite now. (N.b.: Jeff Surgeson noted an issue earlier today with non-mod_rewrite subdirectories, but I've committed a fix and will roll 0.15.1 later today.)
  • Solar_User_Auth now calls session_regenerate_id() on all status changes, and calls to session_start() are no longer silenced.
  • Solar_Filter method names have been made consistent with each other.

Speaking of Solar_Filter, it probably bears comparison with Zend_Filter and Zend_InputFilter, seeing as they were recently reviewed at SitePoint. Solar actually has two separate classes for these functions: Solar_Valid, to check if input matches a particular format, and Solar_Filter, to force input to match a particular format. (Solar_Filter was contributed by Matthew Weier O'Phinney some months ago; thanks, Matthew.)

After Solar 0.15.1 has been out the door a couple more days, I'll be ready to start *adding* functionality instead of adjusting (and breaking, and re-fixing ;-) existing functionality. High on the list of new functions will be:

  • a Solar_User_Access class for standardized application-level access control,
  • integration of the Dojo Toolkit for animations and effects (especially on forms),
  • and a new Solar_App_Todo application (for to-do lists, obviously).

I'll be adding more narrative documentation, and perhaps start blogging about usage, as part of future efforts as well.

UPDATE (12:25 CST) -- Just released Solar 0.15.1 with the subdirectory/mod_rewrite fix.