Paul M. Jones

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

Solar 0.14.0 Released

Second release in two days! Amazing how "employed" I am, after having quit Zend. ;-)

Solar is a simple object library and application repository for PHP5. It is framework-and-library for web-application development, with an emphasis on distribution, localization, and database portability. You can download it via a PEAR channel and browse the Subversion repository.

Here are some highlights from the change notes:

  • The PEAR installer is still the recommended means of installing and upgrading Solar, but it is no longer required. You can now download the pearball directly, place the files manually, and they'll work just fine.
  • Solar/Layouts has moved to Solar/App/Layouts.
  • There are no more app-specific Public/ directories; instead, all public resources for Solar apps are in Solar/App/Public/, and are broken down by images/, scripts/, and styles/.
  • Solar_Filter has more methods, and the names have changed (see the change notes for full details).
  • Solar_Valid has changed some method names (see the change notes for full details)
  • Solar_Controller_Front::_setup() no longer registers sql, user, and content objects; these registrations have been moved down to the Solar_App page controller to lazy-load them as late as possible.

Of particular note, the Solar_Form form processor, and the related Solar_View_Helper_Form class, combined with the new default.css file, now highlight the feedback messages differently based on the form status (i.e., validation "success" or "failure").

I have also updated the solarphp.com site with the new documentation, and the new doc-reading infrastructure. It's mostly technical API documentation, but there are some actual tutorial pieces in place. Also, for now, this means no more user-comments on the site, but I'll keep the existing ones and put them back once I build a new commenting app.

The next major tasks are to refine the Solar_Sql_Select column deconfliction logic, and/or add Solar_Sql_TableRow and _TableRowset classes, perhaps to replace Solar_Sql_Result.

After that, I'll be ready to move Solar out of "development" to an alpha or even beta status. That will be a happy day. :-)


Solar 0.13.0 Released

Solar is a simple object library and application repository for PHP5. It is framework-and-library for web-application development, with an emphasis on distribution, localization, and database portability. You can download it via a PEAR channel and browse the Subversion repository.

The rest of this entry is taken from the migration notes; be sure the read the change log for full details.

Config Key Changes

I've tried to make config keys consistent and standard across all classes. In particular, note these changes:

  • Keys related to username values now use 'handle' (vs 'username')
  • Keys related to password values now use 'passwd' (vs 'password')
  • Keys related to form submission values now use 'submit' (vs 'op')
  • Keys indicating the name of a driver class are now 'driver' (vs 'class')
  • Keys indicating the configuration array for a driver class are now 'config' (vs 'options')

In particular, these config key changes affect Solar_Sql and Solar_User_*.

You should make sure to change your Solar.config.php array to reflect the new keys; e.g., $config['Solar_Sql']['class'] should become $config['Solar_Sql']['driver'].

Also, you should make sure your authentication forms use the new 'handle

Locale Key Changes

Similarly, I've tried to make locale keys consistent and standard across all classes.

  • 'ERR_*' keys are now reserved for exception and error messages, instead of generic feedback (such as form processing).
  • 'SUBMIT_*' keys take the place of earlier 'OP_*' keys, to indicate a form submission value, or a sub-action value (like 'next' or 'previous')
  • New 'SUCCESS_*' and 'FAILURE_*' keys are for non-exception non-error user feedback, such as in form processing
  • Label keys now use '_SUBMIT', '_HANDLE', and '_PASSWD' in relation to the standard config keys for submissions, usernames, and passwords.

Solar_Filter and Solar_Valid

These classes are no longer static; you have to instantiate them. Also, the custom() methods have been renamed to callback() to more clearly indicate how they work.

URI Generation

The Solar_Uri class has two new config keys, 'action' and 'public', to indicate the base href values for actions and public files.

For example, if your front-controller is at '/~username/solar/front.php', set that as your 'action' config key value. Then, all calls to exportAction() will be prefixed with that value. Similarly, if all your public files (CSS, JavaScript, etc) are at '/~username/public', set that as your 'public' config key value, and all calls to exportPublic() will be prefixed with that value.

These keys make it easy to set defaults that will be used throughout Solar, e.g. in Solar_View helpers for action() and publicHref().

View Processing

Solar_View takes the place of Solar_Template, and is a new "flavor" of Savant3 specifically for Solar integration.

There are new helpers as well, in particular the above-mentioned action() and publicHref() for URI generation, as well as new form and form-element helpers with built-in localization using Solar::locale().

