Building an Object-Oriented File System in PHP

a web framework for easier maintenance and authoring


Using a simple file storage convention and some PHP code, you can create a hierarchical file system that mimics many object-oriented concepts, facilitates reuse, and simplifies your Web development efforts.

Note: the system I describe here can be downloaded from my programs section. It is more complete than the example code included with this article.

About

The term "Object Oriented" is vague and frequently misunderstood. Jonathan Rees (with more insight than the average programmer) attempted to define its meaning, but even his definition does not necessarily encompass the topic completely.

Despite the difficulties of pinning down the terminology, for the purposes of this article, you can assume an "object-oriented file system" has a specific meaning -- it is a way to take advantage of traditional object-oriented concepts such as inheritance, by organizing your files and directories as if they were a class hierarchy. This may seem like an odd idea, but in practice, it is extremely useful. Adopting an object-oriented file system provides the following benefits:
  • Context Sensitivity: Perhaps the most important benefit of an object-oriented file system is that it can automatically configure themselves based on context. The class hierarchy serves primarily as an easy way to define context for your site. Then the various page elements can be selected automatically based on that context.
  • Reduced Duplication: The idea is that changes should be made once, and only once. A single piece of information should not be stored in more than one place, nor should a concept be implemented repeatedly. For example, you can easily change the appearance of an entire site, or any subsection of it, by making a single change. Instead of writing the same things over and over, this will let you tell the system only how this page differs from other pages. Anything that is the same is taken care of automatically.
  • Ease of Specialization: While it is easy to do things automatically from context, the system also makes it easy to give pages unique behavior. The context can be overridden without difficulty, to make pages or sections stand out from the rest of the site.
  • Easy Maintenance: Moving files and directories around is easy when you don't have to worry about breaking links. Just make sure that you move your file or directory into one with a compatible interface, and the system will sort out the details automatically. By interface, I mean the parent classes or folders must implement the features your migrant page needs -- images, files, functions, templates, or other objects which make up a complete page. But if these things are not available, it is still fairly simple to import components from brother or sister classes. This allows you to use both vertical and horizontal inheritance.
  • Clean, Clear URLs: Readers and search engines both like to use your Web address to guess what your content will be. URLs which describe their content in human-friendly terms will be easier to find, easier to remember, easier to share and link to, and get higher search engine placement. Organizing your content into a class hierarchy has the side effect of producing human-friendly and spider-friendly URLs.
An object-oriented file system might be better labelled as a contextual framework. Though it provides very helpful features, you will still need to apply basic organization skills to make it work well. The framework is both a mechanism to provide basic object-oriented features and an approach or policy for organizing content. By using both the mechanism and the policy, you can create and maintain sites with relatively little effort.

The Basics: Includes

At the core of the OO-FS system is inheritance. What this means, essentially, is that any request for an object or file should search upward through the parent classes of the current page if the requested object or file is not found in the current directory. In this way, subdirectories (or, child classes) can inherit the attributes (files) of their parents.

A php page commonly requests other files by using PHP's include(), or HTML's <a href> and <img> tags. I'll start with include(), because it is the easiest to set up. A simple, though somewhat ugly statement in a .htaccess file is all you need:

php_value include_path ".:./php:..:../php:../..:../../php:../../..:../../../php"

This defines a list of directories to search when you call include(). Each item on the list is separated by colons. The "." is the current directory, and ".." is the parent of the current directory. To go one more level up, it uses "../..", and you can extend this as many levels as appropriate, by adding more items with an extra "../" prepended to each. You should make the list as long as the depth of your site's deepest nested content, so that the most buried content can still inherit from the root.

Also, this introduces a convention. Each include file is kept in a php/ subdirectory. The idea here is to keep the files more organized, with pages in folder/, code in folder/php/, graphics in folder/gfx/, and downloadable files in folder/files/.

To see how the include_path works, pretend you have a page located at /animals/wolves/index.php, and that it executes include("header.inc"). PHP will search for header.inc in several places, until it finds the file or runs out of places to look. Specifically, it will look here:
  1. /animals/wolves/header.inc
  2. /animals/wolves/php/header.inc
  3. /animals/header.inc
  4. /animals/php/header.inc
  5. /header.inc
  6. /php/header.inc
