Class autoloader in wordpress plugin - php

I want to write a class autoloader to use in a wordpress plugin. This plugin will be installed on multiple sites, and i want to minimize the chance of conflicts with other plugins.
The autoloader will be something like this:
function __autoload($name) {
//some code here
}
My main issue is, what happens if another class also uses a function like this? I think it will be bound to give problems. What would be the best way to avoid something like that?
I am trying to not use namespaces so the code will also work on previous versions of php.

Use some implementation like this one.
function TR_Autoloader($className)
{
$assetList = array(
get_stylesheet_directory() . '/vendor/log4php/Logger.php',
// added to fix woocommerce wp_email class not found issue
WP_PLUGIN_DIR . '/woocommerce/includes/libraries/class-emogrifier.php'
// add more paths if needed.
);
// normalized classes first.
$path = get_stylesheet_directory() . '/classes/class-';
$fullPath = $path . $className . '.php';
if (file_exists($fullPath)) {
include_once $fullPath;
}
if (class_exists($className)) {
return;
} else { // read the rest of the asset locations.
foreach ($assetList as $currentAsset) {
if (is_dir($currentAsset)) {
foreach (new DirectoryIterator($currentAsset) as $currentFile)
{
if (!($currentFile->isDot() || ($currentFile->getExtension() <> "php")))
require_once $currentAsset . $currentFile->getFilename();
}
} elseif (is_file($currentAsset)) {
require_once $currentAsset;
}
}
}
}
spl_autoload_register('TR_Autoloader');
Basically this autoloader is registered and has the following features:
You can add a specific class file, if not following a specific pattern for the location of the files containing your classes (assetList) .
You can add entire directories to your search for your classes.
If the class is already defined, you can add more logic to deal with it.
You can use conditional class definition in your code and then override your class definition in the class loader.
Now if you want want to do it in OOP way, just add the autoloader function inside a class. (ie: myAutoloaderClass) and call it from the constructor.
then simply add one line inside your functions.php
new myAutoloaderClass();
and add in the constructor
function __construct{
spl_autoload_register('TR_Autoloader' , array($this,'TR_Autoloader'));
}
Hope this helps.
HR

I recommend using namespaces, and PSR-4. You could simply copy this autoloader example from FIG.
But if you don't want to, you can use an autoloader like this one, that defines an convention for WP classes names, and uses that to find the class file.
So, for example, if you call a 'Main' class, then this autoloader will try to include the class file from path:
<plugin-path>/class-main.php

Related

Require dynamic class using namespace in PHP

I was studying a couple PHP frameworks and then decided to build my own, of course. But i'm facing one issue. I have a Router class that handles dynamically the HTTP requests and it basically explodes the URL into elements dividing it by the slash and storing it into an array, then a function is called to check if the first element is a valid Controller. If it is valid, the function should require it, but that's where i'm stuck, because it seems that i can't require a file like:
if (file_exists(CONTROLLERS_DIR . $this->url[0] . '.php')) { require \App\Controllers\$this->url[0] }
How can I require a file like that using namespaces?
Thanks.
"How can I require a file like that using namespaces?"
You can't. Namespaces have nothing to do with it.
"PHP Namespaces provide a way in which to group related classes, interfaces, functions and constants." ~ Namespaces overview
require is about file dependancies, regardless the namespace:
if (file_exists(CONTROLLERS_DIR . $this->url[0] . '.php')) {
require(CONTROLLERS_DIR . $this->url[0] . '.php');
}
EDIT: You might want to instantiate a class using a namespace and class name retrieved in run-time though, i.e. something like:
namespace \App\Controllers;
class C {
protected $_i;
public function __construct($i){ $this->_i = $i; }
public function foo(){ echo $this->_i; }
}
and somewhere:
$className = "C"; // or $className = $this->whatever...
$class = "\\App\\Controllers\\".$className;
$instance = new $class(7);
$instance->foo(); // outputs 7
I've built couple of frameworks and I understand what you trying to do...
Basically when you have some path for example "HelloWorld\addComment"
You want to create controller instance
\App\Controllers\HelloWorldController
There are multiple ways to solve it, the one I like is:
Using spl autoloader
http://php.net/manual/en/function.spl-autoload.php
In the link I provided you got the examples you need.
Then you can end up just doing
$controller = new \App\Controllers\HelloWorldController();
You should put the HelloWorldController at the right namespace + maintain directory structure matching the namespace
app
Controllers
HelloWorldController
spl autoloader will do the right including for you, often the default implementation is sufficient - but it is easy to create your own spl autoloader and register it
Later you can test if the $controller has the method you need via method_exist or reflection...

