November 2005 Archives

Document Modeling with Bricolage

Previous Perl.com articles have reviewed where Bricolage fits into the universe of content management systems and worked through Bricolage installation and Bricolage configuration. Now it's time to go through the steps required to model the structure of an existing web page in Bricolage. Part of the motivation for the redesign of the Bricolage website last summer was to create good examples of document types and templates for use in Bricolage itself. You can take advantage of that work by analyzing a page on the current Bricolage site to determine how to break it down into its basic elements.

First, here's a brief introduction to document types in Bricolage.

The Elements of Bricolage Stories

Bricolage features two types of documents: stories and media. Stories contain text content and metadata; media are just like stories, but can have a single media file associated with them (an image file, movie, sound file, PDF, etc.). Whether a document is a story document or a media document, elements define its structure.

Elements are the basic building blocks of all documents in Bricolage. There are two types of elements: container elements and field elements. Container elements can contain any number of fields and other container elements. Fields, on the other hand, contain text content. Bricolage presents fields as standard HTML fields such as text, textarea, pulldown, radio, etc.--even a date and time widget. In a document model, one container element is the top element and it, along with all of the fields and subelements it contains, constitutes the structural model for the content of documents based on it.

The important point here is that Bricolage encourages a highly structured model for your documents; documents based on the models are thus structurally consistent. It also makes it easy to write incredibly flexible templates to output content in a variety of formats. (The next article in this series will cover Bricolage templating.) Document models can also be deeply hierarchical, to whatever extent is necessary to accurately model the structure of the documents being managed. Be careful, though, because if the model has too many levels of hierarchy, it will be more difficult for users to conceptualize when editing documents, as well as more work to drill down into deeply nested elements in the Bricolage user interface.

Document Analysis

Bricolage.cc Article Screenshot
Figure 1. A screenshot of the article being analyzed

Document analysis is the process of analyzing the layout of a document and breaking it down into its basic elements. Examining a page on a website, you must determine how all of the basic parts fit together and in what hierarchy, so that you can model the element structure necessary to accurately represent the document in Bricolage. That is, determine the container elements and fields that would be necessary to accurately recreate the document in Bricolage.

For the sake of this article, consider the structure of a typical page on the Bricolage website, because it makes a nice representative sample of the elements articles on the site will likely need (see Figure 1).

Identifying Content

The first thing to do is to determine what part of that page constitutes content and what does not. The term "content" here distinguishes those parts of the page that are important to the document itself, as opposed to the site overall or to a section of the site. For example, the banner at the top of the page appears on every page on the site; it is not specific to this document, nor is it significant to the document's contents. Likewise, the footer section is global to the site and contributes nothing to the document. The Recent News list in the right-hand column also has nothing to do with the contents of the article, it being a simple list of the five most recent articles published on the entire site.

These other components are includes, because they're included on many pages--or even on every page of the site. It also doesn't hurt that web servers generally pull them into the layout via a server-side include technology (such as mod_include, HTML::Mason, PHP, or JSP). Because they're not significant to the content of the document, you can ignore them for the rest of this analysis. Figure 2 depicts everything that remains.

Article Content Only
Figure 2. The important part of the document for the purposes of analysis: the actual content

Defining the Top Element

Having isolated the content of the page, you can start breaking its content down into its component parts. First, give the document type a name; this name will also be the name of the top-level element. Because this is an article in the Bricolage website, this is simple: it's Article.

With that out of the way, it's time to pick out the field subelements of the document element. As fields are meaningful blocks of text, this is generally simple to do: they're headlines, paragraphs, subheads, and the like. Figure 2 indeed shows meaningful blocks of text:

Headline
This is the title of the article. In this case, it's "David Wheeler Interviewed on Online Tonight."
Dateline
This is the date for the article, here "2002.12.18."
Paragraph
These blocks of text make up the bulk of the content of the article. The first paragraph starts with "Bricolage maintainer and lead developer David Wheeler appeared on the Online Tonight with David Lawrence radio show."
Header
Section headers break up the content into sections, such as "How it All Started" and "Bricolage vs. Blogging Tools."

Identifying Subelements

Related Image
Figure 3. The components making up a "Related Image" element

Pretty simple, right? Well the interesting part comes when you identify the container subelements of the layout. The way to do so is to look for areas where content has been logically grouped together as a unit; for instance, to combine an image and its caption. Such is exactly the case with the picture of David Lawrence. Call this container element a "Related Image," because it creates a link to an image document that's related to the current article content. Figure 3 illustrates the breakdown of the newly identified element: It has a link to a related media document and it has an "Alt Text" field and a "Caption" field.

Related Audio
Figure 4. The pieces of the "Related Audio" element include a tooltip (not shown)

Another clearly grouped collection of content is the box entitled "David Wheeler Takes to the Airwaves." This one has a link to an audio document and a description of its contents. It also has a speaker icon, but this isn't really content; it's more a hint to the viewer as to what she's linking to. That is, it's not content, but presentation. It adds no semantic meaning to the article. To parallel the "Related Image" subelement, call this one "Related Audio." Figure 4 highlights its subelements.

Pull Quote
Figure 5. The "Pull Quote" element has a Paragraph field and an attribution field

Finally, there's one last subelement, the "Pull Quote." It simply groups together a quotation paragraph and an attribution. See Figure 5.

A Note on Layout

Ideally, your analysis is now complete, as you've successfully identified all of the elements of our Article document type using this layout. There is one more thing to consider, though: the placement of the Related Audio, Related Image, and Pull Quote subelements.

The ideal in content management is to separate content from presentation. You've successfully done so here: you've identified the parts of an article (paragraphs, pull quotes, headers, etc.) without specifying anything about the layout (colors, type faces, spacing, placement, etc.). In looking at this example more closely, note that the related audio box is on the right, the related image is on the left, and the pull quote is on the right again. The question is, when a template formats this document for output, how will it determine what goes on the left and what goes on the right?

You might guess that related audio always goes on the right, related images always on the left, and pull quotes always on the right--but if an article has no related audio or pull quotes and five different related images, the layout would end up looking lopsided. Your editors (those who have the least bit of design sensibility) won't like that.

Another way to handle it is to come up with an algorithm to place these suckers. You might decide, for example, that between the three boxed subelements (Related Audio, Related Image, and Pull Quote), placement might rotate back and forth between the right and the left, or perhaps one on the right, two on the left, two on the right, etc. Of course, the problem with this approach is that, depending on how algorithmic you need it to be, the algorithm might be tricky to write. Plus, your editors still might not like it, and ask you, "Why can't I just tell it to put it on the left?"

