Autoloader appears to be omniscient | Nested includes not needed - php

I use this simple autoloader code:
function __autoload( $class_name ) { include 'class.' . $class_name . '.php'; }
and I make this call new SDB();
SDB actually inherits from SDBOne which I never include...yet the auto-loader loads it.
This means it knows to load modules nested / included in other modules.
So from the looks of it I never need to require or include anything if I use the auto-loader.
However, if I use a stand alone module where there is no auto-loader, I then need to include the class it inherits from.
Should I use
require,
require_once,
include,
include_once.
I would guess to go with require_once because I want an error not a warning...plus when I use the code with the autoloader I don't want it loaded twice.
Basically I have 2 types of use for SDB : one with the autoloader present and one with out.
Reference
http://php.net/manual/en/language.oop5.autoload.php

You are correct, require_once would be the correct way to include a parent class or dependent class in one file. That way if its included multiple times, the require_once prevents errors that would arise from redeclaring a class.
The autoloader is autoloading SDBOne automatically because it needs that class defined before it can extend SDB from it. The same thing is happening to autoload the parent class on demand as happens when you try to load the inherited class.
Also, you should consider switching to spl_autoload_register so that your code will work well with other code that may use an autoloader. The SPL autoloader supports multiple autoloaders and creates a stack in the order they are registered. This way if the first autoloader doesn't satisfy the requirement, it keeps trying subsequently registered autoloaders until the class is loaded or cannot be found.
Another note on preventing errors, you may want to change your autoload function as follows:
function __autoload( $class_name ) {
$file = 'class.' . $class_name . '.php';
if (file_exists($file)) {
include $file;
}
}
Because if the class being autoloaded doesn't exist, you will get errors about including a non-existent file. This is especially important when using spl_autoload_register; you don't want your autoloader to emit unnecessary warnings about missing files if another autoloader will be ultimately responsible for loading the class in question.
Hope that helps.

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.

Force autoloading of a specific class

This previous question shows how to force autoloading of all classes.
However I need to force autoloading of just one class on its own. How could I do that?
It mustn't:
Involve changing the class's source code
Rely on any part of the class's source code (methods, variables, modifiers e.g. it must be free to change from concrete to abstract without affecting this).
Also, preferably it would not involve coding the class's name as a string. (To help with IDE refactoring and so on).
The best option I have found so far would be to just use spl_autoload_call():
spl_autoload_call("Jodes\\MyClass");
or for non-namespaced classes:
spl_autoload_call("MyClass");
I had the same need recently. Doing require_once was not an option because the class really needed to be located by the autoloader because of some more complex rules, there was no way to know exactly the path to the file with the class.
Although the function spl_autoload_call($classname) is designed to do precisely this, it suffers from a fundamental flaw: it will throw a FATAL ERROR in case you call it twice for the same classname or even if some of the child classes of that class were already loaded. This happens because it's not possible to redeclare classes in PHP:
<?php
spl_autoload_call('TheClass');
// no problem
spl_autoload_call('TheClass');
// PHP Fatal error: Cannot declare class TheClass, because the name is already in use in ... on line ...
My solution for this problem is no rely on the side-effect of class_exists($classname) which was not designed for this purpose, but is more configurable and, therefor, offers more control about triggering the autoloader.
Even better, it has absolutely no problem on multiple calls or if something was already loaded in the inheritance chain. It simply has the side-effect (if you want) of requiring the file, if the class is not there yet!
<?php
class_exists('TheClass');
// no problem
class_exists('TheClass');
// already exists. No need to autoload it and no fatal errors!
With this you have a safe and idempotent way to load the class through the autoloader.
And, if you don't want to hard-code the string with the classname in there, from PHP version 5.5 onward, you can use the ::class pseudo constant, which is resolved at compile time to the string with the fully qualified classname (including namespaces):
<?php
class_exists(TheClass::class);
I had exactly the same problem and tried different autoload functions. On a Windows PHP 8.1.5 version I had these two different autoloaders:
set_include_path(get_include_path() . PATH_SEPARATOR . 'class/');
spl_autoload_extensions('.class.php');
spl_autoload_register();
or
$autoloader = function( string $class_name )
{
$filename = $_SERVER["DOCUMENT_ROOT"].'/class/' . str_replace( '\\', '/', $class_name) . '.class.php';
require_once $filename;
};
// our class loader
spl_autoload_register( $autoloader );
which seemingly do the same thing.
As one class file in my application also create some supporting global function variables that can be used during construction of the said class I needed to force the class file loading before accessing said functions variables and construction. I did this with the class_exist() on the specific class.
Using the spl_auoload_extenions version this worked fine; using the spl_autoload_register($autoloader) version causes an undefined variable when using the function variables.

