PHP namespace autoload - php

Trying to understand how namespaces and autoload works on PHP
Server.php located at core/server.php
namespace core\server
{
class Main
{
public function getTopic()
{
$get_params = $_GET;
if (empty($get_params)) $get_params = ['subtopic' => 'test'];
return $get_params;
}
}
}
and Index.php
spl_autoload_register();
use core\server as subtopic;
$test = new subtopic\Main();
var_dump($test);
It cant load the class core/server/Main

Autoload doesn't work that way.
First I will explain how autoloaders works.
spl_autoload_register() is a function to register a function you have in your code to server as an autoloader, the standard function would be:
define('APP_PATH','/path/to/your/dir');
function auto_load($class)
{
if(file_exists(APP_PATH.$class.'.php'))
{
include_once APP_PATH.$class.'.php';
}
}
spl_autoload_register('auto_load');
The constant APP_PATH would be the path to your directory where your code lives.
As you noticed it the param that is passed to spl_autoload_register is the name of my function, this register the function so when a class is instanciated it runs that function.
Now an effective way to use autoloaders and namespaces would be the following:
file - /autoloader.php
define('APP_PATH','/path/to/your/dir');
define('DS', DIRECTORY_SEPARATOR);
function auto_load($class)
{
$class = str_replace('\\', DS, $class);
if(file_exists(APP_PATH.$class.'.php'))
{
include_once APP_PATH.$class.'.php';
}
}
spl_autoload_register('auto_load');
file - /index.php
include 'autoloader.php';
$tree = new Libs\Tree();
$apple_tree = new Libs\Tree\AppleTree();
file - /Libs/Tree.php
namespace Libs;
class Tree
{
public function __construct()
{
echo 'Builded '.__CLASS__;
}
}
file - /Libs/Tree/AppleTree.php
namespace Libs\Tree;
class AppleTree
{
public function __construct()
{
echo 'Builded '.__CLASS__;
}
}
I'm using namespaces and autoload to load my functions in a nicely way, you can use namespace to describe in what dir your class resides and uses the autoloader magic to load it without any problems.
Note: I used the constant 'DS', because in *nix it uses '/' and in Windows it uses '\', with DIRECTORY_SEPARATOR we don't have to worry where the code is going to run, because it will be "path-compatible"

Related

Oriented Object PHP autoloader issue

I encounterd a little problem with my classes : they simply do not load through my autoloader.
I get this message error :
Warning:
require(C:\wamp64\www\blog\appAutoloader.php/Table/PostsTable.php):
failed to open stream: No such file or directory in
C:\wamp64\www\blog\app\Autoloader.php on line 23
Fatal error: require(): Failed opening required
'C:\wamp64\www\blog\appAutoloader.php/Table/PostsTable.php'
(include_path='.;C:\php\pear') in
C:\wamp64\www\blog\app\Autoloader.php on line 23
Factory class :
use Core\config;
use Core\Database\MysqlDatabase;
class App {
public $title = "My super site";
private $db_instance;
private static $_instance;
public static function getInstance()
{
if (is_null(self::$_instance))
{
self::$_instance = new App();
}
return self::$_instance;
}
public static function load()
{
session_start();
require ROOT . '/app/Autoloader.php';
App\Autoloader::register();
require ROOT .'/core/Autoloader.php';
Core\Autoloader::register();
}
public function getTable($name)
{
$class_name = '\\App\\Table\\' . ucfirst($name) .'Table';
return new $class_name($this->getDb());
}
public function getDb()
{
$config = Config::getInstance(ROOT . '/config/config.php');
if (is_null($this->db_instance)) {
$this->db_instance = new MysqlDatabase($config->get('db_name'), $config->get('db_user'), $config->get('db_pass'), $config->get('db_host'));
}
return $this->db_instance;
}
}
Namespace App autoloader class :
<?php
namespace App;
class Autoloader {
static function register()
{
spl_autoload_register(array(__CLASS__, 'autoload')); // __CLASS__ load the current class
}
static function autoload($class)
{
if (strpos($class, __NAMESPACE__ .'\\') === 0) {
$class = str_replace(__NAMESPACE__ . '\\', '', $class); // _NAMESPACE_ load the current name_space
$class = str_replace('\\', '/', $class);
require __DIR__ . 'Autoloader.php/' . $class . '.php'; // __DIR__ = the parent folder. Here "app"
}
}
}
Namespace Core autoloader class :
<?php
namespace Core;
class Autoloader {
static function register()
{
spl_autoload_register(array(__CLASS__, 'autoload')); // __CLASS__ load the current class
}
static function autoload($class)
{
if (strpos($class, __NAMESPACE__ .'\\') === 0) {
$class = str_replace(__NAMESPACE__ . '\\', '', $class); // _NAMESPACE_ load the current name_space
$class = str_replace('\\', '/', $class);
require __DIR__ . 'Autoloader.php/' . $class . '.php'; // __DIR__ = the parent folder. Here "app"
}
}
}
Empty PostTable
namespace App\Table;
use Core\Table\Table;
class PostsTable extends Table
{
}
Index page :
define('ROOT', dirname(__DIR__));
require ROOT . '/app/App.php';
App::load();
$app = App::getInstance();
$posts = $app->getTable('Posts');
var_dump($posts->all());
How to make it works please?
AS I said in the comments check this path
require(C:\wamp64\www\blog\appAutoloader.php/Table/PostsTable.php)
Doesn't look right to me
require(C:\wamp64\www\blog\ [appAutoloader.php] /Table/PostsTable.php)
What's that bit doing there....
Also namespace of App is not app for the folder its App because this may work on Windows but you will find it does not work on Linux. Because Linux paths are case sensitive, and windows are not.
Further this makes little to no sense
require __DIR__ . 'Autoloader.php/' . $class . '.php'; // __DIR__ = the parent folder. Here "app"
Require 2 files? Paths don't work that way, not that I am aware of at least.
On top of that your implementation ignores the _ Typically underlines will be part of the class name but are replaced by directory, this allows a shorter namespace. So for example instead of having a namespace like this
Namespace \APP\Table;
class PostsTable ..
You could have a class in the same place Like so
Namespace \APP;
class Table_PostsTable ..
With a shorter namespace but still located in the App/Table/PostsTable.php file. However, that's just how I read the spec for PSR autoloaders.
PRO TIP
Take this path C:\wamp64\www\blog\appAutoloader.php/Table/PostsTable.php open the file browser on you desktop and see if it pulls up the file by pasting it into the navigation bar. It wont, but you can be sure your path is wrong by eliminating the code.

