Solar: From Blog to Docs

Quick note: the blog entries of the past few days have been converted to manual entries at the Solar website.

The translation went pretty quickly, because I wrote the original blog entries using Solar_Markdown, which converted them to HTML for the blog. Then I did some quick edits and re-converted them using Solar_Markdown_Wiki (which is the markup format for all the class API and manual entries at the Solar website).

Sending Mail with Solar

Ah, sending mail, the bane of everyone who has ever used the php mail() function for anything remotely non-trivial. There are lots of entries in the “helps you send mail” category in PHP userland:

While each of these will work with Solar, the new Solar_Mail and Solar_Smtp packages work “natively”, in that they support automatic configuration, locale and exception inheritance, and so on. Read on for some examples on how to use them.

Continue reading

Brief Intro to Solar_Http_Request and Response

As I noted in an earlier post, Solar how has classes to represent simple HTTP requests and responses.

Solar_Http_Request

The Solar_Http_Request class represents a standalone HTTP request (i.e., it’s not an HTTP client, just a request). It uses adapters, so you can (in the future) change between a cURL, pecl_http, or sockets adapter – but for now, the only adapter is the streams-based one.

It’s a fluent class, which makes the code a little prettier at times. Here’s a basic GET request that sends “?foo=bar” as the query string.

$request = Solar::factory('Solar_Http_Request');
$request->setUri('http://example.com/index.php')
        ->setContent(array('foo' => 'bar'));

$response = $request->fetch();

(You could also set the query string in the URI if you wanted, but using setContent() does all the esaping for you.)

Here’s the same thing as a POST:

$request = Solar::factory('Solar_Http_Request');

$request->setUri('http://example.com/index.php')
        ->setContent(array('foo' => 'bar'))
        ->setMethod('post');

$response = $request->fetch();

And here’s a request with a lot of customization:

$request = Solar::factory('Solar_Http_Request');

$request->setUri('http://example.com/index.php')
        ->setMethod('post')
        ->setHeader('X-Zim', 'Kneel before Zim!')
        ->setUserAgent('Irken Robot Gir')
        ->setBasicAuth('username', 'password')
        ->setContent(array('foo' => 'bar'));

$response = $request->fetch();

Right now, Solar_Http_Request doesn’t handle file uploads, but that functionality is planned for a future release. However, it does provide SSL support through PHP streams.

Solar_Http_Response

What you get back from the request is a Solar_Http_Response object. You can explore the response by getting the response code, response text, HTTP version, headers, cookies, and content – all pretty standard stuff.

You can also build a new Solar_Http_Response on your own and send that from your application. This has the benefit of giving you relatively good control over headers, response codes, etc., so that you can pass the values around to different parts of your application before sending the final response. (It also sanitizes header values automatically, which can be a nice addition for secure coding practices.)

$response = Solar::factory('Solar_Http_Response');
$response->setResponseCode('200') // auto-sets response text to "Ok"
         ->setHeader('X-Example', 'Custom header value')
         ->setCookie('foo', 'bar') // sets cookie foo=bar
         ->setContent('<p>Hello world!</p>');

$response->display(); // sends sanitized headers and prints the content

Solar_Controller_Page, for example, composes a Solar_Http_Response internally, which it then returns for sending.

Testivus (for the Rest of Us) and the Testing Gene

The esteemed Sebastian Bergmann, author of PhpUnit, makes a great comment in my earlier post on testing and ravioli code.

In the comment, he links to the Testivus Manifestivus from Alberto Savoia, which is about as non-dogmatic about testing as you can get, while still highlighting the importance of testing. You should read the whole thing, but here are some of the highlights.

Less testing dogma, more testing karma

Dogma can be informally described as authoritative principles, beliefs, often considered to be absolutely true. Testivus tries to keep testing dogma to a minimum. What works for some people sometimes, may not work for some other people, or even the same people at some other time.

Karma, on the other hand, can be informally explained as: “Do good things, and good things will happen to you.” We believe that writing and running tests is a good thing so if you write and run tests good things will happen to you … well perhaps just to your code.

We’d like to say that this is the central tenet of Testivus, but calling something a tenet would be too dogmatic.

Any tests are better than no tests

Self-explanatory and inspired by Martin Fowler, who once wrote “Imperfect tests, run frequently, are much better than perfect tests that are never written at all”.

Savoia follows with “Testing beats debugging”, “Test first, during, or after – whatever works best for you”, “If a method, technique, or tool, gives you more or better tests use it”. While I may have issues with test-first and TDD, I am fully in support of Testivus.

What’s intersting about Testivus is that it is the result of an earlier Savoia post about susceptibility to test-infection. That entry from Savoia is good too; he seems to approach things from a “how do people actually work” point of view, rather than “what would a perfect mode of working be”. Strongly suggest you read it, if only to determine if you are a T1, T2, or T3 (I think of myself as in the T2 camp). TDD dogmatists (hi Noel Darlow!) take note: for whatever reason, some people are highly resistant to test-first, and airs of moral superiority and/or condescension do little to help your cause among the T2 and T3.