Why not? The separation of content from presentation is the ideal, yes, but it's not always practical. Plus, sometimes, you can give people the sort of control they need without unduly sacrificing the separation. In this case, solve the problem simply by adding an extra field to each of the subelements: "Position," with two possible values, "right" and "left." Now, when an editor adds a pull quote to a document, she can specify the placement in such a way that the template can easily place it. As for the separation of content from presentation: this information is more metadata about the element than content, and you can ignore it when you need to create purely semantic representations (such as XML).

The upshot is that during a document analysis, you must pay careful attention to the semantically meaningful parts of a document and model the appropriate elements, and then weigh the importance of providing additional, non-semantic presentation metadata fields to give editors the control they need to get their jobs done. Go for the middle road and keep everyone happy.

Table 1
Table 1. The completed document model. In Bricolage, all subelements are optional, meaning that users can add any number of Related Image, Related Audio, and Pull Quote elements to a document.

The Document Model

Having performed your document analysis (and of course, in practice, you should examine several different pages on a site to ensure that you don't miss any elements that are present in some articles but not others), you can now synthesize the breakdown of the elements into a comprehensive model. In practice, that's pretty simple. See Table 1 for the complete representation. Based on the analysis, you've decided on the types of the fields (text, textarea, pulldown, etc.), whether they're a required part of the element they're in, and whether they're repeatable. For example, the headline of the article is required, but not repeatable, because all articles must have a headline but not more than one headline. Paragraphs, on the other hand, are not required, but there can be more than one. You can also say that paragraphs are required and repeatable, in which case at least one would need to be in the element. When doing your own document analysis, take the path that makes the most sense to you.

Container elements, on the other hand, currently have no such occurrence specification. You can add any number of any of the defined subelements to a document, including none. We may add occurrence specification for container elements in a future version of Bricolage.

This example is pretty simple. However, the Article document type for the Bricolage website is quite a bit more complex. See the complete model of the Article document type. In fact, the Bricolage site contains a graphical representation of all of the Bricolage website document models. (I used the special Bricolage introspection template to generate the tables displaying the models.)

Element Administration

Navigate to the Element Manager
Figure 6. Navigate to the Element Type Manager and the Element Manager to administer elements

The next step is to model, in Bricolage, the elements you've discovered. There are two administrative tools to use to create the elements: The Element Type Manager and the Element Manager. To access these managers, use the Bricolage side menus (see Figure 6) to navigate to ADMIN -> PUBLISHING -> and select the appropriate manager. You will see a search interface to search for and edit element or element-type objects. Clicking the Edit link for an element type, for example, brings up the Element Type Profile, where you can edit the attributes of the element type.

Element Types

Each element in Bricolage has an associated element type that defines a few attributes that its associated element share. There are three different kinds of element types: story element types, which define the structure of story documents; media element types, which define the structure of media documents; and subelement element types, which define the structure of container subelements. See Figure 7 for a snippet of the element type interface. The attributes you can set on element types are:

Stories Element Type
Figure 7. A segment of an element type profile called "Story"

Page
Indicates whether an element is a page. Sometimes documents need to have multiple pages through which readers can navigate. You may recognize multi-page documents from Perl.com, where you navigate from page to page via Next Page and Previous Page links.
Fixed
A fixed document is typically one that does not have the date as a part of its URI. Output channels defined in Bricolage specify two URI formats for the documents published to them. One of those formats is the "fixed URI format." If a story based on an element has the "Fixed" checkbox checked in its element type, the story uses the "fixed URI format." Otherwise, it'll use the non-fixed URI format. (The next article will cover output channels in detail.)
Related Story
Elements associated with an element type with the Related Story checkbox checked can have an associated story document. This is useful for creating lists of stories related to the current story, for example.
Related Media
The Related Media checkbox is just like the Related Story checkbox, except that it allows the creation of a relationship to a media document rather than to a story document. The Related Image and Related Audio elements depend on this checkbox being checked.
Media Type
This applies only to media element types--that is, elements that define the structure of media documents. This attribute is a pulldown list, featuring the options Image, Video, Audio, or Other. Of these options, only Image has any effect; image elements automatically have fields associated with them for height, width, color depth, and a few others. Bricolage automatically populates these fields when you upload an image file.

Given how element types work, the Article document type requires the following element types:

Audio
There are some media documents to manage. The first is Audio, required to manage the audio recordings associated with the Related Audio subelement. The Audio element type is a media element type with its media type set to Audio, surprisingly enough.
Image
The Image element type sets attributes for image document types. It is just like the Audio element type, except that it has its media type set to Image instead of Audio. Go figure.
Story
This defines the shared attributes of story elements, of which the Article element is one. It does not use a fixed URL (so that the article's cover date shows up in the URI), and has neither a related story nor related media. The Page attribute is irrelevant to this element type.
Subelement
This simple element type defines generic subelements. There are no special attributes of these subelements, so don't check any. The Pull Quote subelement is an example: it's not a page, and it doesn't have an associated media document or story document.
Related Media
This subelement element type defines elements that have media files associated with them. It has its Related Media checkbox checked, and will be used to define Related Audio and Related Image elements.

Creating the Related Image element
Figure 8. Creating the Related Image element

Elements

Now it's time for the building blocks. You defined several elements in your analysis of the Bricolage.cc page. Now you need to model them in Bricolage, basing each on an element type. When administering elements, you typically first create those farthest down the hierarchy, because you'll need to have them available to associate when you create those farther up. Start with the Pull Quote, Related Image, and Related Audio elements, and then create the Article story type element.

First create the Related Image element (Figure 8). The Key Name field uniquely identifies the element throughout Bricolage, while the burner determines the kind of templates that will be used to format Related Image elements. Be sure to select the Related Media element type! This is useful for many things, including giving templates the ability to easily get a handle on an element, as well as providing the file name for templates based on this element. It's generally a good idea to make the key name the same as the element name, but lowercased and with spaces replaced with underscores (uppercase letters and spaces are not allowed in key names).

The Burner pulldown list allows selection of the burner that will be used to format the element. As mentioned in the Bricolage introduction article, Bricolage currently supports three templating systems for formatting content: Mason, Template Toolkit, and HTML::Template (but with PHP support arriving in the forthcoming Bricolage 1.10, due out in September; stay tuned!). The Bricolage objects that push stories through the templates in a particular output channel are burners, ostensibly because they "burn" files to disk (but rumor has it that the name really came about when the original developer of the Mason burner wanted to write a burn_one() method). I've selected Mason for this example because all of the Bricolage site templates use Mason.

Element custom field definition
Figure 9. The fields subelements defined for the Related Image element

Click the Next button to create the Related Image element. Now you need to add any necessary subelements and fields. Look back at the model in Table 1: the Related Image element has no subelements, but it does have a few fields. Figure 9 pulls out the relevant part of the element profile. (Click an Edit link to edit an individual field element definition, or use the Add New Field section to add new fields to the element.) Note that you've already added the necessary fields. Should you decide that they should display in a different order, simply change the values of the numbered fields (changing one will cause the others to renumber automatically). You can also edit or delete each one to change its values.

The Add New Field section allows you to define new fields for the element, selecting from several available types (text, textarea, pulldown, date, etc.) and filling in the appropriate fields. Each option features different fields to fill in, but the important ones are:

Key Name
This field uniquely identifies the field among all the fields in the container element. While no field in a single element can have the same key name, a field in one element can have the same key name as a field in another element. Like container element key names, they must be lowercase and have no spaces. Again, I tend to use a key name that is the same as the name except for those two rules.
Label
This is the name of the field. A field can have the same name as another field, but that would be silly, now wouldn't it?
Default Value
The default value for a field when adding a new one to an element. For example, a URL field might have a default value of "http://" as a sort of reminder to editors as to what belongs in the field. It might also make sense to use the most common value for the default (such as with the Position field, where the default is "left").
Size
The size of the field in the display. Text fields use this value to set the width of the field (in characters), while Select fields use it to set the number of values to be displayed in a scrollable select field. The textarea field has Rows and Columns fields, instead, to indicate the size at which to display the field in the interface.
Maximum Size
The maximum size of the allowed value. This option limits the length of a text or textarea value to a certain number of letters.
Required
Indicates whether, when adding a new element, the field will be included in the element or the editor needs to add by the editor. The "required" moniker is unfortunate in this case, because it does not mean that the field is required to have a value, only that it's required to be present in the user interface.
Repeatable
Indicates whether or not a field is repeatable. For example, a paragraph field would be repeatable, because we want an editor to be able to add more than one paragraph to a story.
Position
Indicates the order in which the field appears in the user interface. This value affects only required fields, and causes all required fields to be displayed in the order indicated. This does not prevent an editor from reordering fields, however.
Options
This field allows you to specify a range of values for a field. It applies only to Radio Button, Pulldown, and Select fields. Each value goes on a single line, with the option first and then an optional label, separated by a comma.
Allow Multiple
This checkbox is specific to the Select field. When checked, users can select more than one value.

You may edit each of these fields, with the exception of the key name, by clicking its Edit link.

Following the model (Table 1), creating the necessary fields for the Related Audio element becomes simple. The Title, Link Text, and Tooltip fields are simple non-repeating text fields; the Description is a non-repeating textarea field; and the Position field is a non-repeating pulldown field. It's fine to accept the default values for the other fields (size, columns, rows, max size, etc.). For the Position field, however, add two values to the Options field:

left,Left
right,Right

Also set the default value. I've opted for "left" (and, indeed, that's what shows up as the default value for the field in Figure 9), but you could also set it to "right."

Creating the Related Audio and Pull Quote elements is much the same. The only difference is in the fields included and in the selection of an element type (Related Audio is a Related Media type, like Related Image, but Pull Quote is a Subelement).

Now, the Article element--the element that determines the structure of all articles, and therefore defines the Article document type--is a bit different. Defining its fields is of course the same, but you actually have modeled more fields than you need. You see, all documents in Bricolage have a Title field and a Cover Date. You can use these in place of custom Headline and Dateline fields; therefore, you don't have to define them. What's left, Paragraph and Header, define in the same way as fields in all elements.

Subelements of Article
Figure 10. The subelements of the Article element. Click the Add Subelement button to search for other elements and add them to Article

Defining subelements is almost the same, although the other elements had no subelements of their own. Figure 10 shows the Existing Subelements section of the element profile. In this example, the Related Audio and Related Image subelements have already been associated with the Article element, but you still need to add the Pull Quote element. Click the Add Subelement button to bring up a page very similar to the element manager: there's a search box, and you can view the results of your search. Unlike the element manager, however, this interface displays only subelements (that is, those elements that are associated with subelement element types, not story or media element types). Type in and search on the string "pull", and then when you get the search results, select the Add to Element checkbox, and click the Add Elements button. Figure 11 illustrates this process.

Adding the Pull Quote subelement
Figure 11. Use the search field to find the element you want, check the "Add to Element" checkbox, and click the Add Elements button.

Now the Article document type is complete! Well, almost. I've left out that there are other fields to fill in for story type and media type elements: sites and output channels. Because Bricolage is designed to manage multiple sites, each story or media type element must be associated with one or more sites. Each site associated with the element can create documents based on the element. By default, Bricolage has only one Site, Default, so you can just leave it associated with the Article element (if your installation has only one site, then that one site will be associated with all new story and media type elements).

As for output channels, each site has a primary output channel associated with the element. You can add extra output channels as needed. Bricolage has a single output channel by default, called Web, and you can leave this as the selected value. Again, the next article will cover output channels in more depth.

Now the Article document type is complete, and you're ready to start creating documents based on it.

Categories

There is one final topic to discuss before you can create a story document based on the Article document type: categories. Categories are hierarchically organized content locations. Think of them as similar to a file system of directories (or folders, for you Mac fans out there). Indeed, categories in Bricolage are used in the URIs of story and media documents, so the translation is close (modulo date parts and slugs). They are also used to categorize content, so it makes sense to create category hierarchies that make sense for your site.

Creating a new category
Figure 12. Create new categories starting with top-level categories added as subcategories to the root category, "/"

For example, if your site were a newspaper featuring movie and book reviews, you might want to create a category named Reviews as a subcategory of the root category ("/"), and then subcategories of the Reviews category named Books and Movies. The full URIs for these categories would be /reviews, /reviews/books, and /reviews/movies, respectively.

To create categories for your site, navigate to the Categories manager under ADMIN -> PUBLISHING and click "Add a New Category" for each category you wish to add. The root category already exists for each site, so start by creating your top-level categories and make your way down. See Figure 12 for an example creating a top-level Reviews category.

Navigating to the Story workflow
Figure 13. Navigate to the Story workflow and click New Story to create and edit a new story

Document Editing

Having created the categories you need, you can create a story document based on the Article story type. Open the Story workflow in the side navigation and select New Story (Figure 13) to bring up the New Story screen shown in Figure 14. Enter a title and slug. A slug is a very short hint (one or two words, separated by an underscore or dash) as to the contents of the story. Generally, the slug should complement the category selection rather than repeat it. For example, if you were creating a review of The Princess Bride in your /reviews/books category, you might make the slug "princess_bride." Select the Article story type, and cover date and click the Create button. This action creates a new story in the Story workflow and puts it on that workflow's "start desk," in this case the Edit desk.

Creating a new story
Figure 14. Create a new story based on the Article story type. Be sure to enter a meaningful slug and cover date, and put the article in the relevant category

Editing story content
Figure 15. Add fields and elements to the story in the Content section of the story profile

The new story will then open in the story profile. The first section of the profile, Properties, mostly covers the same data points as the New Story screen. It's the second section, Content, that is most interesting (Figure 15). The bottom of this section has an Add Element button and a select list. From this list, you can select the fields or container subelements you'd like to add to the story. After adding a field, you can edit it, as with the Teaser and Long Teaser fields in Figure 15.

Notice that the names of the elements available in the Add Element select list is identical to the names of the container elements and fields associated with your modeled Article story type element. Because these are the only content containers that editors can add to a story, the structural integrity of the story strictly adheres to the model you created. The advantage is that the structure of the stories to be published to the site will be consistent across stories. This approach enables the maintenance of document standards and styles across a site, which can be very important in a large organization with thousands of pages of content to maintain. It also allows for simple yet extremely flexible templating, so that you can output documents in a variety of formats (XHTML, RSS, PDF, etc.) while maintaining identity and design standards.

Why are container elements and fields all listed together in one select list? Because as an editor works on a story, she doesn't need to think in great detail about what to add. It's unnatural for a writer to have to stop and think, "Do I want to add a container element or a field?" as she decides from which list to select. She only has to think, "I need to add a paragraph" or "I think a pull quote would work well here." The difference is important to us a document modelers and, later, as template developers, but for editors, the distinction is irrelevant.

Editing the Related Image subelement
Figure 16. Edit the fields of the Related Image subelement. Click the Edit button in the Related Media section to search for an image document to relate to this element

If you elect to add a subelement, say by selecting the Related Image option, you will then see the profile for that element. Figure 16 illustrates the Related Image element profile. Note how the required fields for this element (Title, Alt Text, Position, and Caption) are in the profile. Furthermore, because this is a related media element, there's an extra section called, curiously enough, Related Media. By clicking the Edit button, you can search your existing media documents to create a relationship, much as you searched for subelements to add to an element in the element manager. In Bricolage 1.10.0, you can also upload a media file directly in this interface, implicitly creating a media document in the same category and with the same cover date as the current story.

Click the Save button to save the element. Back in the main story profile, the new Related Image subelement will appear in the Content Section. You can re-order elements and fields by selecting new values from the Position select lists adjacent to each element. To edit a field, simply do so directly. To edit the Related Media subelement again, click its Edit button and you'll return to its profile.

Before finishing with this story, there's one more editing interface that's important to know. Adding individual fields to a story so as to keep their contents in independent records is all well and good, but the first time you tell an editor that she has to add each paragraph one at a time, duck. Just as an editor doesn't want to think about whether to add a container element or field to a story, she also doesn't want to have to go to a select list, click a button, and wait for a browser reload every time she wants to add a single paragraph.

Super Bulk Edit
Figure 17. Super Bulk Edit allows editors to use POD-like syntax to edit all of the fields in an element in a single textarea box

For this reason, Bricolage features two other interfaces for editing. The first, Bulk Edit, allows editors to edit all instances of a repeatable field in a single textarea. This is convenient if the majority of the content of a story is paragraphs, for example. The editor can simply write all the paragraphs she wants in a textarea box, separating each with a blank line. To use it, simply select the type of repeatable field to edit and click the Bulk Edit button to the right of the Add Element button and list.

Most documents are not simple lists of paragraphs though (if they were, you wouldn't need a tool as sophisticated as Bricolage). Instead, they're a mix of different blocks of content; the so-called Super Bulk Edit feature will likely prove to be more useful. To use it, select Super Bulk Edit from the Bulk Edit select list and then click the Bulk Edit button. Doing so will bring up the screen depicted in Figure 17. Here, you can edit all of the fields and subelements of the current element in a single textarea field. Each individual field or subelement has POD-like tags that start with an equal sign (=) and end with the key name of the element or field. The tags for subelements each exist on a line by themselves, while field tags identify the field to which the contents below them belong. You can even select a default element (say, Paragraph) from the Default Element select list, and then any blocks of text that have no POD tag will default to that element. This approach makes it easy for writers to edit all the different parts of an element without having to select, reload, and wait for every one. They can also create and reorder subelements, although they cannot change the contents of subelements themselves.

Click Save to return to the story profile, and the Save button there to save the story and return to My Workspace. Now you have created a story based on the article model. Go ahead, create as many more as you like.

Up Next: Templating

The next article will show how to write your first templates in Bricolage to format stories for output, so that you can actually see how your documents will look when they're published. Stay tuned!

Building E-Commerce Sites with Handel


Over the years of doing various levels of web-based programming, I've come feel like Dante taking a trip through the nine circles of web programmer hell. There are certain things we must endure over and over, from project to project, that seem to take an exorbitant amount of coding to accomplish with little forward progress on the actual project itself. Some of these circles invariably include HTML forms, form validation code, time translation or formatting code, Unicode or encoding code, and, since the dot-com boom, shopping cart/checkout code.

While the CPAN community has solved most of the problems quite nicely with modules like Data::FormValidator, HTML::FillInForm, DateTime, and the various FromForm/QuickForm/FormBuilder modules, I still yearned for a lightweight, straightforward shopping cart module that didn't involve installed an entire CMS or B2B solution. Thus, Handel.

Later I will show you how to get a functional shopping cart up and running using no lines of code. You heard that correctly: no lines of code. Zero. None. Nada.

What Handel Is

Handel is a set of modules to perform the repetitive tasks associated with creating a shopping cart and a checkout process on the Web in Perl.

This includes the usual actions, such as adding items to the cart, updating item quantity, and removing items, as well as less common features, including saving the current contents of the cart to a wish list and restoring wish lists back into the current shopping cart.

Handel doesn't just provide shopping carts. It has order add/update features, taglib support for AxKit, plugin support for Template Toolkit, and helper support for Catalyst. It also includes a plugin-based checkout processing pipeline where orders are passed through a step-by-step process that (with the right plugins loaded) can do anything from address validation and shipping calculation to credit-card verification and email/fax order confirmations.

What Handel Is Not

From the beginning, the two main goals of Handel were to be situation-agnostic and to be a set of building blocks, not an out-of-the-box solution.

Handel needed to be reusable from the standpoint that it should be able to interact with your shopping cart's contents and process an order from anywhere without rewriting the core set of data/processing code. It should be able to get the cart's contents from within a shell script, from a terminal app, from a web page, or from a web service. E-commerce isn't always just about putting up a website. Many times, there will be both a front-end, web-based system and a back-end, GUI-based application.

I also decided early on that shopping carts and checkout processes can't possibly be everything for everyone. While there are many things in common (part numbers and prices), every site has different requirements on how to process an order and the steps necessary to place that order. It is for this reason that the checkout process by itself does absolutely nothing, but instead employs the help of plugins to add requirements or features.

Getting Started

Before you start, you need to have a few things installed from CPAN. These include:

Perl 5.8.1 or greater? What about 5.6.x? Yeah, sorry. While Handel runs on Perl 5.6.1 or greater, Catalyst requires 5.8.1 or newer. That's a good thing. Now upgrade your Perl version! :-)

Creating the Database

First, you must create the database. For the sake of this experiment^H^H^H^Harticle, I use Sqlite. In the real world, you would probably want to use MySQL/PostgreSQL instead.

Download the handel.sqlite.sql schema script to your hard drive. Now, in your favorite shell, type the following to create the database:

[claco@cypher ~] $ wget 
    http://handelframework.com/svn/CPAN/Handel/tags/0.26/sql/handel.sqlite.sql
handel.sqlite.sql                             100% of 2300  B  115 kBps
[claco@cypher ~] $
[claco@cypher ~] $ sqlite3 handel.db < handel.sqlite.sql
[claco@cypher ~] $

This should have created an sqlite v3 database in your working directory. Remember this file name and location. You're going to need it to set up the DSN.

Creating the Application

If you haven't used or even heard about Catalyst yet, now is probably a good time to read Jesse Sheidlower's article introducing the Catalyst MVC framework. Because Handel isn't bound to any specific front-end GUI, you need to use other tools to interact with the data. For the purposes of this introduction, Catalyst will provide the front-end web interface for use.

To create the new web application, simply call catalyst.pl, passing it the name of the new application. Make sure to do this in the directory in which you wish to have the new application's directory created.

$ catalyst.pl MyStore

You should end up with output resembling:

[claco@cypher ~] $ catalyst.pl MyStore
created "MyStore"
created "MyStore/script"
created "MyStore/lib"
created "MyStore/root"
created "MyStore/t"
created "MyStore/t/M"
created "MyStore/t/V"
created "MyStore/t/C"
created "MyStore/lib/MyStore"
created "MyStore/lib/MyStore/M"
created "MyStore/lib/MyStore/V"
created "MyStore/lib/MyStore/C"
created "MyStore/lib/MyStore.pm"
created "MyStore/Build.PL"
created "MyStore/Makefile.PL"
created "MyStore/README"
created "MyStore/Changes"
created "MyStore/t/01app.t"
created "MyStore/t/02pod.t"
created "MyStore/t/03podcoverage.t"
created "MyStore/script/mystore_cgi.pl"
created "MyStore/script/mystore_fastcgi.pl"
created "MyStore/script/mystore_server.pl"
created "MyStore/script/mystore_test.pl"
created "MyStore/script/mystore_create.pl"
[claco@cypher ~] $

Now run the newly created application, just to see what you have so far. In your terminal, change directory into the newly created app and start its server:

[claco@cypher ~] $ cd MyStore
[claco@cypher ~/MyStore] $ script/mystore_server.pl

You should see a bunch of debugging information streaming to the console:

[Wed Sep 21 19:19:16 2005] [catalyst] [debug] Debug messages enabled
[Wed Sep 21 19:19:16 2005] [catalyst] [debug] Loaded dispatcher
    "Catalyst::Dispatcher"
[Wed Sep 21 19:19:16 2005] [catalyst] [debug] Loaded engine
    "Catalyst::Engine::HTTP"
[Wed Sep 21 19:19:16 2005] [catalyst] [debug] Found home
    "/usr/home/claco/MyStore"
[Wed Sep 21 19:19:16 2005] [catalyst] [debug] Loaded private actions:
.=-------------------------------------+--------------------------------------=.
| Private                              | Class                                 |
|=-------------------------------------+--------------------------------------=|
| /default                             | MyStore                               |
'=-------------------------------------+--------------------------------------='

[Wed Sep 21 19:19:16 2005] [catalyst] [info] MyStore powered by Catalyst 5.33
You can connect to your server at http://localhost:3000/

Now point your browser to the address listed in the debug output. This is usually http://localhost:3000/. If everything is working properly, you should have a nice new shiny web application looking something like Figure 1.

New MyStore Application Screenshot
Figure 1. A NewStore application

Of course, your new store doesn't do anything yet. :-) It's time to fix that. Stop the application by hitting CTRL-C.

^C
[claco@cypher ~/MyStore] $

Flip The More Magic Switch

Now it's time for the magic. Handel comes with a set of helpers for Catalyst. A helper is a module that Catalyst loads when running the create.pl script. The helpers generate TT template files; controller, model, or view modules; and just about anything else you can code into one.

Because this is a new web application, Handel can create the cart, the checkout process, and the order views using Handel::Scaffold. Handel::Scaffold is a meta-helper that uses other individual helpers to get the job done. If you want to add cart/order/checkout features to an existing Catalyst application, use the individual Catalyst helpers to create just the cart or just the checkout functionality.

Back in the terminal, use MyStore's create script to call Handel::Scaffold, passing it the DSN for the database created earlier:

[claco@cypher ~/MyStore] $ script/mystore_create.pl Handel::Scaffold \
    dbi:SQLite:dbname=/home/claco/handel.db

Once again, you will see some output scroll by:

created "/usr/home/claco/MyStore/script/../lib/MyStore/V/TT.pm"
created "/usr/home/claco/MyStore/script/../t/V/TT.t"
created "/usr/home/claco/MyStore/script/../lib/MyStore/M/Cart.pm"
created "/usr/home/claco/MyStore/script/../t/M/Cart.t"
created "/usr/home/claco/MyStore/script/../lib/MyStore/M/Orders.pm"
created "/usr/home/claco/MyStore/script/../t/M/Orders.t"
created "/usr/home/claco/MyStore/script/../root/cart"
created "/usr/home/claco/MyStore/script/../lib/MyStore/C/Cart.pm"
created "/usr/home/claco/MyStore/script/../root/cart/view.tt"
created "/usr/home/claco/MyStore/script/../root/cart/list.tt"
created "/usr/home/claco/MyStore/script/../t/C/Cart.t"
created "/usr/home/claco/MyStore/script/../root/orders"
created "/usr/home/claco/MyStore/script/../lib/MyStore/C/Orders.pm"
created "/usr/home/claco/MyStore/script/../root/orders/list.tt"
created "/usr/home/claco/MyStore/script/../root/orders/view.tt"
created "/usr/home/claco/MyStore/script/../t/C/Orders.t"
created "/usr/home/claco/MyStore/script/../root/checkout"
created "/usr/home/claco/MyStore/script/../lib/MyStore/C/Checkout.pm"
created "/usr/home/claco/MyStore/script/../root/checkout/edit.tt"
created "/usr/home/claco/MyStore/script/../root/checkout/preview.tt"
created "/usr/home/claco/MyStore/script/../root/checkout/payment.tt"
created "/usr/home/claco/MyStore/script/../root/checkout/complete.tt"
created "/usr/home/claco/MyStore/script/../t/C/Checkout.t"
[claco@cypher ~/MyStore] $

That's it! You now have a shopping cart, an order list/view, and a checkout process framework without writing a single line of code!

Exploring the Results

I'm sure that you want to explore some of the features of your newly created codebase. Start the web application again:

[claco@cypher ~/MyStore] $ script/mystore_server.pl

When the application starts this time, there's quite a bit more output:

[Wed Sep 21 20:10:04 2005] [catalyst] [debug] Debug messages enabled
[Wed Sep 21 20:10:04 2005] [catalyst] [debug] Loaded dispatcher
    "Catalyst::Dispatcher"
[Wed Sep 21 20:10:04 2005] [catalyst] [debug] Loaded engine
    "Catalyst::Engine::HTTP"
[Wed Sep 21 20:10:04 2005] [catalyst] [debug] Found home
    "/usr/home/claco/MyStore"
[Wed Sep 21 20:10:05 2005] [catalyst] [debug] Loaded components:
.=----------------------------------------------------------------------------=.
| MyStore::C::Cart                                                            |
| MyStore::C::Checkout                                                        |
| MyStore::C::Orders                                                          |
| MyStore::M::Cart                                                            |
| MyStore::M::Orders                                                          |
| MyStore::V::TT                                                              |
'=----------------------------------------------------------------------------='

[Wed Sep 21 20:10:05 2005] [catalyst] [debug] Loaded private actions:
.=-------------------------------------+--------------------------------------=.
| Private                              | Class                                 |
|=-------------------------------------+--------------------------------------=|
| /default                             | MyStore                               |
| /cart/add                            | MyStore::C::Cart                      |
| /cart/clear                          | MyStore::C::Cart                      |
| /cart/restore                        | MyStore::C::Cart                      |
| /cart/default                        | MyStore::C::Cart                      |
| /cart/end                            | MyStore::C::Cart                      |
| /cart/save                           | MyStore::C::Cart                      |
| /cart/view                           | MyStore::C::Cart                      |
| /cart/delete                         | MyStore::C::Cart                      |
| /cart/empty                          | MyStore::C::Cart                      |
| /cart/begin                          | MyStore::C::Cart                      |
| /cart/destroy                        | MyStore::C::Cart                      |
| /cart/update                         | MyStore::C::Cart                      |
| /cart/list                           | MyStore::C::Cart                      |
| /orders/view                         | MyStore::C::Orders                    |
| /orders/default                      | MyStore::C::Orders                    |
| /orders/begin                        | MyStore::C::Orders                    |
| /orders/list                         | MyStore::C::Orders                    |
| /orders/end                          | MyStore::C::Orders                    |
| /checkout/payment                    | MyStore::C::Checkout                  |
| /checkout/default                    | MyStore::C::Checkout                  |
| /checkout/edit                       | MyStore::C::Checkout                  |
| /checkout/end                        | MyStore::C::Checkout                  |
| /checkout/preview                    | MyStore::C::Checkout                  |
| /checkout/begin                      | MyStore::C::Checkout                  |
| /checkout/update                     | MyStore::C::Checkout                  |
| /checkout/complete                   | MyStore::C::Checkout                  |
'=-------------------------------------+--------------------------------------='

[Wed Sep 21 20:10:05 2005] [catalyst] [debug] Loaded public actions:
.=-------------------------------------+--------------------------------------=.
| Public                               | Private                               |
|=-------------------------------------+--------------------------------------=|
| /cart/add                            | /cart/add                             |
| /cart/clear                          | /cart/clear                           |
| /cart/delete                         | /cart/delete                          |
| /cart/destroy                        | /cart/destroy                         |
| /cart/empty                          | /cart/empty                           |
| /cart/list                           | /cart/list                            |
| /cart/restore                        | /cart/restore                         |
| /cart/save                           | /cart/save                            |
| /cart/update                         | /cart/update                          |
| /cart/view                           | /cart/view                            |
| /checkout/complete                   | /checkout/complete                    |
| /checkout/default                    | /checkout/default                     |
| /checkout/edit                       | /checkout/edit                        |
| /checkout/payment                    | /checkout/payment                     |
| /checkout/preview                    | /checkout/preview                     |
| /checkout/update                     | /checkout/update                      |
| /orders/list                         | /orders/list                          |
| /orders/view                         | /orders/view                          |
'=-------------------------------------+--------------------------------------='

[Wed Sep 21 20:10:05 2005] [catalyst] [info] MyStore powered by Catalyst 5.33
You can connect to your server at http://localhost:3000/

Cart, orders, checkout. Oh my!

The Shopping Cart

The shopping cart code base includes the usual actions: add, update, delete, and empty, as well as the ability to save the cart's contents and restore a saved cart back into the current cart. To try it out, you need to add something to the cart!

Download the test products page and load it in a browser of your choice (Figure 2).

Product Page Screenshot
Figure 2. The product page

Once you load the page, pick a hot new product and click Add To Cart (Figure 3). By default, the product's page points to http://localhost:3000/cart/. You may have to alter the page's contents to match the address of the MyStore server.

Shopping Cart Page Screenshot
Figure 3. The shopping cart page

Save this cart. Hit the Save Cart button and leave the field next to it blank.

Cart Save Error Screenshot
Figure 4. The shopping cart with an error

Take note of the error at the top of the page in Figure 4--"The Name field is required to save a cart." The code and template generated by Handel uses Data::FormValidator and HTML::FillInForm where appropriate. Not only didn't you have to write any cart code, you didn't have to write and forms validation code either!

Go ahead and enter a name for your saved cart and hit Save Cart again. If all goes well, Handel saves the shopping cart's contents into a list under the name specified in the Name field. To view the list of saved shopping carts, click View Saved Carts at the top of the page (Figure 5).

Saved Carts Screenshot
Figure 5. Viewing saved carts

When restoring wish lists, there are three options: Append, Merge, and Replace. As the titles imply, Append simply adds the saved cart items to the current cart. There is no attempt made to prevent duplicate parts. Merge adds the saved cart's contents into the current cart. If it finds a part with the same identifier, it adds the quantity of the two parts together. Replace empties the current cart and them restores the saved cart's contents.

Because the cart is currently empty, select any of those methods and hit Restore Cart. You should be back at the cart page with your cart items restored.

Checkout

On the cart page, hit the Checkout button. This will take you to the BillTo/ShipTo edit screen (Figure 6).

BillTo/ShipTo Edit Page Screenshot
Figure 6. The BillTo/ShipTo page

In either of the first name fields, enter your first name. Scroll to the bottom of the page and click Continue. If everything is working correctly, you should see a page resembling Figure 7.

BillTo/ShipTo Error Page Screenshot
Figure 7. An error in billing/shipping

Once again, the generated code from Handel has taken care of validating required form fields for you and the fill-in forms kept the data you entered, all without writing any code. (Noticing a theme yet?)

Take some time on your own to work your way through the rest of the checkout process. This includes a preview page, the credit card payment page, and an order completion page. Once you've completed your order, you should receive a "thank you" page with links to view all of your orders (Figure 8).

Order Complete Page Screenshot
Figure 8. The Order Complete page

Orders List

Last but not least, check out the orders list. On your order completion screen, click on the View Orders link. Here you will find a list of any orders you have placed (Figure 9). Click the View Order link next to any order for more details about that order (Figure 10).

Orders List Page Screenshot
Figure 9. The Orders List page.

Order Details Page Screenshot
Figure 10. The order details page

Where To Go From Here

As you've hopefully seen in the article, by combining the powers of Handel and Catalyst, you need not suffer through the dreary task of writing another shopping cart from scratch ever again. With the plugin architecture Handel provides, you will find that you can do even the more complicated checkout tasks using nothing more than a few lines of code to glue various business modules into plugins.

With Handel as the core, we can look forward to new and improved Perl-based E-commerce solutions and new ways to add E-commerce features to existing projects without having to reinvent the wheel--again!

Making Sense of Subroutines


Editor's Note: This article has a followup in Advanced Subroutine Techniques.

A subroutine (or routine, function, procedure, macro, etc.) is, at its heart, a named chunk of work. It's shorthand that allows you to think about your problem in bigger chunks. Bigger chunks means two things:

  • You can break the problem up into smaller problems that you can solve independently.
  • You can use these solutions to solve your overall problem with greater confidence.

Well-written subroutines will make your programs smaller (in lines and memory), faster (both in writing and executing), less buggy, and easier to modify.

You're Kidding, Right?

Consider this: when you lift your sandwich to take a bite, you don't think about all the work that goes into contracting your muscles and coordinating your movements so that the mayo doesn't end up in your hair. You, in essence, execute a series of subroutines that say "Lift the sandwich up to my mouth and take a bite of it, then put it back down on the plate." If you had to think about all of your muscle contractions and coordinating them every time you wanted to take a bite, you'd starve to death.

The same is true for your code. We write programs for a human's benefit. The computer doesn't care how complicated or simple your code is to read--it converts everything to the same 1s and 0s whether it has perfect indentation or is all on one line. Programming guidelines, and nearly every single programming language feature, exist for human benefit.

Tell Me More

Subroutines truly are the magical cure for all that ails your programs. When done right, you will find that you write your programs in half the time, you have more confidence in what you've written, and you can explain it to others more easily.

Naming

A subroutine provides a name for a series of steps. This is especially important when dealing with complicated processes (or algorithms). While this includes ivory-tower solutions such as the Guttler-Rossman transformation (for sorting), this also includes the overly complicated way your company does accounts receivables. By putting a name on it, you're making it easier to work with.

Code Reuse

Face it--you're going to need to do the same thing over and over in different parts of your code. If you have the same 30 lines of code in 40 places, it's much harder to apply a bugfix or a requirements change. Even better, if your code uses subroutines, it's much easier to optimize just that one little bit that's slowing the whole application down. Studies have shown that 80 percent of the application's runtime generally occurs within one percent of an application's code. If that one percent is in a few subroutines, you can optimize it and hide the nasty details from the rest of your code.

Testability

To many people, "test" is a four-letter word. I firmly believe this is because they don't have enough interfaces to test against. A subroutine provides a way of grabbing a section of your code and testing it independently of all the rest of your code. This independence is key to having confidence in your tests, both now and in the future.

In addition, when someone finds a bug, the bug will usually occur in a single subroutine. When this happens, you can alter that one subroutine, leaving the rest of the system unchanged. The fewer changes made to an application, the more confidence there is in the fix not introducing new bugs along with the bugfix.

Ease of Development

No one argues that subroutines are bad when there are ten developers working on a project. They allow different developers to work on different parts of the application in parallel. (If there are dependencies, one developer can stub the missing subroutines.) However, they provide an equal amount of benefit for the solo developer: they allow you to focus on one specific part of the application without having to build all of the pieces up together. You will be happy for the good names you chose when you have to read code you wrote six months ago.

Consider the following example of a convoluted conditional:

if ((($x > 3 && $x<12) || ($x>15 && $x<23)) &&
    (($y<2260 && $y>2240) || ($z>foo_bar() && $z<bar_foo()))) {

It's very hard to exactly what's going on. Some judicious white space can help, as can improved layout. That leaves:

if (
     (
       ( $x > 3 && $x < 12) || ($x > 15 && $x < 23)
     )
     &&
     (
       ($y < 2260 && $y > 2240) || ($z > foo_bar() && $z < bar_foo())
     )
   )
{

Gah, that's almost worse. Enter a subroutine to the rescue:

sub is_between {
    my ($value, $left, $right) = @_;

    return ( $left < $value && $value < $right );
}

if (
    ( is_between( $x, 3, 12 ) ||
      is_between( $x, 15, 23 )
    ) && (
      is_between( $y, 2240, 2260 ) ||
      is_between( $z, foo_bar(), bar_foo() )
    ) {

That's so much easier to read. One thing to notice is that, in this case, the rewrite doesn't actually save any characters. In fact, this is slightly longer than the original version. Yet, it's easier to read, which makes it easier to both validate for correctness as well as to modify safely. (When writing this subroutine for the article, I actually found an error I had made--I had flipped the values for comparing $y so that the $y conditional could never be true.)

How Do I Know if I'm Doing It Right?

Just as there are good sandwiches (turkey club on dark rye) and bad sandwiches (peanut butter and banana on Wonder bread), there are also good and bad subroutines. While writing good subroutines is very much an art form, there are several characteristics you can look for when writing good subroutines. A good subroutine is readable and has a well-defined interface, strong internal cohesion, and loose external coupling.

Readability

The best subroutines are concise--usually 25-50 lines long, which is one or two average screens in height. (While your screen might be 110 lines high, you will one day have to debug your code on a VT100 terminal at 3 a.m. on a Sunday.)

Part of being readable also means that the code isn't overly indented. The guidelines for the Linux kernel code include a statement that all code should be less 80 characters wide and that indentations should be eight characters wide. This is to discourage more than three levels of indentation. It's too hard to follow the logic flows with any more than that.

Well-Defined Interfaces

This means that you know all of the inputs and all of the outputs. Doing this allows you to muck with either side of this wall and, so long as you keep to the contract, you have a guarantee that the code on the other side of the interface will be safe from harm. This is also critical to good testing. By having a solid interface, you can write test suites to validate both the subroutine and to mock the subroutine to test the code that uses it.

Strong Internal Cohesion

Internal cohesion is about how strongly the lines of code within the subroutine relate to one another. Ideally, a subroutine does one thing and only one thing. This means that someone calling the subroutine can be confident that it will do only what they want to have done.

Loose External Coupling

This means that changes to code outside of the subroutine will not affect how the subroutine performs, and vice versa. This allows you to make changes within the subroutine safely. This is also known as having no side effects.

As an example, a loosely coupled subroutine should not access global variables unnecessarily. Proper scoping is critical for any variables you create in your subroutine, using the my keyword.

This also means that a subroutine should be able to run without depending upon other subroutines to be run before or after it. In functional programming, this means that the function is stateless.

Perl has global special variables (such as $_, @_, $?, $@, and $!). If you modify them, be sure to localize them with the local keyword.

What Should I Call It?

Naming things well is important for all parts of your code. With subroutines, it's even more important. A subroutine is a chunk of work described to the reader only by its name. If the name is too short, no one knows what it means. If the name is too long, then it's too hard to understand and potentially difficult to type. If the name is too specific, you will confuse the reader when you call it for more general circumstances.

Subroutine names should flow when read out loud: doThis() for actions and is_that() for Boolean checks. Ideally, a subroutine name should be verbNoun() (or verb_noun()). To test this, take a section of your code and read it out loud to your closest non-geek friend. When you're done, ask them what that piece of code should do. If they have no idea, your subroutines (and variables) may have poor names. (I've provided examples in two forms, "camelCase" and "under_score." Some people prefer one way and some prefer the other. As long as you're consistent, it doesn't matter which you choose.)

What Else Can I Do?

(This section assumes a strong grasp of Perl fundamentals, especially hashes and references.)

Perl is one of a class of languages that allows you to treat subroutines as first-class objects. This means you can use subroutines in nearly every place you can use a variable. This concept comes from functional programming (FP), and is a very powerful technique.

The basic building block of FP in Perl is the reference to a subroutine, or subref. For a named subroutine, you can say my $subref = \&foobar;. You can then say $subref->(1, 2) and it will be as if you said foobar(1, 2). A subref is a regular scalar, so you can pass it around as you can any other reference (say, to an array or hash) and you can put them into arrays and hashes. You can also construct them anonymously by saying my $subref = sub { ... }; (where the ... is the body of the subroutine).

This provides several very neat options.

Closures

Closures are the main building blocks for using subroutines in functional programming. A closure is a subroutine that remembers its lexical scratchpad. In English, this means that if you take a reference to a subroutine that uses a my variable defined outside of it, it will remember the value of that variable when it was defined and be able to access it, even if you use the subroutine outside of the scope of that variable.

There are two main variations of closures you see in normal code. The first is a named closure.

{
    my $counter = 0;
    sub inc_counter { return $counter++ }
}

When you call inc_counter(), you're obviously out of scope for the $counter variable. Yet, it will increment the counter and return the value as if it were in scope.

This is a very good way to handle global state, if you're uncomfortable with object-oriented programming. Just extend the idea to multiple variables and have a getter and setter for each one.

The second is an anonymous closure.

Recursion

Many recursive functions are simple enough that they do not need to keep any state. Those that do are more complicated, especially if you want to be able to call the function more than once at a time. Enter anonymous subroutines.

sub recursionSetup {
    my ($x, $y) = @_;

    my @stack;

    my $_recurse = sub {
        my ($foo, $bar) = @_;

        # Do stuff here with $x, $y, and @stack;
    };
    my $val = $_recurse->( $x, $y );

    return $val;
}

Inner Subroutines

Subroutine definitions are global in Perl. This means that Perl doesn't have inner subroutines.

sub foo {
    sub bar {
    }

    # This bar() should only be accessible from within foo(),
    # but it is accessible from everywhere
    bar():
}

Enter anonymous subroutines again.

sub foo {
    my $bar = sub {
    };

    # This $bar is only accessible from within foo()
    $bar->();
}

Dispatch Tables

Often, you need to call a specific subroutine based some user input. The first attempts to do this usually look like this:

if ( $input eq 'foo' ) {
    foo( @params );
}
elsif ( $input eq 'bar' ) {
    bar( @params );
}
else {
    die "Cannot find the subroutine '$input'\n";
}

Then, some enterprising soul learns about soft references and tries something like this:

&{ $input }( @params );

That's unsafe, because you don't know what $input will to contain. You cannot guarantee anything about it, even with taint and all that jazz on. It's much safer just to use dispatch tables:

my %dispatch = (
    foo => sub { ... },
    bar => \&bar,
);

if ( exists $dispatch{ $input } ) {
    $dispatch{ $input }->( @params );
}
else {
    die "Cannot find the subroutine '$input'\n";
}

Adding and removing available subroutines is simpler than the if-elsif-else scenario, and this is much safer than the soft references scenario. It's the best of both worlds.

Subroutine Factories

Often, you will have many subroutines that look very similar. You might have accessors for an object that differ only in which attribute they access. Alternately, you might have a group of mathematical functions that differ only in the constants they use.

sub make_multiplier { 
    my ($multiplier) = @_;

    return sub {
        my ($value) = @_;
        return $value * $multiplier;
    };
}

my $times_two  = make_multiplier( 2 );
my $times_four = make_multiplier( 4 );

print $times_two->( 6 ), "\n";
print $times_four->( 3 ), "\n";

----

12
12

Try that code and see what it does. You should see the values below the dotted line.

Conclusion

Subroutines are arguably the most powerful tool in a programmer's toolbox. They provide the ability to reuse sections of code, validate those sections, and create new algorithms that solve problems in novel ways. They will reduce the amount of time you spend programming, yet allow you to do more in that time. They will reduce the number of bugs in your code ten-fold, and allow other people to work with you while feeling safe about it. They truly are programming's super-tool.

Visit the home of the Perl programming language: Perl.org

Sponsored by

Monthly Archives

Powered by Movable Type 5.13-en