How to use roots classes in all namespaces automatically with spl_autoload?

It is possible to make accessible a Class within spl_autoload_register (automatically)?
For example, I using spl_autoload_register in index.php:
<?php
class Utils {
public function example() {
echo 'Hello word!';
}
}
spl_autoload_register(function($class)
{
$relative_class = strtolower(str_replace('\\', '/', $class));
$file = './src/' . $relative_class . '.php';
if (is_file($file)) {
require_once $file;
}
});
$user = new \Controllers\Foo\User;
This new \Controllers\Foo\User; autoload this file ./src/controllers/foo/user.php
user.php:
<?php
namespace Controllers/Foo;
class User
{
public function foo() {
//Something...
}
}
If I need to use a Utils class I'll have to add new \Controllers\Foo\User in the file user.php like this:
public function foo() {
\Utils::example();
}
or
<?php
namespace Controllers/Foo;
use \Utils as Utils;
class User
{
public function foo() {
Utils::example();
}
}
It is possible to make accessible to Utils class within spl_autoload_register (automatically)? I would use without use \Utils as Utils; and without backslash (\Utils::).
You can't. That's the whole point of using namespaces. You can reference a class whitout backslash or use statement only if it is in the same namespace you are using it. You can't hack the autoloader to automagically import the target class into the current namespace changing its namespace on the fly.
If your class doesn't belong to a named namespace, then it is in the global namespace and you still have to use with \ or use. The same for import or use keywords in python, java, go, .net, c/c++, etc.
There is no standard anything to do this besides using use ... as ... or "backslash" (\), however we can "cheat the PHP" using eval() within the spl_autoload_register() to extend the Utils class within the namespace.
Only use this if really necessary, prefer to use "backslash" (\) or use \Utils as Utils
Example (read comments in code):
<?php
class Utils {
public static function example() {
echo 'Hello World!';
}
}
spl_autoload_register(function($class)
{
$relative_class = strtolower(str_replace('\\', '/', $class));
$file = './src/' . $relative_class . '.php';
if (is_file($file)) {
$np = explode('\\', $class); //Dividi string
//Check if class exists in namespace (prevent conflicts)
if (class_exists(implode('::', $np)) === false) {
//Remove "class name", use only "namespace"
array_pop($np);
//evaluate a namespace in eval (extends the Utils class)
eval(
'namespace ' . implode('\\', $np) . ' {' . PHP_EOL .
'class Utils extends \Utils {}' . PHP_EOL .
'}'
);
}
require_once $file;
}
});
$user = new \Controllers\Foo\User;
$user->foo(); //Show "Hello word!"
I admit that it is an ugly hack and maybe I will not use, but it's still a hint yes.
Note: use \Utils as Utils not work in eval

spl_autoload_register loads class twice