Can php spl_autoload_register & composer autoloader work together?

After a little bit of research and have been unable to locate a solution to my problem. I am utilizing an API that is namespaces that I downloaded via composer. The API has it dependences that I allow composer to manage and autoload for me. Separate from this I have about 10 classes that I have autoloaded with utilizing php's spl_autoload_register. Recently, I started mixing the classes to finish up part a project and the whole thing has gone to crap. My custom classes cannot use the composer classes and visa versa. Is there a method I can use to autoload classes that are in two separate folders and that are loaded with two separate outloader.
Here is the code that I currently use. The vender/autoload.php is no different then your typical composer autoloader. Thanks for any assistance.
require 'vendor/autoload.php';
require 'functions/general.php';
require 'include/mailgun.php';
function my_autoloader($class) {
require 'classes/' . $class . '.php';
}
spl_autoload_register('my_autoloader');
Well, actually composer utilizes spl_autoload_register, so answer is 'yes', they can. The autoloading mechanism is supported by autoloader stack - basically, if class didn't appear in runtime after one autoloader has been run, next one is used, until stack runs out of autoloaders and PHP reports an error about a class it can't find. Every spl_autoload_register call basically adds new autoloader, so there may be plenty of autoloaders in memory. The main point of this story is autoloader that can't load class does nothing, the autoloading block of code simply ends with no action taken so next autoloader may take responsibility of class loading. The only thing you need to implement is check in your autoloader that it can handle current class loading (checking that file exists before requiring it is enough, though you should think about possible directory nesting in case of namespaces), and everything should work smooth in future:
function my_autoloader($class) {
$path = sprintf('classes/%s.php', $class);
if (file_exists($path)) {
require 'classes/' . $class . '.php';
}
}
After further research due to ideas that the both #Etki and #Sarah Wilson gave me I came up with the following solution. Thank You both for your input.
require 'vendor/autoload.php';
require 'functions/general.php';
require 'include/mailgun.php';
function autoLoader ($class) {
if (file_exists(__DIR__.'/classes/'.$class.'.php')) {
require __DIR__.'/classes/'.$class.'.php';
}
}
spl_autoload_register('autoLoader');
Hint: I added the __DIR__ to the beginning of the file paths inside the autoLoader function.
If this is for a small project where you are including files from two or three folders you can use a simple if statement.
function my_autoloader($class) {
if(file_exists('functions/'.$class.'.php')){
require 'functions/'$class.'.php';
} else if(file_exists('vendor/'.$class.'.php')){
require 'vendor/'.$class.'.php';
} //add more as needed
}
spl_autoload_register('my_autoloader');
For larger applications, I recommend naming your classes files and your classes for what they are or do, and have them in a specific folder by their names Example: controllers/userController.php for userController class functions/generalFunctions.php generalFunctions class then you can check the names and if it has controller then you include it from the controllers folders. Create an array within the my_autoloader function or in a separate file like.
loadMap.php
return [
'controller' => 'path/to/controllers/',
'function' => 'path/to/functions/',
'helper' => 'path/to/helpers',
'etc' => 'path/to/etc/'
]
filewithautoloadfunction.php
function my_autoloader($class){
$loadMap = require 'path/to/loadMap.php';
foreach($loadMap as $key => $path){
if(stripos($key, $class){
require_once $path.$class.'.php';
}
}
}
spl_autoload_register('my_autoloader');

inheritance and external files. How do I go about it?

I have 3 files.
vehicleClass.php
motorbikeClass.php (extends vehicleClass)
index.php
My question is... How do I connect all 3. On my index page, do I have to have
include 'classes/vehicleClass.php';
Do I need to have an include for the motorbike class or will the extend (inheritence) cover this?
You can let php autoload your files, by registering your own autoload function. If you have, in example, all your class files in the directory DOCROOT . 'classes' (DOCROOT being your document root), you can use a function like this (example):
function class_loader($class) {
include DOCROOT . 'classes/' . $class . '.class.php';
}
spl_autoload_register('class_loader');
Now if you try to create an object of class 'foo', it will try to include the file DOCROOT . '/classes/foo.class.php' before creating the object. You might want to extend the function a bit, eg. to lowercase file names (include DOCROOT . 'classes/'. strtolower($class) .'.class.php';). This way you can place class Foo in foo.class.php.
Short answer: a class that is extending (or otherwise using) another class must already have defined the parent class before the definition of the child class. Your assumption is correct, your VehicleClass must be included (or better, require'd) prior to your definition of MotorBike class.
However, most frameworks don't go about and include every depedency before all class definitions. This would become unwieldy on any system that has any amount of complexity to it. Instead, the developers of PHP have provided methods for autoloading classes. Using spl_autoload_register will allow you to write a function that will attempt to load in the source file for a given class whenever it is referenced but a definition for it has not yet been found.
Furthermore, once you get a system together that becomes complex, you don't want to store all of your files in a single place. Most frameworks leverage the filesystem and namespaces to help better organize all of their classes. Because of this, the PSR-0 standard was developed in order to help facilitate autoloading between frameworks. Take a look at this question for examples of PSR-0 compliant autoloaders.
Example of PSR-0 compliant class:
<?php namespace Vendor\Package;
class ClassName { }
This file would live in the filesystem at /Vendor/Package/ClassName.php
What you have to do is include 2 files in the index.php.
For example, your index.php page could be something like this.
<?php
require 'classes/vehicleClass.php';
require 'classes/motorbikeClass.php';
// Assuming your class name is MotorBike
$motorBike = new MotorBike();
// And just call the method you want, for example If you have a method called bikeName
echo $motorBike->bikeName();
?>
I hope you get an idea now.
P/S: I prefer require over include. :) Include() should work fine too.

cascading use namespace in php with child classes

I am using an aliased and namespaced class in a parent class successfully but it doesn't seem to be available in the child class. The actual error is from the autoloader. The weird thing is that the function does work in the parent class and loads fine. How can I make a class brought in by use available in subclasses?
edit: the recipes are stateless -- would it make sense to make them singletons in Base and then reference them as members in the child class MyTest?
I have the two files:
Base.php:
namespace selenium;
use selenium\recipe\Cms as Cms;
class Base extends \PHPUnit_Framework_TestCase
{
public function __construct()
{
Cms::staticfunc(); //works fine
}
}
MyTest.php:
class MyTest extends \selenium\Base
{
public testMyTest()
{
Cms::staticfunc(); //errors here
}
}
From comment:
i was hoping for a way to cascade the use without duplicating that line among the 20 or so child classes
That is one of the biggest issues I have with PHP namespacing, that you have to call use for every file the current script needs access to. It's the same situation we used to face having to call require_once 20 times on some scripts in order to bring in the necessary libraries.
What I prefer to do is namespace my files (as they reside on the filesystem, like Zend Framework does) and use an autoloader to avoid the whole mess. I currently use ZF autoloader, which can be used outside of the framework, or you can also use the vanilla PHP implementation using SplAutoload.
-- Update --
I have a library which I have written over the last few years which is namespaced as Hobis_Api, and are located on the filesystem with the same convention; ~/projects/projects/dp/hobis/lib/Hobis/Api/*. In order to register the namespace with Zend_Loader I do the following:
// Be sure to set the include path to include the Zend and Hobis_Api files
// Not sure how your setup is, but would look something like:
set_include_path(get_include_path() . ':' . DIRNAME(__FILE__));
require_once 'Zend/Loader/Autoloader.php';
$autoloader = Zend_Loader_Autoloader::getInstance();
$autoloader->registerNamespace(
array(
'Hobis_Api_'
)
);
Normally the above code would go into some bootstrap file, which you can call from a centralized script in order to register the autoloader, once.
Now, if your include path is set correctly, anytime you reference Hobis_Api_* it will be autoloaded for you, so you don't need to call use or require_once, example usage:
// SomeScript.php
// Notice no requires
// I can make a call to Hobis_Api_Image without error
$image = Hobis_Api_Image;
$image->setHeight(400);

Categories