When changing your Solar_Template scripts to Solar_View, note that these changes are needed:

  • Solar_Template => Solar_View
  • $tpl->addPath('template', ...) => $view->addTemplatePath(...)
  • $tpl->addPath('resource', ...) => $view->addHelperPath(...)
  • $this->eprint() => echo $this->escape()
  • $this->locale() => $this->getText() || $this->getTextRaw()
  • $this->eprint($this->locale(...)) => echo $this->getText(...)
  • $this->actionLink() => $this->action()
  • $this->ahref() => $this->anchor()
  • $this->form() is completely different; review the Solar_View_Helper_Form documentation to see all the changes. I'll write more about this later.

Front-Controller

The front controller is much lighter now. All layout processing has been moved into the page controller. Also, the way the front controller maps page requests to page classes has been changed. Previously, you would set up an "_app_class" array that mapped short page names to full class names:

<?php
    $_app_class = array(
        'bookmarks' => 'Solar_App_Bookmarks',
        'hello'     => 'Solar_App_Hello',
        'other'     => 'Vendor_App_Other',
    );
?>

In the spirit of auto-discovery, override, and fallback, the front-controller now uses a simpler array of base class names; the short-page name is appended to the class name to get the proper class.

<?php
    $_classes = array(
        'Solar_App',
        'Vendor_Pages',
        'Another_Vendor_App',
    );
?>

If the front-controller receives a request for the page 'hello', it looks first for 'Another_Vendor_App_Hello', then falls back to 'Vendor_Pages_Hello', and finally defaults to 'Solar_App_Hello'.

This allows you to have your own vendor-specific versions of Solar applications. It also means you don't need to add to the map every time you add a new page controller class; just put your app in one of the noted class directories, and the front-controller will find it automatically.

Page Controller

Conversely, the page-controller has become somewhat more robust. It now handles layout processing by reusing the internal view object, which means that all page-level variables are now available to the layout.

In addition, all Solar_App_* classes extend the new Solar_App class, which provides a series of variables specifically for the layout (e.g., $layout_title, $layout_links, etc).

The technique for indicating what variables should be mapped from the URI into the $_info array has changed. Previously, you would key on the action, and provide an array of variable names:

<?php
    $_action_info = array(
        'read' => array('area', 'page');
    )
?>

This would map a URI of "read/Main/HomePage" to the "list" action, and populate $_info['area'] and $_info['page'].

The problem here is that you couldn't tell the page-controller how to map default action variables. E.g., if $_action_default is 'read', then the URI 'Main/HomePage' should map to the 'read' action, but instead it would map to the 'Main' action (which probably doesn't exist).

The new page-controller code uses this format for mapping variables ...

<?php
    $_action_info = array(
        'read' => 'area/page',
    );
?>

... and *does* map variables using the default action_info map, so that calls to non-existent actions map properly. Thus, the 'Main/HomePage' URI will map to the 'read' action and populate the 'area' and 'page' $_info keys for you. This should help make your URIs prettier.

Setting default values for info mapping is easier too; for example:

<?php
    $_action_info = array(
        'read' => 'area=Main/page=HomePage',
    );
?>

Public Directories

The Solar/Layout and the Solar/App classes now have a Public/ directory. These are for storing stylesheets, JavaScript files, images, etc. The problem is that, because Solar is outside the webroot, you need to be able to put these files in a place accessible via http.

There are two ways to do this; either way, you need to create a 'public/Solar' directory in your web root. Then in that location, either:

  • Create a symbolic link to the related Solar "Public/" directories, or
  • Copy the related Solar "Public/" directories

The main drawback is that you need to link to (or copy) each Public/ directory; each new app means a new link. I'm going to try to find some way of either collecting all Public/ resources in one logical place, or automating the linking/copying process as part of the installation process.

Next Tasks

I need to update the site itself with the new documentation. After that, I will...

  • Revisit Solar_Filter and Solar_Valid to standardize the method names
  • Make Solar_Sql_Select a "fluent" class

After that, I think I'm going to be able to call the current codebase relatively stable; not release-candidate quality, but certainly preview-release or beta-level.

Of course I need to continue writing documentation and tests, but since this release should be the last major shakeup for a long time, docs should be easier to build and longer-lasting.


Differences Between Solar and Zend Framework

(This is an edited and collated version of an email thread from the solar-talk list.)

