Используйте автозагрузку и пространства имен в PHP
Автозагрузка PHP и пространства имен обеспечивают удобство и огромные преимущества.
На языке PHP автозагрузка — это способ автоматического включения файлов классов проекта в ваш код. Предположим, у вас есть сложный объектно-ориентированный проект PHP с более чем сотней классов PHP. Прежде чем использовать их, вам необходимо убедиться, что все ваши классы загружены. Цель этой статьи – помочь вам понять, что, почему и как происходит в PHP с автозагрузкой, а также с пространствами имен и ключевым словом use
.
Что такое автозагрузка?
В сложном проекте PHP вы, вероятно, используете сотни классов. Без автозагрузки вам, скорее всего, придется включать каждый класс вручную. Ваш код будет выглядеть так:
<?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();
В лучшем случае это утомительно, а в худшем — невозможно.
Что, если вместо этого вы могли бы заставить PHP автоматически загружать файлы классов, когда вам это нужно? Можно, с автозагрузкой.
Автозагрузка PHP 101
Для создания автозагрузчика требуется всего два шага.
- Напишите функцию, которая ищет файлы, которые необходимо включить.
- Зарегистрируйте эту функцию с помощью базовой функции PHP
spl_autoload_register()
.
Вот как это сделать для приведенного выше примера:
<?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!
Вот и все. Вам больше не придется вручную require_once
создавать каждый файл класса в проекте. Вместо этого с помощью вашего автозагрузчика система автоматически требует файл по мере использования его класса.
Чтобы лучше понять, что здесь происходит, выполните точные шаги приведенного выше кода:
- Функция
my_custom_autoloader
ожидает один параметр с именем$class_name
. Учитывая имя класса, функция ищет файл с этим именем и загружает этот файл.
- Функция
spl_autoload_register()
в PHP ожидает один вызываемый параметр. Вызываемым параметром может быть множество вещей, например имя функции, метод класса или даже анонимная функция. В данном случае это функция с именемmy_custom_autoloader
.
- Таким образом, код может создать экземпляр класса с именем
SomeClass1
без предварительного запроса его PHP-файла.
Так что же происходит, когда этот скрипт запускается?
- PHP понимает, что класс с именем
SomeClass1
еще не загружен, поэтому выполняет зарегистрированные автозагрузчики.
- PHP выполняет пользовательскую функцию автозагрузки (
my_custom_autoloader
) и передает строкуSomeClass1
в качестве значения для$class_name
.
- Пользовательская функция определяет файл как
$file=__DIR__.'/includes/SomeClass1.php';
, ищет его существование (file_exists()
), затем (пока как файл найден) помечает его как требуемый с помощьюrequire_once __DIR__.'/includes/SomeClass1.php';
. В результате автоматически загружается PHP-файл класса.
Ура! Теперь у вас есть очень простой автозагрузчик, который автоматически загружает файлы классов при первом создании экземпляров этих классов. В проекте среднего размера вы избавляете себя от написания сотен строк кода.
Что такое пространства имен PHP?
Пространства имен — это способ инкапсуляции функций или свойств. Простой (и практичный) аналог — это структура каталогов операционной системы. Файл foo.txt
может существовать как в каталоге /home/greg
, так и в /home/other
, но в двух копиях foo.txt
не может сосуществовать в одном каталоге.
Кроме того, чтобы получить доступ к файлу foo.txt
за пределами каталога /home/greg
, вы должны добавить имя каталога к имени файла, используя разделитель каталогов, чтобы получить </home/greg/foo.txt.
Вы определяете пространство имен в верхней части файла PHP, используя ключевое слово namespace
:
<?php
namespace Jonathan;
function do_something() {
echo "this function does a thing";
}
В приведенном выше примере я инкапсулировал функцию do_something()
в пространство имен
Jonathan
. Это подразумевает ряд вещей — самое главное, ни одна из этих вещей не конфликтует с одноименными функциями в глобальной области видимости.
Например, предположим, что у вас есть приведенный выше код в отдельном файле с именем jonathan-stuff.php
. В отдельном файле у вас есть это:
<?php
require_once "jonathan-stuff.php";
function do_something(){ echo "this function does a completely different thing"; }
do_something();
// this function does a completely different thing
Никакого конфликта. У вас есть две функции с именем do_something()
, они могут сосуществовать друг с другом.
Теперь все, что вам нужно сделать, это выяснить, как получить доступ к методам в пространстве имен. Это делается с помощью синтаксиса, очень похожего на структуру каталогов, с обратными косыми чертами:
<?php
\Jonathan\do_something();
// this function does a thing
Этот код выполняет функцию с именем do_something()
, находящуюся в пространстве имен Jonathan
.
Этот метод также (и чаще) используется с классами. Например:
?php
namespace Jonathan;
class SomeClass { }
Это можно реализовать следующим образом:
<?php
$something = new \Jonathan\SomeClass();
Благодаря пространствам имен очень большие проекты могут содержать множество классов с одинаковыми именами без каких-либо конфликтов. Довольно мило, правда?
Какие проблемы решают пространства имен?
Чтобы увидеть преимущества пространств имен, достаточно оглянуться назад, на PHP без пространств имен. До версии PHP 5.3 инкапсулировать классы было невозможно, поэтому всегда существовал риск конфликта с другим классом с тем же именем. Было (и в некоторой степени до сих пор остается) нередко использование префиксов в именах классов:
<?php
class Jonathan_SomeClass { }
Как вы понимаете, чем больше база кода, тем больше классов и длиннее префиксы. Не удивляйтесь, если вы когда-нибудь откроете старый PHP-проект и обнаружите имя класса длиной более 60 символов, например:
<?php
class Jonathan_SomeEntity_SomeBundle_SomeComponent_Validator { }
В чем разница между написанием такого длинного имени класса и написанием длинного имени класса, например \Jonathan\SomeEntity\SomeBundle\SomeComponent\Validator
? Это отличный вопрос, и ответ заключается в простоте использования этого класса более одного раза в данном контексте. Представьте, что вам пришлось использовать длинное имя класса несколько раз в одном файле PHP. В настоящее время у вас есть два способа сделать это.
Без пространств имен:
<?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();
Ох, это много печатать. Вот оно с пространством имен:
<?php
namespace Jonathan\SomeEntity\SomeBundle\SomeComponent;
class Validator { }
В другом месте кода:
<?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();
Это, конечно, не намного лучше. К счастью, есть третий путь. Вы можете использовать ключевое слово use
для извлечения пространства имен.
Ключевое слово use
Ключевое слово use
импортирует заданное пространство имен в текущий контекст. Это позволяет вам использовать его содержимое без необходимости обращаться к его полному пути каждый раз, когда вы его используете.
<?php
namespace Jonathan\SomeEntity\SomeBundle\SomeComponent;
class Validator { }
Теперь вы можете сделать это:
<?php
use Jonathan\SomeEntity\SomeBundle\SomeComponent\Validator;
$a = new Validator();
$b = new Validator();
$c = new Validator();
$d = new Validator();
$e = new Validator();
Помимо инкапсуляции, реальная сила пространств имен заключается в импорте.
Теперь, когда у вас есть представление о том, что такое автозагрузка и пространства имен, вы можете объединить их, чтобы создать надежный способ организации файлов вашего проекта.
PSR-4: стандарт автозагрузки PHP и пространств имен.
Стандартная рекомендация PHP (PSR) 4 — это широко используемый шаблон для организации проекта PHP таким образом, чтобы пространство имен класса соответствовало относительному пути к файлу этого класса.
Например, вы работаете над проектом, использующим PSR-4, и у вас есть класс в пространстве имен \Jonathan\SomeBundle\Validator();
. Вы можете быть уверены, что файл этого класса можно найти в следующем относительном месте файловой системы:
.
Чтобы подчеркнуть эту мысль, вот еще примеры того, как существует файл PHP для класса в проекте, использующем PSR-4:
Пространство имен и класс:
\Project\Fields\Email\Validator()
- Расположение файла:
<относительный корень>/Project/Fields/Email/Validator.php
- Расположение файла:
Пространство имен и класс: \Acme\QueryBuilder\Where
- Расположение файла:
<относительный корень>/Acme/QueryBuilder/Where.php
Пространство имен и класс: \MyFirstProject\Entity\EventEmitter
- Расположение файла:
/MyFirstProject/Entity/EventEmitter.php
На самом деле это не на 100% точно. Каждый компонент проекта имеет свой относительный корень, но не сбрасывайте со счетов эту информацию: знание того, что PSR-4 подразумевает расположение файла класса, поможет вам легко найти любой класс в большом проекте.
Как работает PSR-4?
PSR-4 работает, потому что это достигается за счет функции автозагрузчика. Взгляните на один пример функции автозагрузчика PSR-4:
<?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;
}
}
Теперь предположим, что вы только что создали экземпляр класса new \Foo\Bar\Baz\Bug();
.
- PHP запускает автозагрузчик с параметром
$class
, используя строковое значение$class="\Foo\Bar\Baz\Bug".
- Используйте
str_replace()
, чтобы заменить все обратные косые черты на прямые (как это происходит в большинстве структур каталогов), превращая пространство имен в путь к каталогу.
- Найдите этот файл в расположении
/src/Foo/Bar/Baz/Bug.php.
- Если файл найден, загрузите его.
Другими словами, вы меняете Foo\Bar\Baz\Bug
на /src/Foo/Bar/Baz/Bug.php
, а затем находите этот файл.
Композитор и автозагрузка
Composer — менеджер пакетов PHP с командной строкой. Возможно, вы видели проект с файлом composer.json
в корневом каталоге. Этот файл сообщает Composer о проекте, включая зависимости проекта.
Вот пример простого файла composer.json
:
{
"name": "jonathan/example",
"description": "This is an example composer.json file",
"require": {
"twig/twig": "^1.24"
}
}
Этот проект называется «jonathan/example» и имеет одну зависимость: механизм шаблонов Twig (версии 1.24 или выше).
Установив Composer, вы можете использовать файл JSON для загрузки зависимостей проекта. При этом Composer генерирует файл autoload.php
, который автоматически обрабатывает автозагрузку классов во всех ваших зависимостях.
(Джонатан Даггерхарт, CC BY-SA 4.0)
Если вы включите этот новый файл в проект, все классы в вашей зависимости будут автоматически загружены по мере необходимости.
PSR делает PHP лучше
Благодаря стандарту PSR-4 и его широкому распространению Composer может генерировать автозагрузчик, который автоматически обрабатывает загрузку ваших зависимостей по мере их создания в проекте. В следующий раз, когда вы будете писать код PHP, помните о пространствах имен и автозагрузке.