avoid redeclaring an interface for more one time

I have an interface which I named Manager, and two classes UtilisateurManager and StageManager, and the both of them implements the Manager interface.
In each class I included the interface Manager as the following :
require '../helpers/Manager.class.php';
Then I needed to use these two classes at once in a php script, but I got the error that I can't redeclare class Manager.
I tried to work with the function class_exists() but it's not useful in my case.
How can I solve this problem ?
Maybe you can use require_once instead of require.
require_once '../helpers/Manager.class.php';
You need to implement a simple autoloader which will try to load class/interface only if it isn't already loaded, and forget about direct require/include then. It is considered good practice to leave the filesystem details out of the class file: you may reuse it in other project where your directories system might change, so you'll need to edit the class again.
A simple example:
class Autoloader
{
protected $directories = array();
public function register()
{
// register 'loadClass' method as an autoloader
spl_autoload_register(array($this, 'loadClass'));
}
public function addSource($dir)
{
$this->directories[] = $dir;
}
public function loadClass($className)
{
foreach ($this->directories as $dir) { // search in every registered sources root
$fileName = $dir.DIRECTORY_SEPARATOR.$className.'.class.php';
if (file_exists($fileName) {
include($fileName);
return; // halt when file was found
}
}
}
}
// bootstrap file
$autoloader = new Autoloader;
$autoloader->register();
(spl_autoload_register documentation)
Please note that this realization will cycle through the folders, and this is certainly not the desired solution. The best option today is to build project according to PSR-4 spec - autoloader implementation will have to check existence of a single file only. It may be a little hard to dive into namespaces without any preparation, but once you get familiar with this coding style, you will forget about direct file load.
The last thing, i want to warn you about your naming convention - your Interface is stored in a .class.php file, and this is confusing.

Autoloaders in PHP - two running at a time

I understand how to register autoloaders and even how to create one, that's no issue at all. How ever the main issue is - how do you have two auto loaders running side by side for something like:
class project_one_folder_class extends project_two_folder_class{}
You'll notice that the child class belongs to a project which is reaching out and calling the parent class which is locate in a different project.
The way the projects are linked project two's classes are always seen by the auto loader, how ever project one's classes are never seen.
So the way I thought around this was to write two auto loaders and register them because php will look in on then the other. How ever php seems to be looking in only one and not the other.
how would you solve this?
Edit
Project two is parent, Project one is child. This is more expanded question then What was posted on this question.
To better expand this is my class.
class AisisCore_Loader_AutoLoader{
protected static $_instance;
public function get_instance(){
if(null == self::$_instance){
self::$_instance = new self();
}
return self::$_instance;
}
public function reset_instance(){
self::$_instance = null;
}
public function register_auto_loader(){
spl_autoload_register(array($this, 'load_class'));
spl_autoload_register(array($this, 'load_child_class'));
}
public function load_class($class){
$path = str_replace('_', '/', $class);
if(file_exists(get_template_directory() . '/' . $path . '.php')){
require_once(get_template_directory() . '/' . $path . '.php');
}
}
public function load_child_class($class){
$path = str_replace('_', '/', $class);
if(file_exists(get_stylesheet_directory() . '/' . $path . '.php')){
require_once(get_stylesheet_directory() . '/' . $path . '.php');
}
}
}
Currently this class will load anything in the parent project. It will even load parent project objects in the child project. How ever no child object can be loaded using this class as it is not found.
Those familiar with WordPress will instantly say, yes its because you have get_template_directory when you want get_stylesheet_directory How ever - Knowing this - I want to write then two auto loaders, one that will load child projects objects using get_stylesheet_directory and then one that will load parent objects via get_stylesheet_directory so that:
class project_one_folder_class extends project_two_folder_class{}
works and loads, with out error.
This is a little rough, but you actually only need one autoloader that just checks within multiple directories:
abstract class Hakre_Theme_AutoLoader
{
public static $directories;
public static function init()
{
// configure paths
self::$directories = array(
get_template_directory(), // current theme, parent in case of childtheme
get_stylesheet_directory(), // current theme, child in case of childtheme
);
// please double check if you really need to prepend
return spl_autoload_register('Hakre_Theme_AutoLoader::autoload', true, true);
}
public static function autoload($class)
{
$filename = str_replace('_', '/', $class) . '.php';
foreach (self::$directories as $directory) {
$path = $directory . '/' . $filename;
if (is_file($path)) {
require($path);
return; # leave because the class has been loaded
}
}
}
}
Hakre_Theme_AutoLoader::init();
## If you're unsure all worked out properly:
var_dump(Hakre_Theme_AutoLoader::$directories);
This should actually do it, however I've not tested it. See the var_dump, you can debug if the correct directories are registered or not. No need to have multiple callbacks only because you want to check in two directories.
Well, of course you could just look up a second path when the first file_exists returns false - in the very same autoloader.
You can also register a second autoloader for the second path.
PHP docs addresses this:
If there must be multiple autoload functions, spl_autoload_register() allows for this. It effectively creates a queue of autoload functions, and runs through each of them in the order they are defined. By contrast, __autoload() may only be defined once.
Separate the autoloading process!
PHP is working like this: At some point, the code execution stumbles upon a class reference that is not yet loaded. $obj = new project_one_folder_class()
Now the registered autoloader functions are called, starting with the one added first (unless the ones added later are using the "prepend" parameter).
The first (and all subsequent) autoload function gets the name of the class that should be loaded. The function now has the task to decide
Is it it's task to load this class? If not, do nothing, let the other functions try to load it.
If this function is responsible to load the class, try to make up a path and filename out of the known class name, and require this file.
Step 1 is usually solved by looking if the class name to be loaded starts with a certain prefix. In your case, the autoloader responsible for loading classes starting with "project_one_" should NOT try to load any other classes starting with something else.
Step 2 takes place if the autoloader knows that it must perform the autoloading. It now transforms the class name into a relative path and filename, adds the base directory, and then requires the file.
Requiring a file that defines a class which extends another class (or implements interfaces) can then trigger another run of autoloading with any still unknown classes. In your case, the autoload process is started again with the class name "project_two_folder_class". Note that the first autoloader for "project_one_" classes will be called again with this second class name, but it must not do anything, because it does not know how to load these classes. This is up to the autoloader that knows about "project_two_" classes.
Order of registration should not matter for the different autoloaders. If it does, autoloaders misbehave.
Because it is a common pattern to simply transform any underscore characters in class names into DIRECTORY_SEPARATOR, as well as the backslashes from namespaces, add ".php" at the end, and then try to load this file from a defined base directory, the code for two of these autoloaders will be identical, and the problem is reduced to configuring only one autoload function with these two cases: Which prefix should be present, and in which base directory should the file be expected to be, provided that the class name can be transformed into a relative path to the file.
Checking in every case whether the file exists in a certain directory or not is not optimal for performance. You'll end up doing plenty of checks for files that you will be able to know you cannot load by simply looking at the class name.
And by the way: Autoloader functions do not need to be singletons. In fact, your singleton implementation is broken, and useless. Instantiate the autoloader with new, maybe pass some configuration values into the constructor, and call spl_autoload_register(), and you should be done.
You can add more than one loader, just register each function with spl_autoload_register().
procedural style:
spl_autoload_register('loader_function1'));
// your second class location
spl_autoload_register('loader_function2'));
or in a OO style
spl_autoload_register(array($this, 'loader_function1'));
spl_autoload_register(array($this, 'loader_function2'));
However based on your question: you probably can accomplish the same thing by having a more flexible autoloader
see my answer in a different question here:
Class autoloader in wordpress plugin