Even though (or perhaps because) the Solar project pre-dates the newly-released Zend Framework by about a year, I've had a couple of private emails asking me if I will discontinue Solar, or rewrite Solar on top of the Zend Framework. The short answer to both of these, at least for now, is "No -- Solar is not going away." Especially now that I'm not at Zend any more. ;-)

The long answer is, "Even though Solar and Zend Framework share a similar underlying architecture, Solar has implementation goals that are quite different than Zend Framework; these differences make it reasonable to continue Solar as a separate project."

Here are some of the main similarities between the two projects:

  • An overarching "main" class of static methods to provide common functionality for the remaining library classes, with things like a registry, a class loader, a file-finder on the include path, etc.
  • Significant adherence to PEAR standards for class naming convention and directory structure, where the class "Example_Class_Name" maps to "Example/Class/Name.php".
  • Classes for common web application needs: input validation and filtering; database API abstraction and query generation; URI manipulation; model, view, and controller classes; and so on. Some of these even have similar names, owing to their common adherence to Design Patterns.
  • They're both still under development. ;-)

But there are some significant technical differences between the two projects. Here are some examples just from the overarching "main" Solar class:

  • Solar uses a unified constructor mechanism. All Solar classes are constructed in exactly the same way, using an array of config options as the only constructor parameter. This makes is possible to provide ...
  • A unified configuration mechanism. Basically, you have a single config file that returns a PHP array keyed on class names. Classes can look up their own configuration automatically when constructed, because the __construct() signature is always the same. This also means that Solar can have ...
  • A universal factory method. A call to Solar::factory('Class_Name'), optionally with a factory-time config to override the defaults, will get you a new instance of any Solar-compatible class. This factory mechanism is used for the Solar::registry() (originally called Solar::shared()), and is also used for ...
  • A unified dependency-injector method. In short, you can call Solar::dependency() and it will retrieve a dependency object for you, either from the registry, from a passed object, or by constructing a new object for you on-the-fly. All you need to do is provide the class name and a specification for the dependency.

Solar currently provides other features not yet present in the Zend Framework:

  • built-in localization
  • read-once flashes
  • automated exception generation with localized messages
  • form processing
  • fully-automated form output generation (whole forms with layout, not just form elements)
  • user authentication and role classes

And of course, Solar is licensed under the LGPL (which is FSF/OSI approved).

So what does Zend Framework have that Solar does not?

  • A whole lot of specialty classes. The Zend Feed, Service, and XMLRPC libraries are fantastic, as well as the PDF generator and the Lucene-based search engine in the incubator. These are all excellent work. Solar has nothing to compare with these; I would call them the real gems of the Zend Framework.
  • About a dozen regular individual and corporate developers, and the backing of a real business, to support and drive its development. Solar only has one guy (yours truly) doing regular development, and the good will of a few regular contributors and users (Clay, Matthew, Jean-Eric, Jeff, Stuardo, and others).
  • A great marketing apparatus. Solar, by contrast, has only word-of-mouth and a few blog entries.
  • More automated tests, and more-and-better documentation. These things I really need to work on for Solar; in fact, documentation has been almost the only thing I've been working on for the past few days (more on that in a later post).

Mike Naberezny brings up a great point about the original question of whether or not Solar is going to continue in relation to Zend Framework:

I think that the underlying premise of these questions is very wrong. It is not necessary to choose one over the other -- you are always free to mix and match them at will. That's always been the PHP way.

I can only agree -- it was like when I got emails asking "how is Solar different from PEAR?". The three (PEAR, Solar, Zend Framework) are not really that different architecturally; Solar owes quite a lot to the PEAR standards for class naming, directory hierarchy, coding standards, and so on. Each of the projects has its own dependencies and idiosyncrasies, but if you know how each project works, then you can work on combining pieces from them effectively. The key differences are in the implementations, the little tweaks each makes to the related design patterns, and the society/culture/support-group that grows up around them. (That last part about culture is too often neglected; I like to think the Solar social norms are more approachable for anyone with a learning/mentoring attitude toward programming.)

