In the projects I was recently working on, I am using an inheritance hierarchy of Page and Widget classes to allow a fine-grained reuse of markup-generating code.
Now I'd like to get some feedback if this is generally a good idea, or what I can do better.
I will try to give a simplified example. Now rip it apart!
The basic idea is that the render() method calls a lot of other methods for specific pieces of the page. These other methods are defined and overridden in subclasses, thus customizing the look of the page.
Code PHP:class PageWithHTML extends VisualComponent { function render() { header('Content-type: text/html;charset="utf-8"'); ?><!DOCTYPE ....> <html ....> <head> <link rel="shortcut icon" href="<?=$this->getFaviconURL() ?>" /> <?php $this->includeScriptfiles(); $this->includeStylesheets(); ?> </head> <body> <?php $this->body(); ?> </body> </html><?php } } class PageWithSiteLayout extends PageWithHTML { function body() { ?>some html ...<?php $menu_items = $this->getMenuItems(); foreach ($this->getMenuItems() as $key => $menu_item) { if ($this->getActiveMenuItemKey == $key) { ?>show $menu_item, highlighted<?php } else { ?>show $menu_item, not highlighted<?php } } ?>some more html ...<?php $this->leftSidebarContent(); ?>some more html ...<?php $this->mainContent(); ?>footer html ...<?php } function getMenuItems() { return array( 'messages' => 'My Messages', 'blog' => 'My Blog', ); } } class BlogPage extends PageWithSiteLayout { function getActiveMenuItemKey() { return 'blog'; } function mainContent() { ?>blogging is fun<?php } }
and, for using this:
Code PHP:$page = new BlogPage(); $page->render();
The trick with all the tiny methods is that they provide fine-grained extension points to modify the behaviour of the parent class.
The great thing about all this:
- I only have to redefine the things which are different from the base class. No boilerplate html, effective layout reuse.
- It has the taste of "Inversion of Control", a concept which has my sympathy. It's the parent class method calling the subclass method, not the other way round.
What makes me unhappy, especially if my methods become very small and fine-grained:
- OOP PHP and HTML will be mingled all over the place. Designers will hate me for that, if they don't like PHP.
- If I want to put the HTML into templates, I will end up with tons of micro template files.
- While I don't have boilerplate html, I have a lot of boilerplate PHP: A lot of methods with big definition, but little information value.
- The inheritance hierarchy can grow quite deep, with a high number of methods accumulating with each subclass.
- Depending on behaviour defined in a base class makes things quite static. What if I want to replace the layout defined in PageWithSiteLayout with PageWithMySkin ?
It gets more extreme in another example, where my class hierarchy is something like
- ItemlistWithPagination extends ItemlistWidget
- MailboxWidget extends ItemlistWithPagination
- MailboxWidget_Received extends MailboxWidget
- MailboxWidget_Sent extends MailboxWidget
- MailboxWidget_Spam extends MailboxWidget
or, for the forum it would be
- ForumTopicWidget extends ItemlistWithPagination
Methods in this example allow to redefine individual table cells, like this
Code PHP:class ItemlistWidget { protected function showItems_table() { echo ' <table>'; // table headline if (!$this->hideColumnTitles()) { echo ' <tr class="title">'; foreach ($this->getTableColumns() as $key => $value) { echo ' <th class="'.$key.'">' . $value . '</th>'; } echo ' </tr>'; } // table rows with items $items = $this->getItems(); if (!is_array($items) && !is_object($items)) { throw new PException('"ItemlistWidget::getItems()" must return an array or an object.'); } $index = 0; foreach ($items as $itemkey => $item) { echo ' <tr class="' . ($index%2 ? 'odd' : 'even') . '">'; foreach ($this->getTableColumns() as $key => $value) { $methodname = 'tableCell_'.$key; echo ' <td class="'.$key.'">'; if (method_exists($this, $methodname)) { $this->$methodname($item, $itemkey); } else { $this->tableCell($key, $item, $itemkey); } echo ' </td>'; } echo ' </tr>'; ++$index; } echo ' </table>'; } } class ItemlistWithPagination extends ItemlistWidget {..} class MailboxWidget extends ItemlistWithPagination {..} class MailboxWidget_Sent extends MailboxWidget { protected function getTableColumns() { return array( 'checkbox' => 'Select', 'contact' => 'From/To', 'title' => 'Text', 'dateSent' => 'Date' ); } protected function tableCell_dateSent($message) { ?>some html showing $message->date_sent..<?php } }
As I read here and there (especially on SitePoint), experienced developers prefer composition over inheritance. How would the alternative look like?
Any way one could combine this with skinning? Or caching of static page elements?







Bookmarks