Update (2007-08-19): Alberto Savoia in the comments below notes that there are new extended versions of the Testivus Manifestivus.

Thanks for the new links.

TDD, Test-First, and Ravioli Code

I know that test-first and test-driven development (TDD) are popular methodologies these days, but something about those processes has always met with a level of mental resistance from me. Even though it sounds good in theory, I have been intuitively wary of the “test-first” mentality for some reason. My own approach is closer “remember to code so you can test it later” and then “test-last” after the API is mostly stable. This comment from Slashdot is a good summary of my feelings on the matter.

Thinking about testing early — good. Writing unit tests — good. The test driven development mentality (write tests instead of design, write unit tests before coding) — bad. … Thinking about testing early is useful, it may cause you to think about corner cases. But writing them first causes 2 problems — you end up writing the code to solve the tests (rather than solving the problem) and/or you end up throwing away half the test suite in the middle when you refactor the design.

AuMatar

Just that you have tests does not guarantee that you’re solving the right problem, or solving it well, or that your solution is comprehensible to others.

In the same conversation, someone brings up the term “ravioli code”, which I had never heard before. The idea is that instead of long strings of procedural “spaghetti” that are difficult to untangle, there are lumps of methods jumbled together:

The problem is that it [Ravioli Code] tends to lead to functions (methods, etc.) without true coherence, and it often leaves the code to implement even something fairly simple scattered over a very large number of functions. Anyone having to maintain the code has to understand how all the calls between all the bits work, recreating almost all the badness of Spaghetti Code except with function calls instead of GOTO. It is far better to ensure that each function has a strong consistent description … rather than splitting it up into smaller pieces (“stage 1 of preparing to frobnicate the foo part of the foobar”, etc.) with less coherence. The principal reason why this is better is precisely that it makes the code easier overall to understand.

People who deliberately split code up into ravioli, and especially those who advocate that others do so, are “dangerous idiots” precisely because they’ve lost sight of the fundamental need for comprehensibility …

dkf

I think that a lot of TDD and test-first idealists and evangelists* end up with ravioli code that is well-tested but still difficult to comprehend.

Some people will read this and think that I am against unit testing; that would be an incorrect interpretation. TDD and test-first dogmatism are what bother me, not testing per se.

(* Note that I said “idealists and evangelists” — not all of the test-firsties are like this.)

Speaking at PHP Works 2007

I got stiff-armed for ZendCon 2007 (apparently they don’t want presentations on competing frameworks ;-).

However, the PHP Architect folks graciously accepted two of my talk proposals for php|works 2007 in Atlanta:

In a way, the two talks complement each other, but you’ll have to attend both to see why. 😉

Hope to see you at the conference!

Solar 0.28 Alpha Released

Last Friday I released Solar 0.28 alpha. (As usual, the guys on the mailing list got notification of this on the same day.)

This is the first release in four months. The last time I delayed so long between releases I gave the change notes inline, but I won’t punish readers that way again. 😉 If you really want to, you can see the very very long list of change notes here.

Over the next several days, I’ll post more about individual developments in the framework, but here’s a little to pique your interest:

In other news, it looks like Enygma at phpdeveloper.org gave Solar a try and liked it. Head over there and give him some comment-love! 🙂

Solar Views and Layouts

Looks like the Zend Framework project doesn’t have “complex views” settled just yet. I’m sure they’ll hit on a solution soon. In the mean time, let me show you how easy it is to work with views and layouts in Solar, including automatic format discovery and inherited layouts.

Basic Directory Structure

By way of introduction, here is the directory structure for an example application. We’ll call the top-level namespace “Vendor”, and the application itself “Example”. (These would live in the PEAR directory next to the Solar installation.)

 Vendor/
     App/
         Example.php
         Example/
            View/
                hello.php
            Layout/
                default.php

Page Controller, View, and Layout

The example application is a simple “Hello World” page controller:

/* Vendor/App/Example.php */

class Vendor_App_Example extends Solar_Controller_Page {

    protected $_layout = 'default';

    public $foo;

    public $zim;

    public function actionHello()
    {
        // let's set some properties
        $this->foo = 'bar';
        $this->zim = 'gir';

        // Solar_Controller_Page automatically finds and renders the
        // 'hello.php' view, then takes that output and automatically
        // injects it into the 'default.php' layout.
    }
}

The view script in this case is dirt-simple, but you can use Solar_View helpers to jazz it up.

        <!-- Vendor/App/Example/View/hello.php -->
        <p>Hello, world!</p>
        <p>Foo is <?php echo $this->escape($this->foo) ?>.</p>