I keep trying to know what the problem is with this very simple class loader script.
The class loader looks like this:
#src/vendors/Autoloading/lib/ClassLoader.php
namespace App\Vendors\Autoloading;
class ClassLoader
{
private $path;
function __construct($path)
{
$this->path = $path;
}
public function load($class)
{
if(file_exists( $class = str_replace(array('\\', '_'), DIRECTORY_SEPARATOR, $this->path) . '.php')){
require $class;
return true;
}
}
public function register()
{
return spl_autoload_register([$this, 'load']);
}
}
The initial class loader had more methods and some functions to validate the file names ...
but, in the process of debugging I had to narrow it to that.
So, that class loader is being required inside an autoload.php file, as you can see below.
#src/vendors/autoload.php
namespace App\Vendors;
require 'Autoloading/lib/ClassLoader.php';
$autoload = new Autoloading\ClassLoader('path/Foo/FooClass');
$autoload->register();
The FooClass.php is located in src/Foo/FooClass.php
namespace App\Foo;
class FooClass{}
and there is actually no problem with the autoloading part, the class gets loaded just fine, but it is done twice which shows me the below error. I am calling it from an index.php file
<?php
use \App\Foo\FooClass;
FooClass::somefunction();
Just using that generates this error.
Fatal error: Cannot redeclare class path\foo\FooClass in /path/to/index.php on line 4
Your autoloading function is wrong:
public function load($class)
{
if (
file_exists(
$class = str_replace(
array('\\', '_'),
DIRECTORY_SEPARATOR,
$this->path)
. '.php')
) {
require $class;
return true;
}
}
The function is calles with the name of the class to be loaded (if impossible, the function should do nothing).
What you do is ignoring the class name, and create a new one based on the path the autoloader is created with. This will always load the same file, with the same class, even if there are different classes to be loaded.
And this explains why you get the error, because no matter which class name gets passed, you always include the one file that is related to the path.
You probably want to use a proper PSR-0 or PSR-4 autoloader. I would recommend using the one that comes with Composer, as you are likely to be using Composer sooner or later yourself. Why not starting today?
Check if the class already exists using class_exists before checking for the file
Try using require_once instead of require. Your autoloader won't keep track of what files have been loaded so far.
Here's how I set mine up. I don't use a class. Maybe this will help
function PlatformAutoloader($classname) {
try {
// Change \ to / so namespacing will work
$classname = strtolower(str_replace('\\', DIRECTORY_SEPARATOR, $classname));
if(!#include_once(DIR_CLASSES . DIRECTORY_SEPARATOR . $classname . '.php')) return false;
} catch(Exception $e) {
return false;
}
}
spl_autoload_register('PlatformAutoloader');

__autoload with namespace class gives fatal error for file not found

I was going through the __autoload feature that PHP provides.
However this is the index.php I have:-
define('app_path', realpath('../'));
$paths = array(
app_path, get_include_path());
set_include_path(implode(PATH_SEPARATOR, $paths));
function __autoload($classname)
{
$filename = str_replace('\\', '/', $classname.'.php');
require_once $filename;
}
use \engine\controllers as Controllers;
$data = new Controllers\base(); // This one is line no. 25 (Here is error)
echo $data->mind('Hi');
And this one as my base.php:-
namespace controllers;
class base {
public function __construct() {
echo __CLASS__ . '<br/>';
echo __NAMESPACE__;
}
public function mind($myname)
{
echo $myname;
}
}
and throws this error:
My directory structure is as follows:
app -> engine -> controller -> base.php
app -> index.php
I am not sure whats wrong is happening. I am just learning how to use namespace and __autoload
I also tried spl_autoload_register but did not success. Kindly suggest.
EDIT:1
Also, if i want to replace it with spl_autoload_register how that can be implemented.
Not sure, but worth a try:
In base.php, change to namespace engine\controllers; on line 1.
And in index.php, change to use engine\controllers as Controllers; (remove leading backslash) on line 23.

PHP Namespaces autoload

I have the following directory structure:
/var/www/Project1/Project1.php
/var/www/Project1/User/UserProfile.php
Inside Project1.php:
<?php
namespace Project1;
set_include_path( __DIR__ );
spl_autoload_extensions('.php');
spl_autoload_register();
use User\UserProfile;
$u = new Avatar();
...
?>
Inside UserProfile.php:
<?php
namespace Project1\User;
class Avatar{
}
...
?>
When I execute php Project1.php I get:
PHP Fatal error: spl_autoload9(): Class User\UserProfile could not be loaded
I don't see the problem.
spl_autoload_register(); when called with no params will just register the default autoloader which fails to handle namespaces with your project layout. You'll have to register your own method to make it work. Like this:
spl_autoload_register('my_autoload');
And here comes the autoload function. This function expects the classes to be stored in a way like:
/path/to/project/Namespace/Classname.php
/path/to/project/Namespace/Subnamespace/Classname.php
You can name the classes like \Namespaces\Classname or the old style way Namespace_Classname:
function my_autoload ($classname) {
// if the class where already loaded. should not happen
if (class_exists($classname)) {
return true;
}
// Works for PEAR style class names and namespaced class names
$path = str_replace(
array('_', '\\'),
'/',
$classname
) . '.php';
if (file_exists('/path/to/project/' . $tail)) {
include_once 'path/to/project/' . $tail;
return true;
}
return false;
}
Note that the function is taken from my github package Jm_Autoloader. The package provides more functionality as multiple include paths, path prefixes and static autoloading (with a predefined assoc array class name => file name). You can use it if you like ;)

Categories