Currently, I have a pretty average php auto loader loading in my classes. I've come to a point in development where I will need to override a class with another class based on a variable. I'm running a custom SaaS application, and we have the occasional organization that will demand some weird change to the way the system functions. In the past, we've filled up our code with garbage by massive IF statements for orgs, such as
if(ORGID == 'ABCD'){
//do this insane thing
}else{
//Normal code here.
}
So, I've been toying with the idea of a dynamic auto loader. ORGID is one of the very first defines in the application. The entire application is running under a fixed namespace of COMPANY\PRODUCT; Here's a code sample of what I was thinking I could do.
class MyLoader {
static public function load($name) {
$temp = explode('\\',$name);
$class = array_pop($temp);
$name = str_replace('_',DIRECTORY_SEPARATOR,$class);
if(file_exists(ROOT.'includes/_classes/' . $name . '.php')){
include(ROOT.'includes/_classes/' . $name . '.php');
}
}
}
spl_autoload_register(__NAMESPACE__ .'\MyLoader::load');
Since ROOT and ORGID are defined before the autoloader comes into play, I thought about doing this
class MyLoader {
static public function load($name) {
$temp = explode('\\',$name);
$class = array_pop($temp);
$name = str_replace('_',DIRECTORY_SEPARATOR,$class);
if(file_exists(ROOT.'includes/_classes/' . ORGID . '/' . $name . '.php')){
include(ROOT.'includes/_classes/' . ORGID . '/' . $name . '.php');
}elseif(file_exists(ROOT.'includes/_classes/' . $name . '.php')){
include(ROOT.'includes/_classes/' . $name . '.php');
}
}
}
spl_autoload_register(__NAMESPACE__ .'\MyLoader::load');
While this works, I have to copy/paste my entire class into the org specific class file, then make changes. I can't extend the primary class, because the classes share the same name. The only option I've been able to come up with that would allow me to extend my classes in such a way is to never load the base class.
Instead of
$myObj = new myClass();
I call
$myObj = new customMyClass();
and I have a file called customMyClass(); which simply extends myClass without making any changes to it. This way, the auto loader will load customMyClass and then load myClass. If an organization has their own customMyClass in their organization folder, then it will load in that class, which will then properly load in myClass.
While this works, we have hundreds of class files, which would double if we had a custom file for each.
I've seen a couple of examples that use eval to handle similar situations. Is that really the only way to do this type of thing?
UPDATE:
Just so I'm clear, the end goal is so that the thousands of places we've called $myObj = new myClass(); doesn't need to be rewritten.
Related
In relation to WordPress you have two main functions: get_template_directory(), get_stylesheet_directory(), The first of these will look in the theme folder, while the other will look in the child theme directory.
If we combine these, we can make a auto loader, which consists of:
public function get_instance(){
if(null == self::$_instance){
self::$_instance = new self();
}
self::$_directories = array(
get_template_directory(),
get_stylesheet_directory(),
);
return self::$_instance;
}
public function load_class($class){
$path = str_replace('_', '/', $class);
foreach(self::$_directories as $directories){
if(file_exists($directories . '/' . $path . '.php')){
require_once($directories . '/' . $path . '.php');
}
}
}
These functions would obviously belong in a class that is then instantiated. All were doing here is saying, look for a class using naming standards of Folder_SubFolder_ClassName, by substituting '_' for '/' and appending .php to the end. Not that hard.
It works every time. Child theme classes can load parent theme classes and parent theme classes can call each other with out worrying about require_once()
Now lets throw something into the mix. Lets assume your theme uses custom/packages and you have a class in there, the obvious answer is to change the get_instance() to:
public function get_instance(){
if(null == self::$_instance){
self::$_instance = new self();
}
self::$_directories = array(
get_template_directory(),
get_stylesheet_directory(),
get_template_directory() . '/custom/packages/'
);
return self::$_instance;
}
this way classes, a class such as: PackageName_Folder_SubFolder_ClassName{} should be loaded because it resides inside custom/packages/PackageName/Folder/SubFolder/ClassName.php and we told the auto loader, to look in these three places for the class.
so whats the issue?
I get an error stating that PackageName_Folder_SubFolder_ClassName doesn't exist or cant be found.
Why? What am I doing, or assuming, wrong?
I assume the issue is due to the location of the /location/packages.
Put an echo in the load_class foreach loop that outputs $directories . '/' . $path . '.php'. I bet this will let you figure it out. You may need to hardcode the public_html path into the autoload feature.
I'm building a script that got a static class used to load few things including files and views.
class load
{
public static function view($file_path, $area)
{
debug::log('getting view <b>' . $area . $file_path . '</b>.');
ob_start();
self::file($file_path, 'areas/' . $area . '/views');
debug::log('flushing view <b>' . $area . $file_path . '</b>.');
eturn ob_get_clean();
}
public static function file($file, $folder)
{
if(is_file($file_path = ROOT . '/' . $folder . '/' . $file))
{
if(require_once $file_path)
{
debug::log('file <b>' . $file_path . '</b> included.');
return true;
}
}
else
debug::kill('requested file <b>' . $file_path . '</b> does not exist.');
}
}
In the controller Im calling the view method to get a view:
$html = load::view('public', 'path/to/view/file.php');
Obviously, Im not able to access the variables from the controller at the view file using this practice, so I did a small modification on the view class to capture the vars:
public static function view($file_path, $area, $vars = array())
And added the following lines of codes to get the keys into vars:
while(list($n_list_var,$v_list_var)=each($vars))
$$n_list_var = $v_list_var;
But again I can't access the vars since Im using a method to load a file.
I have a method to load the files because I wanna test and log each file include attempt and not repeat the code every time I need include a file. And I have the loader view inside the loader class so I have all the methods of this kind together. Should I give up on using a class to load files? Should I use the loader view method on a extendable class from my controller?
Instead of going ahead and modify my entire script I would like to hear some opinions ... what would be the best practice to go? Or is there a way to solve my problem? Maybe using __set and __get magic methods?
Thanks,
Why not just pass a $vars argument to load::file() and extract( $vars ) (possibly moving the vars you use inside file() into class variables to prevent them from being overwritten)?
I'm suggesting using extract() instead of:
while(list($n_list_var,$v_list_var)=each($vars))
$$n_list_var = $v_list_var;
By the way, it would be a good idea to name your class Load.
For instance, say I have the following code.
$foo = new bar();
And an autoloader like this.
function autoload($class_name) {
$class_file_name = str_replace('_', '/', $class_name) . '.php';
if (file_exists($class_file_name)) {
include($class_file_name);
}
}
But the class I really want to load is in the folder 'foo/bar.php', and the real class name is actually foo_bar. Is there a way to dynamically change the name of the class being autoloaded? For instance, something like this?
function autoload(&$class_name) {
$class_name = 'foo_' . $class_name;
$class_file_name = str_replace('_', '/', $class_name) . '.php';
if (file_exists($class_file_name)) {
include($class_file_name);
}
}
I know if something like this is possible, it is not exactly best practice, but I would still like to know if it is.
No. You could load a different file. You could load no file. You could load several files. But after autoloading, PHP expects the class to exist like it was called.
If you call a class X, you can't magically give PHP class Y.
Maybe it's enough to set up the filesystem like that, but still keep literal class names?
PS
I've wanted this for a while too. When I didn't have access to namespaces yet. Now that I do, all my problems are solved =) If you do have access to namespaces, you should 'study' PSR-0.
Maybe you can use class_alias, this way:
PHP requires class X
You want load class Y instead.
You create an alias X for class Y, so X will behave like Y.
E.g.:
function autoload($class_name) {
$real_class_name = 'foo_' . $class_name;
$class_file_name = str_replace('_', '/', $real_class_name) . '.php';
if (file_exists($class_file_name)) {
include($class_file_name);
class_alias($real_class_name, $class_name);
}
}
Hope it can help you.
i made a custom class loader function in php
something like..
load_class($className,$parameters,$instantiate);
its supposed to include the class and optionally instantiate the class specified
the problem is about the parameters. ive been trying to pass the parameters all day
i tried
load_class('className',"'param1','param2'",TRUE);
and
load_class('className',array('param1','param2'),TRUE);
luckily nothing works xD
is it possible to pass the params?
i even tried..
$clas = new MyClass(array('param1','param2'));
here it is..
function load_class($class, $param=null, $instantiate=FALSE){
$object = array();
$object['is_required'] = require_once(CLASSES.$class.'.php');
if($instantiate AND $object['is_required']){
$object[$class] = new $class($param);
}
return $object;
}
if you are in PHP 5.x I really really recommend you to use autoload. Prior to PHP 5.3 you should create sort of "namespace" (I usually do this with _ (underscore))
autoload allows you to include classes on the fly and if your classes are well designed the overhead is minimun.
usually my autoload function looks like:
<?php
function __autoload($className) {
$base = dirname(__FILE__);
$path = explode('_', $className);
$class = strtolower(implode('/',$path));
$file = $base . "/" . $class;
if (file_exists($file)) {
require $file;
}
else {
error_log('Class "' . $className . '" could not be autoloaded');
throw new Exception('Class "' . $className . '" could not be autoloaded from: '.$file);
}
}
this way calling
$car = new App_Model_Car(array('color' => 'red', 'brand' => 'ford'));
the function will include the class
app/model/car.php
Seems to me that you should be using __autoload() to just load classes as they are referenced and circumvent having to call this method manually. This is exactly what __autoload() is for.
I'm getting a "Call to a member function process_data() on a non-object in page.class.php on line 35" even though the object has been called.
Here is the index.php extraction showing the object being instantised
// require our common files
require("modules/module.php");
require("registry/objects/datetime.class.php");
require("registry/objects/page.class.php");
// load in all the objects
$datetime = new dateandtime;
$page = new page;
$module = new module;
It then passes to the Process class
require("template.class.php");
$template = new template($php_path . "controllers/themes/adm/" . $page . ".html");
// Place in both commonly used language and page specific language
$template->language($php_path . "controllers/language/en/adm/common.php");
$template->language($php_path . "controllers/language/en/adm/" . $page . ".php");
// Tell the page's module to load in data it needs
$module->process_data("module_" . $page);
// Output the final result
$template->output();
It's at this point PHP is throwing the error. The contents of the module.php file is as follows
class module {
public function process_data ($child) {
require($child . ".php");
read_data();
return true;
}
}
I've tried moving the instance declaration to within the second pasted code, but that generates more errors, because the class that "module" calls in then uses some of the "template" classes as well - so the same issue occurs just further down the line.
What am I getting wrong her, or completely missing, I'm sure it's the latter but I really need help here. Thanks
It looks to me as if variable $module was not in the scope when you try to call object method. Could you try var_dump($module) before $module->process_data("module_" . $page). What is the result of this function? Quick solution may be declaring $module global, but globals are not a very good idea anyway (but you may check if it works).