I'm trying to build my own framework for internal usage.
I got structure like this:
index.php
boot /
booter.php
application /
controllers /
indexcontroller.php
core /
template.class.php
model.class.php
controller.class.php
cache /
memcached.php
something /
something.php
Booter.php contains: (it's currently working only with files located in core directory):
class Booter
{
private static $controller_path, $model_path, $class_path;
public static function setDirs($controller_path = 'application/controllers', $model_path = 'application/models', $classes_path = 'core')
{
self::$controller_path = $controller_path;
self::$model_path = $model_path;
self::$class_path = $classes_path;
spl_autoload_register(array('Booter', 'LoadClass'));
if ( DEBUG )
Debugger::log('Setting dirs...');
}
protected static function LoadClass($className)
{
$className = strtolower($className);
if ( file_exists(DIR . '/' . self::$model_path . '/' . $className . '.php') )
{
require(DIR . '/' . self::$model_path . '/' . $className . '.php');
}
else if ( file_exists(DIR . '/' . self::$class_path . '/' . $className . '.class.php') )
{
require(DIR . '/' . self::$class_path . '/' . $className . '.class.php');
}
else if ( file_exists(DIR . '/' . self::$controller_path . '/' . $className . '.php') )
{
require(DIR . '/' . self::$controller_path . '/' . $className . '.php');
}
if ( DEBUG )
Debugger::log('AutoLoading classname: '.$className);
}
}
My application/controllers/indexcontroller looks like this:
<?
class IndexController extends Controller
{
public function ActionIndex()
{
$a = new Model; // It works
$a = new Controller; //It works too
}
}
?>
And here comes my questions:
[Question 1]
My code is working currently like this:
$a = new Model; // Class Model gets included from core/model.class.php
How I can implement including files by classes with namespaces? For example:
$a = new Cache\Memcached; // I would like to include file from /core/CACHE/Memcached.php
$a = new AnotherNS\smth; // i would like to include file from /core/AnotherNS/smth.php
and so on. How I can produce the handling of the namespace?
[Question 2]
Is it a good practice to use single autoload for Classes, Controllers and models or I should define 3 different spl_autoload_register with 3 different methods and why?
I normally have a bootstrap.php file inside a folder conf that is in the application root. My code is normally located inside an src folder, also located in the root, so, this works fine for me:
<?php
define('APP_ROOT', dirname(__DIR__) . DIRECTORY_SEPARATOR);
set_include_path(
implode(PATH_SEPARATOR,
array_unique(
array_merge(
array(
APP_ROOT . 'src',
APP_ROOT . 'test'
),
explode(PATH_SEPARATOR, get_include_path())
)
)
)
);
spl_autoload_register(function ($class) {
$file = sprintf("%s.php", str_replace('\\', DIRECTORY_SEPARATOR, $class));
if (($classPath = stream_resolve_include_path($file)) != false) {
require $classPath;
}
}, true);
You can generalize this into your "Booter" class and append directories to the include path. If you have well defined namespace names, there will be no problems with colisions.
Edit:
This works if you follow PSR-1.
Question 1:
In your autoloader, change the \ (for the namespace) into DIRECTORY_SEPARATOR. This should work:
protected static function LoadClass($className)
{
$className = strtolower($className);
$className = str_replace('\\', DIRECTORY_SEPARATOR, $className);
...
}
Always use DIRECTORY_SEPARATOR, especially if the software has the potential to be used on other platforms.
Question 2:
I would use one and separate your classes by namespace. However, I think that comes down to how you want to structure your framework and how you separate your code. Someone else may be able to better answer that question.
Related
This question already has answers here:
Best Way To Autoload Classes In PHP
(8 answers)
Closed 1 year ago.
I'm learning to use php autuoloader...
As much as I understand, we can use __autoloader or spl_autoloader_* to auto load files.
Assume this is my directories structure :
ROOT
|
|
ADMIN
| |
| |
| DIST
| |
| SOME_FOLDER
| SOME_FOLDER
| TPL
| |
| |
| SOME_FOLDER1
| |
| test.php
| SOME_FOLDER2
| |
| example1.php
| example2.php
| example3.php
|
|
CLASSES
|
basics.php
class1.php
class2.php
class3.php
class4.php
|
index.php
I made this class for autoload files in CLASSES directory :
basics.php :
class MyAutoLoader
{
public function __construct()
{
spl_autoload_register( array($this, 'load') );
}
function load( $file )
{
//spl_autoload( 'load1' );
echo 'Try to call ' . $file . '.php inside ' . __METHOD__ . '<br>';
require( $file . '.php' );
}
}
and in index.php I will include basics.php and every thing is fine for files are stored in CLASSES folder...
require_once("CLASSES/basics.php");
$loaderObject = new MyAutoLoader();
with this code, I can declare class1 ... class3
Now I want to have an autoloder that could be load files in SOME_FOLDER2 which in this case are example1.php , example2.php and example3.php
I tried some cases but the files in SOME_FOLDER2 won't be load using autoloader.
My attempts :
I made a function named load2 in MyAutoLoader class that try to include files from SOME_FOLDER2
function load2( $file )
{
//spl_autoload_register('load2');
echo 'Inside LOADER2 ' . __METHOD__ . '<br>';
require ( 'ADMIN/TPL/' . $file . '.php' );
}
And I changed spl_autoload_register in MyAutoLoader constructor :
$allMethods = get_class_methods( 'MyAutoLoader' );
$allMethods = array_splice( $allMethods, 1 );
foreach( $allMethods as $method )
{
spl_autoload_register( array($this, $method) );
}
But none of them didn't work for me...
Would you please tell me what's wrong in my code or what is my misunderstanding about auloader?
Thanks in Advance
I think your problem basically boils down to not checking if the file exists before requireing it... that will produce a fatal error if the file doesn't exist in the first folder that you attempt to include from.
I don't know your use case, but here are some suggestions:
Can you just use a single autoloader?
function my_autoload($class_name) {
if (is_file('CLASSES/' . $class_name . '.php')) {
require_once 'CLASSES/' . $class_name . '.php';
} else if (is_file('ADMIN/TPL/SOME_FOLDER2/' . $class_name . '.php')) {
require_once 'ADMIN/TPL/SOME_FOLDER2/' . $class_name . '.php';
}
}
spl_autoload_register("my_autoload");
Or if you need to declare them independently (ie. two or more autoloaders):
function classes_autoload($class_name) {
if (is_file('CLASSES/' . $class_name . '.php')) {
require_once 'CLASSES/' . $class_name . '.php';
}
}
spl_autoload_register("classes_autoload");
function admin_autoload($class_name) {
if (is_file('ADMIN/TPL/SOME_FOLDER2/' . $class_name . '.php')) {
require_once 'ADMIN/TPL/SOME_FOLDER2/' . $class_name . '.php';
}
}
spl_autoload_register("admin_autoload");
Or make your autoloader class generic:
class MyAutoLoader {
private $path;
public function __construct($path) {
$this->path = $path;
spl_autoload_register( array($this, 'load') );
}
function load( $file ) {
if (is_file($this->path . '/' . $file . '.php')) {
require_once( $this->path . '/' . $file . '.php' );
}
}
}
$autoloader_classes = new MyAutoLoader('CLASSES');
$autoloader_admin = new MyAutoLoader('ADMIN/TPL/SOME_FOLDER2');
If you don't want to manually keep the list of child folders inside ADMIN/TPL up to date you could even do something like this to autoload from any of them (this obviously assumes that all subfolders of ADMIN/TPL contains classes):
function my_autoload($class_name) {
if (is_file('CLASSES/' . $class_name . '.php')) {
require_once 'CLASSES/' . $class_name . '.php';
} else {
$matching_files = glob('ADMIN/TPL/*/' . $class_name . '.php');
if (count($matching_files) === 1) {
require_once $matching_files[0];
} else if (count($matching_files) === 0) {
trigger_error('Could not find class ' . $class_name . '!', E_USER_ERROR);
} else {
trigger_error('More than one possible match found for class ' . $class_name . '!', E_USER_ERROR);
}
}
}
spl_autoload_register("my_autoload");
Your problem may actually be as simple as this:
Your basics.php is inside the folder CLASSES, so when you include or require files from within basics.php it starts in that folder.
So if you want to include files from ADMIN/TPL/SOME_FOLDER2 you actually need to "jump" up one level first, ie require '../ADMIN/TPL/SOME_FOLDER2/' . $file . '.php';
To avoid this kind of confusion I would recommend adding a constant to the beginning of index.php like this:
define('BASEPATH', __DIR__);
...and then prepending that to all require and include statements - ie:
class MyAutoLoader {
public function __construct() {
spl_autoload_register( array($this, 'load') );
spl_autoload_register( array($this, 'load2') );
}
function load( $file ) {
echo 'Try to call ' . $file . '.php inside ' . __METHOD__ . '<br>';
$classfile = BASEPATH . '/CLASSES/' . $file . '.php';
if ( is_file( $classfile ) ) {
require( $classfile );
}
}
function load2( $file ) {
echo 'Try to call ' . $file . '.php inside ' . __METHOD__ . '<br>';
$classfile = BASEPATH . '/ADMIN/TPL/SOME_FOLDER2/' . $file . '.php';
if ( is_file( $classfile ) ) {
require( $classfile );
}
}
}
I use of spl_autoload_register for class auto loading like bellow
spl_autoload_register(array($this, 'mainLoader'));
function mainLoader($class) {
$dirs = explode(CLASS_SEPARATOR, $class);
$dirsLen = count($dirs);
$class_name = $dirs[$dirsLen - 1];
if ($dirsLen > 1) {
$paths = array_slice($dirs, 0, $dirsLen - 1);
$path = ROOT . DIRECTORY_SEPARATOR . implode(DIRECTORY_SEPARATOR, $paths) . DIRECTORY_SEPARATOR;
set_include_path(get_include_path() . PATH_SEPARATOR . $path);
} else {
set_include_path(get_include_path() . PATH_SEPARATOR . ROOT);
}
spl_autoload_extensions(".php");
spl_autoload($class_name);
}
input class name can be class, dir_class, dir_dir_class, ...
and file can be ROOT/class.php Root/dir/class.php , Root/dir/dir/class.php, ...
but when I run program Error to me
Fatal error: Class 'class' not found in ...
why my auto loader don't work correctly??
note: this function work good in Windows but don't work in Linux Ubuntu 14.04
You're probably using camelCase names to your files.
Unix is case sensitive in file names and spl_autoload will work just with lower case.
I have a file that auto loads each of my classes.
This is what it contains:
spl_autoload_register(function($class){
require_once 'classes/' . $class . '.php';
});
require_once 'functions/sanitize.php';
require_once 'functions/hash.php';
But when I require_once this file from another php file that is inside my ajax folder, it will try looking for the classes, the function will look from my classes with the path: main_folder/ajax/classes instead of just main_folder/classes.
Does anyone know how to fix this?
FIX:
spl_autoload_register(function($class){
if (file_exists('classes/' . $class . '.php')) {
require_once 'classes/' . $class . '.php';
}
elseif (file_exists('../classes/' . $class . '.php')) {
require_once '../classes/' . $class . '.php';
}
elseif (file_exists('../../classes/' . $class . '.php')) {
require_once '../../classes/' . $class . '.php';
}
You should simple use this function just once - in the main file (usually index.php) and not in another files.
However if it's not possible (but I don't see any reason when could it be not possible) you can change it for example that way:
spl_autoload_register(function($class){
if (file_exists('classes/' . $class . '.php')) {
require_once 'classes/' . $class . '.php';
}
elseif (file_exists( $class . '.php')) {
require_once $class . '.php';
}
});
Here is a proper way, It should universally work.
EDIT: Make sure to specify levels on dirname in order to find the correct path.
spl_autoload_register(function ($classname) {
$file_realpath = dirname(realpath(__FILE__), levels: 1 /* Change that? */) . DIRECTORY_SEPARATOR . dirname($classname);
$classpath = sprintf('%s' . DIRECTORY_SEPARATOR . '%s.php', $file_realpath, basename($classname, '.{php,PHP}'));
if (file_exists($classpath)) {
echo "Loading <b>$classpath</b>...<br>";
require($classpath);
}
});
You need to know the absolute path to the classes directory, then just use a full qualified path to get there.
Make sure your document root is correctly configured with your http server. Furthermore this is usually solved by routing all requests to index.php and deriving the base path (document root) from there. Here is your code modified:
spl_autoload_register(function($class) {
$resolved = $_SERVER['DOCUMENT_ROOT'] . DIRECTORY_SEPARATOR . 'classes' . DIRECTORY_SEPARATOR . $class . '.php';
if(file_exists($resolved)) {
require_once $resolved;
} else {
// make it known to your that a class failed to load somehow
return;
}
})
Here is another implementation that I use for simple projects
I've just begun using autoloader lazy loading in my app, and I'm running afoul of namespacing. The autoloader is trying to load things like new DateTime() and failing. Is there a trick to making my autoloader spcific to only my own namespaced classes?
Here is the code I have currently. I suspect it is a mess, but I'm not seeing just how to correct it:
<?php namespace RSCRM;
class Autoloader {
static public function loader($className) {
$filename = dirname(__FILE__) .'/'. str_replace("\\", '/', $className) . ".php";
if (file_exists($filename)) {
include_once($filename);
if (class_exists($className)) {
return TRUE;
}
}
return FALSE;
}
}
spl_autoload_register('\RSCRM\Autoloader::loader');
Happy to RTM if someone can point to a solid example.
What I use is actually adapted from the autoloader used to Unit Test a few of the AuraPHP libraries:
<?php
spl_autoload_register(function ($class) {
// a partial filename
$part = str_replace('\\', DIRECTORY_SEPARATOR, $class) . '.php';
// directories where we can find classes
$dirs = array(
__DIR__ . DIRECTORY_SEPARATOR . 'src',
__DIR__ . DIRECTORY_SEPARATOR . 'tests' . DIRECTORY_SEPARATOR . 'src',
__DIR__ . DIRECTORY_SEPARATOR . 'install' . DIRECTORY_SEPARATOR . 'src',
);
// go through the directories to find classes
foreach ($dirs as $dir) {
$file = $dir . DIRECTORY_SEPARATOR . $part;
if (is_readable($file)) {
require $file;
return;
}
}
});
Just make sure the array of '$dirs' values point to the root of your namespaced code.
You can also take a look at the PSR-0 example implementation (http://www.php-fig.org/psr/psr-0/).
You might also want to look an into existing autoloader, like Aura.Autoload or the Symfony ClassLoader Component, although those might be overkill depending on what your requirements are.
I hope this helps.
I am trying to autoload classes that are in different folders. Is that possible?
function __autoload($class){
require $class.'.php';
require 'libs/'.$class.'.php';
//this won't work.
}
I want to autoload classes that are either in libs folder or on the root. Any thoughts? Thanks a lot.
First, I suggest you look into spl_autoload_register, which is superior for doing this.
Second, you should use is_file to test whether the file exists, then try to load it. If you require a file that doesn't exist, your script will halt.
spl_autoload_register(function($class) {
if (is_file($class . '.php')) {
require $class . '.php';
} elseif (is_file('libs/' . $class . '.php')) {
require 'libs/' . $class . '.php';
}
});
If you have multiple folders where the file could be, you could do something like this:
spl_autoload_register(function($class) {
$folders = array ('.', 'libs', 'somewhere');
foreach ($folders as $folder) {
if (is_file($folder . '/' . $class . '.php')) {
require $folder . '/' . $class . '.php';
}
if (class_exists($class)) break;
}
});