Skip to content

Latest commit

 

History

History
executable file
·
210 lines (145 loc) · 21.5 KB

autoload.md

File metadata and controls

executable file
·
210 lines (145 loc) · 21.5 KB

Автозагрузка, неймспейсы и PSR-4

Когда пишут ООП-код, каждый класс помещают в отдельный файл. Чтобы использовать эти классы, мы должны подключить (выполнить) файлы с ними. Выглядит это примерно так:

require_once __DIR__ . '/Some/Class.php';
require_once __DIR__ . '/Some/Other/Class.php';

(__DIR__ содержит путь к текущей папке). Когда число классов увеличивается, писать эти require становится неудобно. К счастью, в PHP есть решение проблемы — автозагрузка, то есть автоматическая загрузка файла с классом при первом обращении к этому классу.

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

PHP позволяет тебе зарегистрировать функцию-автозагрузчик с помощью функции spl_autoload_register. Можно зарегистрировать любое число автозагрузчиков. В случае обращения к несуществующему в данный момент классу, PHP будет вызывать по очереди все зарегистрированные автозагрузчики, передавая им имя класса. Если автозагрузчик знает, где лежит этот класс, он должен подключить файл с ним, PHP увидит, что класс появился, и продолжит выполнение программы. Иначе PHP вызовет следующий автозагрузчик. Если ни один автозагрузчик не подключит файл с классом, то будет выведена ошибка об обращении к несуществующему классу.

Вот примеры кода, который вызовет срабатывание автозагрузки:

// Будут вызваны все автозагрузчики для загрузки класса A
// Если ни один из них не найдет класс, то произойдет ошибка и 
// выполнение скрипта прервется
$a = new A; 

// Будут вызваны автозагрузчики для загрузки класса B
B::someMethod();