Having said that, a programmer or team is probably going to pick a core body of code to work from (even if it's internal-only) and then add to it from other sources. I got the idea the questions were asked in this sense; i.e., "Is Solar going away as a core foundation to start from?" not "Should we use it exclusively?" Of course it would make no sense to use Solar or PEAR or Zend Framework exclusively, but each serves as a pretty good base to work from in their own ways.

Limiting yourself to one source of libraries means giving up one of the primary advantages for using PHP and only limits what you can do.

Totally agreed here; there's no reason not to mix pieces from different code bases, especially regarding ZF and Solar (since their architectures are so similar, and since each has things the other does not).


Resigned from Zend

As my family and a few close friends already know, I have quit Zend. While unfortunate, it was necessary.

My sister Amy once told me that, sometimes, "people don't quit their jobs, they quit their managers" and that is definitely the case here. The people I worked with are some of the best I've ever known (Mike, Matthew, Lucas, and everyone else). The work itself was great; although I got to develop some portions of the new Zend Framework, most of my work was internal-only, and I'm happy I was able to be of service to them. Unfortunately, my manager and I just did not work well together. I thought I could adapt myself to his style, but after three months of back-and-forth it wasn't happening. In the end, there was no reasonable solution other than to tender my resignation, which I did last Tuesday, and my last full day was last Wednesday.

So what's next? I've got enough saved to live on comfortably for the next few months, longer if I go into austerity measures. I think I'm going to take some "me" time for the next few weeks. I can catch up on my reading, work on bringing the core of Solar to a full stable release, drive around and see some friends and family, even start blogging more. After that I can start looking for work again; I think I've earned a bit of vacation time. :-)

Update (next day) ... I don't know what to say; the flood of support and good will from everyone has been amazing. I had no idea. I think I've replied personally to everyone who posted a comment or sent an email; if I have not, please accept my apologies, and my thanks for your good wishes. Thank you all so much. :-)


Savant2 Bugfix Release

David Mytton of Olate noted a typo related to setting __autoload() usage in the Savant2 constructor; I have rolled a new release (version 2.4.3) with that fix in place. Thanks, David!

(Savant is a template system for PHP; it uses PHP itself for the template markup, and provides object-oriented plugins and filters. Savant2 is for PHP4 and PHP5, while Savant3 is the PHP5-only and E_STRICT compliant.)


Solar 0.12.0 Released

