I am trying to put all the peaces together I found about autoloading a class in composer but I can't make it work. Every example I see is missing some part. Basically it comes down to two files with 4 lines:
index.php
$loader = require 'vendor/autoload.php';
$loader->add('Vendor\\', __DIR__.'/../app/');
new Vendor_Package_Obj();
app/Vendor/Package/Obj.php
class Obj {}
I also tried psr-4 and all thinkable combinations of folders and names for `Vendor Package Obj? but no luck finding a working solution.
How can I autoload a file with composer using any of those standards?
According to PSR-4, The fully qualified class name MUST have a top-level namespace name, also known as a "vendor namespace" and underscores have no special meaning in any portion of the fully qualified class name.
Try this:
cd ~
mkdir -p testproj/src/MyApp/Package
cd testproj
composer init && composer update
Create your index.php with this content:
<?php
$loader = require 'vendor/autoload.php';
$loader->add('MyApp\\', __DIR__.'/src/');
$a = new MyApp\Package\Obj();
var_dump($a);
And put the Obj class (src/MyApp/Package/Obj.php) :
<?php
namespace MyApp\Package;
class Obj
{}
Now when you run the code:
php index.php
You should get this as output:
class MyApp\Package\Obj#2 (0) {
}
Also directory scaffolding should look like this:
testproj
├── composer.json
├── index.php
├── src
│ └── MyApp
│ └── Package
│ └── Obj.php
└── vendor
├── autoload.php
└── composer
├── ClassLoader.php
├── autoload_classmap.php
├── autoload_namespaces.php
├── autoload_psr4.php
└── autoload_real.php
Related
I am creating a PHP project and want to implement PSR-4 autoloading.
I don't know which files I need to create in the vendor directory to implement autoloading for class files.
If you are using composer, you do not create the autoloader but let composer do its job and create it for you.
The only thing you need to do is create the appropriate configuration on composer.json and execute composer dump-autoload.
E.g.:
{
"autoload": {
"psr-4": {"App\\": "src/"}
}
}
By doing the above, if you have a file structure like this
├── src/
│ ├── Controller/
│ ├── Model/
│ ├── View/
│ └── Kernel.php
├── public/
│ └── index.php
└── vendor/
After executing composer dump-autoload the autoloader will be generated on vendor/autoload.php.
All your classes should be nested inside the App namespace, and you should put only one class per file.
E.g.:
<?php /* src/Controller/Home.php */
namespace App\Controller;
class Home { /* implementation */ }
And you need only to include the autoloader in your entry-point script (e.g. index.php).
<?php
require '../vendor/autoload.php';
Which will allow you to simply load your classes directly from anywhere after this point, like this:
use App\Controller\Home;
$homeController = new Home();
This is explained at the docs, here.
I am creating a PHP project and want to implement PSR-4 autoloading.
I don't know which files I need to create in the vendor directory to implement autoloading for class files.
If you are using composer, you do not create the autoloader but let composer do its job and create it for you.
The only thing you need to do is create the appropriate configuration on composer.json and execute composer dump-autoload.
E.g.:
{
"autoload": {
"psr-4": {"App\\": "src/"}
}
}
By doing the above, if you have a file structure like this
├── src/
│ ├── Controller/
│ ├── Model/
│ ├── View/
│ └── Kernel.php
├── public/
│ └── index.php
└── vendor/
After executing composer dump-autoload the autoloader will be generated on vendor/autoload.php.
All your classes should be nested inside the App namespace, and you should put only one class per file.
E.g.:
<?php /* src/Controller/Home.php */
namespace App\Controller;
class Home { /* implementation */ }
And you need only to include the autoloader in your entry-point script (e.g. index.php).
<?php
require '../vendor/autoload.php';
Which will allow you to simply load your classes directly from anywhere after this point, like this:
use App\Controller\Home;
$homeController = new Home();
This is explained at the docs, here.
I'm having no end of trouble with setting up class auto-loading with Composer, I've read and watched millions of reference materials at this point & somehow I appear to still be missing something crucial.
Some quick version information:
PHP - v5.6.17
Composer - v1.0-dev (alpha11, global install)
PHPUnit - v4.2.6
Temperament - frustrated
The project layout is relatively simple:
.
├── composer.json
├── framework
│ ├── classes
│ │ ├── Test1.php
│ ├── config
│ │ ├── Test2.php
│ ├── financial.html
│ ├── form.html
│ ├── index.php
│ ├── models
│ │ ├── Base.php
│ ├── sample.html
│ └── services
│ └── validate.service.php
├── phpunit.xml
├── test
│ └── FormTest.php
└── vendor
├── autoload.php
├── composer
│ ├── autoload_classmap.php
│ ├── autoload_files.php
│ ├── autoload_namespaces.php
│ ├── autoload_psr4.php
│ ├── autoload_real.php
│ ├── ClassLoader.php
│ ├── installed.json
│ └── LICENSE
└── constants.php
"composer.json" currently contains the following:
{
"name": "testframework",
"require": {
"PHP": ">=5.6.17"
},
"autoload": {
"psr-4": {
"Config\\": "framework/config/",
"Classes\\": "framework/classes/",
"Models\\": "framework/models/"
},
"files": [
"vendor/constants.php"
]
}
}
Whenever I make changes to the directory structure, rename classes or modify "composer.json" I run:
% composer validate (only when modifying "composer.json")
% composer dump-autoload -o
I'm aiming to auto-load all classes from the "classes", "config" and "models" folders within "framework"; "index.php" currently implements "framework/models/Base.php", this is shown below.
<?php
require_once( dirname( __DIR__ ) . '/vendor/autoload.php' );
use Models\Base;
$derp = new BaseModel();
?>
I've verified that the above points to the correct location & returns: "/var/www/testproject/vendor/autoload.php"
Base.php contains the following script:
<?php namespace Models;
class BaseModel {
public $submitAction = null;
protected $complete;
public function __construct() {}
/**
* Expose
* Exposes the error code names for external processing.
*
* #return An array containing all error code names in this object.
*/
public static function Expose() {
$reflector = new ReflectionClass( 'ErrorCodes' );
return $reflector->getConstants();
}
}
I've gone through every permutation of the namespacing I can think of, have been endlessly modifying the "composer.json", adding and removing segments as per the documentation, working purely with classmap (which didn't work), testing "PSR-0" (also didn't work), using a bigger hammer (also didn't work).
Obviously I'm doing something horrifically wrong, I just don't seem to be able to puzzle out what; can anyone see where I'm going astray?
Many thanks,
N00b
Edit: Huge oversight, the "vendor/composer" directory contains the following relating to Composer's auto-loading.
"autoload_classmap.php":
<?php
// autoload_classmap.php #generated by Composer
$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);
return array(
'Models\\BaseModel' => $baseDir . '/framework/models/Base.php',
);
"autoload_files.php":
<?php
// autoload_files.php #generated by Composer
$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);
return array(
'a9da57e70f975fe4a3c7dad63939dcd8' => $vendorDir . '/constants.php',
);
and "autoload_psr4.php":
<?php
// autoload_psr4.php #generated by Composer
$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);
return array(
'Models\\' => array($baseDir . '/framework/models'),
'Config\\' => array($baseDir . '/framework/config'),
'Classes\\' => array($baseDir . '/framework/classes'),
);
The errors I'm seeing are, from PHP:
Fatal error: Class 'BaseModel' not found in
/var/www/testproject/framework/index.php on line 8
and from PHPUnit:
user% phpunit
PHPUnit 4.2.6 by Sebastian Bergmann.
Configuration read from /var/www/testproject/phpunit.xml
PHP Fatal error: Class 'BaseModel' not found in
/var/www/testproject/test/FormTest.php on line 6
In your index.php you're not importing the right class:
use Models\Base;
$derp = new BaseModel();
You should import the Models\BaseModel class:
use Models\BaseModel;
$derp = new BaseModel();
Also, the file name should match the class name. The BaseModel class should be located in the framework/models/BaseModel.php file instead of framework/models/Base.php.
Note that you don't need to optimise the autoloader during development (that's what -o flag is doing). Only use it in production, otherwise you'll have to dump the autoloader each time you add a new class.
More about the autoloading standards can be read here:
http://www.php-fig.org/psr/psr-4/
http://www.php-fig.org/psr/psr-0/
The code below is me attempting to load Mustache into a Composer library (meaning the library itself is also being loaded by composer by the full project) I'm making for a project.
<?php
namespace TradeDefender\SiteEngine;
require '../../vendor/autoload.php';
class MessageEngine{
function test(){
$m = new Mustache_Engine;
return "hello";
}
}
?>
The directory structure for the library itself looks like this:
.
├── lib
│ └── TradeDefender
│ ├── Api
│ ├── Conn
│ └── SiteEngine
└── vendor
├── composer
└── mustache
I'm suspecting that it's due to me setting a namespace in the class, but I'm not sure how to fix it. The error itself is that it's not able to find the Class Mustache_Engine in the SiteEngine folder. The autoloader itself is being loaded just fine.
Any ideas? Thanks.
The problem was that I was loading the Mustache_Engine from the locally defined namespace rather than the global namespace. To load from the global namespace I had to put a \ infront of Mustache_Engine, like so:
$m = new \Mustache_Engine;
Directory structure
learner#debian:~$ tree ~/bin
/home/learner/bin
└── php
├── Body
│ ├── Brain.php
│ └── Cell
│ └── Neuron.php
└── main.php
3 directories, 3 files
First code example
~/bin/php/main.php:
<?php
spl_autoload_register(function ($class) {
$path = str_replace('\\', DIRECTORY_SEPARATOR, $class) . '.php';
echo "----- Autoload $class from $path -----\n";
include $path;
});
use Body\Brain;
$brain = new Brain();
?>
~/bin/php/Body/Brain.php:
<?php
namespace Body;
use Body\Cell\Neuron;
class Brain
{
public function __construct()
{
$this->brain = new Neuron();
$this->brain->talk();
}
}
?>
~/bin/php/Body/Cell/Neuron.php:
<?php
namespace Body\Cell;
class Neuron
{
public function talk()
{
echo "I am Neuron!\n";
}
}
?>
I am able to execute main.php from my home directory in the following
manner and it works fine:
learner#debian:~$ php ~/bin/php/main.php
----- Autoload Body\Brain from Body/Brain.php -----
----- Autoload Body\Cell\Neuron from Body/Cell/Neuron.php -----
I am Neuron!
I am surprised that this works. I expect it to fail when
$this->brain = new Neuron(); is executed in
~/bin/php/Body/Brain.php. When this line is encountered, the
autoloader tries to execute include 'Body/Cell/Neuron.php' but there
is no such subdirectory called Body inside ~/bin/php/Body.
Second code example
Let me show you why I expect the first code example to fail by showing another code example that is using include statements instead of autoloader.
~/bin/php/main.php modified to:
<?php
include 'Body/Brain.php';
use Body\Brain;
$brain = new Brain();
?>
See that the autoloader is missing now from the above code, and the same
include statement is being used now which was being executed by the
autoloader earlier.
~/bin/php/Body/Brain.php modified to:
<?php
namespace Body;
include 'Body/Cell/Neuron.php';
use Body\Cell\Neuron;
class Brain
{
public function __construct()
{
$this->brain = new Neuron();
$this->brain->talk();
}
}
?>
Note that the same include statement has been added to this code that
was being executed by the autoloader earlier to load Body\Cell\Neuron.
Trying to execute this code results in failure.
learner#debian:~$ php ~/bin/php/main.php
PHP Warning: include(Body/Cell/Neuron.php): failed to open stream: No such file or directory in /home/learner/bin/php/Body/Brain.php on line 4
PHP Warning: include(): Failed opening 'Body/Cell/Neuron.php' for inclusion (include_path='.:/usr/share/php:/usr/share/pear') in /home/learner/bin/php/Body/Brain.php on line 4
PHP Fatal error: Class 'Body\Cell\Neuron' not found in /home/learner/bin/php/Body/Brain.php on line 12
This failure is expected because while executing
include 'Body/Cell/Neuron.php'; in ~/bin/php/Body/Brain.php, it
doesn't find a subdirectory called Body in ~/bin/php/Body.
Question
I know that I can easily fix the second code example by editing
~/bin/php/Body/Brain.php to use
include 'Cell/Neuron.php'; instead of
include 'Body/Cell/Neuron.php';. However, my question is not about
why the second code example doesn't work, but about why the first code
example works.
When the PHP interpreter is unable to include 'Body/Cell/Neuron.php'
from ~/bin/php/Body/Brain.php in the second code example, how
does the autoloader succeed in doing the same include from the same
PHP file in the first code example?
Or am I mistaken? Could it be that in the first code example the
autoloader is always executing the include statements from
~/bin/php/main.php regardless of where the classes are first
being used, so include 'Body/Cell/Neuron.php' is done at
~/bin/php/main.php and it succeeds because there indeed is a
subdirectory called Body in ~/bin/php? If this is the case,
where can I read about it in the official documentation?
To summarize our understanding, could you please tell me where the
registered autoloader function is executed from, in general? From the script
where the function is defined? From the script where the function
is registered? Or from the script where a new class is encountered?
Okay it's so:
In your first code example you are defining the autoloader. It will include every needed class with include. Imagine it just copy and pastes the code in currently running php script.
Now in the Brain.php file (which has been included, let me say: actually it's running in main.php because it has been "copy and pasted" into main.php) the class Neuron is needed. In main.php the autoloader is denfined -> the autoloader is called and loads the Neuron class. I think you understand.
Your second example doesn't work because when you are going to include Brain.php the PHP parser will before including Brain.php into main.php, try to include Body/Cell/Neuron.php into Brain.php. And you know that this can't work.
The autloader is excuted from/in the script you placed it. In your example in main.php
(I hope I hasn't confused you more)
I am answering my own question here. With some experiments it appears to me that the registered autoloader function executes in the context of the script where it is defined, and not where it is registered as an autoloader, and not where new classes are instantiated or used.
However, since this is only an experiment that tries to prove my theory, if anyone can answer my question by quoting official documentation, I'll mark that as the correct answer.
Experiment setup
Directory structure:
learner#debian:~$ tree ~/bin
/home/learner/bin
└── php
├── AutoLoader
│ ├── AutoLoader.php
│ └── ClassLoader
│ └── ClassLoader.php
├── Body
│ ├── Brain.php
│ └── Cell
│ └── Neuron.php
└── main.php
5 directories, 5 files
~/bin/php/AutoLoader/ClassLoader/ClassLoader.php contains the autoload function called ClassLoader::loadClass.
~/bin/php/AutoLoader/AutoLoader.php registers the autoload function by calling spl_autoload_register('Autoloader\\ClassLoader\\ClassLoader::loadClass');.
Source code
~/bin/php/AutoLoader/AutoLoader.php:
<?php
namespace AutoLoader;
include 'ClassLoader/ClassLoader.php';
class AutoLoader
{
static public function registerAutoloader()
{
spl_autoload_register(
'Autoloader\\ClassLoader\\ClassLoader::loadClass');
}
}
?>
~/bin/php/AutoLoader/ClassLoader/ClassLoader.php:
~/bin/php/Body/Brain.php:
<?php
namespace Body;
use Body\Cell\Neuron;
class Brain
{
public function __construct()
{
$this->brain = new Neuron();
$this->brain->talk();
}
}
?>
~/bin/php/Body/Cell/Neuron.php:
<?php
namespace Body\Cell;
class Neuron
{
public function talk()
{
echo "I am Neuron!\n";
}
}
?>
~/bin/php/main.php:
<?php
include 'AutoLoader/AutoLoader.php';
use AutoLoader\AutoLoader;
use Body\Brain;
AutoLoader::registerAutoloader();
$brain = new Brain();
?>
Experiment 1
Executing the above code results in error:
learner#debian:~$ php ~/bin/php/main.php
----- Autoload Body\Brain from Body/Brain.php -----
PHP Warning: include(Body/Brain.php): failed to open stream: No such file or directory in /home/learner/bin/php/AutoLoader/ClassLoader/ClassLoader.php on line 9
PHP Warning: include(): Failed opening 'Body/Brain.php' for inclusion (include_path='.:/usr/share/php:/usr/share/pear') in /home/learner/bin/php/AutoLoader/ClassLoader/ClassLoader.php on line 9
PHP Fatal error: Class 'Body\Brain' not found in /home/learner/bin/php/main.php on line 8
Observation: The autoloader could not include Body/Brain.php from ~/bin/php/Body/Brain.php.
Experiment 2
Moving Body directory to ~/bin/php/AutoLoader and executing the code still results in error:
learner#debian:~$ mv ~/bin/php/Body ~/bin/php/AutoLoader
learner#debian:~$ tree ~/bin
/home/learner/bin
└── php
├── AutoLoader
│ ├── AutoLoader.php
│ ├── Body
│ │ ├── Brain.php
│ │ └── Cell
│ │ └── Neuron.php
│ └── ClassLoader
│ └── ClassLoader.php
└── main.php
5 directories, 5 files
learner#debian:~$ php ~/bin/php/main.php
----- Autoload Body\Brain from Body/Brain.php -----
PHP Warning: include(Body/Brain.php): failed to open stream: No such file or directory in /home/learner/bin/php/AutoLoader/ClassLoader/ClassLoader.php on line 9
PHP Warning: include(): Failed opening 'Body/Brain.php' for inclusion (include_path='.:/usr/share/php:/usr/share/pear') in /home/learner/bin/php/AutoLoader/ClassLoader/ClassLoader.php on line 9
PHP Fatal error: Class 'Body\Brain' not found in /home/learner/bin/php/main.php on line 8
Observation: The autoloader could not include Body/Brain.php from ~/bin/php/AutoLoader/Body/Brain.php.
Experiment 3
Moving Body directory to ~/bin/php/AutoLoader/ClassLoader and executing the code results in success.
learner#debian:~$ mv ~/bin/php/AutoLoader/Body ~/bin/php/AutoLoader/ClassLoader/Body
learner#debian:~$ tree ~/bin
/home/learner/bin
└── php
├── AutoLoader
│ ├── AutoLoader.php
│ └── ClassLoader
│ ├── Body
│ │ ├── Brain.php
│ │ └── Cell
│ │ └── Neuron.php
│ └── ClassLoader.php
└── main.php
5 directories, 5 files
learner#debian:~$ php ~/bin/php/main.php
----- Autoload Body\Brain from Body/Brain.php -----
----- Autoload Body\Cell\Neuron from Body/Cell/Neuron.php -----
I am Neuron!
Observation: The autoloader could include Body/Brain.php from ~/bin/php/AutoLoader/ClassLoader/Body/Brain.php.
Conclusion
The conclusions of the three experiments seem to indicate that the autoloader is including files with respect to ~/bin/php/AutoLoader/ClassLoader/ directory, i.e. the directory that contains the script that defines the autoloader function.