// Здесь тоже будут вызваны автозагрузчики для проверки, существует такой
// класс или нет, но даже если класс не будет найден, ошибки не произойдет, 
// просто class_exists вернет false и код внутри if не выполнится
if (class_exists('C')) {
// ..    

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

Функция-автозагрузчик имеет примерно такой вид:

function ($className) {
    // ...
}

Она получает от PHP только имя класса. Для того, чтобы по имени класса можно было понять какой файл надо подключить, обычно используют либо заранее подготовленный список файлов, либо придумывают договоренность, по которой из имени класса можно получить имя файла. Например, файлы называют так же, как и класс, с точностью до регистра букв: class SomeClass описывается в файле SomeClass.php. Одна из таких договоренностей называется PSR-4, о ней ниже.

Помни:

  • автозагрузчик не должен выдавать ошибку, если он не может найти файл с классом - может быть, этот класс подгрузит другой автозагрузчик
  • пиши автозагрузчик только для своих файлов и не пытайся подгружать классы от сторонних библиотек - они сами об этом позаботятся
  • не используй функцию __autoload(), она устарела много лет назад (например, она не позволяет сделать больше одного автозагрузчика)
  • в линуксе регистр букв в именах файлов имеет значение
  • в PHP уже есть готовая реализация функции-автозагрузчика spl_autoload
  • не изобретай свои правила сопоставления имен классов и файлов, а используй общепринятый стандарт PSR-4 (о нем ниже)
  • вообще, автозагрузчик не обязан создавать класс именно за счет подключения файла, он может создать класс каким-то другим способом, например, с помощью конструкции eval. Но не стоит так делать, так как скорее всего в твоем коде потом будет очень сложно разобраться.

Пример функции-автозагрузчика:

spl_autoload_register(function ($class) {
    // Получаем путь к файлу из имени класса
    $path = __DIR__ . $class . '.php';
    // Если в текущей папке есть такой файл, то выполняем код из него
    if (file_exists($path)) {
        require_once $path;
    }
    // Если файла нет, то ничего не делаем - может быть, класс 
    // загрузит какой-то другой автозагрузчик или может быть, 
    // такого класса нет
});

Подробнее про автозагрузку можно почитать тут:

Неймспейсы (пространства имен)

Классы можно помещать в так называемые пространства имен (неймспейсы), иначе говоря, полное имя класса может состоять из нескольких частей, которые разделены бекслешем \ (не путай с прямым слешем /). Например: полное имя \Megasoft\Megacms\SomeClass, где \Megasoft\Megamcs - это пространство имен, а SomeClass - краткое имя класса.

Неймспейсы, как и имена классов, принято писать с большой буквы.

Пространства имен позволяют записывать длинные имена классов в более удобном виде. Длинные имена появляются по 2 причинам:

  1. борьба с конфликтом имен. Представь, что Вася и Петя каждый пишет свои библиотеки и оба решают использовать класс с одинаковым названием, например User. Или функцию getUser(). Если мы попробуем подключить в проекте обе этих библиотеки, произойдет ошибка, так как нельзя создать 2 класса с одинаковыми именами. Чтобы избежать таких конфликтов, нужно, чтобы имена классов были уникальными, потому библиотеки приписывают к классам и функциям свое название в начало:

    Petya_User
    petyaGetUser()
    Vasya_User
    vasyaGetUser()
    Zend_Mail
    sfController
    

    Если не приписывать в начало имя библиотеки, то конфликт обязательно будет, так как в мире тысячи библиотек и в каких-нибудь двух обязательно будут совпадающие названия.

  2. большие библиотеки и фреймворки состоят из большого числа классов (сотни и тысячи). Чтобы не запутаться, эти классы собирают в отдельные модули и добавляют имя модуля в начало имени класса. При этом сами модули могут быть дополнительно разделены на подмодули. В итоге получаются длинные имена классов:

    • Zend_Db_Table_Row_Abstract — это класс фреймворка Zend Framework 1, модуль Db, подмодуль Table, подмодуль Row.
    • sfDatabaseConfigHandler — класс фреймворка Symfony1, модуль Database, подмодуль Config

Таким образом, имена классов получаются длинными. Это неудобно, так как они увеличивают длину строк и ухудшают читаемость кода, но без этого были бы конфликты и путаница.

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

\Symfony\Routing\Router
\Doctrine\ORM\Mapping\Id
\Petya\User
\Vasya\User

Пространство имен (неймспейс) задается для всего файла сразу, и относится ко всем упомянутым в нем названиям классов. Оно задается с помощью ключевого слова namespace. Это слово нужно писать в самом начале файла:

namespace \MegaLibrary\Database;

// Этот класс получит полное имя \MegaLibrary\Database\User
class User { } 

// PHP попытается создать объект класса \MegaLibrary\Database\SomeClass
$x = new SomeClass;

Если мы в этом файле попробуем обратиться к какому-то классу, например, SomeClass, то PHP припишет к нему неймспейс и будет искать класс \MegaLibrary\Database\SomeClass. Если мы хотим использовать в этом файле класс из другого неймспейса, то мы должны писать либо его полное имя, начиная с бекслеша (например $x = new \Some\Other\Class(...)), либо задать для него короткий синоним. Это можно сделать с помощью с помощью специальной конструкции use, которая пишется в начале файла. Вот пример конструкции use:

use \Symfony\Routing\Router;

$router = new Router();

Эта строчка говорит о том, что далее в коде короткое название Router будет обозначать класс \Symfony\Routing\Router (иногда это еще называют "импортируем класс Router"). Конструкция use ... as также позволяет задать произвольный синоним для класса. Эта возможность используется, когда надо подключить классы, у которых одинаковые короткие имена:

// Объявляем, что имя User будет обозначать класс \Some\Library\User
use \Some\Library\User;
// Объявляем, что имя OtherUser будет обозначать класс \Other\User
use \Other\User as OtherUser; 

$user1 = new User;
$user2 = new OtherUser;

Таким образом, благодаря неймспейсам мы можем использовать длинные имена классов, не сильно ухудшая читабельность кода.

Как правило, имена неймспейсов соответствуют именам папок, в которых хранится код. Например, класс \Some\Class может хранится в файле Some/Class.php. PHP не требует этого, но такой подход позволяет избежать путаницы.

По умолчанию, если ты не указываешь неймспейс, то твои классы создаются в глобальном, или корневом, неймспейсе (то есть класс SomeClass получает полное имя \SomeClass). Подробнее неймспейсы и правила работы с ними описаны в мануале: http://php.net/manual/ru/language.namespaces.php

Повторим особенности неймспейсов:

  • имена неймспейсов принято писать с большой буквы
  • слова namespace и use пишутся в самом начале файла и действуют только в пределах этого файла. Они не влияют на другие файлы, которые могут вызываться из текущего
  • ради читаемости после слов namespace и группы слов use оставляют одну пустую строчку
  • слово use не вызывает срабатывания автозагрузки и даже не проверяет, существует ли указанный в нем класс. Эта конструкция просто задает короткий синоним для длинного имени класса.
  • современные IDE (вроде PhpStorm, Eclipse PDT или Netbeans) умеют вставлять в код строку use автоматически при использовании короткого имени класса. Не требуется писать ее руками. Некоторые IDE также умеют автоматически вставлять строку namespace, определяя неймспейс по папкам, в которых хранится файл

Хотя это редко используется, но функции и константы так же создаются в неймспейсах. Начиная с PHP5.6, их стало можно импортировать (создавать короткие синонимы) с помощью конструкций use function \Namespace\functionName и use const \Namespace\CONSTANT. Подробности описаны в официальном мануале PHP.

PSR-4

Когда ты пишешь автозагрузчик, нужны какие-то правила, чтобы узнать по полному имени класса путь к файлу с ним. Желательно, чтобы правила были общие, чтобы каждый разработчик не придумывал свой велосипед.

Эту проблему (как назвать файл с классом) решает рекомендация PSR-4 (англ). Она советует называть файл в соответствие с кратким именем класса и класть его в папки, имена которых совпадают с неймспейсами. Например, класс

MyLibrary\A\B\C\D

нужно поместить в файл

MyLibrary/A/B/C/D.php

Также, если все наши классы находятся в одном неймспейсе (например MyLibrary), мы можем не создавать для него папку и класть файл с классом в A/B/C/D.php

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

Обычно в качестве неймспейса верхнего уровня выбирают название приложения, иногда вместе с названием компании или ником разработчика (например в фреймворке Symfony все классы лежат внутри неймспейса \Symfony\). Если приложение маленькое, то все классы можно сложить в один неймспейс, а по мере роста добавляются дополнительные уровни вложенности и получается что-то вроде \Symfony\Component\HttpFoundation\Request.

Если ты придерживаешься этого стандарта, то во-первых, ты молодец, во-вторых ты можешь не писать свой автозагрузчик, а взять любой готовый автозагрузчик PSR-4 (например, встроенный в менеджер пакетов composer, который используется для установки сторонних библиотек и может заодно генерировать автозагрузчик). Единственное, что тебе надо указать — это корневой неймспейс твоего проекта и корневую папку. Вот пример кода, который надо вписать в файл конфигурации composer.json, чтобы сказать, что классы из неймспейса MyLibrary находятся в папке src, а классы из MyApp\Plugins надо искать в plugins:

{
    "autoload": {
        "psr-4": {
            "MyLibrary\\": "src/",
            "MyApp\\Plugins\\": "plugins/"
        }
    }
}

Обрати внимание, что регистр тут имеет значение! Если ты напишешь PSR-4 вместо psr-4, то composer просто тихо проигнорирует указанную тобой настройку, а ты будешь долго искать, в чем же проблема.

Мы пишем бекслеш 2 раза, так как таковы правила формата JSON (чтобы в строку вставить бекслеш, его надо написать 2 раза, бекслеш в JSON - это экранирующий символ). Бекслеш нужен, так как без него (если написать MyLibrary) правило будет относиться к любым классам, имена которых начинаются с MyLibrary, например MyLibrarySomething\Class.

В примере выше классы будут искаться так:

MyLibrary\Some\Class -> src/Some/Class.php
MyApp\Plugins\Some\Class -> plugins/Some/Class.php

После того, как ты добавишь информацию в composer.json, надо выполнить команду php composer.phar dump-autoload или composer dump-autoload (разумеется сам композер должен быть у тебя уже установлен). Она сгенерирует файлы автозагрузчика и положит их в папку vendor. Тебе остается только подключить их в своей программе через require_once __DIR__ . '/vendor/autoload.php';. Таким образом, следование PSR-4 и использование композера позволяет нам не писать свой автозагрузчик.

Официальная документация: