I’m learning about dealing with PHP’s lack of multiple inheritance. It was really driving me up the wall. I did come up with a solution for multiple inheritance in PHP, but I’ll explain why I don’t recommend using it. Along the way I learned a lot about multiple inheritance vs. composition in PHP. I’ll do my best to sum up both ideas with really simple example code.
The Problem
I have 3 related objects. I grouped them into pages.
- Page A
- Page B
- Page C
Page A and B can both be filtered.
- Page A is a FilteredPage
- Page B is a FilteredPage
- Page C
Page B and Page C have a form that needs to be checked.
- Page A is a FilteredPage
- Page B is a FilteredPage and a FormPage
- Page C is a FormPage
Since Page A isn’t a FormPage, it doesn’t make sense for it to have the functionality of a FormPage. Since Page C isn’t a FilteredPage, it doesn’t make sense for it to have the functionality of a FilteredPage. In my mind, I figured this would work:
- class Page {}
- class FilteredPage extends Page {}
- class FormPage extends Page{}
- class PageA extends FilteredPage {}
- class PageB extends FilteredPage, FormPage {}
- class PageC extends FormPage {}
Unfortunately, the bolded section doesn’t work. PHP doesn’t support multiple inheritance.
The Not Quite Multiple Inheritance Approach – Decorator-ish
I spent some time smoothing walls with my skull, but eventually I came to a working solution. This allowed me to have multiple classes operating on a single class’s properties at runtime, though it doesn’t really represent multiple inheritance.
class Page { protected $dynamic_data = array(); function __get($name) { if (!empty($this->dynamic_data[$name])) { return $this->dynamic_data[$name]; } return false; } function __set($name, $value) { $this->dynamic_data[$name] = $value; } }
First off, the base class. It has magic __get and __set methods defined, which I see as the major down-point in this scheme.
class PageA extends Page {} class PageB extends Page {} class PageC extends Page {}
The Page definitions.
interface PageDecorator { function process(Page $page); }
The base decorator interface that allows for a generic call.
class FormPageDecorator implements PageDecorator { function process(Page $page) { $page->form = '<form><input type="submit" value="submit"></form>'; } }
class FilteredPageDecorator implements PageDecorator { function process(Page $page) { $page->filters = array('this', 'that', 'other'); } }
These decorators add properties to the class.
$page_a = new PageA(); FilteredPageDecorator::process($page_a); print_r($page_a->filters); //Prints the array of filters.
$page_b = new PageB(); FilteredPageDecorator::process($page_b); FormPageDecorator::process($page_b); print_r($page_b->filters); //Prints the array of filters. print_r($page_b->form); //Prints a form.
$page_c = new PageC(); FormPageDecorator::process($page_c); print_r($page_c->form); //Prints a form.
This is how you’d implement it to get the required results. The super nifty thing about this strategy is that you can add the decorator conditionally, and it takes 1 line of code to add or remove a Decorator. There’s nothing hard-coded into the class. The down-sides are many, the major one being that the magic __get and __set methods on the page make error checking a hassle and it’s a much bigger challenge to lock down the scope.
The Composition Approach
abstract class Page { function __construct() { $this->setup(); } abstract protected function setup(); }
The base Page class. Note that there are no magic methods. Also, the abstracted method implies that the subclasses have to do some work.
class PageA extends Page { public $filteredPage; protected function setup() { $this->filteredPage = new FilteredPage(); } } class PageB extends Page { public $filteredPage, $formPage; protected function setup() { $this->filteredPage = new FilteredPage(); $this->formPage = new FormPage(); } } class PageC extends Page { public $formPage; protected function setup() { $this->formPage = new FormPage(); } }
These are the individual pages. Obviously the features are hard-coded in.
class FilteredPage { protected $filters = array('this', 'that', 'other'); function getFilters() { return $this->filters; } } class FormPage { protected $form = '<form><input type="submit" value="submit"></form>'; function getForm() { return $this->form; } }
These are the filters. They’re arguably simpler than the not-quite-multiple-inheritance approach, but more obvious is that they have very strict get methods. The interface is super obvious and all interactions with the classes are locked down to what I want.
$page_a = new PageA(); print_r($page_a->filteredPage->getFilters()); //Prints the array of filters.
$page_b = new PageB(); print_r($page_b->filteredPage->getFilters()); //Prints the array of filters. print_r($page_b->formPage->getForm()); //Prints a form.
$page_c = new PageC(); print_r($page_c->formPage->getForm()); //Prints a form.
Finally, this is the implementation of the composition approach. It’s a little annoying that you can’t just call $page_a->getFilters(), since the filters are really just supposed to be related to PageC, but the approach feels much more solid. The other relative downsides are that the FormPage and FilteredPage classes can’t assume that their parent class is a Page – they can only operate in their specific scope, whereas in the previous example the Decorators knew exactly what type of class they were working with.
Conclusion: Multiple Inheritance vs. Composition
For this problem, unless you really really need the dynamic capabilities of the multiple inheritance approach or the superclass scope of the decorators, I’d go with composition. It avoids the trauma that the magic __get and __set methods cause, and it forces a much stricter interface and separation of responsibility.
Happy coding :)
Welcome, its 2012. You are discussing things from the last decade, it seems..
Unfortunately I can’t really stand on the shoulders of giants – I’m self-taught and it shows :)
even in 2012, i found your simple explanation of this topic useful and easy to follow. your writing style and approach are applaudable.
All instances of “ in your code have been replaced with HTML entities, makes it kind of hard to read. Just FYI.
:/ will fix ASAP, thanks for the heads up :)
Fixed! Just a checkbox I missed in Crayon.
Awesome page with genuinely good material for readers wanting to gain some useful insights on that topic! But if you want to learn more, check out UY6 about Advertise. Keep up the great work!
This is quality work regarding the topic! I guess I’ll have to bookmark this page. See my website ZQ3 for content about Airport Transfer and I hope it gets your seal of approval, too!