Чат веб-разработчиков
Пространства имён и автозагрузка классов в PHP

Неймспейсы и автозагрузка в PHP

В этом уроке мы коснемся архитектуры приложений. Если быть точнее – мы научимся тому, как в современном программировании на PHP принято хранить классы в отдельных файлах, и о том, как избегать при этом бесконечных строчек с include и require для подключения этих файлов.

На самом деле, в PHP всё довольно просто с правилами по реализации большинства частей приложения. Для этого есть уже придуманные умными людьми стандарты – PSR (PHP Standards Recommendations). В них описано, как нужно писать ту или иную составляющую вашей программы.

В этом уроке мы затронем стандарт PSR-4. В нём говорится о том, что каждый класс должен храниться в отдельном файле и находиться в пространстве имён. Давайте обо всём по порядку.

Пусть у нас есть классы User и Article. Нам нужно сохранить их в разных файлах. Для этого давайте создадим рядом с папкой www папку src, а внутри неё папку MyProject. Внутри папки MyProject создадим папку Models, а в ней создадим ещё 2 папки – Articles и Users. И уже в этих папках создадим файлы Article.php и User.php. Должно получиться следующее:

Архитектура проекта

Давайте теперь опишем в этих двух файлах наши классы.
src/MyProject/Models/Articles/Article.php

Код доступен только после покупки курса ООП в PHP.

src/MyProject/Models/Users/User.php

Код доступен только после покупки курса ООП в PHP.

Первую часть сделали – теперь у нас каждый класс лежит в отдельном файле. Давайте теперь перейдём в наш index.php, лежащий в директории www и запишем в него логику для работы с этими классами.

Код доступен только после покупки курса ООП в PHP.

Давайте теперь попробуем запустить этот скрипт в браузере.
Разумеется, мы получим ошибку.

Ошибка из-за ненайденного класса

Нашему скрипту не удалось найти класс User. Давайте подключим файлы с нужными нам классами в начале index.php

Код доступен только после покупки курса ООП в PHP.

Если мы сейчас запустим этот скрипт, то всё у нас прекрасно отработает и мы увидим результат var_dump().

Результат успешной работы скрипта

Итак, с первым пунктом про хранение классов в отдельных файлах мы разобрались.

Пространства имён - namespaces

Теперь вернёмся к пространствам имён – неймспейсам. Тут всё довольно просто – класс можно поместить в отдельное именованное пространство и в дальнейшем использовать его по этому полному имени. Для того чтобы указать это пространство для конкретного класса используется слово namespace, за которым следует само имя. Указывается оно в файле с классом, перед определением класса. На примере класса User это будет выглядеть следующим образом:
src/MyProject/Models/Users/User.php

Код доступен только после покупки курса ООП в PHP.

Теперь мы можем говорить, что класс User находится в неймспейсе MyProject\Models\Users.

Давайте проделаем аналогичные действия с классом Article.
src/MyProject/Models/Articles/Article.php

Код доступен только после покупки курса ООП в PHP.

Теперь, чтобы в файле index.php работать с данными классами, мы должны указать полное имя класса – это имя класса с указанием его неймспейса. Делается это следующим образом.
www/index.php

Код доступен только после покупки курса ООП в PHP.

Если мы сейчас запустим скрипт, то снова столкнёмся с ошибкой.

Ошибка из-за разных неймспейсов

Но на этот раз, она уже другая. А именно – третий аргумент, переданный в конструктор класса Article должен быть объектом класса MyProject\Models\Articles\User, а передан объект класса MyProject\Models\Users\User. Заметили ошибку? Неймспейс не тот. Дело в том, что если в файле с классом указан неймспейс, то все классы, которые указываются в данном файле будут искаться в том же неймспейсе. Так как у нас класс User находится в другом неймспейсе, то мы должны явно это указать. Вот так:
src/MyProject/Models/Articles/Article.php

Код доступен только после покупки курса ООП в PHP.

Либо же указать в начале файла о каком классе идёт речь, когда мы используем в коде только слово User. Делается это с помощью слова use после указания текущего неймспейса, но перед описанием класса.

Код доступен только после покупки курса ООП в PHP.

