I am fiddling around with autoloading, and trying to make a file structure that abides to the PSR-0 Standard. However I am getting this error:
Warning: require(GetMatchHistory\api.php): failed to open stream: No such file or directory in C:\xampp\htdocs\Test\Test.php on line 15
Fatal error: require(): Failed opening required 'GetMatchHistory\api.php' (include_path='.;C:\xampp\php\PEAR') in C:\xampp\htdocs\Test\Test.php on line 15
And here is my file structure:
I am using Test PHP with this autoloader function:
function autoload($className)
{
$className = ltrim($className, '\\');
$fileName = '';
$namespace = '';
if ($lastNsPos = strrpos($className, '\\')) {
$namespace = substr($className, 0, $lastNsPos);
$className = substr($className, $lastNsPos + 1);
$fileName = str_replace('\\', DIRECTORY_SEPARATOR, $namespace) . DIRECTORY_SEPARATOR;
}
$fileName .= str_replace('_', DIRECTORY_SEPARATOR, $className) . '.php';
require $fileName;
}
spl_autoload_register('autoload');
$Query = new GetMatchHistory_api();
This function is copy-pasted from the suggested PSR-0 function here
Am I misunderstanding with regards to how the structure should be? The class within "api.php" is GetMatchHistory_api
Edit: Another problem
I have used the suggested answer by MrCode, however now I am having an issue where the autoloader wont load a class that is in another directory.
use Classes\Queries\History\GetMatchHistoryAPI;
use Classes\Queries\History\GetMatchHistoryBASE;
use Classes\Utilities\send;
When I am calling the send class, from within a function inside GetMatchHistoryAPI, I am receiving the error:
Warning: require(Classes\Queries\History\send.php): failed to open stream: No such file or directory in C:\xampp\htdocs\Test\Test.php on line 15
However, as you can tell from the above image, the send class is not in that file path. Why is this error occurring?
Based on that structure, you would need to rename the class to Classes_Queries_GetMatchHistory_api and change the code that instantiates it to:
$Query = new Classes_Queries_GetMatchHistory_api();
The reason for this is your Test.php resides at the root and the api class is in the directory Classes/Queries/GetMatchHistory.
Example using namespaces instead of the underscore method:
api.php:
namespace Classes\Queries\GetMatchHistory;
class api
{
}
Test.php:
spl_autoload_register('autoload');
$Query = new Classes\Queries\GetMatchHistory\api();
Or using use:
use Classes\Queries\GetMatchHistory\api;
spl_autoload_register('autoload');
$Query = new api();
To address your comment:
I was going to have the same class names, but under a different folder (so GetMatchHistory - api.php and GetMatchDetails - api.php). I guess this would make it too ambiguous when calling the classes however
Namespaces are designed to solve this very problem. Namespaces allow you to have classes with the same name (but in different namespaces) and avoid any conflicts.
As an example, you have an api class under both GetMatchHistory and GetMatchDetails.
File: Classes/Queries/GetMatchHistory/api.php
namespace Classes\Queries\GetMatchHistory;
class api
{
public function __construct(){
echo 'this is the GetMatchHistory api';
}
}
File: Classes/Queries/GetMatchDetails/api.php
namespace Classes\Queries\GetMatchDetails;
class api
{
public function __construct(){
echo 'this is the GetMatchDetails api, I am separate to the other!';
}
}
File: Test.php (usage example)
spl_autoload_register('autoload');
$historyApi = new Classes\Queries\GetMatchHistory\api();
$detailsApi = new Classes\Queries\GetMatchDetails\api();
If you like, you can give an alias instead of typing out the whole fully qualified namespace:
use Classes\Queries\GetMatchHistory\api as HistoryApi;
use Classes\Queries\GetMatchDetails\api as DetailsApi;
$historyApi = new HistoryApi();
$detailsApi = new DetailsApi();
As you can see, namespaces make it possible to have multiple different classes with the same name, without having conflicts or making it ambiguous.
Related
I'm having a problem with PHP autoload with a specific class, where it keeps saying it's not found.
Error:
Warning: require_once(Models/BaseModel.php): failed to open stream: No such file or directory in /var/www/html/icompare/init.php on line 29
Fatal error: require_once(): Failed opening required 'Models/BaseModel.php' (include_path='.:/usr/share/php:/var/www/html/icompare/lib') in /var/www/html/icompare/init.php on line 29
How my autoload is started in init.php:
set_include_path(get_include_path().PATH_SEPARATOR.APP_ROOT_PATH.DIRECTORY_SEPARATOR.'lib');
spl_autoload_extensions(".php");
spl_autoload_register(function($className){
$className = str_replace('\\', DIRECTORY_SEPARATOR, $className);
$file = $className.".php";
// echo 'Loading file: '.$file.PHP_EOL;
require_once $file;
});
My folder structure:
BaseModel class (it's not the full file):
<?php
/**
* Model Base CRUD Banco de Dados
*/
namespace Models;
class BaseModel {
protected $tableName = null;
public function find($value, $field = 'id', $fieldType = \PDO::PARAM_STR){
if(!isset($this->tableName) || empty($this->tableName)){
return null;
}
$DB = new \DB;
$sql = sprintf("SELECT * FROM %s WHERE %s = :value", $this->tableName, $field);
$stmt = $DB->prepare($sql);
$stmt->bindParam(':value', $value, $fieldType);
$stmt->execute();
Somehow PHP is looking for a "Models/" capitalized folder?
Have you guys ever experienced that before?
You're generating the filename from the classname by doing a string replace of backslash to slash:
$className = str_replace('\\', DIRECTORY_SEPARATOR, $className);
Since the classname you're trying to load is \Models\BaseModel, the filename that gets generated is .../Models/BaseModel.php. However, your file's actual name is .../models/BaseModel.php, so the require fails because unixy operating systems have case sensitive filenames. If you follow PSR-4 (which you should) then your namespaces should match your directories and your files should match your classes. Thus, the class \Models\BaseModel should be defined in a file named .../Models/BaseModel.php.
I read on SO and experiment with some answers but my code does not work:
I have two classes: C:\Apache24\htdocs\phpdb\classes\dbconnection\mysqlconnection\MySqlConnection.php
and C:\Apache24\htdocs\phpdb\classes\utilities\mysqlutilities\CreateTableDemo.php.
In CreateTableDemo I have the following code:
namespace utilities\mysqlutilities;
use dbconnection\mysqlconnection\MySqlConnection as MSC;
spl_autoload_register(function($class){
$class = 'classes\\'.$class.'.php';
require_once "$class";
});
I get the following warning:
`Warning: require_once(classes\dbconnection\mysqlconnection\MySqlConnection.php): failed to open stream: No such file or directory in C:\Apache24\htdocs\phpdb\classes\utilities\mysqlutilities\CreateTableDemo.php on line 10`.
I understand the warning, the script does not find the namespaced class in the same folder, so I changed the spl_autoload_register to look for a relative path: __DIR__."\\..\\..\\classes\\.$class.'.php'. I get the
warning: `Warning: require_once(C:\Apache24\htdocs\phpdb\classes\utilities\mysqlutilities\..\..\classes\dbconnection\mysqlconnection\MySqlConnection.php): failed to open stream: No such file or directory in C:\Apache24\htdocs\phpdb\classes\utilities\mysqlutilities\CreateTableDemo.php on line 10`.
I cannot find a way to direct the script to the namespaced class.
Thanks in advance for any help.
Create a autoloader-class in a separate file:
class Autoloader {
static public function loader($path) {
$filename = __DIR__.'/classes/'.str_replace("\\", '/', $path).".php";
if (file_exists($filename)) {
include($filename);
$aClass = explode('\\', $path);
$className = array_pop($aClass);
if (class_exists($className)) {
return TRUE;
}
}
return FALSE;
}
}
spl_autoload_register('Autoloader::loader');
And include it in you index file (or whatever).
It will load all your namespaced classes located in folder "classes".
require_once '/PATH_TO/autoload.php';
BTW: the trick is to replace the backslashes to regular slashes.
Works fine for me.
EDIT: Place the autoloader.php at same level like your "classes" folder is. :-)
It's failing because the path to the class is wrong relative to where you are requiring from. Try:
namespace utilities\mysqlutilities;
use dbconnection\mysqlconnection\MySqlConnection as MSC;
spl_autoload_register(function($class){
$exp = explode('classes', __DIR__);
$base = reset($exp);
$class = $base."classes".DIRECTORY_SEPARATOR.$class.".php";
require_once $class;
});
I am using classes with Wordpress and I am trying to autoload them in my functions.php file:
spl_autoload_register(function($class) {
include('classes/'.$class.'.php');
});
This is what my classes directory looks like:
classes/
project/
Application.php
Core.php
Site.php
Helpers.php
utils/
Helpers.php
Twig.php
views/
Layout.php
Modules.php
pages/
Home.php
Each class is namespaced based on the directory it is in. For example:
$homeClass = new \views\pages\Home();
When I autoload the classes, I get this error:
PHP Warning: include(classes/project\Application.php): failed to open stream: No such file or directory
Obviously the backslashes that are part of the namespacing don't work in the path. I could update my function to replace the backslashes with forward slashes like this:
spl_autoload_register(function($class) {
include('classes/'.str_replace("\\", "/", $class).'.php');
});
But it seems odd that that would be required. Am I missing something?
I have an example here.
Basically a better version of spl_autoload_register since it only tries to require the class file whenever you initializes the class.
Here it automatically gets every file inside your class folder, requires the files and initializes it. All you have to do, is name the class the same as the file.
index.php
<?php
require_once __DIR__ . '/app/autoload.php';
$loader = new Loader(false);
User::dump(['hello' => 'test']);
autoload.php
<?php
class Loader
{
public static $library;
protected static $classPath = __DIR__ . "/classes/";
protected static $interfacePath = __DIR__ . "/classes/interfaces/";
public function __construct($requireInterface = true)
{
if(!isset(static::$library)) {
// Get all files inside the class folder
foreach(array_map('basename', glob(static::$classPath . "*.php", GLOB_BRACE)) as $classExt) {
// Make sure the class is not already declared
if(!in_array($classExt, get_declared_classes())) {
// Get rid of php extension easily without pathinfo
$classNoExt = substr($classExt, 0, -4);
$file = static::$path . $classExt;
if($requireInterface) {
// Get interface file
$interface = static::$interfacePath . $classExt;
// Check if interface file exists
if(!file_exists($interface)) {
// Throw exception
die("Unable to load interface file: " . $interface);
}
// Require interface
require_once $interface;
//Check if interface is set
if(!interface_exists("Interface" . $classNoExt)) {
// Throw exception
die("Unable to find interface: " . $interface);
}
}
// Require class
require_once $file;
// Check if class file exists
if(class_exists($classNoExt)) {
// Set class // class.container.php
static::$library[$classNoExt] = new $classNoExt();
} else {
// Throw error
die("Unable to load class: " . $classNoExt);
}
}
}
}
}
/*public function get($class)
{
return (in_array($class, get_declared_classes()) ? static::$library[$class] : die("Class <b>{$class}</b> doesn't exist."));
}*/
}
You can easily manage with a bit of coding, to require classes in different folders too. Hopefully this can be of some use to you.
It's not odd at all!
Registering your autoloaders using namespaces as a means to denote a directory structure is fairly common practice. I would recommend following PSR-4 conventions, but if you you're too far in to refactor your naming conventions, then I would recommend using the DIRECTORY_SEPARATOR constant to help PHP decide whether to use '/' or '\', as Windows servers use the latter, and Linux servers use the former.
I've made a class for this requirement, compatible with PSR-4.
You can reach it here:
https://github.com/pablo-pacheco/wp-namespace-autoloader
The explanation is all there but basically it's a composer dependency. You just have to require it in your project:
"require": {
"pablo-pacheco/wp-namespace-autoloader": "dev-master"
}
And then call the class
<?php
new \WP_Namespace_Autoloader( array(
'directory' => __DIR__, // Directory of your project. It can be your theme or plugin. __DIR__ is probably your best bet.
'namespace' => __NAMESPACE__, // Main namespace of your project. E.g My_Project\Admin\Tests should be My_Project. Probably if you just pass the constant __NAMESPACE__ it should work
'classes_dir' => 'src', // (optional). It is where your namespaced classes are located inside your project. If your classes are in the root level, leave this empty. If they are located on 'src' folder, write 'src' here
) );
I appreciate any kind of feedback!
Try this class, it autoloads with or without namespaces
This question already has answers here:
How do I use PHP namespaces with autoload?
(13 answers)
Closed 6 years ago.
I am trying to understand how to define a working autoloader for a trivial application with namespaces.
I created following structure:
public
index.php
src
Models
WordGenerator.php
Obviously, later it will grow. The WordGenerator.php file is following:
<?php
namespace Models;
class WordGenerator {
public $filename;
public function __construct($filename) {
$this->_filename = $filename;
}
}
Now I try to create instance of this object in index.php:
<?php
use \Models;
$wg = new WordGenerator("words.english");
Obviously I get fatal error because I did not define autoloader. However though, according to documentation all I can pass is $classname. How should I define the autoloader function to take namespace declared by use statement then??
[edit]
I was wrong understood a moment ago. So here's my code for index.php:
spl_autoload_register(function ($className) {
$className = ltrim($className, '\\');
$prepend = "..\\src\\";
$fileName = "{$prepend}";
$namespace = '';
if ($lastNsPos = strrpos($className, '\\')) {
$namespace = substr($className, 0, $lastNsPos);
$className = substr($className, $lastNsPos + 1);
$fileName = $prepend.str_replace('\\', DIRECTORY_SEPARATOR, $namespace) . DIRECTORY_SEPARATOR;
}
$fileName .= str_replace('_', DIRECTORY_SEPARATOR, $className) . '.php';
require $fileName;
});
//require '../src/Models/WordGenerator.php';
use Models;
$wg = new WordGenerator("words.english");
echo $wg->getRandomWord();
Now, this does not work and I get:
Warning: The use statement with non-compound name 'Models' has no effect in C:\xampp\htdocs\Hangman\public\index.php on line 22
Warning: require(..\src\WordGenerator.php): failed to open stream: No such file or directory in C:\xampp\htdocs\Hangman\public\index.php on line 16
Fatal error: require(): Failed opening required '..\src\WordGenerator.php' (include_path='.;C:\xampp\php\PEAR') in C:\xampp\htdocs\Hangman\public\index.php on line 16
However when I change new WordGenerator to new \Models\WordGenerator it works. Now, the question is: how can I pass namespaces declared by use statement to autoloader function to make it work properly?
Your use statement is incorrect. You want use Models\WordGenerator;
The use operator doesn't declare anything but an alias. Consider the expanded form:
use Models\WordGenerator as WordGenerator;
Which is equivalent.
You can alias a namespace rather than a class, but it doesn't work in the way you're attempting. For example:
use Models as Foo;
$wg = new Foo\WordGenerator("words.english");
Would work. However:
use Models;
Is equivalent to:
use Models as Models;
Which effectively does nothing.
For more information see Using namespaces: Aliasing/Importing in the PHP manual.
Please look at documentation. Autoloader must be defined by you at beginning of you script. At provided link there is example autoloader and more complex solution
PHP 5.3.3-pl1-gentoo (cli) (built: Aug
17 2010 18:37:41)
Hi all, I use a simple autoloader in my project's main file (index.php):
require_once("./config.php");
require_once("./app.php");
require_once("./../shared/SqlTool.php");
function __autoload($className) {
$fn = 'file-not-exists-for-{$className}';
if (file_exists("./specific/php/{$className}.php")) { $fn = "./specific/php/{$className}.php"; } else
{ $fn = "./../shared/{$className}.php";}
require_once($fn);
}
$sql = new SqlHD(); // class SqlHD, in ./specific/php/SqlHD.php extends SqlTool
$web = new HTMLForm($sql); // class HTMLForm in HTMLForm.php
$app = new App($sql, $web); // class App in App.php
$app->Main();
The problem: without that require_once("./../shared/SqlTool.php");, script can't execute SqlHD.php, because it can't find SqlTool.php by itself, and for some reason it doesn't uses autoload routine defined in main file.
I tried this:
spl_autoload_register(__NAMESPACE__ .'\Test::load');
class Test {
static public function load($className){
$fn = 'file-not-exists-for-{$className}';
if (file_exists("./specific/php/{$className}.php")) { $fn = "./specific/php/{$className}.php"; } else
{ $fn = "./../shared/{$className}.php}";}
echo realpath($fn);//"$curRealDir Filename $fn\n";
echo "\n";
require_once($fn);
}
}
Well,
PHP Warning:
require_once(./../shared/SqlTool.php}):
failed to open stream: No such file or
directory in
/home/beep/work/php/hauthd/index.php
on line 20 PHP Fatal error:
require_once(): Failed opening
required './../shared/SqlTool.php}'
(include_path='.:/usr/share/php5:/usr/share/php')
in
/home/beep/work/php/hauthd/index.php
on line 20
So it doesn't reacts to any request from extended class.
Last second idea: put spl_autoload_register to each file. But cannot put it to "extends" directive itself!
P.S. May rewrite SqlTool.php using Factory pattern so it would automatically return an instance of project-specifc class, but it seems to be not a best way, or it is..?
If SqlHD extends SqlTool, then your __autoload() function should include this automatically.
Note you have an extra '}' in your filename which is probably messing this up. (Which you have also copy 'n' pasted into your 2nd code snippet.)
{ $fn = "./../shared/{$className}.php}";}
As an aside, I think you only need to require() inside your __autoload() function, rather than require_once(), since your __autoload() function is only called if it has not already been loaded.
[Edit: removed incorrect relative path suggestion - w3d spotted the real problem. Leaving the rest here just for info]
Also you can change the require_once in the autoload function to just require - by definition the function will only run if the class has not already been included.
You could greatly simplify your autoload by utilising the include path, as then PHP would check the different locations for you. E.g. something like this:
set_include_path(
realpath('./specific/php') . PATH_SEPARATOR .
realpath('./../shared') . PATH_SEPARATOR .
get_include_path()
);
function __autoload($className) {
require "$className.php";
}