Paul M. Jones

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

How Fast Is Your Framework?

UPDATE (2007-01-01): I have conducted a new series of benchmarks; see them here. The numeric results below should be considered as outdated, although the history, methodology, and approach are still valid.

Wherein I relate the results of benchmark testing on three (plus one)four frameworks: Cake, Solar, Symfony, and Zend Framework(plus Cake). Anger-inducing broad-brush overview: Solar is 4x faster than Zend, and almost 2x faster than Symfony. Read on for all the nuances and caveats.

UPDATE: Have updated the Cake results, now that I've figured out how to turn off database access.

UPDATE 2: Added note about "views" in Zend Framework, also added list of loaded files and classes for each framework.

Introduction

"Figures don't lie, but liars can figure." -- Anon.

Recently I became curious how many requests-per-second the Solar framework could handle. This was related to the recent Mashery launch; we needed to be ready to receive a ton of visitors to the site (I won't disclose those numbers at this time ;-).

Most Mashery applications are built on top of the Solar framework, but when benchmarking those applications, I needed to know what the theoretical limit to responsiveness was, as imposed by the Solar framework.

After that, I figured it would be interesting to know the theoretical limits to responsiveness imposed by other frameworks, in particular Cake, Symfony, and the Zend Framework. This blog entry reports my findings.

Goals and Caveats

The purpose of this is not to say that one framework is better than another, or to engage in one-upmanship. The results do not say whether an application will run faster or slower on a particular framework; bad application code will slow you down dramatically on any framework. Similarly, use of full-page caching will bypass the framework completely, raising the responsiveness to something closer to the web-server's maximum.

Instead, the purpose of this is to tell you what the maximum possible responsiveness of the framework will be under the simplest and most straightforward conditions, while using the framework's controller and view components. Anything you add as application code will only reduce its responsiveness from there.

In addition, these results do not constitute an endorsement or indictment of any particular framework; they all have different strengths and weaknesses. I think the ones I tested are the most full-featured, in that they provide full-up MVC separation, make good use of development patterns, and offer an existing set of plugins or added functionality like caching, logging, access control, authentication, form processing, and so on.

Finally, it is entirely possible that I have missed some important speed-enhancing technique with one or more of the frameworks. If so, it is due to ignorance on my part, not maliciousness. I have done my best to honestly and fairly represent each of the frameworks here, and I would be very happy to hear what I can do to speed up any of the frameworks I benchmark below.

Methodology

For each of the frameworks tested, I attempted to use the most-minimal controller and action setup possible, effectively a "Hello World!" implementation using the stock framework components and no configuration files (or as few as the framework would let me get away with). This was the only way I could think of to make sure the tests were identical on each framework.

The minimalist approach has a number of effects:

  • It negates most "if you cache, it goes faster" arguments. The "view" in each case is just a literal string "Hello World!" with no layout wrapper. (For Symfony and Cake, this meant editing the layout files to only echo the view output.)

  • It negates the "but the database connection speed varies" argument. Database access is not used at all in these benchmarks. (I could not figure out how to turn off database access in Cake, which is why I don't include it as a peer in the results.)

  • There's no application code to execute; the controller actions in each framework are empty and only call the view, which itself is only the literal "Hello World!" text. This shows us the maximum possible throughput; adding application code will only reduce responsiveness from this point.

Technology and Setup

All the benchmark tests were run on this hardware (admittedly underpowered):

  • Macintosh Mini G4 with 1G RAM and 7200 RPM hard drive
  • Stock installation of Apache 1.3.33
  • Custom compiled PHP 5.2.0
  • APC 3.0.11 enabled with shm_size of 30 and stat turned on

Each framework benchmark uses the following scripts or equivalents ...

  • Bootstrap file
  • Default configs (or as close as possible)
  • Front-controller or dispatcher
  • Page-controller or action-controller
  • One action with no code, other than invoking a View processor
    • No authentication, no access control, no filters, no database, etc.
    • (although Cake kind of forces the database on you, so it's not a full peer in this report)
  • Static view with only literal text "Hello World!"
    • No PHP, helpers, layout, escaping, etc.

... so the benchmark application in each case is very small.

I used the Apache benchmark "ab" tool for measuring requests-per-second, on localhost to negate network latency effects, with 10 concurrent requests for 60 seconds. The command looks like this:

ab -c 10 -t 60 http://localhost/[path]

Benchmark Test Results

First, we need to see what the maximum responsiveness is without any framework code. The web server itself is capable of delivering a huge number of static pages: 808 req/sec (!!!) for a file with only "Hello World!" in it.

Document Path:          /helloworld.html
Document Length:        12 bytes
Requests per second:    808.95 [#/sec] (mean)
Time per request:       12.36 [ms] (mean)
Time per request:       1.24 [ms] (mean, across all concurrent requests)
Transfer rate:          238.69 [Kbytes/sec] received

Invoking PHP to "echo 'Hello World!'" slows things down a bit, but it's still impressive (remember, APC is turned on for all requests): 597 req/sec.

Document Path:          /helloworld.php
Document Length:        12 bytes
Requests per second:    597.61 [#/sec] (mean)
Time per request:       16.73 [ms] (mean)
Time per request:       1.67 [ms] (mean, across all concurrent requests)
Transfer rate:          107.00 [Kbytes/sec] received

Not too shabby for a consumer Macintosh Mini optimized for desktop use.

Cake

The Cake page-controller looks like this:

<?php
class HelloWorldController extends AppController {
    var $layout = null;
    var $autoLayout = false;
    var $uses = array();
    function index()
    {
    }
}
?>

The page-controller automatically uses a related "index.thtml" view script.

Respresentative result from three "ab" runs on Cake 1.1.10.3825: 17 req/sec.

Document Path:          /cake/helloworld/index
Document Length:        12 bytes
Requests per second:    17.13 [#/sec] (mean)
Time per request:       583.67 [ms] (mean)
Time per request:       58.37 [ms] (mean, across all concurrent requests)
Transfer rate:          5.09 [Kbytes/sec] received

Solar

The Solar bootstrap file looks like this:

<?php
error_reporting(E_ALL|E_STRICT);
ini_set('display_errors', true);

require_once 'Solar.php';
Solar::start();

$front = Solar::factory('Solar_Controller_Front');
$front->display();

Solar::stop();
?>

The Solar page-controller code looks like this:

<?php
Solar::loadClass('Solar_Controller_Page');
class Solar_App_HelloMini extends Solar_Controller_Page {
    public function actionIndex()
    {
    }
}
?>

The page-controller automatically uses a related "index.php" file as its view script, and uses no layout.

Representative result from three "ab" runs on Solar 0.25.0: 45 req/sec.

Document Path:          /index.php/hello-mini/index
Document Length:        12 bytes
Requests per second:    44.78 [#/sec] (mean)
Time per request:       223.32 [ms] (mean)
Time per request:       22.33 [ms] (mean, across all concurrent requests)
Transfer rate:          16.75 [Kbytes/sec] received

Symfony (Stable and Beta)

The Symfony action controller code looks like this:

<?php
class worldActions extends sfActions
{
  public function executeIndex()
  {
  }
}
?>

The action controller automatically uses a related "indexSuccess.php" file as its view script. However, there is a default "layout.php" file that wraps the view output and adds various HTML elements; I edited it to look like this instead:

<?php echo $sf_data->getRaw('sf_content') ?>

If there is some way to make Symfony not use this layout file, please let me know.

Representative result from three "ab" runs on Symfony 0.6.3-stable: 26 req/sec.

Document Path:          /symfony/web/world/index
Document Length:        12 bytes
Requests per second:    25.60 [#/sec] (mean)
Time per request:       390.70 [ms] (mean)
Time per request:       39.07 [ms] (mean, across all concurrent requests)
Transfer rate:          8.40 [Kbytes/sec] received

Representative result from three "ab" runs on Symfony 0.7.1914-beta: 24 req/sec.

Document Path:          /symfony/web/world/index
Document Length:        12 bytes
Requests per second:    23.94 [#/sec] (mean)
Time per request:       417.72 [ms] (mean)
Time per request:       41.77 [ms] (mean, across all concurrent requests)
Transfer rate:          7.86 [Kbytes/sec] received

Zend Framework (0.2.0 and incubator)

The Zend Framework bootstrap file looks like this:

<?php
error_reporting(E_ALL|E_STRICT);
date_default_timezone_set('Europe/London');

set_include_path(
    '.'
    . PATH_SEPARATOR . './incubator/library'
    . PATH_SEPARATOR . get_include_path()
);

require_once 'Zend.php';
require_once 'Zend/Controller/Front.php';

Zend_Controller_Front::run('./application/controllers');
?>

The Zend Framework action controller code looks like this:

<?php
require_once 'Zend/Controller/Action.php';
class IndexController extends Zend_Controller_Action
{
    public function indexAction()
    {
        include dirname(dirname(__FILE__)) . '/views/index.php';
    }
}
?>

Zend Framework has a couple of special cases:

  1. The front-controller can be used by itself or with a Rewrite Router. On advice of Matthew Weier O'Phinney, I used the front-controller without the router, which slows things down.

  2. Zend Framework is still in heavy development; again, on advice of Matthew Weier O'Phinney, I added the "incubator" directory to the include_path to be sure the most-recent code is being used.

  3. The Zend_View class apparently has some issues on PHP 5.2.0 that preclude its use, so I just do a plain "include" to get the view script. This is much faster than using a separate View class, but in normal use you would have a Zend_View instance here instead.

Even with that special treatment, the respresentative result from three "ab" runs on Zend Framework 0.2.0 is the lowest of the bunch: 11 req/sec.

Document Path:          /zf/index
Document Length:        12 bytes
Requests per second:    11.17 [#/sec] (mean)
Time per request:       895.28 [ms] (mean)
Time per request:       89.53 [ms] (mean, across all concurrent requests)
Transfer rate:          2.66 [Kbytes/sec] received

"This Is Stupid And Doesn't Mean Anything!"

"There are three kinds of lies: lies, damn lies, and statistics." -- variously attributed to B. Disraeli, M. Twain, and others.

"A man's got to know his limitations." -- Dirty Harry Callahan

I can hear the objections now:

  • "Not realistic!"
  • "Not comprehensive!"
  • "Doesn't account for features that I like!"
  • "Who cares, I don't need that level of responsiveness!"
  • "Doesn't matter if Framework X is slower, I'm more productive with it!"

Yes, yes, you're all correct. But when you get to the point when you have to scale across multiple servers, and you're looking to squeeze out every possible request you can, it'll be good to know what the realistic top-limit is.

Last caveat: they say "your mileage may vary." In these cases, your mileage is almost certain to vary. The absolute numbers will be different from server to server. Data and output caching can help in many cases, but the moment you invoke the front-controller and page-controller, you're going to hit these relative top limits no matter how much partial caching you use:

                  Req/Sec    % Relative
Cake              17         154%
Solar             45         409%
Symfony (avg)     25         227%
Zend Framework    11         100% (baseline)

And again, it only gets slower from there; these numbers were for the simplest possible "Hello World!" application in each framework.

Appendix: What Classes Get Loaded?

John Herren in the comments below asks how many files are loaded each time; below I give the counts as reported by get_included_files(), as well as the classes used by each framework in the benchmark application.

Cake (26)

34 files total, including these classes:

  1. Object
  2. CakeSession
  3. Security
  4. NeatArray
  5. Inflector
  6. Configure
  7. Dispatcher
  8. Router
  9. Controller
  10. Component
  11. View
  12. Helper
  13. ConnectionManager
  14. DataSource
  15. DATABASE_CONFIG
  16. Model
  17. ClassRegistry
  18. AppModel
  19. HelloWorld
  20. AppController
  21. HelloWorldController
  22. SessionComponent
  23. DboSource
  24. DboMysql
  25. HtmlHelper
  26. SessionHelper

Solar

18 files total, including these classes:

  1. Solar
  2. Solar_Base
  3. Solar_Controller_Front
  4. Solar_Uri
  5. Solar_Uri_Action
  6. Solar_Request
  7. Solar_Controller_Page
  8. Solar_App_HelloMini
  9. Solar_Session
  10. Solar_View
  11. Solar_Struct
  12. Solar_View_Helper
  13. Solar_Class_Stack
  14. Solar_Path_Stack
  15. Solar_View_Helper_GetTextRaw

Symfony (43)

42 files total, including these classes:

  1. sfConfig
  2. sfToolkit
  3. Symfony
  4. sfConfigCache
  5. sfException
  6. sfComponent
  7. sfAction
  8. sfActions
  9. sfActionStack
  10. sfActionStackEntry
  11. sfContext
  12. sfController
  13. sfFilter
  14. sfExecutionFilter
  15. sfRenderingFilter
  16. sfFilterChain
  17. sfRequest
  18. sfResponse
  19. sfStorage
  20. sfUser
  21. sfView
  22. sfWebController
  23. sfFrontWebController
  24. sfWebRequest
  25. sfSessionStorage
  26. sfPHPView
  27. sfRouting
  28. sfDatabaseManager
  29. myFrontWebController
  30. myWebRequest
  31. sfParameterHolder
  32. sfWebResponse
  33. sfBasicSecurityUser
  34. myUser
  35. sfSecurityFilter
  36. sfBasicSecurityFilter
  37. worldActions
  38. sfCommonFilter
  39. sfFlashFilter
  40. sfValidatorManager
  41. sfOutputEscaper
  42. sfOutputEscaperGetterDecorator
  43. sfOutputEscaperArrayDecorator

Zend (28)

34 files total, including these classes:

  1. Zend
  2. Zend_Exception
  3. Zend_Registry_Exception
  4. Zend_Registry
  5. Zend_Controller_Front
  6. Zend_Controller_Exception
  7. Zend_Controller_Plugin_Abstract
  8. Zend_Controller_Request_Abstract
  9. Zend_Controller_Response_Abstract
  10. Zend_Controller_Plugin_Broker
  11. Zend_Controller_Router_Exception
  12. Zend_Controller_Request_Http
  13. Zend_Http_Exception
  14. Zend_Uri
  15. Zend_Uri_Exception
  16. Zend_Uri_Http
  17. Zend_Filter
  18. Zend_Filter_Exception
  19. Zend_Uri_Mailto
  20. Zend_Http_Request
  21. Zend_Controller_Request_Exception
  22. Zend_Controller_Router
  23. Zend_Controller_Dispatcher_Exception
  24. Zend_Controller_Action
  25. Zend_Controller_Action_Exception
  26. Zend_Controller_Dispatcher
  27. Zend_Controller_Response_Http
  28. IndexController

Belated Quick Hit: Solar 0.25.0 released

I released Solar version 0.25 a week ago today, but my workload has prevented me from blogging it (and from blogging other stuff too). No major changes, just minor bugfix and minor feature enhancements; you can view the change notes here.


Solar 0.24.0 Released

Last night, I released version 0.24.0 of Solar, the simple object library and application repository. Solar is a PHP 5 framework for rapid application development.

On an administrative note, Clay Loveless has set up a Trac installation for us, so now you can report bugs and request enhancements without having to join the Solar-Talk mailing list. You can keep up with the Trac issues via the Solar-Trac mailing list if you like.

You can read the change notes for a full run-down, but there are a few changes in particular that I want to highlight.

Solar_Request

In previous releases, the Solar arch-class had a series of methods to read from the superglobals: Solar::post(), for example, would read a $_POST key and return it (or a default value of your choosing if the key did not exist). Those methods and their related support methods have been removed from the arch-class and placed in their own class, the new Solar_Request.

For now, Solar_Request acts as a singleton, since it is tied back to the super globals. Future releases will convert it to a true standalone object, and classes that need access to the request environment will receive a dependency injection of a Solar_Request object.

Whereas you would have used Solar::post() in earlier versions of Solar, you now create a Solar_Request object and use the $request->post() method. Solar_Request provides access to these superglobals after some minimal filtering (essentially just stripping magic quotes):

  • get() -- $_GET
  • post() -- $_POST
  • cookie() -- $_COOKIE
  • env() -- $_ENV
  • server() -- $_SERVER
  • files() -- $_FILES
  • http() -- ...

Wait a minute, there's no "HTTP" superglobal! One of the things that Solar_Request does for you is some basic cleaning and normalizing of the $_SERVER['HTTP_*'] values into their own storage array, so you can ask for the HTTP headers directly. For example, to get the value of the "X-Requested-With" header, ask for $request->http(‘X-Requested-With').

Solar_Request also lets you check how the current request was made:

  • isGet() is true if this was a GET request
  • isPost() is true if this was a POST request
  • isPut() is true if this was a PUT request
  • isDelete() is true if this was a DELETE request
  • isXml() is true if this was an XMLHTTP request (Ajax!)

Many thanks to Clay Loveless for getting Solar_Request up and running.

Solar_Session

Similar to Solar_Request, Solar_Session (here) provides an interface to the $_SESSION superglobal. It also takes over for the previous Solar_Flash class by providing read-once "flash" support via the $_SESSION values.

In Solar, we aggressively segment the global name space so that different classes don't step on each other's toes. Solar_Session makes this segmentation easier, by providing a config key to tell the particular instance what class name space it should be working within the $_SESSION array.

Progressive Ajax Enhancement

It's not much, but I've added the tiniest bit of Scriptaculous enhancement to the reference application that comes with Solar, Solar_App_Bookmarks. In the "edit" view, instead of using a simple class to indicate success or failure of a save, I've put in a pair of effect calls:

// output a form from the "formdata" processor
echo $this->form(array('id' => 'form-bookmark'))
      ->auto($this->formdata)
      // ...
      ->fetch();

// add an effect for success message lists
$this->jsScriptaculous()->effect->highlight(
    "#form-bookmark ul.success",
    array(
        'duration' => 3,
        'endcolor' => '#aaaaff',
        'restorecolor' => true,
    )
);

// add an effect for failure message lists
$this->jsScriptaculous()->effect->highlight(
    "#form-bookmark ul.failure",
    array(
        'duration' => 3,
        'endcolor' => '#ffaaaa',
        'restorecolor' => true,
    )
);

That's all it takes. Again, Clay Loveless has done wonderful work with his JavaScript helpers for Solar.


Zend PHP 5 Certified

I just got a note from Dhwani Vahia that I passed the Zend certification exam for PHP 5. Woohoo! :-)

Many thanks to the folks at PHP Architect for making the test available free of charge at php|works. That's twice now I've been able to take it on their dime (although the test in Cancun last May was a lot more fun, for obvious reasons ;-).


AJAX Is Interesting, But The Basics Are Better

I had the good luck to share a cab back to the airport with Wez Furlong after php|works. Wez had presented a new talk about how to work with email properly (jokingly subtitled "Not PDO" by everyone there), and was curious to know how it was received. I replied that I liked it, but struggled for a moment on "why" ... and then it hit me: the reason I liked it was that it was about a fundamental operation that still seems to trip developers up regularly.

Although I told Cal Evans when he interviewed me that the next technology I'm really interested in is AJAX (particularly its implementation in Protaculous), I'm not as excited by it as everyone else seems to be. I think it's neat, but good grief, if wide swaths of developers can't do input filtering properly, what good can adding AJAX do? Get the basics right first, then you can do progressive enhancement as you go.



Teaser for php|works

I've noted before that I'll be presenting at php|works in a couple of days; it's in the 4-5pm slot on Wednesday (13 Sep). The synopsis is public, but I wanted to give a little teaser about the presentation, in hopes of drumming up greater attendance. The talk is titled "Organizing Your PHP Projects", and the subtitle is "Project Planning in One Lesson."

Fans of Henry Hazlitt's "Economics in One Lesson" will recognize the kind of talk I have in store, although I don't have the time available to expound in detail the way Hazlitt does. The talk will consist of a one-sentence lesson for you to remember when deciding how to organize your PHP project, whether it's a library, an application, a CMS, or a framework. I'll then explain how to apply the One Lesson in your project, and the various follow-on effects the One Lesson will have on your project organization.

Also, I can guarantee that I will be the only presenter to provide examples and analogies using such sources as livestock, Jimmy Hoffa, and the Mir space station in a relevant and entertaining fashion. ;-)

Finally, as part of the research for this talk, I examined 40+ public PHP projects, and found a few distinct organizational patterns that I'll give in some detail. Among other things, you will learn exactly what the difference is between a framework and a CMS, and how they got to be that way. Here's one of the core slides on that part of the talk:

See you at php|works!

UPDATE: (2006-09-14) The slides from my talk are now available as a PDF file. (I'll create an audio version of the presentation after I get back from the conference.)


Solar 0.23.0 Released

The new release is out; you can download it from the usual location. As always, you should read the full change notes.

This release of Solar includes new Prototype and Scriptaculous ("Protaculous" ;-) Ajax support in the form of view helpers, ext/json compliant JSON support, and a new plugin-aware Markdown engine. Read on for more.

Breaks

There is one backwards-compatibility break in this release. Previously, the Solar_View::partial() method would output the results of the partial template directly. Now it just returns the results. This means that you need to change all calls from $this->partial(...) to echo $this->partial(...). This change makes the partial() method act like all other view helpers.

Protaculous Ajax

Clay Loveless has been hard at work adding view helpers that interface to the Prototype and Script.aculo.us JavaScript libraries. (I have taken to calling this popular combination “Protaculous”. ;-)

These JavaScript packages provide all sorts of Ajaxy Web 2.0 buzzword-compliant goodness, but you generally need to access them in JavaScript directly. Clay’s view helpers for the Control and Effect libraries of Scriptaculous let you access them via PHP. The helpers keep track of what effects and requests you make, and load all the necessary files for you automatically.

Clay has built the helpers so they use “unobtrusive JavaScript”. This means that you address HTML elements using CSS selectors instead of using inline scripts on elements.

To show off some of the possibilities, Clay has added a new demo app under Solar_App_HelloAjax.

JSON Support

Clay Loveless has also added a new Solar_Json class for encoding and decoding JavaScript object notation; this helps when passing data via Ajax requests. (The work is based on code from Michal Migurski, Matt Knapp, and Brett Stimmerman, in their Services_JSON package.)

Although full JSON support will exist in a future version of PHP, this class will help users who need the functionality right now. (Clay has gone to a lot of effort to make Solar_Json behavior functionally identical to the upcoming PHP JSON extension.)

Many thanks, Clay, for your continuing efforts on this portion of Solar.

Markdown

Markdown is a simplifed syntax and text processor that converts structured text markup to XHTML, written by John Gruber. PHP-Markdown is a direct port of Gruber’s original Perl code to PHP 4 (and non-strict PHP 5) by Michel Fortin. Now the same text-processing capability exists in Solar as Solar_Markdown.

The major benefit of the Solar version over previous implementations is that it is “plugin aware”, much like the PEAR Text_Wiki package. Each syntax rule is a separate class; you can mix and match syntax rules to fit your particular needs without having to rewrite the entire processing and rendering engine.

Solar now comes with three Markdown rule sets:

  • Solar_Markdown proper, an implementation of the original markup rules in the Perl and PHP versions. It provides for headers, lists, strong and emphasis, links and inline URIs, code examples, and much more. In general, it leaves HTML in place, so it may not be suitable for untrusted or anonymous environments.

  • Solar_Markdown_Extra, an implementation of Michel Fortin’s PHP-Markdown Extra that extends the basic Markdown syntax to support tables, definition lists, and id-able headers.

  • Solar_Markdown_Wiki, which provides Mediawiki-style page links, interwiki links, and code documentation constructs, in addition to tables and definitions lists from the Solar_Markdown_Extra rules. It is very aggressive about escaping HTML for output, which makes it more suitable for untrusted environments.


Birthday Dinner

My good friend Benito recently treated me to a spectacular birthday dinner (a week in advance of the occasion I might add). Dude, thanks again; that ribeye, and the fresh Brandy Boy tomato off the vine, was just stunning. And the wine! Mmm mmm.

This reminds me of one of my favorite dumb jokes.

Q: When's your birthday? A: August 8.

Q: No, I mean, what year? A: Every year. (ba-dum ching ;-)


Solar and TypeKey Authentication

As astute observers will have realized, the most-recent release of Solar had a new authentication adapter in it: Solar_Auth_Adapter_Typekey.

This particular class comes from work I'm doing at my current employer, Mashery. The Mashery folks are very open-source friendly, and approve of contributing back to useful projects, so I want to go out of my way and thank Mashery explicitly for this. (Thanks Mashery!)

Likewise, the core code in Solar_Auth_Adapter_Typekey comes directly from the PEAR Auth_TypeKey work by Daiji Hirata (which strangely has not been accepted yet at PEAR; their loss is Solar's gain). Thanks, Daiji, for contributing this code to Solar!

In this article, I'm going to talk a bit about Solar authentication in general, and then TypeKey authentication in specific.

Solar Authentication

Solar uses a facade class for authenticating users, Solar_Auth. That class acts as an interface to one of many underlying adapter classes that point to a backend storage service, such as an Apache htpasswd file, an SQL database table, an LDAP server, and so on. Anyone familiar with PEAR Auth will be familiar with this style of operation.

Let's instantiate an authentication object that uses the "htaccess" adapter, and start an authenticated session:

<?php
    $config = array(
        'adapter' => 'Solar_Auth_Adapter_Htaccess',
        'config' => array(
            'file' => '/path/to/htaccess.file',
        ),
    );

    $auth = Solar::factory('Solar_Auth', $config);

    $auth->start();
?>

Note: if you use the Solar.config.php file to set defaults for the Solar_Auth and Solar_Auth_Adapter_Htaccess classes, you can skip setting $config altogether and just call Solar::factory('Solar_Auth').

The start() call does a lot of work for you. By default, it checks $_POST for a series of variables indicating a login or logout attempt (and processes them according to the backend adapter). It also updates the idle and expire times of any currently authenticated user.

Note: the authentication process is highly configurable; at construction time, you can specify to use $_GET or $_POST, what variable indicates a login or logout attempt, the variable names for handle and passwd, the idle and expire times, and much more. See the page on config keys here.

You can check current authentication information this way:

<?php
    $auth = Solar::factory('Solar_Auth');
    $auth->start();

    // is the current user authenticated?
    $auth->isValid();

    // what is the current status code?
    //
    // 'ANON'    : The user has not attempted to authenticate
    // 'WRONG'   : The user attempted authentication but failed
    // 'VALID'   : The user is authenticated and has not timed out
    // 'IDLED'   : The authenticated user has been idle for too long
    // 'EXPIRED' : The max time for authentication has expired
    //
    $auth->status;

    // what's the current username?
    $auth->handle;

    // some adapters report the user's email address,
    // display name ("moniker"), and/or website URI.
    $auth->email;
    $auth->moniker;
    $auth->uri;

    // when did the user initally sign in, and when was the
    // last activity in this session?  reported as a unix
    // timestamp.
    $auth->initial;
    $auth->active;
?>

Of course, the user has to type this information into a form somewhere on your website. Here's a very simple example form:

<form action="index.php" method="post">
    <p>Username: <input type="text" name="handle" /></p>
    <p>Password: <input type="password" name="passwd" /></p>
    <p><input type="submit" name="submit" value="Sign In"/></p>

</form>

You can see a more complex authentication form that shows current status, handle, etc. here.

About TypeKey Authentication

TypeKey is an authentication service provided by the folks at Six Apart. The idea is that instead of signing up for a user account at every website you want to authenticate to, you can sign up for one account at TypeKey. Then other sites can check if you have been authenticated to a TypeKey account. This has a lot of obvious benefits for users (like having to remember only one password) and site owners (not having to maintain accounts).

The problem with TypeKey authentication is that, as far as site owners is concerned, it's vastly different from "standard" handle + passwd authentication. Instead of the user typing their username and password credentials into your site, you just show a link to the TypeKey website. The user presents his credentials to TypeKey, not to your site; TypeKey then redirects to your site again and sends a pack of information for you to verify.

You can read more about the authentication process at the TypeKey API page; you will see that it's a lot more complicated that just checking a username and password against a data store. This leads to an interesting problem for adapter-based authentication systems; it means that you have to abstract two different styles of authentication algorithms, not just one. Lucky for us, the Solar_Auth system is abstracted well enough to handle this. :-)

Pre-Requisities

First, because of the DSA encryption algorithm used in processing TypeKey authentication signatures, PHP has to have been compiled with either bcmath (--enable-bcmath) or the GMP extension (--with-gmp). These extensions provide the large-integer math functions necessary for decryption processing.

Next, you need a TypeKey account and the related "token" from that account to identify your website (the one where users will return to after authenticating with TypeKey).

And of course, you need the latest release of Solar.

TypeKey Login Link

Instead of a "normal" login form with username and password fields, you need to present a link to TypeKey for your users to click on. The link must include your TypeKey token and the URI of the page you want to return to as GET vars. Here's a detailed code example to show you what goes into building the link:

<?php
    $token = "your_typekey_token";

    $href = "https://www.typekey.com:443/t/typekey/login"
          . "?t=" . urlencode($token)
          . "&return=" . urlencode($_SERVER['REQUEST_URI']);

    echo '<a href="' . $href . '">Sign In</a>';
?>

TypeKey Adapter Setup

Now we need to tell Solar_Auth to expect TypeKey logins; we do this using the TypeKey adpater class. Then simply call the start() method.

<?php
    $config = array(
        'adapter' => 'Solar_Auth_Adapter_Typekey',
        'config'  => array(
            'token' => 'your_typekey_token',
        ),
    );

    $auth = Solar::factory('Solar_Auth', $config);

    $auth->start();
?>

That's all there is to it! Using the TypeKey adapter, Solar_Auth will recognize all TypeKey login attempts and track idle/expire times just as with other authentication sources. The adapter does all the work for you.

Again, my thanks to Daiji Hirata for his work on the core code for this adapter, and to Mashery for allowing me to include it as part of Solar.