tags:

views:

81

answers:

5

Suppose you are building a web application that is going to be a packaged product one day, one that users will want to be able to extend and customize.

It comes with a core library consisting of PHP files containing classes:

/library/
/library/frontend.class.php
/library/filesystem.class.php
/library/backend.class.php

Now, suppose you want to keep a clean core that users can't patch. Still, you want the user to be able to customize every nut and bolt if need be.

My current idea is to create an autoloading mechanism that, when a class is instantiated, first loads the core include:

/library/frontend.class.php

then, it switches to the user directory and looks whether there is an include of the same name:

 /user/library/frontend.class.php

if one exists, it includes that as well.

Obviously, the user include must contain a class definition that extends the definition in the core include.

Now my question is, how would I instantiate such a class? After all, I can always be sure there is a definition of:

class frontend_core

but I can not be sure there is a

class frontend_user extends frontend_core

However, I would like to be able to rely on, and instantiate, one class name, regardless of whether there was a custom extension to the class or not.

Is there a clever way, idea, or pattern how to achieve this?

Of course, I could write a simple factory helper function that looks for the user class first and then for the core class and returns an initialized object, but I would really like to keep this as clean and simple as possible, because as I said, it is going to be a packaged product.

I am looking for a smart trick or pattern that uses as little code, and introduces as little new functionality, as possible.

A: 

I would implement hooks in the core, so users dont have to hack the core, but are still able to extend the core using hooks

streetparade
@streetparade I am using a hook system already in various places in the app. Overriding or extending the core classes will still be necessary for deeper customizations. I want to offer both possibilities.
Pekka
+3  A: 

Why don't you follow the approach as used by Propel? You generate your base classes and already provide an empty User class (extending the base class) where your users can put their overrides/specific implementation details, and in your code you always refer to the User classes. So basically you just use the inverse of the logic you described.

If the explanation above isn't clear, check out http://propel.phpdb.org/trac/wiki/Users/Documentation/1.4/QuickStart#a6.UsingtheGeneratedSQLandOMFiles and generate code for a small database. The base classes are in the om folder, the (by default empty) user classes are in the root folder.

wimvds
I wanted to keep the user directory empty if there are no customizations, but on the other hand, this is a nice idea. It provides easy entry points for the user to get started, maybe even with code examples and explanations in the empty class files. I will think about it.
Pekka
But it also adds superfluous classes to be loaded when the user does not make use of it, e.g. User_Frontend plus Core_Frontend instead of just Core_Frontend when user chooses to go with the default.
Gordon
A: 

I think it's more complicated with a single filename when you want to use inheritance as well. Basically class user_frontend extends core_frontend has to know where to find both classes. Both must be included.

If you just want to do new Frontend you could use PHP5.3's class_alias to point Frontend to the main class to use. Below 5.3. you could use a ServiceFinder, that knows how to map Service Names to Classes and then get the Frontend with $service->get('frontend') or use a Dependency Injection framework.

Edit I removed the Loader code given before, because it was suffering from exactly this problem.

Gordon
A: 

I'd go with using the constructor of the core class to determine the user class to load, and then implement a factory method in the core class to generate instances of the user class. By making the constructor of the user class protected, and having the user class extend the core class you can be sure that code elsewhere cannot instantiate the user class.

C.

symcbean
A: 

You could have a loader class that will decide which class to instance:

Loader::instance()->load('Frontend')
solomongaby