Что такое autoload php
Предисловие переводчика
Данная статья является вольным переводом-пересказом поста The End of Autoloading
. Оригинальная статья не первой свежести, поэтому код приведенный в примерах может быть не актуален. В теме, которую затрагивает статья, самое главное — общий взгляд, а не конкретные примеры.
Предисловие
Автозагрузка в PHP отлично экономит время. Это позволяет писать скрипты не задумываясь о путях к библиотекам, которые вы используете. Но с приходом неймспейсов и под влиянием Java-стиля современных фреймворков ситуация изменилась. В ближайшем будущем автозагрузка будет повсеместно, но без единой выгоды старого ее стиля.
До автозагрузки. Пути к файлам.
До появления автозагрузки, в каждом скрипте необходимо было указывать пути к используемым библиотекам. Исходный код выглядел так:
Зависимости явно прописывались в начале каждого скрипта. В этом примере зависимость очевидна, даже если вызов PEAR_DependencyDB находится на 328 строке.
Приход SPL-автозагрузки.
Позже появилась функция spl_autoload_register(), позволившая убрать вызовы require/require_once. Замечательным было то, что теперь появилась возможность использовать классы без необходимости знания где они расположены. Например:
Смотрите, здесь нет ни единого вызова require/require_once, при том, что этот класс зависим от sfActions, PostPeer, и Criteria классов. Разработчики смогли заниматься бизнес-логикой, не тратя время на поиск путей зависимостей. Это было действительно удобно.
Реализации автозагрузки.
Реализации автозагрузки варьируются. Некоторые библиотеки используют список всех классов, которые нужно подключить. Например:
Эта техника позволила скрыть пути к классам, но заставила разработчика библиотеки обновлять “карту” автозагрузки, каждый раз, при добавлении нового класса. Другая техника использует проход по структуре папок проекта в поиске нужного класса. Такой подход позволил подменять классы фреймворка своими, т.к. проход по папкам происходил в определенном порядке: пользовательские папки, проекта, плагинов и фреймворка. Таким образом разработчик мог создать класс ClassName, который подменит ClassName предоставленный плагином, т.к. загрузится раньше.
Автозагрузка с неймспейсами
Приход неймспейсов изменил техники автозагрузки. Авторы фреймвроков объединились, что бы унифицировать техники автозагрузки, для возможности технического взаимодействия между различными библиотеками. Было решено, что явное лучше неявного и полное имя класса будет относительным путем к файлу.
Были выработаны принципы именования и файловой структуры, а также реализация класса SplClassLoader. Этот подход сейчас используется практически во всех современных фреймворках. Пример кода:
Здесь все также нет require благодаря автозагрузке. Автозагрузчик ищет Symfony\Framework\WebBundle\Controller класс в файле Symfony/Framework/WebBundle/Controller.php.
Все хорошо, все довольны. Зависимости явные и подмена класса происходит легко:
Конец все удобствам.
Использование use в предыдущем примере вам ничего не напоминает? Это ведь очень похоже на старый добрый require/require_once, не так ли?
Привнесенная неймспейсами многословность в первую очередь снижает легкость использования автозагрузки. Но проблема не только в том, что нужно писать больше кода, в этом вам может помочь IDE с автодополнением, а в том, что вам нужно знать полные имена нужных вам классов. Вы должны очень хорошо знать классы фреймворка, чтобы использовать их. Это шаг назад по сравнению с автозагрузкой “первого поколения”, где было достаточно знать имя класса.
Другого пути нет.
Правда было бы замечательно, использовать современные библиотеки без знания их файловой структуры? Что если бы вы могли написать контроллер так:
Умный автозагрузчик перехватил бы вызов класса Controller, загрузил бы файл Symfony/Framework/WebBundle/Controller.php и динамически создал алиас с Symfony\Framework\WebBundle\Controller на Controller. К сожалению в PHP use создает алиас во время компиляции, поэтому такой ход не сработает. Конечно есть возможность сделать подобное используя eval, но это, наверное, даже хуже, чем подключать файлы вручную. Также создание таких алиасов при работе с фреймворком не возможно по причине конфликта с ленивой загрузкой и конфликта имен классов, например:
Symfony\Framework\WebBundle\Command
и
Symfony\Components\Console\Command\Command
Пока авторы фреймворков не изменят свой взгляд на автозагрузку, будущее PHP мне видится многословным.
Решение проблемы.
Лично я, думаю, что многословность сильно замедляет разработку. Например возьмем микрофреймворки – они дают возможность обработать запрос быстро, при минимуме MVC-разделения. Сравним код примерного приложения написанного с использованием Slim (автозагрузка без неймспейсов) и Silex (автозагрузка с неймспейсами):
Во втором примере, автозагрузка делает все только сложнее.
Разработчики современных фреймворков объясняют, что многословность – это цена, которую мы платим за качество кода. Я не уверен, что хочу платить эту цену. Я не хочу видеть как PHP превращается в Java, где код превосходен с точки зрения Computer Science, но очень затратен в написании. Это побуждает желание к использованию других языков, где вопрос автозагрузки с неймспейсами не стоит и при этом, быстрая разработка возможна.
Возьмем например Ruby. Существует такой фреймворк как Sinatra, используя который HelloWorld-приложение становится очень лаконичным:
Ой, смотрите, здесь же используется require! И при этом, все пишется очень быстро и легко в использовании.
Автозагрузка классов в PHP
Давайте рассмотрим несколько примеров, чтобы лучше понять как это всё работает (исходные файлы можно скачать в конце статьи).
Создадим простой проект, где будет два класса. Но перед этим я хочу отдельно отметить, что существуют общепринятые стандарты кодирования, которые именуются как PSR. В отношении автозагрузки используется PSR-4 (и старый PSR-0). В этом стандарте предлагается несколько правил по которым принято работать с файлами классов. Они несложные, и если кратко, то вот основные:
Исходя из этого создадим проект на два класса. Разместим их в каталоге lib :
Файл index.php будет главным. Файлы классов пусть будут простыми:
В классах я пока специально не указываю namespace.
PHP определит, что класс неизвестен и будет запущена функция автозагрузчика. В этой функции мы формируем имя файла и если он есть, то происходит его подключение. В результате в браузере будет выведено:
Если мы попробуем создать объекты уже подключенных классов, то автозагрузчик не сработает:
Если мы попробуем использовать несуществующий класс, например
то получим ожидаемую ошибку: Fatal error: Class ‘Class3’ not found.
Теперь рассмотрим вопрос использования namespace. В нашем автозагрузчике мы использовали каталог lib в качестве хранилища всех классов. Но, в реальном проекте каталоги могут быть совершенно другими, а значит каталог нужно определять исходя из вызываемого класса. Согласно стандарту это будет так:
Очевидно, что из автозагрузчика нужно убрать lib и добавим ещё вывод результирующего имени файла для контроля:
То есть автозагрузчик получает полное имя класса, включая namespace из-за чего имя файла формируется неверно. По этой причине имя класса следует предварительно обработать, чтобы отдельно получить каталоги и имя файла. Реализация функции может быть различной, я покажу свой вариант:
Если с этой функцией выполнить:
То есть имя файла сформировано верно, но класс всё равно не найден. В чём же дело?
После чего всё замечательно работает:
Чтобы избежать таких проблем, автозагрузчик должен ориентироваться на базовый каталог и подключать файлы относительно него. В простом случае, базовый каталог задаётся с помощью константы. Например так:
Если классы подключаются из нескольких базовых каталогов, то в автозагрузчике последовательно проверяются файлы, пока не будет найден подходящий. Другой способ — это зарегистрировать ещё одну функцию автозагрузчика, которая будет проверять уже «свой каталог».
В некоторых php-фреймворках используются т.н. карты классов (classmap), которые представляют собой массив, где указываются соответствия класса и его php-файла. Конечно это не самая лучшая идея, поскольку требует подготовки этого массива. Когда классов много, то classmap оказывается очень большим (хороший антипример — Yii, где нужно указывать более 400 классов).
__autoload
__autoload — Попытка загрузить неопределённый класс
Эта функция объявлена УСТАРЕВШЕЙ, начиная с PHP 7.2.0 и была УДАЛЕНА в версии PHP 8.0.0. Использовать эту функцию крайне не рекомендуется.
Описание
Вы можете определить эту функцию для автозагрузки классов.
Список параметров
Имя загружаемого класса
Возвращаемые значения
Функция не возвращает значения после выполнения.
Смотрите также
User Contributed Notes 7 notes
Even I have never been using this function, just a simple example in order to explain it;
// we’ve called a class ***
$obj = new myClass ();
?>
*** At this line, our «./myClass.php» will be included! This is the magic that we’re wondering. And you get this result «myClass init’ed successfuly. «.
So, if you call a class that named as myClass then a file will be included myClass.php if it exists (if not you get an include error normally). If you call Foo, Foo.php will be included, and so on.
And you don’t need some code like this anymore;
include_once( «./myClass.php» );
include_once( «./myFoo.php» );
include_once( «./myBar.php» );
$obj = new myClass ();
$foo = new myFoo ();
$bar = new myBar ();
?>
Your class files will be included «automatically» when you call (init) them without these functions: «include, include_once, require, require_once».
We you create an object of the class and If the PHP engine doesn’t find the class file included in the script then __autoload() magic method will automatically trigger.
You can implement it as given below example:
If you can keep file name and class name as same, it will be good programming practice. It helps to __autoload function to load file without checking any condition.
Автоматическая загрузка классов
Большинство разработчиков объектно-ориентированных приложений используют такое соглашение именования файлов, в котором каждый класс хранится в отдельно созданном для него файле. Одной из наиболее при этом досаждающих деталей является необходимость писать в начале каждого скрипта длинный список подгружаемых файлов.
spl_autoload_register() предоставляет более гибкую альтернативу для автоматической загрузки классов. По этой причине использовать __autoload() не рекомендуется, а сама функция в будущем может перестать поддерживаться или быть удалена.
До версии 5.3.0, исключения, вызванные в функции __autoload, не могли быть перехвачены в блоке catch и завершались с неисправимой ошибкой. Начиная с версии 5.3.0 эти исключения можно перехватывать в ближайшем блоке catch. Если бросить определенное пользователем исключение, то класс этого исключения должен быть доступен. Функция __autoload также может использоваться рекурсивно для автоматической загрузки пользовательских классов исключений.
Автоматическая загрузка недоступна в случае использования PHP в командной строке в интерактивном режиме.
Пример #1 Пример автоматической загрузки
В этом примере функция пытается загрузить классы MyClass1 и MyClass2 из файлов MyClass1.php и MyClass2.php соответственно.
$obj = new MyClass1 ();
$obj2 = new MyClass2 ();
?>
Пример #2 Еще один пример автоматической загрузки
В этом примере представлена попытка загрузки интерфейса ITest.
class Foo implements ITest <
>
Пример #3 Автоматическая загрузка с перехватом исключения в версиях 5.3.0+
В данном примере вызывается исключение и отлавливается блоком try/catch.
Результат выполнения данного примера:
В данном примере вызывается недоступное исключение.
spl_autoload
(PHP 5 >= 5.1.0, PHP 7, PHP 8)
spl_autoload — Реализация по умолчанию метода __autoload()
Описание
Список параметров
Имя класса (и пространства имён), которое требуется загрузить.
Возвращаемые значения
Функция не возвращает значения после выполнения.
Ошибки
Список изменений
Версия | Описание |
---|---|
8.0.0 | file_extensions теперь допускает значение null. |
User Contributed Notes 9 notes
Note, that the default autoload implementation is written in C land and is always slightly faster then your native PHP one.
Here is a trick to use default implementation with any configuration:
// You can use this trick to make autoloader look for commonly used «My.class.php» type filenames
spl_autoload_extensions ( ‘.class.php’ );
// Use default autoload implementation
spl_autoload_register ();
?>
This also works with namespaces out of the box. So you can write code like «use My\Name\Object» and it will map to «class/My/Name/Object.class.php» file path!
If you want to make the best use out of autoload with an APC cache don’t use spl_autoload. It uses relative paths and thus will perform a stat even with apc.stat=0 (either that, or it doesn’t work at all).
Instead make a custom function and use require/include with an absolute path (register it with spl_autoload_register).
Do NOT use *_once functions or a relative path. This will fail harder than spl_autoload.
Also avoid using file_exists and is_file. This will also perform a stat.
Why are stats bad? Because they access the file system. PHP does have a stat cache that helps, but it defeats the purpose of apc.stat = 0.
It’s also good to keep in mind that it’s good to keep your custom autoload function simple. This is my Loader class:
?>
Also want to point out that APC does an optimization with require/include (not *_once) with relative paths if require/include is done in the global scope (and isn’t conditional). So it would be a good idea to explicitly include files you know you’re going to use on every request (but don’t use *_once). You could, for example, add a «registerProfiledAutoload» to the above class and keep track of what you’re including to help you determine what you could explicitly include (during development, not production). The key is try not to make heavy use out of autoload.
If you must use relative paths and don’t care about having to lower-case your file-names then spl_autoload works great.
Just thought I’d react to simast at gmail dot com’s note: While he has a point in saying C outperforms PHP, his suggestion is micro-optimization. I’m not 100% against micro-optimizing code, but if you do, go all the way:
set_include_path(
CLASS_DIR.
PATH_SEPARATOR,
get_include_path()
);
Note this function will LOWERCASE the class names its looking for, dont be confused when it cant find Foo_Bar.php
also, unlike most other autoloader code snippets, this function DOES NOT translate underscores to slashes.
class Foo_Bar <>
will load foo_bar.php and will not try to load foo/bar.php
Note that, the orders of file extensions is important for performance. You should make the priority of your favourite file extension higest or use only one extension for your class files. Check out this example:
One small example that shows how you can use spl_autoload function in your MVC, Framewrk’s applications. For example, will use the Loader class.
/**
* Constructor
* Constant contain my full path to Model, View, Controllers and Lobrary-
* Direcories.
*
* @Constant MPATH,VPATH,CPATH,LPATH
*/