(I know I didn't blog about the 0.11.0 release two weeks ago; sorry, been really busy here lately.)

The bad news is that, as promised, there are major changes to some core functions. However, this is the last of the expected major changes, and they promise to make added functionality and extensibility much easier.

The biggest changes are in the overarching Solar class itself and the front controller. Of those, the biggest single change is that Solar::start() no longer sets up shared objects; instead, the overarching Solar class now provides a Registry for you to set up your own shared objects. In addition, we now have read-once flashes, a TwoStepView pattern, and front controller hook scripts to set up the application environment. Finally, the content model appears to be on solid footing with a few changes to the class architecture. Read on for more about these changes (at great length).

Registry

Previously, Solar::start() would create Solar::shared('sql') and Solar::shared('user') objects. To set up shared objects of your own, you had to edit the ['Solar']['shared'] section of the config file. While useful, this holdover from Yawp was not as extensible as it should have been.

To address this, I've implemented a Registry pattern in Solar (read more about the pattern here). Now you can programmatically add and get shared objects using the registry functions. To add an object to the registry, use Solar::register('shared_name', $object_instance). Then you will be able to retrieve the $object_instance from the registry by calling Solar::registry('shared_name').

Solar::shared() and the objects managed by it via Solar::start() and Solar::stop() have been removed entirely. To mimic the previous Solar::shared() behaviors, the Solar_Controller_Front class comes with a default hook script to create Solar::registry('sql') and Solar::registry('user'). Effectively, the only thing you need to do in your apps under the front controller is change calls from Solar::shared('name') to Solar::registry('name').

As an added consequence, the Solar_Base::solar() method providing hooks for auto-shared object behaviors has also been removed, since the very concept of auto-sharing has been obviated. In practice, the only class using the solar() hook method was Solar_User, and that behavior has been replicated in the default front controller hook script.

Locale Management

As a consequence of getting Solar::start() out of the business of creating shared objects, there is no more shared Solar_Locale object. This is because Solar_Locale's only purpose was to be a shared object set up by Solar::start() for shared translation functions across the whole Solar environment.

As a result, the old Solar_Locale methods have been moved into the overarching Solar class itself. Solar::locale() (and $this->locale() for Solar_Base classes) works exactly as before, but to change the locale code, you should call Solar::setLocale($code) instead of Solar::shared('locale')->reset($code). In some ways this is a return to earlier versions of locale management.

Read-Once Flashes

Ruby on Rails has popularized the idea of read-once session values, what they call "flashes." While the name may leave a bit to be desired, the concept itself is very useful. In short, you set a value which propagates via $_SESSION, but the first time it is read, it is deleted (thus "read-once").

Initially this may sound of limited use, but in practice it's very powerful. Things like messages and short-term values can be forwarded through as many redirects as you like until you actually need them; then, when you read the value out, it disappears from the session. The values "flash forward" with the session until you need them. The nice thing is that the code to implement flashes is delightfully simple.

Solar flashes are grouped by class name and a key value under that class, which means that your different classes have their own namespace in the global flash value sets.

There are two ways of working with flashes in Solar. You can use the overarching Solar class for them, via Solar::setFlash($class, $key, $value) and Solar::addFlash($class, $key, $value). The $value gets stored in $_SESSION['Solar']['flash'][$class][$key], until you read it with Solar::getFlash($class, $key), at which time it is returned and deleted from the session.

Additionally, the Solar_Base class comes with the same methods (which in turn call the overarching Solar methods). These add the class name automatically. Outside of Solar_Base, you would have to call Solar::setFlash(get_class($this), $key, $value), but inside a Solar_Base extended class, you can just call $this->setFlash($key, $value). Voila: instant namespacing for your flashes.

Right now, I'm using flashes in Solar in two places. First, the Solar_User_Auth class sets a flash for the authentication message. The first time that message is displayed, it is no longer propagated. Similarly, the Solar_App_Bookmarks app used to propagate a "backlink" as a $_GET variable so that, after editing a bookmark, you could go directly back to your list of bookmarks, sorted and filtered exactly the same way as before. Tracking that information through the URI was ugly at the very least, and cumbersome at worst. With flashes, that information is now forwarded invisibly by the $_SESSION, and removed as soon as you actually use the backlink. Pretty nifty.

Incidentally, to support flashes, Solar::start() now calls session_start() automatically.

Front Controller Improvements

The Solar_Controller_Front class has seen some marked improvements. First, it now sports hooks for __construct() and __destruct() scripts. This means that you can have setup/teardown hooks the same way you do with Solar::start() and Solar::stop(). Solar_Controller_Front now comes with a default __construct() hook script to create shared 'user' and 'sql' objects in the Solar registry; this is where the shared-object construction that used to be in Solar::start() reappears.

The front controller also allows for a TwoStepView pattern now. Previously, the page controller was in charge of its own content, plus the site layout. When mixing and matching application components, as I expect to be the case in Solar, this makes it difficult to maintain a consistent look-and-feel between applications; you would need to change the layout in every separate application.

With a two-step view, the page controller is responsible only for its own output, and that output is then inserted into a site layout by the front controller. The page-controller can also set variables for the layout in the Solar_Controller_Page::$_layout value, so it can "hint" the front controller as to what the title value should be for the page and provide other information for the layout (all without controlling the layout directly). The front controller layout itself is just another Solar_Template script (which itself is extended from Savant3).

However, some page controllers need to explicitly defy the site layout; e.g., RSS feeds need to be presented as they are, without any sitewide theming. To facilitate this, you can set the Solar_Controller_Page::$_layout property to boolean false, and the front controller will take this to mean you need a one-step view (i.e., the page view alone).

Page Controller Improvements

The page controller changes are not as dramatic. When you extend Solar_Controller_Page for your application, you now have access to _preAction() and _postAction() methods that execute before the first action and after the last (but before the view is rendered).

Speaking of the view, the page controller now automatically picks the view for you based on the last action performed, not the first. As always, you can explictly pick the view to use by setting $this->_view.

Regarding the two-step view pattern, if you need to pass information back up the layout, you can do so by setting keys in the $this->_layout property. The front controller will read this data from the Solar_Controller_Page::getLayout() method and use it as "hinting" for the layout. Right now, the only values used by the default layout are $_layout['head']['title'] and ['body']['title'].

Finally, it is much easer to chain methods with _forward() than before. If you want to pass control temporarily to another action, call "$this->_forward('actionName')" in your action script; this will execute the action and return to the current script. If you want to forward to another action and *not* continue the current script, call "return $this->_forward('actionName')". Either way, the page controller properties will persist through all the forwards, which means you can break action scripts into smaller pieces and reuse them as needed.

Content Domain Model

And last, I think I finally have a useful and extensible class mapping for the content domain model. Previously, a Solar_Content object acted as a generic interface to all possible ways of manipulating content. In this new release, the Solar_Content object acts only as a collection point for the database table objects in the content domain, and a Solar_Content_Abstract object acts as the base from which different content node types are manipulated. You can see an example of this in Solar_Content_Bookmarks.

I don't believe I have talked about the Solar_Content domain model before. In short, it's very much like a namespaced wiki. The namespaces are called "areas" and represent logically separated areas of content, such as different sites. Each area has "nodes" of content; a node may be a bookmark, a blog entry, a wiki page, a comment, a trackback, or anything similar. Finally, each node may have a series of "tags" on it; the tags are stored as a text string in with the node itself, but searching them that way is problematic, so there's a separate table for tag-based searches.

All this means that the content domain, as it stands now, is composed of only three tables: one for areas, one for nodes, and one for tags. For areas and tags, you can see why one table each is enough. But how can it be that one table of "nodes" can possibly encompass so many different kinds of content?

It turns out that these different content types are remarkably similar. With few exceptions, they each are composed of the same kinds of elements: a subject or title, an author name and/or email address, a related link or URI pointing to a source, the body of the content itself, and perhaps a short summary of the content.

The elements may be called by different names, but they are effectively all needed no matter what the content type. Trackbacks, pingbacks, comments, wiki pages, blog entries, bookmarks, etc. are all composed of essentially the same pieces, it's just that those pieces are used differently depending on the context.

The problem with a simplified but mostly generic content domain is that you have to define the context for the node data. Previously, I had the node types as separate Solar_Model objects. However, this had a number of problems, most notably that there was no way to extend them from a common generic node type. With that in mind, I've created a Solar_Content_Abstract class that handles all the common node operations, and then I extend it for specific node types to provide context (e.g., Solar_Content_Bookmarks).

There is still the problem of hierarchies, though. Some kinds of content, such as comments and trackbacks, are probably not supposed to be addressed on their own; as such, you can specify that a node is "part_of" another node. This way you can ask for a "master" node and see that it has a number of other nodes associated with it. That's about the deepest the Solar_Content domain goes in a hierarchy; it's very flat, with tags instead of categories.

Next Tasks

The biggest problem now is that the documentation is pretty far out of sync with the codebase. Keeping the docs in sync is turning out to be a much bigger problem than I though it would (and I expected it to be a problem to begin with). So while I'm eager to start adding more classes, content types, and mini-applications, the documentation really has to take priority.

I'm evaluating possible solutions now; the best one I can think of is to start embedding more end-user documentation in the class comments themselves, and use the PHP 5 Reflection API to collect them into a wiki-like structure. That way I can edit the docs as I edit the code, instead of having to update the docs separately from the code. Once the codebase stabilizes, I can start separating the docs back out again into a user-editable wiki.


Solar: Modified Testing Strategy

In the current release of Solar (version 0.10.0) I use the .phpt test format, which is supported very well by Greg BeaverTomas V.V. Cox's "pear run-tests" code. The .phpt testing strategy compares the output of a test script to an expected output string, and passes if the outputs match.

I noticed some time ago that Greg has adopted a testing strategy that combines .phpt with techniques found in PHPUnit and SimpleTest. These testing packages use assertions to compare method returns, rather than comparing output at the end of a test script. Greg's strategy uses assertions within .phpt tests: if there is no output, the test succeeds, but if an assertion fails, it generates unexpected output, which causes the .phpt test to fail.

(Aside: While true testing gurus may not think of this as a robust solution [hi Jason! hi Marcus! hi Sebastian!], I have thus far found it to be a "good-enough" approach that combines simplicity with ... well, more simplicity. :-)

Inspired by Greg's PEAR_PHPTest class, I've developed a bare-bones Solar_Test_Assert class that tests assertions and throws PHP5 exceptions on assertion failures. Add to that a quick hack to run all tests at once recursively through subdirectories (courtesy of the "pear run-tests -r" switch), and you get a convenient all-at-once testing tool for Solar. Thanks, Greg, for producing such useful tools.

I spent one day over the weekend rewriting all the existing Solar tests to use this new strategy, and met with great success. I did run into one problem, though. The class-method tests in the subdirectories use a common prepend file. Normally, you would think that "include '../prepend.php'" would work, but because the tests are running at the command line, PHP doesn't include ".." with respect to the subdirectory; instead, it tries to include ".." with respect to the current directory. This is the proper behavior, but it was causing every test to fail.

Mike Naberezny pointed out a cool trick to work around that behavior; instead of using "..", you can use "dirname(dirname(__FILE__))" to get to the next-higher directory. After applying this fix, all my tests ran from the main directory using "pear run-tests -r". Thanks, Mike.

Update: (2006-02-01) Greg pointed out that the "pear run-tests" is from Tomas V.V. Cox, so I update the above link. Thanks, Tomas, for this great testing tool.


Solar 0.10.0 Released

The good news is that I've rolled the 0.10.0 release for Solar, the simple object library and application repository for PHP5.

The bad news is that it has a ton of changes, and that the release after this will also have large number of changes. This is "bad" in the sense that none of the current users (inluding me) wants to change existing code to comply with the new Solar stuff, but it's "good" in that it prepares Solar for wider acceptance among professional developers.

Read on for a very long description of the changes, especially regarding the new MVC architecture.

Changes For v0.10.0

Code Formatting

The most obvious change is in code formatting. I'm going from tabs to 4-spaces, per the PEAR coding standard (I see their point now). Similarly, the PHP commenting docblocks are now indented one extra space; using spaces as tabs helps with this greatly. The Solar coding standards docs will be updated to reflect this.

However, I'm breaking with the PEAR convention for public/protected naming. The PEAR rule on visibility hints in naming is mostly a holdover from the PHP4 days: only private methods and properties get an underscore prefix, while both protected and public methods get no prefix (this is because protecteds may be extended and made public). This was an attempt to stay consistent with PHP4 code, but in PHP5 I just don't find it to be that great. Thus, from now on, both private and protected properties and methods in Solar will be named with an underscore prefix. For example, the protected $config property in Solar 0.9.0 and earlier is changing to $_config. (Public properties and methods still get no prefix.) Making protecteds into publics is hereby heretical. ;-)

