Перейти к содержимому
Теория · Файлы, пространства имён и Composer · урок 2 из 7

Пространства имён и use

В прошлом уроке файлы стали книгами на полках. Чем больше книг, тем чаще совпадают названия: в библиотеке найдутся пять книг «Основы». Их различает полка: «Основы» с полки «Химия» и «Основы» с полки «Музыка» — разные книги.

С классами то же самое. Ваш проект объявляет класс User, и пакет, который вы скачали, тоже объявляет User. Два класса с одним именем PHP не примет: на втором объявлении скрипт остановится с фатальной ошибкой, потому что имя уже занято.

Поэтому у классов есть полки. Пространство имён — это приставка, которую PHP добавляет к имени каждого класса, объявленного в файле.

Полное имя класса

Пространство имён объявляют первой инструкцией после <?php: слово namespace и имя полки. Части имени разделяют обратной косой чертой \:

PHP
<?php

namespace App\Models;

class User
{
    public function __construct(public string $name) {}
}

$u = new User('Anna');
echo User::class, "\n"; // App\Models\User
echo get_class($u), "\n"; // App\Models\User

Давайте разберём по строкам:

  1. namespace App\Models; ставит приставку App\Models\ на всё, что объявлено в этом файле.
  2. Поэтому class User объявляет класс с полным именем App\Models\User.
  3. Внутри того же пространства к классу можно обращаться коротко: new User('Anna').
  4. User::class и get_class($u) показывают настоящее имя класса — полное.

Короткое имя User — только удобство для записи. PHP везде работает с полным именем, поэтому App\Models\User и Shop\Auth\User — два разных класса, и они друг другу не мешают.

Имена пространств пишут в PascalCase. Первая часть — обычно название проекта или компании: App, Monolog, Symfony.

use: короткое имя

Из другого пространства к классу обращаются по полному имени: new \App\Models\User('Anna'). Обратная косая черта в начале значит «искать от корня, а не на текущей полке». Писать полное имя в каждой строке долго, поэтому в начале файла ставят use — закладку с коротким именем:

PHP
<?php

namespace App\Http;

use App\Models\User;
use App\Models\Order as Purchase;

echo User::class, "\n"; // App\Models\User
echo Purchase::class, "\n"; // App\Models\Order

Что здесь происходит:

  • use App\Models\User; говорит, что в этом файле User значит App\Models\User;
  • слово as даёт закладке другое имя: Purchase значит App\Models\Order;
  • закладки действуют только в своём файле, и в другом файле их нужно поставить заново.

Имя через as выбирают, когда два класса из разных пространств называются одинаково: use Shop\Auth\User as AuthUser;.

Здесь картинка расходится с PHP. Настоящую закладку кладут в книгу, которая стоит на полке. А use не подключает файл и не проверяет, есть ли такой класс.

В этой программе классов User и Order нет вовсе, а имена всё равно напечатались. Строка new User() в ней закончилась бы ошибкой Class "App\Models\User" not found.

С функциями и константами иначе: если в текущем пространстве их нет, PHP берёт глобальные. Поэтому strlen, count и PHP_INT_MAX работают внутри namespace App без всякого use.

Встроенные классы: \Exception

Встроенные классы PHP — Exception, RuntimeException, InvalidArgumentException — лежат в глобальном пространстве, в корне. Внутри namespace App короткое имя Exception значит App\Exception: PHP ищет класс на текущей полке и в корень не заглядывает.

Особенно коварно это в catch. PHP не проверяет, существует ли класс из catch, а только сравнивает с ним брошенный объект. Блок с несуществующим классом не срабатывает, и никакой ошибки нет:

PHP
<?php

namespace App;

try {
    throw new \InvalidArgumentException('empty name');
} catch (InvalidArgumentException $e) {
    echo 'first', "\n";
} catch (\InvalidArgumentException $e) {
    echo 'second: ', $e->getMessage(), "\n"; // second: empty name
}

Разберём по шагам:

  1. throw бросает объект глобального класса \InvalidArgumentException.
  2. Первый catch ждёт App\InvalidArgumentException. Такого класса нет, и блок пропускается молча.
  3. Второй catch называет глобальный класс через \ и ловит исключение.

Исправить первый catch можно двумя способами: написать \InvalidArgumentException или поставить после namespace строку use InvalidArgumentException;. Почему catch (Exception $e) внутри пространства имён ничего не ловит — частый вопрос на собеседовании.

Частая ошибка

Встроенный класс создали без обратной косой черты:

PHP
<?php

namespace App;

throw new Exception('empty name'); // ✗
TEXT
Fatal error: Uncaught Error: Class "App\Exception" not found

В отличие от catch, new требует, чтобы класс существовал. PHP ищет App\Exception, не находит его и бросает Error. Исправление — throw new \Exception('empty name'); или строка use Exception; после namespace.

Главное

  • namespace App\Models; добавляет приставку к каждому классу файла: полное имя класса — App\Models\User.
  • use App\Models\User; — закладка с коротким именем на один файл; файл с классом она не подключает.
  • Внутри пространства имён имя класса без \ ищется на текущей полке, поэтому встроенные классы пишут как \Exception или подключают через use.
  • catch с несуществующим классом молча не срабатывает, а new с таким классом бросает Error.