multiple spl_autoload_register

what is/are the benefit(s) of having multiple spl_autoload_register
example:
spl_autoload_register('autoload_systems');
spl_autoload_register('autoload_thirdparties');
spl_autoload_register('autoload_services');
vs:
using one
spl_autoload_register('autoload'); or __autoload();
and then do the logic inside the function.
eg:
$ftp = new systems_ftp();
$services = new services_cron_email();
If you have one __autoload() and you include a third party library which also had one, it would be clobbered.
Registering multiple autoloads with spl_autoload_register() ensures you can have your code dropped in with existing code (think libraries, etc) without clobbering or shadowing of existing/future autoloads.
In addition, the PHP manual states...
spl_autoload_register() provides a more flexible alternative for
autoloading classes. For this reason, using __autoload() is
discouraged and may be deprecated or removed in the future.
I guess no need to add multiple but it's on your logic.
The way spl_autoload_register works is awesome.
Suppose one have 3rd parties directories and that is usually manages through namespace which also represents their path.
Consider this simple class as autoloader class
class MyAutoLoader {
public function __construct()
{
spl_autoload_register( array($this, 'load') );
}
function load( $class )
{
$filepath = 'classes/'.$class.'.php';
require_once( $filepath);
}
}
Then in index file include this loader file and create instance of this class:
$autoload = new MyAutoLoader();
Now you can directly create the instances of classes.
Suppose we have 'Person' class in directory classes\QMS\SERVICES\Person.php and same path is the namespace of that class
$person = new QMS\SERVICES\Person();
Now when when you will use new it will check if this class exists and value as a string will be passes to 'load' function of class 'MyAutoLoader'. and there it will get included. Or you can change the function 'load' to fix the path of your class files put your if conditions or whatever need to b done to fix the paths.

