PHP Autoloading - php

I've been looking around quite a bit and can't find a concise answer to this question anywhere- perhaps those of you with more experience autoloading than myself could shed some light. Pretty simple question:
I wrote a simple autoloader:
function __autoload($className){
$className = BASEDIR . "include/class/" . str_replace("_", "/", $className) . ".php";
require_once($className);
}
Where BASEDIR is the base project directory. Works great including classes- go figure.
However, when I go to include classes that extend other classes, it starts to break.
Per the suggestions on this site, I'm using PEAR naming conventions for my classes. So my "Event" class (describing an actual event like a party) lives here:
include/class/Event/Event.php
The actual class name is "Event".
When autoloading the class, I reference it like so:
new Event_Event();
The autoloader turns that into:
"include/class/Event/Event.php";
The problem comes when Event is extended by some other class utilizing this naming convention.
I'm using, for example:
class EventInstructed extends Event_Event
The autoloader knows where to look, but since my Event class name is "Event" and not "Event_Event", it is trying to look for the wrong class and can't find it.
The question therefore is, how do I utilize the pear naming conventions correctly, or should I do extra parsing in my autoloader to fix my issue? Explode the string on underscores, grab the class name and use that instead?
Naming conventions are here:
http://pear.php.net/manual/en/standards.naming.php
Thanks!!
edit
My final solution was as mentioned in the accepted answer. Classes are in the hierarchy as such:
include/class/Event/Event.php
include/class/Event/Lesson.php // <-- extends Event
Inside the Event file:
abstract class Event_Event{
//Class code....
}
Inside the Lesson file:
class Event_Lesson extends Event_Event{
//Class code....
}
And finally, the __autoload function:
define("BASEDIR", __DIR__ . "/");
function __autoload($className){
$className = BASEDIR . "include/class/" . str_replace("_", "/", $className) . ".php";
echo $className . "<br / >";
require_once($className);
}

You should always add the namespace like this:
class Event_Instructed extends Event_Event
The file name should be Instructed.php in this case.

You should specify a namespace... Like this:
namespace Event
{
abstract class Event {}
class Lesson extends Event {}
}
this shold work just fine with your autoload function.
P.S.: __autoload function is deprecated. Try making this:
function Autoload ( $Subject )
{ /* ... */ }
spl_autoload_register('Autoload');
This way you'll not need the str_replace in your Autoload function...
You'll also be able to extend in other namespaces..
namespace OtherNamespace
{
class AnotherEvent extends Event\Event {}
}

Related

How to check if Autoload loads only used classes?

Uisng spl_autoload_register for auto loading classes on my code. Example:
<?php
spl_autoload_register(function ($class_name) {
include 'my_class1.php';
include 'my_class2.php';
//...
include 'my_classN.php';
});
$obj = new MyClass1();
?>
What if I used just class MyClass1 in my code, does autoload loads all files or just my_class1.php?
Thanks in advance.
EDIT: DO NOT use above code. Now I using #Alex Howansky's code with PSR-4 autoloading specifications.
NOTE: Autoload requires to use namespaces if classes located in subdirectory relatively to basedir (see examples).
With this code, the first time you refer to any class, it will load every class. This will work, but almost certainly isn't what you want. In an autoloader, you usually just want to load only the one source file containing the one class referenced by $class_name. Something like this:
spl_autoload_register(function ($class_name) {
$filename = '/path/to/classes/' . $class_name . '.php';
require_once $filename;
});
This obviously becomes very difficult if your source file names don't match your class names or you otherwise can't determine the source file names based on the class names. This is why you should use PSR-4 naming conventions.
Just as an addition, you can use the PHP function get_declared_classes() which will list all defined classes in the current script.

Ignoring autoloader in certain files

I have this function below that autoloads classes before registering them as new classes.
function __autoload($controller){
$ce = explode('\\', $controller);
require ROOT . '/app/base/classes/' .
end($ce) . '/class.' .
end($ce) . '.php';
}
How can I ignore this autoloader for one class? The reason behind this is because I installed a package and the class file is another directory to what my class files are in...
Try to require your package file containing the class you need, before calling new OneClass().
For example, if you have [ROOT]/app/base/classes/OneClass/class.OneClass.php alongside with [ROOT]/app/custom/packages/OneClass.php, you may:
require ROOT . '/app/custom/packages/OneClass.php';
$class = new OneClass(); // will be an instance of /app/custom/packages/OneClass.php
But the best solution is using namespaces, as described in PSR-0 and PSR-4 recommendations.
And when you use namespaces, and you have correct directory/file structure/names, and you have correctly described namespaces inside your classes:
$my_class = new \app\base\OneClass();
// The same class name without any conflicts
$class_from_package = new \app\custom\packages\OneClass();
I think it's a more useful approach. You will have some logical directories/files structure which will reflect namespace structure. And if you want load the same class name from other folder, you just will be able to do it by using fully qualified class name with correspond namespace.
It can be a bit weird, to use namespaces, if you did not use them before. You need to support correlations between file path and namespace definition all the time (when moving class file to another folder, for example), but after some time of using it, you will pay no more attention to namespaces than you are paying to tying your shoes before going outdoor.
You can do something like this:
function __autoload($controller){
$ce = explode('\\', $controller);
$ignore=array('class_1', 'class_2');
if (!in_array(end($ce), $ignore)) {
require ROOT . '/app/base/classes/' .
end($ce) . '/class.' .
end($ce) . '.php';
}
}

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...

Class autoloader in wordpress plugin

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

Why namespace is passed to spl_autoload_register and how can I get rid of it?

I'm currently doing this but it doesn't look right to me:
spl_autoload_register(function ($class_name){
$class_name = str_replace('MyNameSpace\\', '', $class_name . '.php');
require $class_name;
});
Please advise.
The namespace is passed because it has to be. How else would the autoloader function know the difference between Foo\Bar and Baz\Bar? :-)
Your method looks okay if you're absolutely sure that you won't ever need to load classes with the same names as those found in MyNameSpace. The canonical method to autoloading classes involves using the parts of the namespace as file system structure, so that, for example, foo\bar\Baz can be found at path foo/bar/Baz.php.

Categories