"Organizing Your PHP Project" Slides Available
The slides from my php|works talk are now available as a PDF file. I'm going to try to create an audio version after I get back from the conference.
Don't listen to the crowd, they say "jump."
The slides from my php|works talk are now available as a PDF file. I'm going to try to create an audio version after I get back from the conference.
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.)
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.
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.
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
.
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 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.
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 ;-)
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 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 callSolar::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.
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. :-)
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.
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>';
?>
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.
Clay Loveless has started adding Prototype/Scriptaculous support in Solar as a series of view helpers. Although I've been calling it this in my notes for months, I wanted to share with the world my shorthand for this library combination: "Protaculous." That is all. :-)
My first article for php|a, Introduction to Solar, is online. Give it a read, let me know what you think.
I released Solar 0.21.0 yesterday, and a quick followup 0.22.0 today. The rest of this entry covers highlights for changes in both versions. The main highlights are three new classes (Solar_Struct, Solar_Sql_Row, and Solar_Sql_Rowset) along with a backwards-compatibility break to how classes store their default configuration values.
There is one really big change to the configuration system. It should not require more than a search-and-replace in your files, but it's still a big deal.
The change is that you now put the default config values for your class in a property called $_{Class_Name} instead of in $_config. (Keep using the $_config property for reading configs, but put the defaults in $_{Class_Name}.) For example ...
Previously, you would do this to define a default config key and value:
<?php
// parent class
class Vendor_Foo extends Solar_Base {
protected $_config = array('foo' => 'bar');
public function getConfig()
{
Solar::dump($this->_config);
}
}
// child class
class Vendor_Foo_Baz extends Vendor_Foo {
protected $_config = array('baz' => 'dib');
}
?>
This was great for reading from the config file, but it meant that the parent class config keys were not inherited by the child class. The child $_config overrides the parent $_config entirely; you would have to put some checking code in your parent constructor to make sure all the keys were there. Thus, a call to Vendor_Foo_Baz::getConfig() would show only the 'baz' key, and not the combination of the parent 'foo' key and the child 'baz' key.
In this release, we change from $_config to $_{Class_Name} for default config values.
<?php
// parent class
class Vendor_Foo {
protected $_Vendor_Foo = array('foo' => 'bar');
public function getConfig()
{
Solar::dump($this->_config);
}
}
// child class
class Vendor_Foo_Baz extends Vendor_Foo {
protected $_Vendor_Foo_Baz = array('baz' => 'dib');
}
?>
(Note from getConfig() that the $_config property does not go away, you just don't put anything in it yourself.)
In the new system, Solar_Base collects all the $_{Class_Name} arrays and merges them all into $_config for you. This means that child classes inherit their parent config keys and values unless you override them in the child class.
Basically, you should keep reading from $_config when getting config values in your class methods. But for property definitions, use $_{Class_Name} for the default config keys and values.
The new Solar_Struct class is an ideological cousin to Matthew Weier O'Phinney's Phly_Hash class (originally Phly_Struct).
When you build a struct instance, you give it a series of keys and values as an array; those keys become properties in the struct object, and the values are used as the property values.
<?php
$data = array(
'foo' => 'bar',
'baz' => 'dib',
'zim' => 'gir',
);
$struct = Solar::factory(
'Solar_Struct',
array('data' => $data)
);
?>
Using this class, you can access data using both array notation ($foo['bar']) and object notation ($foo->bar). This helps with moving data among form objects, view helpers, SQL objects, etc.
<?php
echo $struct['foo']; // 'bar'
echo $struct->foo; // 'bar'
?>
The struct object implements ArrayAccess, Countable, and Iterator, so you can treat it just like an array in many cases.
<?php
echo count($struct); // 3
foreach ($struct as $key => $val) {
echo "$key=$val ";
} // foo=bar baz=dib zim=gir
?>
One problem with Solar_Struct is that casting the object to an array will not reveal the data; you'll get an empty array. Instead, you should use the toArray() method to get a copy of the object data.
<?php
$array = (array) $struct; // $struct = array();
$array = $struct->toArray(); // $struct = array(
// 'foo' => 'bar',
// 'baz' => 'dib',
// 'zim' => 'gir',
// );
?>
One other problem is that double-quote interpolation isn't as intuitive. If you want to use a struct property in double-quotes, you should wrap it in curly braces.
<?php
// won't work right, will show "Object #23->foo"
echo "Struct foo is $struct->foo";
// will work as expected
echo "Struct foo is {$struct->foo}";
?>
The new Solar_Sql_Row class is extended from Solar_Struct, and adds a new method called save().
From now on, any time you call select('row') or fetch() from an SQL class, you're going to get a Solar_Sql_Row object instead of an array. Because Solar_Struct acts like both an array and an object, you shouldn't have to change any of your code to use the new return type.
If you want to, you can start using object notation with rows. For example, instead of $row['id'] you can use $row->id (which is a lot easier to read in most cases).
An additional benefit is that Solar_Sql_Row has a save() method that can be tied back to a table object. Previously, if you wanted to insert or update a row, you had to pass it as an array into its related table object:
<?php
$row['foo'] = "bar";
$row['baz'] = "dib";
$table->save($row);
?>
Now you can save the row directly:
<?php
$row->foo = "bar";
$row->baz = "dib";
$row->save();
?>
Because it is tied back to the source table object, it follows all the validation and insert/update rules of that object, so you don't need to duplicate them just for the row object.
The new Solar_Sql_Rowset class also extends from Solar_Struct, but it works as a collection of Solar_Sql_Row objects. You can iterate through it with foreach() and get one row at a time, working with each and calling save() as you go.
From now on, any time you call select('all') or fetchAll() from an SQL class, you're going to get a Solar_Sql_Rowset object instead of an array. Because Solar_Struct acts like both an array and an object, you shouldn't have to change any of your code to use the new return type.
With Solar_Sql_Row and Solar_Sql_Rowset in place, Solar_Sql_Table now returns these when you call fetch() and fetchAll(). Even better, you can set the new properties $_row_class and $_all_class to a custom class descended from a Row or Rowset; if you do, the fetch*() methods will use those classes as the return objects. This paves the way for the beginning of something like a formal ActiveRecord pattern as described by Martin Fowler (as opposed to the one popularized by Rails.)
Rodrigo Moraes just set up a web interface to search the Solar-Talk mailing list. From his announcement on Solar-Talk:
The Solar-talk archives are now searchable:
It is a very rudimentary mini-app using Solar & Zend_Search_Lucene for the indexing. Some features are still not implemented like pagination and exact phrase match. Messages will be indexed once a day via cron, starting from today at night. Currently it is indexed until last Friday, July 14.
I needed this. Hope others will also find it useful. :-)
Thanks Rodrigo!
(I'm stuck at Sky Harbor in Phoenix because of a delayed flight, but they have free wireless access, so I might as well make a blog post while I'm here. ;-)
Did you know the following is not valid XHTML?
<form action="index.php">
<input type="hidden" name="foo" value="bar" />
</form>
It appears that an input tag is not allowed to appear directly within a form tag. See detailed information here and note that <input> is not a valid child element of <form>. To be valid XHTML, the input tag must be wrapped by another block-level element, such as <p> or <fieldset>.
I can safely say I have always done this the wrong way. ;-) For example, the Solar_View_Helper_Form class puts all hidden fields at the top of the form, and all other fields in a <dl>...</dl> block. As a result, the hidden fields cause the page to be invalid (but the others are valid because they are wrapped properly).
Solution? Wrap the hidden fields in a <fieldset style="display: none">...</fieldset> tag. Now the page validates, and the hidden fieldset doesn't alter the page layout at all. This change has been committed and will be available in the next release of Solar.
(UPDATE: Fixed the link to the XHTML spec; thanks, David Rodger.)