Smarty 3 and spl_autoload_register

I know I need to use spl_autoload_register with Smarty 3. I am registering my autoload function after smarty initializes. But smarty is trying to use my own autoload function instead of the smartyAutoload defined function. Causing an error because it obviously cant find the smarty files using my autoload. Here is the code with everything else cut out to show how it is layed out currently.
I'm sure it is just an order placement issue or something.
<?php
class application {
// include smarty
require_once(SMARTY_DIR.'Smarty.class.php');
// init controller class which initializes smarty
$controller = new Controller();
}
function autoLoader($class) {
// determine what type class it is and call from that directory
$dir = strtolower(strstr($class, '_', true));
$name = substr( strtolower( strstr($class, '_') ), 1 );
switch($dir) {
case 'component':
break;
default:
require_once(LIB_PATH.DS.$name.'.class.php');
break;
}
}
spl_autoload_register("autoLoader");
?>
You can make your autoloader compatbile (that's what spl_autoload_register is actually for) by only require a file if it's one of your classes.
You can do this by checking the class name against a namespace or a whitelist or with is_file (store the whitelist in the file-system):
default:
$path = LIB_PATH.DS.$name.'.class.php';
if (is_file($path)) require_once($path);
break;
I don't know if it's relevant to you, but I tracked my problem down to a file called smarty_internal_templatecompilerbase.php, function callTagCompiler().
My code (inside a *.tpl file) calls a smarty plugin function. My plugin-function is called Load_PO, meaning that the Smarty engine then searches for a class named Smarty_Internal_Compile_Load_PO.
Of course, neither the Smarty auto-loader, nor my custom auto-loader finds this template. The problem, in fact, was that my auto-loader errors when a file is not found.
The problem can be resolved in 2 ways:
Change my auto-loader to ignore searches if (substr($class_name,0,24) = 'Smarty_Internal_Compile_')
Modify file smarty_internal_templatecompilerbase.php, and change function callTagCompiler() as shown below
Change the code
class_exists ($class_name)
To
class_exists ($class_name, false)

Categories