Pattern Vocabulary

After some discussion, I have decided that using pattern-based names where possible is more appropriate than inventing a new vocabulary just for Solar; at least one person has pointed this out to me in the past (hi Travis!) and it took me a while to arrive at the same conclusions as he did.

As a result, the "Solar_Cell" class family has been renamed "Solar_Model". Likewise, the Solar::object() method has been renamed to Solar::factory().

Less Dependence On include_path

You no longer have to have the Solar base directory in your include path; Solar now "finds itself" in the filesystem and automatically prefixes loadClass() requests with the proper directory name. This makes it easier to move between installations of Solar without having to update the include_path ini settings.

However, when you require() Solar the first time, you still need to know where it is, so there is still a little bit needed from you for boostrapping. Even with that, though, I think this is easier than setting .htaccess directives, or getting your hosting provider to change include_path for you in the ini.php file.

Controller Classes

There is a new Controller family of classes: Solar_Controller_Front and Solar_Controller_Page. Their purpose should be obvious from their pattern names; they are a FrontController for routing between Solar applications, and PageController for page-based applications, respectively. Together, they form the basis of ...

New MVC Architecture

This is going to be the biggest news in Solar, even though the MVC classes are only a small part of the code base. We have talked about this on the mailing list before: the Solar_App class for MVC, while really useful, has a number of shortcomings, and needs badly to be updated.

