TranslateProject/sources/tech/20230418.0 ⭐️⭐️ Use autoloading and namespaces in PHP.md

350 lines
14 KiB
Markdown

[#]: subject: "Use autoloading and namespaces in PHP"
[#]: via: "https://opensource.com/article/23/4/autoloading-namespaces-php"
[#]: author: "Jonathan Daggerhart https://opensource.com/users/daggerhart"
[#]: collector: "lkxed"
[#]: translator: " "
[#]: reviewer: " "
[#]: publisher: " "
[#]: url: " "
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 named `my_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 string `SomeClass1` 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 with `require_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][1] 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.
![Screenshot of nested drop down menus highlighting the path example - vendor - twig - autoload.php][2]
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][a]
选题:[lkxed][b]
译者:[译者ID](https://github.com/译者ID)
校对:[校对者ID](https://github.com/校对者ID)
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
[a]: https://opensource.com/users/daggerhart
[b]: https://github.com/lkxed/
[1]: https://opensource.com/article/22/5/composer-git-repositories
[2]: https://opensource.com/sites/default/files/2023-03/composer_autoloading_php_menu.png