Как известно, в PHP, начиная с 5-ой версии, появилась замечательная возможность загружать классы автоматически по мере возникновения в них необходимости. Теперь, вместо того, чтобы писать в файле каждого класса список используемых файлов классов, достаточно где-нибудь в файле инициализации объявить функцию с именем __autoload(), которая получает в качестве параметра имя требуемого класса и пытается его загрузить. Эта функция — своего рода последний рубеж перед возникновением ошибки “Fatal Error”.
У данного метода есть два преимущества:
Не нужно отслеживать где и какие классы мы используем. Ведь ошибиться здесь очень легко. Например, в одном классе мы используем другой класс, который уже был использован и загружен ранее, и который мы забыли явно объявить через require_once. Скрипт работает нормально, т.к. класс загружен. Но вот мы начали вызывать этот класс из другого места и там требуемый класс еще не был загружен — возникнет фатальная ошибка. Если необходимость в этом классе возникает не постоянно, а лишь в некоторых редких ситуациях (например, при обработке исключений), то отловить это будет крайне сложно.
Нет избыточных вызовов require_once. Ведь если файл с классом загружен, то нет смысла вызывать каждый раз в каждом классе require_once одних и тех же файлов классов.
Однако, есть у данного метода и недостатки. Функцию автоматической загрузки класса можно объявить только одну. Если проект состоит из нескольких независимых частей, написанными разными разработчиками, у которых разные соглашения относительно месторасположения и именования файлов с классами, то возникает проблема. Решается она исключительно созданием гибрида из двух этих функций.
Но даже если сторонние скрипты не используют __autoload() (возможно, они вообще ориентированы на PHP4), как правило, они имеют встроенный механизм загрузки, который может выглядеть, например, так:
if (!class_exists($ClassName)) {
$PrefixArray = $Context->Configuration['LIBRARY_NAMESPACE_ARRAY'];
$PrefixArrayCount = count($PrefixArray);
$i = 0;
for ($i = 0; $i < $PrefixArrayCount; $i++) {
$File = $Context->Configuration['LIBRARY_PATH'].$PrefixArray[$i].'/'.$PrefixArray[$i].'.Class.'.$ClassName.'.php';
if (file_exists($File)) {
include($File);
break;
}
}
// If it failed to find the class, throw a fatal error
if (!class_exists($ClassName)) $Context->ErrorManager->AddError($Context, 'ObjectFactory', 'NewObject', 'The "'.$ClassName.'" class referenced by "'.$ClassLabel.'" does not appear to exist.');
}
(фрагмент кода форума Vanilla 1.1.4)
Проблема в том, что функция class_exists(), которая всего лишь проверяет загружен класс или нет, в случае отсутствия класса вызывает функцию __autoload() (кстати, в документации про это нет ни слова и лично мне такое поведение PHP кажется неправильным), и если вы используете пример из документации:
<?php
function __autoload($class_name) {
require_once $class_name . '.php';
}
$obj = new MyClass1();
$obj2 = new MyClass2();
?>
то это приведет к фатальной ошибке, т.к. ваш __autoload() не найдет класс, относящийся к чужому скрипту, да его может и вовсе не существовать — логика программы может быть построена так: если класс есть, то делаем одно, если класса нет, то делаем другое. А тут у нас жестко — require_once. Нет класса? Фатальная ошибка!
Таким образом, функция, которая предоставляет PHP-движку “a last chance to load the class before PHP fails with an error”, сама приводит к фатальной ошибке. Причем, сообщение PHP о фатальной ошибке будет указывать не на то место, где было обращение к несуществующему классу, а всегда на одно и то же место — место require_once в __autoload(), что при отладке совершенно бесполезно.
Решение состоит в том, чтобы заменить require_once на @include_once. Последний не вызывает фатальной ошибки, а “@” вообще отключает вывод варнингов. Мой init.php выглядит следующим образом:
<?php
define('STD_CLASSES_DIR', dirname(__FILE__).'/std_classes');
define('PEAR_DIR', dirname(__FILE__).'/PEAR');
ini_set('include_path', implode(PATH_SEPARATOR, array(
dirname(dirname(__FILE__)),
STD_CLASSES_DIR,
PEAR_DIR,
dirname(__FILE__).'/includes',
)));
require_once 'class_func.php';
function __autoload($class) {
@include_once(convert_class_to_filename($class));
}
if (ini_get('magic_quotes_gpc'))
include_once 'magic_quotes_off.php';
mb_internal_encoding('UTF-8');
?>
А class_func.php выглядит так:
<?php
function convert_class_to_filename($class) {
return str_replace('_', '/', $class).'.php';
}
function class_file_exists($class) {
$file = convert_class_to_filename($class);
$dirs = explode(PATH_SEPARATOR, ini_get('include_path'));
foreach ($dirs as $dir) {
if (is_file($dir.'/'.$file))
return true;
}
return false;
}
?>
В моем случае всегда есть однозначное соответствие между именами классов и именами соответствующих файлов на диске и это очень удобно.