First, Solar_App was not as automated as it could have been. You had to specify local directories, talk to the view object directly when assigning variables, etc. In the new architecture, Solar apps extend Solar_Controller_Page, and are a great deal more automatic. The controller picks the right view for the action script automatically, but you can also tell the controller explicitly what view to use. Regardless, your action scripts only manipulate the controller properties, and these are assigned by the controller to the view automatically. This dramatically improves documentation as well.

Second, and more importantly, the "model" portions did not lend themselves to distribution and auto-configuration very well at all. For example, if you had a "Blog" model class in your application, it was just named "Blog.php". The problem comes when someone else has a "Blog.php" model in a different application: the two names conflict. This makes sharing and distribution difficult, because there's no automatic deconfliction or namespacing. In the same way, it's difficult to have entries in Solar.config.php that automatically match up with the Blog class.

As a result, I have moved models entirely outside the Solar_App architecture. Your Solar-based classes should have their own top-level vendor directory anyway (this is part of the namespacing deconfliction), so any models for your Vendor_App classes should go in (e.g.) your Vendor_Model namespace. Under this architecture, VendorOne_Model_Blog and VendorTwo_Model_Blog class names don't conflict, and you can specify separate configs for them in Solar.config.php by their class names.

I know that removing models from the app-specific architecture sounds unintuitive, but after considering the problem of name deconfliction and configuration namespacing for the past couple of months, this solution seems simple and straightforward when it comes to distributed applications that may wish to draw on each other's models.

