14 KiB
Use autoloading and namespaces in PHP
In the PHP language, autoloading is a way to automatically include class files of a project in your code. Say you had a complex object-oriented PHP project with more than a hundred PHP classes. You'd need to ensure all your classes were loaded before using them. This article aims to help you understand the what, why, and how of autoloading, along with namespaces and the use
keyword, in PHP.
What is autoloading?
In a complex PHP project, you're probably using hundreds of classes. Without autoloading, you'd likely have to include every class manually. Your code would look like this:
<?php
// manually load every file the whole project uses
require_once __DIR__.'/includes/SomeClass1.php';
require_once __DIR__.'/includes/SomeClass2.php';
require_once __DIR__.'/includes/SomeClass3.php';
require_once __DIR__.'/includes/SomeClass4.php';
require_once __DIR__.'/includes/SomeClass5.php';
require_once __DIR__.'/includes/SomeClass6.php';
require_once __DIR__.'/includes/SomeClass7.php';
require_once __DIR__.'/includes/SomeClass8.php';
require_once __DIR__.'/includes/SomeClass9.php';
require_once __DIR__.'/includes/SomeClass10.php';
// ... a hundred more times
$my_example = new SomeClass1();
This is tedious at best and unmaintainable at worst.
What if, instead, you could have PHP automatically load class files when you need it? You can, with autoloading.
PHP autoloading 101
It only takes two steps to create an autoloader.
- Write a function that looks for files that need to be included.
- Register that function with the
spl_autoload_register()
core PHP function.
Here's how to do that for the above example:
<?php
/**
* Simple autoloader
*
* @param $class_name - String name for the class that is trying to be loaded.
*/
function my_custom_autoloader( $class_name ){
$file = __DIR__.'/includes/'.$class_name.'.php';
if ( file_exists($file) ) {
require_once $file;
}
}
// add a new autoloader by passing a callable into spl_autoload_register()
spl_autoload_register( 'my_custom_autoloader' );
$my_example = new SomeClass1(); // this works!
There you go. You no longer have to manually require_once
every single class file in the project. Instead, with your autoloader, the system automatically requires a file as its class is used.
For a better understanding of what's going on here, walk through the exact steps in the above code:
- The function
my_custom_autoloader
expects one parameter called$class_name
. Given a class name, the function looks for a file with that name and loads that file. - The
spl_autoload_register()
function in PHP expects one callable parameter. A callable parameter can be many things, such as a function name, class method, or even an anonymous function. In this case, it's a function namedmy_custom_autoloader
. - The code is therefore able to instantiate a class named
SomeClass1
without first having required its PHP file.
So what happens when this script is run?
- PHP realizes that there's not yet a class named
SomeClass1
loaded, so it executes registered autoloaders. - PHP executes the custom autoload function (
my_custom_autoloader
), and it passes in the stringSomeClass1
as the value for$class_name
. - The custom function defines the file as
$file = __DIR__.'/includes/SomeClass1.php';
, looks for its existence (file_exists()
), then (as long as the file is found) marks it as required withrequire_once __DIR__.'/includes/SomeClass1.php';
. As a result, the class's PHP file is automatically loaded.
Huzzah! You now have a very simple autoloader that automatically loads class files as those classes are instantiated for the first time. In a moderately sized project, you've saved yourself from writing hundreds of lines of code.
What are PHP namespaces?
Namespaces are a way to encapsulate like functionalities or properties. An easy (and practical) analog is an operating system's directory structure. The file foo.txt
can exist in both the directory /home/greg
and in /home/other
, but two copies of foo.txt
cannot coexist in the same directory.
In addition, to access the foo.txt
file outside of the /home/greg
directory, you must prepend the directory name to the file name using the directory separator to get /home/greg/foo.txt
.
You define a namespace at the top of a PHP file using the namespace
keyword:
<?php
namespace Jonathan;
$a = 'The quick brown fox...';
function do_something() {
echo "this function does a thing";
}
In the above example, I've encapsulated both the $a
variable, and the do_something()
function within the namespace
of Jonathan
. This implies a number of things—most importantly, neither of those things conflicts with variables or functions of the same name in the global scope.
For example, say you have the above code in its own file named jonathan-stuff.php
. In a separate file, you have this:
<?php
require_once "jonathan-stuff.php";
$a = 'hello world';
function do_something(){ echo "this function does a completely different thing"; }
echo $a;
// hello world
do_something();
// this function does a completely different thing
No conflict. You have two functions named do_something()
, and two variables named $a
, and they are able to co-exist with one another.
Now all you have to do is figure out how to access the namespaced variables and methods. This is done with a syntax very similar to a directory structure, with backslashes:
<?php
echo \Jonathan\$a;
// The quick brown fox...
\Jonathan\do_something();
// this function does a thing
This code echoes the value of the variable $a
residing within the Jonathan
namespace. It also executes the function named do_something()
residing within the Jonathan
namespace.
This method is also (and more commonly) used with classes. For example:
?php
namespace Jonathan;
class SomeClass { }
This can be instantiated like so:
<?php
$something = new \Jonathan\SomeClass();
With namespaces, very large projects can contain many classes that share the same name without any conflicts. Pretty sweet, right?
What problems do namespaces solve?
To see the benefits namespaces provide, you have only to look back in time to a PHP without namespaces. Before PHP version 5.3, you couldn't encapsulate classes, so they were always at risk of conflicting with another class of the same name. It was (and still is, to some degree) not uncommon to prefix class names:
<?php
class Jonathan_SomeClass { }
As you can imagine, the larger the code base, the more classes, and the longer the prefixes. Don't be surprised if you open an old PHP project some time and find a class name more than 60 characters long, like:
<?php
class Jonathan_SomeEntity_SomeBundle_SomeComponent_Validator { }
What's the difference between writing a long class name like that and writing a long class name like \Jonathan\SomeEntity\SomeBundle\SomeComponent\Validator
? That's a great question, and the answer lies in the ease of using that class more than once in a given context. Imagine you had to make use of a long class name multiple times within a single PHP file. Currently, you have two ways of doing this.
Without namespaces:
<?php
class Jonathan_SomeEntity_SomeBundle_SomeComponent_Validator { }
$a = new Jonathan_SomeEntity_SomeBundle_SomeComponent_Validator();
$b = new Jonathan_SomeEntity_SomeBundle_SomeComponent_Validator();
$c = new Jonathan_SomeEntity_SomeBundle_SomeComponent_Validator();
$d = new Jonathan_SomeEntity_SomeBundle_SomeComponent_Validator();
$e = new Jonathan_SomeEntity_SomeBundle_SomeComponent_Validator();
Oof, that's a lot of typing. Here it is with a namespace:
<?php
namespace Jonathan\SomeEntity\SomeBundle\SomeComponent;
class Validator { }
Elsewhere in the code:
<?php
$a = new \Jonathan\SomeEntity\SomeBundle\SomeComponent\Validator();
$b = new \Jonathan\SomeEntity\SomeBundle\SomeComponent\Validator();
$c = new \Jonathan\SomeEntity\SomeBundle\SomeComponent\Validator();
$d = new \Jonathan\SomeEntity\SomeBundle\SomeComponent\Validator();
$e = new \Jonathan\SomeEntity\SomeBundle\SomeComponent\Validator();
That certainly isn't much better. Luckily, there's a third way. You can leverage the use
keyword to pull in a namespace.
The use keyword
The use
keyword imports a given namespace into the current context. This allows you to make use of its contents without having to refer to its full path every time you use it.
<?php
namespace Jonathan\SomeEntity\SomeBundle\SomeComponent;
class Validator { }
Now you can do this:
<?php
use Jonathan\SomeEntity\SomeBundle\SomeComponent\Validator;
$a = new Validator();
$b = new Validator();
$c = new Validator();
$d = new Validator();
$e = new Validator();
Aside from encapsulation, importing is the real power of namespaces.
Now that you have an idea of what both autoloading and namespaces are, you can combine them to create a reliable means of organizing your project files.
PSR-4: The standard for PHP autoloading and namespaces
PHP Standard Recommendation (PSR) 4 is a commonly used pattern for organizing a PHP project so that the namespace for a class matches the relative file path to the file of that class.
For example, you're working in a project that makes use of PSR-4 and you have a namespaced class called \Jonathan\SomeBundle\Validator();
. You can be sure the file for that class can be found in this relative location in the file system: <relative root>/Jonathan/SomeBundle/Validator.php
.
Just to drive this point home, here are more examples of where a PHP file exists for a class within a project making use of PSR-4:
-
File location:
<relative root>/Project/Fields/Email/Validator.php
-
File location:
<relative root>/Acme/QueryBuilder/Where.php
-
File location:
<project root>/MyFirstProject/Entity/EventEmitter.php
-
Namespace and class:
\Project\Fields\Email\Validator()
-
Namespace and class:
\Acme\QueryBuilder\Where
-
Namespace and class:
\MyFirstProject\Entity\EventEmitter
This isn't actually 100% accurate. Each component of a project has its own relative root, but don't discount this information: Knowing that PSR-4 implies the file location of a class helps you easily find any class within a large project.
How does PSR-4 work?
PSR-4 works because it's achieved with an autoloader function. Take a look at one PSR-4 example autoloader function:
<?php
spl_autoload_register( 'my_psr4_autoloader' );
/**
* An example of a project-specific implementation.
*
* @param string $class The fully-qualified class name.
* @return void
*/
function my_psr4_autoloader($class) {
// replace namespace separators with directory separators in the relative
// class name, append with .php
$class_path = str_replace('\\', '/', $class);
$file = __DIR__ . '/src/' . $class_path . '.php';
// if the file exists, require it
if (file_exists($file)) {
require $file;
}
}
Now assume you've just instantiated the new \Foo\Bar\Baz\Bug();
class.
- PHP executes the autoloader with the
$class
parameter using the string value$class = "\Foo\Bar\Baz\Bug".
- Use
str_replace()
to change all backslashes into forward slashes (like most directory structures use), turning the namespace into a directory path. - Look for the existence of that file in the location
<relative root>/src/Foo/Bar/Baz/Bug.php.
- If the file is found, load it.
In other words, you change Foo\Bar\Baz\Bug
to /src/Foo/Bar/Baz/Bug.php
then locate that file.
Composer and autoloading
Composer is a command-line PHP package manager. You may have seen a project with a composer.json
file in its root directory. This file tells Composer about the project, including the project's dependencies.
Here's an example of a simple composer.json
file:
{
"name": "jonathan/example",
"description": "This is an example composer.json file",
"require": {
"twig/twig": "^1.24"
}
}
This project is named "jonathan/example" and has one dependency: the Twig templating engine (at version 1.24 or higher).
With Composer installed, you can use the JSON file to download the project's dependencies. In doing so, Composer generates an autoload.php
file that automatically handles autoloading the classes in all of your dependencies.
If you include this new file in a project, all classes within your dependency are automatically loaded, as needed.
PSR makes PHP better
Because of the PSR-4 standard and its widespread adoption, Composer can generate an autoloader that automatically handles loading your dependencies as you instantiate them within your project. The next time you write PHP code, keep namespaces and autoloading in mind.
via: https://opensource.com/article/23/4/autoloading-namespaces-php
作者:Jonathan Daggerhart 选题:lkxed 译者:译者ID 校对:校对者ID