As with most 2-step view implementations, the view output is “injected” into the layout script. In this case, let’s use a bare-bones HTML layout.

<!-- Vendor/App/Example/View/default.php -->
<html>
    <head>
        <title>Example</title>
    </head>
    <body>
        <?php echo $this->layout_content ?>
    </body>
</html>

(The $layout_content property is automatically populated by the page-controller with the output of the rendered view.)

When you browse to http://example.com/example/hello, you should see this output from the application:

<!-- Vendor/App/Example/Layout/default.php -->
<html>
    <head>
        <title>Example</title>
    </head>
    <body>
        <!-- Vendor/App/Example/View/hello.php -->
        <p>Hello, world!</p>
        <p>Foo is bar.</p>
    </body>
</html>

Variable Assignment

Wait, how did $foo get into the view? The page-controller automatically assigns all public properties of the controller to the view object, so you don’t have to think about what gets set and what doesn’t. If a controller property is public, the view can use it.

Likewise, the page-controller assigns the same variables to the layout, so you have full access to them in your layouts as well. For example, we could change the layout script to use $zim as the title …

<!-- Vendor/App/Example/View/default.php -->
<html>
    <head>
        <title><?php echo $this->escape($this->zim)</title>
    </head>
    <body>
        <?php echo $this->layout_content ?>
    </body>
</html>

… and the output would become:

<!-- Vendor/App/Example/View/default.php -->
<html>
    <head>
        <title>Gir</title>
    </head>
    <body>
        <!-- Vendor/App/Example/View/hello.php -->
        <p>Hello, world!</p>
        <p>Foo is bar.</p>
    </body>
</html>

Other Layouts, or No Layout

If you want to use a layout other than the default one, just change $this->_layout to the one you want to use. First, add the layout script:

 Vendor/
     App/
         Example.php
         Example/
            View/
                hello.php
            Layout/
                default.php
                other.php

Then ask for it in your action:

/* Vendor/App/Example.php */

class Vendor_App_Example extends Solar_Controller_Page {

    protected $_layout = 'default';

    protected $_action_default = 'hello';

    public $foo;

    public $zim;

    public function actionHello()
    {
        // let's set some properties
        $this->foo = 'bar';
        $this->zim = 'gir';

        // let's use some other layout
        $this->_layout = 'other';
    }
}

If you don’t want to use a layout at all, set $this->_layout = null.

You can do the same thing for views; by default, the page controller looks for a view that matches the action name, but you can set $this->_view to the name of any view you like.

Multiple Formats

Now let’s say that we want to expose an XML version of our view. The Solar page-controller can look at the format-extension on a request and render the right view for you automatically. All you need to do it provide the view script for it – you do not have to change your controller logic at all.

Let’s add the XML view for our “hello” action (“hello.xml.php” below).

 Vendor/
     App/
         Example.php
         Example/
            View/
                hello.php
                hello.xml.php
            Layout/
                default.php
                other.php

The hello.xml.php view script looks like this:

<hello>
    <foo><?php echo $this->escape($this->foo) ?></foo>
    <zim><?php echo $this->escape($this->zim) ?></zim>
</hello>

Now when you browse to http://example.com/example/hello.xml (notice the added “.xml” at the end), you will get this output:

<hello>
    <foo>bar</foo>
    <zim>gir</zim>
</hello>

You can do this for any output format you like: .atom, .rss, and so on – and not have to change your controller logic at all.

Wait a minute, what happened to the layout? The Solar page-controller knows that if it receives a non-default format request, it should turn off the layout and use only the view.

Shared Layouts

Now, what if you have a layout or view that you want to share among multiple page controllers? This is pretty easy, too. First, define a “base” controller from which other controllers will extend, then put the shared layouts there.

 Vendor/
     App/
         Base.php
         Base/
            Layout/
                default.php
                other.php

The base controller might look like this:

/* Vendor/App/Base.php */
class Vendor_App_Base extends Solar_Controller_Page {
    // nothing really needed here, unless you want
    // shared methods and properties too
}

Now the “example” controller extends the base controller:

/* Vendor/App/Example.php */
class Vendor_App_Example extends Vendor_App_Base {
    // ...
}

And you can remove the layouts from the example controller; it will automatically look up through the class-inheritance hierarchy to find the requested layout.

 Vendor/
     App/
         Base.php
         Base/
            Layout/
                default.php
                other.php
         Example.php
         Example/
            View/
                hello.php

You can override the shared layouts with local ones if you want to. If you have Example/Layout/default.php the page-controller will use it instead of the shared one.

This works with views too. Put any views you want to share in Base/View, and the page-controller will find them if they don’t exist in the Example/View directory.

That’s All For Now

Questions? Comments? Leave a message below, or join the Solar mailing list and chime in there – we’d be happy to have you around.