Therefore, with some renaming based on the new pattern vocbulary standards, we now have the following directory structure; basically, the old "controllers/" directory is now "Actions/", the "models/" directory is replaced with a Models directory higher in the hierarchy, and the "helpers/" directory is removed completely (you can keep action helpers in an Actions/Helpers/ directory itself if you wish).

Solar/
  App/
    Bookmarks.php        -- the page-based application controller
    Bookmarks/
      Actions/           -- application actions
        edit.action.php
        tag.action.php
        user.action.php
      Locale/            -- locale strings
        fr_FR.php
        en_US.php
      Views/             -- views related to actions
        auth.php
        edit.view.php
        error.view.php
        footer.php
        header.php
        list.view.php
        rss.view.php

In future, I'll add directories for view helpers (likely in Views/Helpers/) and a directory for public assets such as stylesheets and javascript code (likely in Public/).

Front-Controller Unification

Finally, Solar_App applications now expect to be unified under a front controller. (This is the "Solar_Site" class we've talked about in the past.)

Thus, instead of having (e.g.) bookmarks.php and blog.php in your web directory, you will have only (e.g.) front.php, which will run the Solar_App_Bookmarks app for you when you browse to "front.php/bookmarks". The front-controller code looks similar to the old app-based code:

    require_once 'Solar.php';
    Solar::start();
    $front = Solar::factory('Solar_Controller_Front');
    $front->display();
    Solar::stop();

Under this, and with the new Solar_Controller_Page based applications, instead of browsing to ...

    http://example.com/bookmarks.php/edit/?id=0

... you can browse to this instead:

    http://example.com/front.php/bookmarks/edit/0

This way, your applications can all route between another using the front.php front controller.

Upcoming Changes

These are changes I expect to make after the 0.10.0 release.

Exceptions

I'm going to start using exceptions more liberally throughout Solar. I've been using exceptions in other work, and I am finally convinced of their usefulness. Frankly, my use of Solar_Error has been mostly to generate script-stopping E_USER_ERROR trace dumps, and that's exactly what exceptions do. I don't think I'll get rid of Solar_Error, as it's useful for internal messaging if nothing else.

Two-Step View

I'm also going to start using the TwoStepView pattern, where the page-based application controller only generates its specific output, and the front controller will generate the surrounding site layout. (Right now the application controller does both, using a header.php and footer.php for the surrounding site layout.) This should help with site-wide theming and application output sharing.

Move Some Solar::start() Code to Solar_Controller_Front

Right now, Solar::start() builds a number of shared objects for applications: an SQL connection, a User object (with automated authentication processing), and so on. Now that Solar has a real front controller, I'm starting to think those processes are more appropriate for starting up the application space, not the general Solar environment. As such, I'm going to experiment with moving them into a _setup() method in Solar_Controller_Front.

Move Shared Objects To A Registry

This one I'm not so sure about. Under the new pattern-based vocabulary, the Solar shared objects should probably be called a "registry". This may entail an actual Solar_Registry class; at the very least, it will mean renaming Solar::shared() to Solar::registry() to retrieved shared objects, and perhaps adding new methods like register() and unregister() to manipulate the registry.



A Spate of Savant News

Savant is an object-oriented template system for PHP; it provides plugins, output filters, path management, stream support, and lots more. Savant uses PHP itself as the template language, so it is both fast and lightweight. Savant3 is for PHP5 E_STRICT compliance, while Savant2 is E_ALL compliant under both PHP4 and PHP5.

Savant3

Savant3 is marked "stable" as of yesterday, bringing to a close the very long beta period. You can read the change log and download it here. Woohoo!

Cyberlot notes his success using Savant3 and gettext; he wrote up an output filter that translates special tags, then caches the result. Nice work! (Update, 10am: see also his brief writeup via the Savant mailing list archive.)

Antti Holvikari noted some bugs (fixed in yesterday's release of Savant3) and wrote a proof-of-concept app using Savant3 inside a PHP_Archive .phar file. Pretty cool, Antti!

Savant2

Also as of yesterday, Savant2 series has a 2.4.2 release. You can read the full change log, but the main points are:

  • Fixed a strpos() param order bug in the code for stream support
  • Use of __autoload() under PHP5 is now configurable; it's off by default, but you can turn it on with setAutoload(true)