PHP Namespaces autoload - php

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 ;)

Related

Namespaces and spl_autoload_register

I have found several SO questions similar to mine, but am struggling to find an answer that helps me, plus I'd really like to know the best practice for autoloading classes that exist within namespaces.
My folder structure:
root
-- classes
--- Users
---- Users.class.php
And users.php;
<?php
namespace CompanyName\ProjectName\Users;
class UserMapper
{
// class code here
}
And my autoload function, which sits in the root folder;
/* autoload classes on instatiation */
spl_autoload_register(function($class)
{
include $_SERVER['DOCUMENT_ROOT'] . '/classes/' . $class . '.class.php';
});
And, let's say I call the user class like so;
<?php
new \CompanyName\ProjectName\User();
Warning: include(/Applications/XAMPP/xamppfiles/htdocs/test_tool/classes/CompanyName\ProjectName\User.class.php): failed to open stream: No such file or directory in...etc
To use spl_autoload_register, do I need to map my folder structure to my namespace structure? I would prefer not to do this as I like to have my classes in the same folder, with sub folders within.
Or do I add extra code to my autoload function?
I have also searched the php manual, and there is no working namespace example, which I find very strange.
Any help would be much appreciated.
Thanks in advance.
Disclaimer: I am a beginer at php, my answer may not be correct
but i'm confident examining and testing the example bellow will help clarify the use of Namespaces with spl_autoload_register for any beginner like me.
Consider this folder structure :
root/ contains index.php.
root/model/ contains A.php & AA.php
A.php :
?php
namespace company\model;
use \company\model\A;
class A
{
public function speak()
{
echo 'hello world! ';
}
}
AA.php :
?php
namespace company\model;
use \company\model\A;
require_once 'A.php';
class AA extends A
{
public function shout()
{
echo 'HELLO WOORLD!!!';
}
}
index.php :
<?php
namespace company;
use \company\model\A;
function classLoader ($className)
{
if (file_exists($className.'.php'))
{
require_once $className.'.php';
} else {
$className = str_replace('\\', '/', $className);
$className = str_replace('company/', '', $className);
if (file_exists($className.'.php'))
require_once $className.'.php';
else
throw new EXCEPTION('classLoader could not find '.$className.'.php .');
}
}
spl_autoload_register(classLoader);
$obj = new A;
//we dont need to write ($obj = new \company\model\A;)
//because of statement at line 4
$obj->speak();
echo '<br/>';
$objA = new \company\model\AA;
$objA->shout();
echo '<br/>';
class AB extends \company\model\AA
{
public function doBoth()
{
$this->speak();
$this->shout();
}
}
$objB = new AB;
$objB->doBoth();

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');

PHP namespace autoload

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"

zend framework2 how does the autoload function work

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 does a php class determine which class to extend, given there are two class with the same name?

Given I have two User classes, one located in /model/User.php and the other located in /assets/User.php
Now I want to have a class which extend the User class from /model/User.php, yet it seems that php is always looking for the one located in /assets/User.php. Any idea how does php determines which class to extend from?
Use namespaces
user.php:
class User {
function someMethod(){}
}
myUser.php:
namespace My;
class User {
function someMethod(){}
}
And you can use it like this:
include 'user.php';
include 'myuser.php';
$user1 = new User();
$user2 = new \My\User();
You can use namespaces: PHP Manual
You should (if you use) setup appropriately your spl_autoload_register function (http://php.net/manual/en/function.spl-autoload-register.php) to be able autoload the objects in files with same name, bud the names of object can not be the same.
You can distinguish the objects by name and path in which the files are stored.
For instance, you require core class in your bootstrap.php and define autoloading function:
<?php // bootstrap.php
require_once Core.php;
spl_autoload_register(array('Core', 'auto_load'));
$myUserClass = new MyClasses_User() // the required files for the class are loaded because of auto_load, PHP determines the right classes due to naming conventions: DirName_ClassName
?>
<?php // Core.php
define('DIR_SEPARATOR', '/');
class Core {
// Autoloading
public static function auto_load($class, $dir_classes = 'classes')
{
$file = str_replace('_', DIR_SEPARATOR, $class); // replace _ by / for path to file
if ($path = find_file_based_on_class_name($dir_classes, $file))
{
// Load the class file
require $path;
}
}
?>
<?php
// classes/Model/User.php
class Model_User {
// define the class
}
?>
// classes/Assets/User.php
<?php
class Assets_User {
// define the class
}
?>
// classes/MyClasses/User.php
<?php
class MyClasses_User exdends Model_User {
// define the class
}
?>

Categories