Paul M. Jones

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

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)

Header Image: Jupiter and Io

So I finally got around to putting up a custom header image for this blog; now it won't look exactly like every other default installation of Wordpress. It's a picture of Io in orbit above Jupiter, taken by the Cassini space probe on its way to Saturn. The original is here.

Although I didn't intend it, there is a minor visual pun here. The Wordpress theme is Kubrick, and the image subject is Jupiter, and given my fascination with the movie 2001 ... well, you see what I mean. What a geek.


Fluent Interfaces Require Fluent Situations

My friend and coworker Mike Naberezny wrote recently of fluent interfaces (a.k.a. object chaining). You should read more about it there, but in the mean time, here's an example usage provided by Mike for a new customer order with multiple lines and a shipping priority:

$customer->newOrder()
         ->with(6, "˜TAL')
         ->with(5, "˜HPK')->skippable()
         ->with(3, "˜LGV')
         ->priorityRush();

The idea is that the fluent interface makes it very easy to read the resulting code in a way that flows naturally. This is cool, but I want to add a caveat to his examples.

I think, for a fluent interface to be effective, you need situations where you actually have all that information at one time so that you can chain the methods in a fluid way. In the above case, we actually know what the whole order is, what the shipping priority is, etc. But very often (I would say "almost always") you *don't* know what the whole customer order is. Instead, you are more likely to iterate through the order lines as the customer adds them, or as you retrieve them from some data source. You might need something more like Mike's initial contra-example:

$order = $customer->newOrder();
foreach ($line as $item) {
    $order->with($item['number'], $item['type']);
}

That's not to say that a fluent interface cannot perform in such a way (it can if written to allow for it). My point is that writing class methods in a fluent interface style may not be worth the effort when you don't often expect to have all the necessary information at once; however, if you *do* expect to have that information, it can be very nice. For example, I could see where a fluent interface could be quite effective in something like Solar_Sql_Select. Currently, you need to do this:

$select = Solar::object('Solar_Sql_Select');
$select->cols('*');
$select->from('table_name');
$select->where('colname = ?', $value);
$select->order('colname DESC');
$result = $select->fetch('all');

But with a fluent interface, one could do it this way (which seems very clean to me at this point):

$select = Solar::object('Solar_Sql_Select');
$select->cols('*')
       ->from('table_name')
       ->where('colname = ?', $value)
       ->order('colname DESC');
$result = $select->fetch('all');

Anybody have thoughts on this particular style of expression?


A Dearth of Blogging Lately

A number of people have emailed me to note that my blog has died since I started at Zend. Very sorry to disappoint those of you who look forward to my missives. :-(

The main reason for my lack of authorship has been that this blog (with some notable exceptions) has mostly been about whatever PHP project I'm currently working on. Since I'm now working on Zend-internal stuff, none of which can be public right now, there's not much I am at liberty to write about, especially given that my other public projects are mostly stable in one sense or another. That, and my free time has been in short supply given the holidays, family commitments, etc.

Having said that, look for more in this space over the next few days and weeks; in the mean time; thanks for your patience. :-)


Hired by Zend

I've recently been hired by Zend as a PHP developer for them. Many thanks to Andi, Daniel, Mike, and especially Matthew for their consideration and support throughout the process.

I start on Tuesday, and will be spending three weeks out in California as part of my initial time with them. After that, I'll be telecommuting from home (in Memphis TN). I'm very excited about this change of employment; Zend are big guns in PHP world, and it's a big deal to me to be working for them. :-)

I'm not sure yet as to my specific duties, although I get the impression it will be for internal development (not client work). I don't know if I'll be part of the framework development process (although I sure hope so!). Obviously there are non-disclosure issues, so I may not be able to talk about my work there much (although again I hope I will be able to do so, as I think Zend suffers from a little closed-mouthedness in regard to the wider PHP community, and could do with some regular blogging).

What does this mean for Solar, Savant, Yawp, and the rest of my public projects? In a philosophical sense, I don't know; but in a practical sense, I see no reason why my work on these will suffer or drag in any way, once the first few weeks in California are complete.

So if I'm less responsive to emails than normal for the next 3-4 weeks, that's why. :-)


Solar 0.9.0 Released

Solar is a simple object library and application repository for PHP 5. You can view the rather extensive change log here.

This release is a major break to backwards compatibility, mostly because we now use PDO for the SQL database API abstraction (as opposed to the homegrown solution I was using in preparation for PDO). The distribution includes a number of migration documents to help users of prior versions move to the new system.

I want to specifically mention one aspect of the Solar SQL system, the Solar_Sql_Select object. This is probably not new in the PHP world, but it's the first time I've done anything like this, so I want to talk about it.

One of the problems with database portability is that you can't depend on a LIMIT to work the same way across database backends; indeed, it may not even exist as such. Different DB abstraction packages use different methods to support LIMIT emulation, either by rewriting the SELECT statement, or by supporting only the portions of a LIMIT clause available to the particular backend.

What Solar_Sql_Select does is let you programmatically build a SELECT statement, and it keeps the clause portions separated internally. It then combines those portions in a manner specific to the database backend driver, putting the appropriate LIMIT clauses in the right place. Here's a quick example; let's build this SELECT statement.

SELECT id, date, type, name
FROM example
WHERE
    date >= '2005-01-01' AND
    date <= '2005-01-31' AND
    type IN('a','b','c')
ORDER BY id
LIMIT 10,50

(The LIMIT in this case is to grab 10 rows starting at row 50.)

The equivalent Solar_Sql_Select code is:



<?php
$select = Solar::object("Solar_Sql_Select");

// the basic columns
$table = 'example';
$cols = array('id', 'date', 'type', 'name');

$select->from($table, $cols);

// WHERE clauses for the date
$select->where('date >= ?', '2005-01-01');

$select->where('date <= ?', '2005-01-31');

// WHERE clause for the type
$types = array('a', 'b', 'c');

$select->where('type IN(?)', $types);

// ORDER and LIMIT
$select->order('id');
$select->limit(10, 50);


$statement = $select->fetch('statement');
?>

Some notes:

  • You can use $select->fetch() to retrieve the 'statement' as built, or actual results using 'all', 'col', 'row', 'PDOStatement', etc.
  • In this example, quoting happens on-the-fly, and quoting an array returns a comma-separated string of the individually-quoted array values. However, you can also use named placeholders (:start_date, :end_date, :type_list) and then use $select->bind() to bind data to those placeholders all at once. All hail the glory of PDO. :-)

In the above Solar-based PHP code, when MySQL is the driver, the $statement contents will look something like the initial example. However, when using the Microsoft SQL driver (which does not support LIMIT, only TOP), the resulting SELECT looks something like this:

SELECT *
FROM (
    SELECT TOP 10 *
    FROM (
        SELECT TOP 60 id, date, type, name
        FROM example
        WHERE
            date >= '2005-01-01' AND
            date <= '2005-01-31' AND
            type IN('a','b','c')
        ) AS solar_limit_rev
        ORDER BY id DESC
    ) AS solar_limit
ORDER BY id ASC

Which looks like a mess, but these guys seem to think it works. The point is that by keeping the SELECT clauses separate until you build the statement, you can manipulate the individual pieces with great precision for better portability.

Another thing about Solar_Sql_Select is that paging is built in. If you wanted to grab page 5, where pages are 10 rows each, you can do this:


<?php
// instead of $select->limit(10,50) ...
$select->paging(10); // 10 rows per page

$select->limitPage(5); // limit to page 5
?>

Also, row-and-page counting is built in.


<?php

// instead of $select->fetch() ...
$total = $select->countPages();

/*
$total = array(
    'count' => number_of_rows,
    'pages' => number_of_pages
);
*/
?>

You can see more extensive Solar_Sql_Select docs here (although they are not "real" docs, just migration examples).