If you're new to PHP or have been coding a while, most of us are familiar with the require
or require_once
statements [1]. It's a very handy way to split up our logic into separate files. We can keep files at less than 100 lines of code this way. While mammoths are wolly cool, let's keep it small.
Say that we initially have an Elephant class like this:
./Elephant.php
<?php
class Elephant
{
public function trumpet(): void
{
echo 'bahruuuuuuhhhhaaaaa';
}
}
We can use this class in another file:
./index.php
<?php
require_once 'Elephant.php';
$elephant = new Elephant();
$elephant->trumpet();
// Output: bahruuuuuuhhhhaaaaa
Similar we would use require_once
within each file that needs code from somewhere else. We will need to do this as we are going to put together a jazz band of the zoo. Each animal will make a sound so for this we will built an interface:
./AnimalSoundInterface.php
<?php
interface AnimalSoundInterface
{
public function getSound(): string;
}
We will now make Elephant implement this interface instead, and make a Giraffe as well.
Elephant.php
<?php
require_once 'AnimalSoundInterface.php';
class Elephant implements AnimalSoundInterface
{
public function getSound(): string
{
return 'bahruuuuuuhhhhaaaaa';
}
}
Giraffe.php
<?php
require_once 'AnimalSoundInterface.php';
class Giraffe implements AnimalSoundInterface
{
public function getSound(): string
{
return 'mmphpmffffmmm';
}
}
Putting these animals into a band and having them perform is a task for a Conductor:
Conductor.php
<?php
require_once 'Elephant.php';
require_once 'Giraffe.php';
class Conductor
{
public function perform(): void
{
$zooBand = [];
$zooBand[] = new Elephant();
$zooBand[] = new Giraffe();
foreach ($zooBand as $animal) {
echo $animal->getSound() . PHP_EOL;
}
}
}
And let's make a conductor and have it perform for us:
./index.php
<?php
require_once 'Conductor.php';
$conductor = new Conductor();
$conductor->perform();
/*
* Output:
* bahruuuuuuhhhhaaaaa
* mmphpmffffmmm
*/
Putting together a zoo band is still very manageable with require_once.
Autoloading and recommendations
PSR-4 is a PHP Standards Recommendations for autoloading of classes from files [2]. This standard makes it possible to build one autoloading implementation and have it work on any code-base that follows it. PSR-4 dictates that classes are placed within a namespace and potential sub-namespaces. The first namespace may be your 'vendor-name'. In our example, just let's say our vendor-name is App. We are not expecting to distribute our code to be used within any other, so this would suffice. Each file but our index.php in our project needs to be supplemented with this namespace right below the initial <?php
:
namespace App;
If we run the index.php
file now we will get a fatal error. Uncaught Error: Class "Conductor" not found
, despite us having the require_once statement. While we are transitioning to using autoloading, it's a little journey. We will now see the flip-side of declaring a namespace for our classes. In index.php
add a use-statement:
<?php
use App\Conductor;
require_once 'Conductor.php';
$conductor = new Conductor();
$conductor->perform();
In fact, let's add use-statements to the other files that uses the require-statement.
Conductor.php
//...
namespace App;
use App\Elephant;
use App\Giraffe;
//...
Elephant.php, Giraffe.php
//...
namespace App;
use App\AnimalSoundInterface;
//...
We are ready to build an autoloader!
Autoloader
The autoloader is a function that will look at each file in our project and determine if it should be loaded, given the way we have used namespaces and use statements.
The following example is fetched from the php-fig/fig-standards github repo, with minor adjustments [3]:
./autoloader.php
<?php
/**
* @param string $class The fully-qualified class name.
* @return void
*/
spl_autoload_register(function ($class) {
// project-specific namespace prefix
$prefix = 'App\\';
// base directory for the namespace prefix
$base_dir = __DIR__ . '/';
// does the class use the namespace prefix?
$len = strlen($prefix);
if (strncmp($prefix, $class, $len) !== 0) {
// no, move to the next registered autoloader
return;
}
// get the relative class name
$relative_class = substr($class, $len);
// replace the namespace prefix with the base directory, replace namespace
// separators with directory separators in the relative class name, append
// with .php
$file = $base_dir . str_replace('\\', '/', $relative_class) . '.php';
// HERE'S THE MAGIC RESULT:
// if the file exists, require it
if (file_exists($file)) {
require $file;
}
});
In essence, the autoloader will be called upon wherever there's a use-statement. It will look at the fully qualified class-name and parse it into a require-statement. This means that the require-statements in our code can be removed. So go ahead and remove them from Conductor.php
, Elephant.php
, Giraffe.php
. Our project still requires ONE require-statement though. In our index.php-file require the autoloader:
<?php
use App\Conductor;
require_once 'autoloader.php';
$conductor = new Conductor();
$conductor->perform();
For a small-scale project it is up to you if you want to implement an autoloader or not. If you are using Composer, the package manager for PHP, then you will have access to its autoloader if you would prefer it. Personally, I think use App\Conductor
looks more neat than require_once 'Conductor.php
. But if you are building a project without classes, then PSR-4 and its autoloading would not make sense. Go ahead with require instead!
[1] - https://www.php.net/manual/en/function.require.php
[2] - https://www.php-fig.org/psr/psr-4/
[3] - https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-4-autoloader-examples.md
Top comments (2)
There are cases when
require
makes sense more than autoload. And I think it is not so known.Good point!
Yeah, I mostly code with classes where I haven't needed to do these things. Symfony does it for me 🤔 The exception is when I use Deployer. It's require-calls to sundown!