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:
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.
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
This defines a list of directories to search when you call
Also, this introduces a convention. Each include file is kept in a
To see how the
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:
- /animals/wolves/header.inc
- /animals/wolves/php/header.inc
- /animals/header.inc
- /animals/php/header.inc
- /header.inc
- /php/header.inc
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
Table 1. "Animal" Site Files: The table provides a list of files in the sample "animals" site, along with a description of each.
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
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.
File | Description |
---|---|
/index.php | Front page of the site |
/php/header.inc | Top of page template |
/php/footer.inc | Bottom of page template |
/php/news.inc | Main site news |
/bears/index.php | Main page about bears |
/bears/php/news.inc | News about bears, overrides default news |
/lions/index.php | Main page about lions |
/wolves/index.php | Main page about wolves |
/wolves/php/header.inc | Top of wolf page template, overrides main one |
/wolves/php/footer.inc | Bottom of wolf page template |
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.
The
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
So, rather than measuring the image size manually and writing a long HTML tag such as:
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:
The files
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:
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, andmenu.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-friendlydiv
s 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 itsmod_time()
function where you want the information.