Overriding core classes with PHP
No idea if it’s possible what I want, but I’m working on an application that requires custom classes to override the core functionality, if these files exist. So as an example, this is my current file structure:
- app
- core
- User.php (class User)
- custom
- User.php (class User)
- index.php (class Bootstrap with autoLoader)
- core
Now I want to check if “custom/User.php” exists and accordingly include and use this. It should extend the core User class.
My scripts looks like this at this moment:
core/User.php
<?php namespace Core; class User { public function getName() { return 'Pieter'; } }
custom/User.php
<?php namespace Custom; class User extends \Core\User { public function getName() { return 'Mr. '.parent::getName(); } }
index.php
<?php class Bootstrap { public function __construct() { spl_autoload_register(array('Bootstrap', 'autoLoader')); $user = new \Custom\User(); print_r($user->getName()); } private function autoLoader($className) { $fileName = str_replace("\\", "/", strtolower($className)).".php"; if (file_exists($fileName)) { require_once $fileName; } } } new Bootstrap();
The example above works fine, but when I delete the Custom\User class it stops working. This is correct behaviour because I’m trying to make an instance of the \Custom\User class which not exists. But what is the best way to change the autoloader to load the correct Core class?
I think there are 2 possibilities:
- Working with className variables. First check if the file exists and attach the correct namespace+classpath to the variable and keep working with that variable to load the class.
//I know the file_exists example won't work because of //the slashes but this is just an example $className = '\Custom\User.php'; if (!file_exists($className)) { $className = '\Core\User.php'; } $user = new $className();
- Create a static function like getService(‘User’) that handles all discussed matter above and returns the correct class.
public static function getService($classObj) { $className = '\Custom\\'.$classObj.'.php'; if (!file_exists($className)) { $className = '\Core\\'.$classObj.'.php'; } return new $className(); } $user = Register::getService('User');
I think the second option is the best one but is there some easier, more proper way to do this?
Thanks in advance.
You might want to consider using dependency injection. Having a service container which you can configure which class should be used. Your code can then just retrieve the user object from the service container. So obtaining the user would be something like
$container->get(‘user’);
All your code can then assume that there is always a user object. In your bootstrap, you can read a configuration file where you can set which object to use (or you could do this dynamically by checking for the existence of the custom user object and fall back to the core object if the custom one does not exist). But I’d go for the configuration option if I were you.
Fabien Potencier wrote some excellent articles about this (as well as an open source DI component):
http://fabien.potencier.org/article/11/what-is-dependency-injection
http://fabien.potencier.org/article/12/do-you-need-a-dependency-injection-container
http://fabien.potencier.org/article/29/pimple-the-small-dependency-injection-container-for-php-5-3
http://fabien.potencier.org/article/62/create-your-own-framework-on-top-of-the-symfony2-components-part-12
Comment by skoop | 04.20.2012 | 10:13 am
Wat je kan doen is een variabele bijhouden, en die dan in uw bootstrap zetten. Standaard is de variabele de Core class name, in de bootstrap kan je die dan (adhv config vars of gelijk wat) wijzigen in uw custom class.
Comment by Anonymous | 04.20.2012 | 10:39 am
Hi,
Both ways aren’t desirably since they’ll need additional configuration. First one needs dev to setup 2 scenarios for when the file is/isn’t there (and as such is “dangerous” since it may be forgotten); second one needs to configure what stuff to inject in the DI (plus, registries are often being crammed with stuff that may not even be used by the script)
There’s third way, but she ain’t pretty and you probably may not want to use it
Behold:
private function autoLoader($className)
{
$fileName = str_replace(“\\”, “/”, strtolower($className)).”.php”;
if (file_exists($fileName)) {
require_once $fileName;
}
else {
$chunks = explode(‘\\’, $className);
// change path to fetch the core file
$fileName = str_ireplace($chunks[0] . ‘/’, ‘core/’, $fileName);
require_once $fileName;
// incredibly nasty shit to create a bogus class that was called, which is just en extending stub of the core class
eval(‘namespace ‘ . $chunks[0] . ‘; class ‘ . $chunks[1] . ‘ extends ‘ . str_replace($chunks[0] . ‘\\’, ‘\\Core\\’, $className) . ‘ {}’);
}
}
Comment by mlitn | 04.20.2012 | 12:01 pm
As said on twitter, I think a DIC is the best approach to solve this issue.
I’ve created some example code here:
https://gist.github.com/2427632
Basically you use a DIC (see the pimple link in the gist) and add your elements.
This can be done automatically(slower in production) or be put in your configuration(very fast in production but you might forget).
You can always send a mail if you want more info.
Comment by Kim | 04.20.2012 | 12:25 pm
I’m trying the Pimple configuration at this moment, looks great!
Comment by Snakehit | 04.20.2012 | 12:41 pm
Thanks for the advice and @scoop: Thanks for the links.
Comment by CP | 05.7.2012 | 9:09 am
Best Classified Software now in Just 99$, 1Classified Script gives an option to BUYER and SELLER to communicate directly.
http://www.1classifiedscript.com
Comment by Mohsin Butt | 02.22.2013 | 11:27 am