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
Related
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.
I wrote an Autoload:
<?php
function autoload($class)
{
try
{
global $path;
$elements = explode("\\", $class);
$name_class = end($elements);
foreach ($elements as $element)
{
if($element != $name_class)
{
$path .= $element."/";
echo "path:".$path."<br/>";
}
}
$file = strtolower(($path).$name_class.".php");
if(file_exists($file))
{
include($file);
return;
}
throw new Exception($class." not founded");
}
catch(Exception $e)
{
echo $e->getMessage();
return;
}
}
class Autoloader
{
public static function autoload($class)
{
autoload($class);
}
}
spl_autoload_register('autoload');
spl_autoload_register(array('autoloader', 'autoload'));
?>
The Autoload finds a relative path and change it in an absolute path.
Now I have problem when I call a class from another file with different namespace.
For example if I have this file Base.php in the directorynamed framework:
namespace Framework;
use Framework\Inspector as Inspector;
class Base
{
private $_inspector;
public function __construct($options = array())
{
$this->_inspector = new Inspector($this);
...
Now I have the file Inspector.php in the same directory with the same namespace:
<?php
namespace Framework;
use Framework\ArrayMethods as ArrayMethods;
use Framework\StringMethods as StringMethods;
use \Exception as Exception;
class Inspector
{
...
?>
When I try to instantiate the class Base, I get the exception message about missing Inspector class.
The same think if I try to extend Base class in other directories. I tried to add some echoes in my autoload function and it printed that he use local namespace of the child class or a class who uses an instance by composition of another class to create the string for finding the php file.
For example when I try the load Inspector in Base with echo I got:
path:Framework/
file:::C:\LightTPD\htdocs\mvc\framework\framework\Base.php (I add some ::: to make more readable)
framework/base.php
path:Framework/Framework/
file:::
framework/framework/inspector.php (trying to use the namespace of inspector after using the one of Base)
Framework\Inspector not foundedpath:Framework/Framework/Framework/
file:::
framework/framework/framework/inspector.php
Framework\Inspector not founded
When I tried to extend class Base by class Driver I got this:
path:Framework/
path:Framework/Configuration/
file:::C:\LightTPD\htdocs\mvc\framework\framework\configuration\Driver.php
framework/configuration/driver.php (everything ok)
path:Framework/Configuration/Framework/ (Framework is from Base; Configuration/Framework is from driver)
file:::
framework/configuration/framework/base.php
Framework\Base not foundedpath:Framework/Configuration/Framework/Framework/
file:::
framework/configuration/framework/framework/base.php
Framework\Base not founded
-###############################################################
The directory tree is:
It's:
-framework: ArrayMethods.php, Base.php,Configuration.php, Inspector.php, StingMethods.php
-configuration: Driver.php
-exception
-core
-exception
How can I fix it?
Sorry I noticed that the problem was the global $path.
Now I wrote the autoload.php in this way:
<?php
function autoload($class)
{
try
{ $path = NULL;
$elements = explode("\\", $class);
$name_class = end($elements);
foreach ($elements as $element)
{
if($element != $name_class)
{
$path .= $element."/";
$file = realpath(strtolower(($path).$name_class.".php")); //realpath is optional
if(file_exists($file))
{
include($file);
//echo $file."<br><br>"; testing in for debugging
return;
}
}
}
throw new Exception($class." not founded");
}
catch(Exception $e)
{
echo $e->getMessage();
return;
}
}
class Autoloader
{
public static function autoload($class)
{
autoload($class);
}
}
spl_autoload_register('autoload');
spl_autoload_register(array('autoloader', 'autoload'));
?>
Now even if it's less perfomant everything it's ok, he work whatever a class call another, it's based only the namespace of a class.
Make sure you include the files path of the class you want to extend(parent class) from to your child class file before the use namespaces of the parent/Base class.
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"
recently I was learning zend framework 2, and there's a problem annoying me for a long time, things look like this:
<?php
namespace Album\Model;
// Add these import statements
use Zend\InputFilter\InputFilter;
use Zend\InputFilter\InputFilterAwareInterface;
use Zend\InputFilter\InputFilterInterface;
class Album implements InputFilterAwareInterface
{
public $id;
public $artist;
public $title;
protected $inputFilter;
public function exchangeArray($data)
{
$this->id = (isset($data['id'])) ? $data['id'] : null;
$this->artist = (isset($data['artist'])) ? $data['artist'] : null;
$this->title = (isset($data['title'])) ? $data['title'] : null;
}
// Add content to these methods:
public function setInputFilter(InputFilterInterface $inputFilter)
{
throw new \Exception("Not used");
}
//....
?>
This code was a section of the "skeleton application" programme, which was a tutorial of ZF2. The first time I see the programme, I don't understand what's the usage of "namespace" and "use", because this two keyword doesn't exist in php5.2(also the same in the earlier edition), so I go to see the manual and try to understand it.I write a programme to simulate what really happens:
<?php
use script\lib\test;
$o = new test();
echo $o->getWelcome();
function __autoload( $className ) {
$classname = strtolower( $classname );
require_once( dirname( __FILE__ ) . '/' . $classname . '.php' );
}
?>
the programme above works well, of course I created two folders named script and lib, and there's a file named test.php.
Seems like every thing is clear, zend framework also has a autoload function, BUT when I noticed the codes in "skeleton application programme", there was a namespace in the beginning, so I adds the namespace to my programme too:
<?php
namespace test;
use script\lib\test;
$o = new test();
echo $o->getWelcome();
function __autoload( $className ) {
$classname = strtolower( $classname );
require_once( dirname( __FILE__ ) . '/' . $classname . '.php' );
}
?>
the page returned me inforamtion as following:
Fatal error: Class 'script\lib\test' not found in E:\wamp\www\test\test_29.php on line 6
I tried to change the namespace's name such as script\lib, script\lib\test...
but it's useless.
Any answer will be appreciated, thanks.
Now I will give you more details about this issue:
To understand the usage of "namespace" and "use", I looked over the materials on php.net:
http://php.net/manual/en/language.namespaces.importing.php
In this page, there was a section of code looks like this:
Example #1 importing/aliasing with the use operator
<?php
namespace foo;
use My\Full\Classname as Another;
// this is the same as use My\Full\NSname as NSname
use My\Full\NSname;
// importing a global class
use ArrayObject;
$obj = new namespace\Another; // instantiates object of class foo\Another
$obj = new Another; // instantiates object of class My\Full\Classname
NSname\subns\func(); // calls function My\Full\NSname\subns\func
$a = new ArrayObject(array(1)); // instantiates object of class ArrayObject
// without the "use ArrayObject" we would instantiate an object of class
?>
Now let's review the programme I write in the above:
<?php
namespace test;
use script\lib\test;
$o = new test();
echo $o->getWelcome();
function __autoload( $className ) {
$classname = strtolower( $classname );
require_once( dirname( __FILE__ ) . '/' . $classname . '.php' );
}
?>
It's the same, I'm trying to simulate that instance, if we don't use the autoload function:
<?php
namespace test;
use script\lib\test;
require_once 'script/lib/test.php';
$o = new test();
echo $o->getWelcome();
?>
It works well too, BUT when I use __autoload function to load the class file, there's something wrong.
I don't konw where's problem, OR any body tried to write an instance to put the "Example #1" into practice? I will wait for your answer.
I think you're misunderstanding what's going on here.
Namespaces allow you to, more or less, create "directories" for your classes. So you can create the \Foo class and the \Test\Foo class (where \ represents the "root" of your application).
The way autoloading works is that your files mirror your namespacing. So foo.php would be in the root of your autoloading but you would create /test/foo.php for \Test\Foo
The use keyword has two uses. One is to alias class files and the other is, in PHP 5.4 or later, to bring in a Trait into your current class.
Now, to your question. First, Let's look at your code
<?php
namespace test;
use script\lib\test;
$o = new test();
echo $o->getWelcome();
This is confusing. You declare a namespace (which you don't need to do here) but then you alias it to script\lib\test. PHP is now looking for a file called /script/lib/test.php, which your error message says doesn't exist. But you said the file does exist so let's look at that
public function getWelcome() {
return 'welcome';
}
This isn't a class. It's a function. For this example you need a complete class
<?php
namespace script\lib;
class test {
public function getWelcome() {
return 'welcome';
}
}
Lastly, let's talk autoloading. You don't need to use use with autoloading. Your autoloader should take care of that for you. You should, however, use spl_autoload_register(), as __autoload() is soon to be depreciated.
From ZF2 docu
Zend\Loader\StandardAutoloader is designed as a PSR-0-compliant autoloader. It assumes a 1:1 mapping of the namespace+classname to the filesystem, wherein namespace separators and underscores are translated to directory separators.
Read more about: PSR-0
So if you're using namespaces the classname that gets send to the autoloader doesn't look like test. It looks like YOUR_NAMESPACE\test. YOUR_NAMESPACE is the namespace that you defined in the class with namespace YOUR_NAMESPACE;
PSR-0 is a standard that says: Your namespace should reflect your filesystem. You only have to replace the backslashes with forward slashes. Or _ with / if you're using pseudo namespaces like in ZF1. (Album_Model_Album)
So output the $className that is sent to your autoloader and you will see..
How can I retrieve a class namespace automatically?
The magic var __NAMESPACE__ is unreliable since in subclasses it's not correctly defined.
Example:
class Foo\bar\A -> __NAMESPACE__ === Foo\bar
class Ping\pong\B extends Foo\bar\A -> __NAMESPACE__ === Foo\bar (it should be Ping\pong)
ps: I noticed the same wrong behavior using __CLASS__, but I solved using get_called_class()... is there something like get_called_class_namespace()? How can I implement such function?
UPDATE:
I think the solution is in my own question, since I realized get_called_class() returns the fully qualified class name and thus I can extract the namespace from it :D
...Anyway if there is a more effective approach let me know ;)
The namespace of class Foo\Bar\A is Foo\Bar, so the __NAMESPACE__ is working very well. What you are looking for is probably namespaced classname that you could easily get by joining echo __NAMESPACE__ . '\\' . __CLASS__;.
Consider next example:
namespace Foo\Bar\FooBar;
use Ping\Pong\HongKong;
class A extends HongKong\B {
function __construct() {
echo __NAMESPACE__;
}
}
new A;
Will print out Foo\Bar\FooBar which is very correct...
And even if you then do
namespace Ping\Pong\HongKong;
use Foo\Bar\FooBar;
class B extends FooBar\A {
function __construct() {
new A;
}
}
it will echo Foo\Bar\FooBar, which again is very correct...
EDIT: If you need to get the namespace of the nested class within the main that is nesting it, simply use:
namespace Ping\Pong\HongKong;
use Foo\Bar\FooBar;
class B extends FooBar\A {
function __construct() {
$a = new A;
echo $a_ns = substr(get_class($a), 0, strrpos(get_class($a), '\\'));
}
}
In PHP 5.5, ::class is available which makes things 10X easier. E.g.
A::class
Use Reflection class.
$class_name = get_class($this);
$reflection_class = new \ReflectionClass($class_name);
$namespace = $reflection_class->getNamespaceName();