If the file is not in any of those locations, the include() will fail.

A Word of Precaution

Before going any further, you may want to protect your code. This scheme places your include files inside of the $DOCUMENT_ROOT folder, which means people can download them and look at the source. Because you may have passwords or other sensitive data in those files, you need a way to prevent unauthorized people from viewing those files. Therefore, give each include file a .inc extension, and add the following to your .htaccess file:

      <files "*.inc">
        Order Deny,Allow
        Deny from All
      </files> 
That causes requests for your code fail, keeping your implementation code and passwords safe.

Templates and Libraries

With just the single tool of inheritance via include(), you can do some powerful things. For example, you can now easily apply templates to entire sections of your site, and easily load functions from libraries without having to care where those libraries are. You can also provide defaults for the entire site and then override them in any folder. To use full-page templates, you only need two lines of code in each page. The header and footer define the common parts of the pages in the site; the main content for each page would go between the header and footer code lines, as shown in the following code fragment:

      <? include("header.inc"); ?>

      (insert page content here)

      <? include("footer.inc"); ?> 
It's important to consider that templates don't need to apply to complete pages -- you can you can easily define smaller parts of the page in exactly the same manner, putting a menu here or a news ticker there. You would write these smaller pieces as separate files, and then simply include() those pieces from your header or footer. By breaking your pages up into their component parts in this way, you can either use or override them easily. For example, consider the following file layout; a small site about animals. Table 1 shows the files and a description of each:

Table 1. "Animal" Site Files: The table provides a list of files in the sample "animals" site, along with a description of each.
FileDescription
/index.phpFront page of the site
/php/header.incTop of page template
/php/footer.incBottom of page template
/php/news.incMain site news
/bears/index.phpMain page about bears
/bears/php/news.incNews about bears, overrides default news
/lions/index.phpMain page about lions
/wolves/index.phpMain page about wolves
/wolves/php/header.incTop of wolf page template, overrides main one
/wolves/php/footer.incBottom of wolf page template
From looking at the first four items in Table 1, you can see that the site provides a default page template, which in turn uses a sub-template to display recent news. Both can be overridden by subdirectories. In this example, the bears/ folder provides its own news widget, which can display news appropriate to bears, rather than the full list of news items displayed by the rest of the site. The lions/ area does nothing special, so it inherits the look and feel of the site's front page. In contrast, the wolves/ section has its own full-page template, giving it a different appearance than every other section.

Basic Element: Hyperlinks

Easy templates are great, but there is more to a useful Web site than just looks. Pages in useful sites are often heavily linked together. As the site grows, managing the links and keeping them working can become a huge problem, particularly when you need to move pages or folders around. Inheritance makes link maintenance significantly easier, although it does not completely alleviate the problem.

Implementing page inheritance will require a change in link syntax. Typically, a site might use HTML such as the following.

      <a href="/folder/page.php">description</a>
In contrast, with the OOP file system, you call a special page() PHP function to create a link:
      <? page("page.php", "description"); ?>
Using this function, you no longer need to provide the full path for links; you just specify enough to uniquely identify a page, and the system figures out the rest of the path from context. For a page in the current directory or its parent directories, the filename is sufficient. For example, to link to /bears/pictures.php from /bears/species/black.php, you would write:
      <? page("pictures.php", "Pictures"); ?>
For a page in a sibling folder, you'll need to provide the sibling name and the file name. For example, if you wanted to link to /animals/bears/habitat.php from /animals/wolves/species/index.php:
      <? page("bears/habitat.php", "Bear Habitats"); ?>
This is significantly easier than specifying either the full path or a relative path in HTML, which would be /animals/bears/habitat.php or ../../bears/habitat.php . The full path works regardless of where it is linked from, but causes problems if the page you link to ever moves. For example, if you renamed /animals/ to /mammals/, all of the links to the /animals/ section would break. Using relative links avoids that problem, but breaks if you move the page you link from. However, using inheritance, neither situation causes a problem.

The page() function works on the same concept as the include() function and its include_path variable. It searches for whatever file name you give it, starting in the current directory and traveling upward through parent directories until it finds something or runs out of places to look. The function itself is fairly simple; it mostly just calls a generic search_parents() function and prints the result as HTML. The search_parents() function does a few sanity checks, then executes the actual search, making sure to keep track of both the real location of the file on disk, and its virtual location on the web server. The actual details and implementation are mundane, but nevertheless important. Look through the commented inherit.inc file for more detail, in the downloadable code provided with this article.