Теперь, когда мы будем использовать класс User, то автоматически будет использоваться класс из неймспейса MyProject\Models\Users\User.

Давайте снова запустим скрипт, и убедимся, что всё у нас теперь работает.

Код доступен только после покупки курса ООП в PHP.

Получили работающий скрипт. Ура! Вот в принципе и всё, что вам нужно знать о неймспейсах. В наших будущих программах мы всегда будем создавать классы внутри неймспейсов.

Автозагрузка

Однако, давайте снова посмотрим на наш файл index.php. представьте, что у нас теперь большой проект и в нём больше 100 классов. Нам придётся сто раз писать require с указанием каждого файла. Утомительно, да? Однако, можно автоматизировать этот процесс, написав функцию автозагрузки классов. Она будет вызываться каждый раз, когда впервые будет встречаться новый класс.

Вы заметили, что мы одинаково называли папки, в которых лежат файлы и нейсмспейсы классов? Это мы делали не просто так, а для того, чтобы можно было преобразовать полное имя класса (включая его неймспейс) в путь до .php-файла с этим классом.

Итак, давайте сделаем эту функцию автозагрузки. Давайте я сначала приведу пример кода, а затем объясню, как это работает. Наш файл index.php принимает следующий вид:

Код доступен только после покупки курса ООП в PHP.

А теперь по порядку о том, что же происходит.

  • Функция spl_autoload_register() принимает первым аргументом имя функции, в которую будет передаваться имя класса, каждый раз, когда этот класс ещё не был загружен. Поэтому мы создаём новую функцию myAutoLoader() и указываем это имя в качестве аргумента функции spl_autoload_register().
  • Теперь по поводу функции myAutoLoader. Каждый раз, когда в коде будет встречаться класс, который ещё не был подключён, в неё первым аргументом будет передаваться полное имя класса (вместе с неймспейсом). И мы должны на основе этого полного имени подключить нужный файл. Так как у нас пути до файлов и их неймспейсы совпадают, то мы просто склеиваем строку из следующих составляющих: текущая директория + поднимаемся на уровень выше + переходим в папку src + полное имя класса + добавляем расширение .php.

Всё! Теперь все классы будут подгружаться автоматически. Давайте запустим скрипт и убедимся, что всё работает.

Давайте добавим отладочную информацию внутри функции myAutoLoader(), чтобы проверить что всё именно так и работает. Добавим var_dump() с выводом переменной $className.

Код доступен только после покупки курса ООП в PHP.

Снова запустим скрипт и посмотрим на вывод.

Код доступен только после покупки курса ООП в PHP.

Мы видим, что в эту функцию попал сначала класс MyProject\Models\Users\User, а затем MyProject\Models\Articles\Article. И для этих классов мы сделали require нужных файлов и они успешно подгрузились.

На этом давайте var_dump уберём.

В функцию spl_autoload_register можно и вовсе передать не имя функции, а прямо саму функцию – не будем сейчас на этом останавливаться более детально. Просто знайте, что так можно:

Код доступен только после покупки курса ООП в PHP.

В таком случае, функция называется анонимной – у неё нет имени. Она просто передаётся в качестве аргумента и имя ей не нужно.

Запустите код ещё раз, и убедитесь, что всё работает как нужно.
Вот такими вот нехитрыми действиями мы сделали автозагрузку классов. PHP – прекрасный язык, не правда ли?

Читайте также
Комментарии


Galay
Galay

Итак, с первым пунктом про хранение классов в отдельных классах мы разобрались.

Наверно имеловь ввиду "хранение классов в отдельных папках"?

ivashkevich
ivashkevich

В отдельных файлах. Спасибо, исправил =)

Sparky
Sparky

описание картинки
при автозагрузке классов возникает ошибка. Не подключаются неймспейсы, как пофиксить? Код идентичный вашему, я так понимаю проблема в "\" слешах.

Sparky
Sparky

Код ошибки:

Warning: require_once(/var/www/html/php-learn/Sparky-project/www/../src/MyProject\Models\Users\User.php): failed to open stream: No such file or directory in /var/www/html/php-learn/Sparky-project/www/index.php on line 11

Fatal error: require_once(): Failed opening required '/var/www/html/php-learn/Sparky-project/www/../src/MyProject\Models\Users\User.php' (include_path='.:/usr/share/php') in /var/www/html/php-learn/Sparky-project/www/index.php on line 11
Sparky
Sparky

окей, помощи особо не вижу, пофиксил сам. Заменил обратные слеши через str_replace, таким образом путь стал правильным

ivashkevich
ivashkevich

Да, всё дело именно в них. Разница в том, что я писал урок под виндой, а там в путях как раз обратные слэши. Ваше решение с использованием str_replace - в самый раз.

Benya
Benya

Та же проблема что у Sparky, что то до конца не понял как ее решить?

den
den

Достаточно просто так:

require_once __DIR__ . '/../src/' . str_replace('\\', '/', $className) . '.php';
}
ivashkevich
ivashkevich

Спасибо что подменили меня =)

ArtemijeKA
ArtemijeKA

Не разобрался с ошибкой:

Fatal error: Uncaught Error: Class 'models\articles\Article' not found in /namespaces/public_html/index.php on line 18

var_dump();

/namespaces/public_html/index.php:11:string 'models\articles\Article' (length=23)
/namespaces/public_html/index.php:12:string '/namespaces/public_html/../src/models/articles/Article.php' (length=125)

PS У меня только нет папки MyProject и все папки маленькими буквами.

ivashkevich
ivashkevich

Если еще актуально - напиши в личку.

ArtemijeKA
ArtemijeKA

Нет, только что понял свой косяк.

ArtemijeKA
ArtemijeKA

Дело в том, что я указал:

namespace models\articles\Article;
namespace models\users\User;

А надо было просто путь до класса без самого класса:

namespace models\articles;
namespace models\users;
ivashkevich
ivashkevich

Нэймспейсы должны быть с большой буквы, это стандарт PSR-4. Чуть позже о нем расскажу.

ArtemijeKA
ArtemijeKA

Получается и папки в src тоже...

ivashkevich
ivashkevich

Да. А зачем не по урокам делали?

ArtemijeKA
ArtemijeKA

А я сразу думал разобрать, что да как, своими руками писал. Время много положил на это правда.

ArtemijeKA
ArtemijeKA

Еще оформительский вопрос остался.
Как правильнее обращаться к классу при создании объекта?
В документации http://php.net/manual/ru/language.namespaces.rationale.php
внизу, в дополнении кто-то написал якобы по PSR стандарту:

<?php $tool = new Pierstoval\Tools\MyTool(); ?>
  • то есть без корневого слеша.
ivashkevich
ivashkevich

Можно и полный неймспейс указывать, а можно через use в начале файла. Второй вариант предпочтительнее.

ArtemijeKA
ArtemijeKA

Я извиняюсь, вопрос был о дотошном перфекционизме как правильнее):

$tool = new Pierstoval\Tools\MyTool();

или

$tool = new \Pierstoval\Tools\MyTool();

Я так понял с корневым \ - более универсально использовать.

ivashkevich
ivashkevich

С корневым правильно. Можете прочитать про PSR-4.

ArtemijeKA
ArtemijeKA

Что-то я не понимаю почему у меня нет ошибки как в этом уроке когда надо добавить в Article.php

public function __construct(string $title, string $text, \MyProject\Models\Users\User $author)

и

public function getAuthor(): \MyProject\Models\Users\User

У меня без явного определения пространства имен отрабатывается код::

{path}/namespaces/public_html/index.php:20:
object(Models\Users\User)[1]
  private 'name' => string 'Иван' (length=8)
{path}/namespaces/public_html/index.php:21:
object(Models\Articles\Article)[2]
  private 'title' => string 'Заголовок' (length=18)
  private 'text' => string 'Текст' (length=10)
  private 'author' => 
    object(Models\Users\User)[1]
      private 'name' => string 'Иван' (length=8)

Все, обнаружил свой косяк: не поставил тип переменной User для переменной $author в Article.php.
Это я так понимаю чтобы автором статьи не мог стать кто-то отличный от User (например кот или пес))

ivashkevich
ivashkevich

Да, Вы всё верно поняли)