Basic Element: Images

Another PHP function lets you link to images as easily as pages, without having to care much about the exact file location. This is particularly helpful with templates, so you can easily inherit the look and feel of a parent directory, or override logos and graphics without changing the actual page layout. And as an added bonus, you get automatic width and height attributes, and even an automatic alt attribute if you forget to provide one.

So, rather than measuring the image size manually and writing a long HTML tag such as:

     <img src="/mammals/bears/gfx/logo.jpg"
       width="400" height="100" alt="Logo" border="0" />
You can simply write:
      <? image("logo.jpg"); ?> 
And in many cases, you won't need to write any code at all. Simply drop a file into the gfx/ subdirectory, and the image should show up on your page automatically. For example, if your site uses /gfx/logo.jpg for its logo, you could apply a different logo to the bears section by creating /bears/gfx/logo.jpg. This would override the regular logo, and show up automatically without writing any code.

It's also very common to use "thumbnail" images -- small images that link to larger or full-sized images. Normally, using thumbnails is a fairly tedious task; you have to load an image in a photo-editing program, resize it, save it, put both copies onto your site, and write HTML code such as the following:

     <a href="/path/to/big.jpg">
       <img src="/path/to/small.jpg" width="200" height="150"
         alt="click for bigger image" border="0" />
     </a>
A small PHP function similar to image() makes this process much easier. Simply put the original image on your site, and call a function:
      <? thumbnail("big.jpg"); ?> 
Doing this has the same effect as the longer method above, except that the Web server will create the thumbnail image automatically the first time it's requested. You can optionally pass numeric values to specify a maximum width or height for the thumbnail image.

The files inherit.inc and thumbnail.inc implement the image() and thumbnail() functions, respectively. To use thumbnail(), make sure to give your Web server permission to write to the gfx/ directory; the server won't create thumbnail images without write permission. The thumbnail() function also requires the free "convert" utility from ImageMagick.

Putting it Together

To use the PHP functions described above and otherwise make the inheritance system work, you will need to add a standard include file to each page. This standard include file sets up the basic inheritance system, loads default settings, loads other libraries you expect to use on every page, and loads placeholders you can use elsewhere to override or enhance the default settings. While not strictly necessary, using a standard include file such as the one in this article's downloadable code adds power and flexibility to the OO file system. Also in the code are details and source to a small working site.

You could specifically add the standard include file by writing the code to load it at the top of each page, or generically add it by writing the code at the top of each header template. The included code demonstrates both methods. Despite the extra line per file, it's useful to put the include line in each page, because you can then modify the defaults before the page template starts to display. Simply put this at the top of each page:
      <? include("std.inc"); ?> 
Then, between this line and the line to include the page header, you can change variables used by the header, and otherwise modify the style before it begins to display. You'd typically use such modifications to set the page title, or set other parameters used by the main template.

Extras

You'll find some small extras included with the source. These are not necessary to implement the OO file system, but you may find them useful. A brief description of each follows:
  • Menus: Three files implement a system for side menus: The side_menu.inc file is generic code for handling a menu definition, menu_style.inc tells the code what HTML you want to display for each type of menu item, and menu.inc is a menu definition. You can easily define the menu contents per folder or per section on a large site. The default HTML style uses CSS-friendly divs to make theming easy, and a simple CSS style is included to demonstrate theming the menu items.
  • Automatic Forwarding: In forward.inc you'll find functions to direct a browser automatically from one place to another. This is particularly useful if you want to move content without breaking incoming links from other sites. Combine this with replacements for the 404 document ("page not found" page) and a bit of string manipulation, and you can redirect entire sections with little effort.
  • Modification Time: This is useful if you want each page to show the last time it was changed, without having to do so manually. Just include mod_time.inc in your template, and call its mod_time() function where you want the information.
I have other components available for this system too, such as an image gallery and basic content management system. Feel free to contact me for more information.
(originally published at Devx 2004-10-25)
Last modified: July 06, 2005 @ 5:18 MDT
Copyright (C) 1996-2024 Selene ToyKeeper