PHP - Предотвратить ошибку автозагрузчика

У меня есть структура MVC в моем php.

Я использую автозагрузку для инициализации правильного контроллера.

Вот очень упрощенная версия моего index.php:

<?php
spl_autoload_extensions('.class.php');
spl_autoload_register();

$controller = strtolower($_GET["controller"]);
$action = strtolower($_GET["action"]);

$obj = new $controller();
$obj->{$action}();

Допустим, пользователь загружает www.example.com/Page/View. Apache перепишет его на www.example.com?controller=Page&action=View. Затем, когда php вызывает $obj = new $controller();, он попытается найти page.class.php в том же каталоге, что и файл (очевидно, в моем приложении файловая структура не так тривиальна, с пространствами имен и т. д.), а затем загрузит класс Page внутри и выполнить Page->View();.

Теперь предположим, что пользователь делает опечатку и пытается загрузить www.example.com/Pagr/View. В идеале он должен получить ответ заголовка 404. Но с текущей реализацией php просто выдаст Fatal error, если не удастся автоматически загрузить pagr.class.php.

Как я могу предотвратить появление этой ошибки? Я провел некоторое исследование и не могу найти способ проверить, может ли класс быть загружен автоматически или нет до вызова new.


person Nikita240    schedule 24.03.2015    source источник
comment
Как насчет использования блока try-catch? попробуйте {$obj = новый $controller(); } catch(ExceptionThrownOnNotClassFound){ // создаем ответ 404 }   -  person Warzyw    schedule 24.03.2015
comment
Не могу поймать фатальную ошибку.   -  person Nikita240    schedule 24.03.2015
comment
Я просто пытаюсь сделать предположение здесь. Возможно, вам поможет проверка, существует ли класс $obj с помощью class_exists.   -  person FranMercaes    schedule 24.03.2015
comment
Просто говорю: не используйте слепо входные значения из $_GET. Пользователь может создать несколько неприятных URL-адресов с непреднамеренными и, возможно, опасными последствиями.   -  person donquixote    schedule 17.09.2015


Ответы (4)


Используйте class_exists(), чтобы проверить, существует ли класс, прежде чем пытаться его загрузить.

Однако вот как бы я это сделал:

class ClassNotFoundException extends Exception {}

function autoloadClass($class) {
  $file = $class . '.class.php';

  if(!file_exists($file)) {
    throw new ClassNotFoundException($class);
  }

  require($file);
}

spl_autoload_register('autoloadClass');

try {
  $obj = new $controller();
}
catch(ClassNotFoundException $e) {
  // call 404 page
}
person halloei    schedule 24.03.2015
comment
class_exists() проверяет, загружен ли уже класс. Он не проверяет, может ли он быть автозагружен. - person Nikita240; 24.03.2015
comment
вы не можете проверить существование файла? - person B-and-P; 24.03.2015
comment
@halloei Ваша реализация является наиболее очевидной, однако люди сообщают, что, по-видимому, php игнорирует исключения, созданные внутри функции __autoload, и все равно выдает Fatal error. См. это. - person Nikita240; 24.03.2015
comment
В настоящее время я думаю, что самый простой способ - это, вероятно, просто попытаться проверить с помощью class_exists и поймать исключение, которое он выдает. Я собираюсь попробовать. - person Nikita240; 24.03.2015
comment
Примечание. До версии 5.3.0 исключения, выдаваемые функцией __autoload, не могли быть перехвачены в блоке catch, что приводило к фатальной ошибке. Начиная с версии 5.3.0 и выше, исключения, генерируемые функцией __autoload, могут быть перехвачены в блоке catch с одним условием. Если создается пользовательское исключение, должен быть доступен пользовательский класс исключения. Функция __autoload может использоваться рекурсивно для автоматической загрузки пользовательского класса исключений. - person halloei; 24.03.2015
comment
Ооо я этого не видел. Это все меняет. - person Nikita240; 24.03.2015
comment
То, что вы предлагаете здесь, не является хорошей идеей. Генерация исключений или возникновение ошибок в автозагрузчике не позволяет другим автозагрузчикам выполнять свою работу. Вместо этого используйте class_exists(). - person donquixote; 17.09.2015

@halloei правильно, но требует комментария, чтобы объяснить, почему это правильно. class_exists — еще одна забавная функция PHP, которая пытается автоматически загрузить класс, если он не существует. Это довольно нелогично, но так оно и работает (это поведение можно отключить вторым аргументом). Таким образом, class_exists сообщит вам не о том, существует ли класс, а о том, существует ли он или находится ли он в пределах досягаемости зарегистрированных автозагрузчиков.

person Etki    schedule 24.03.2015
comment
Хм. Когда я пытаюсь запустить class_exists для класса, который не может быть загружен автоматически, он выдает ошибку LogicException. Очевидно, это ошибка в php. - person Nikita240; 24.03.2015
comment
@ Nikita240 Я думаю, что проверка пути / перехват исключений - ваш единственный вариант здесь. Но я настоятельно рекомендую вам избегать этого шаблона и переписывать архитектуру просто потому, что любой может вызвать __construct() для любого класса или даже сделать что-то еще хуже. - person Etki; 24.03.2015
comment
Можно поподробнее об архитектуре? - person Nikita240; 24.03.2015
comment
@ Nikita240 Я бы реализовал простой маршрутизатор, просто сопоставление шаблонов регулярных выражений с конкретными действиями контроллера (например, ['user/(\d+)' => 'User/Display']) и проверил uri запроса на соответствие этим регулярным выражениям. Насколько я понимаю, это небольшой проект, поэтому реализовать его так просто не составит труда (я брал фреймворк для любого проекта, но здесь это не мое решение), и это ограничило бы то, что может быть казненным. Случай «Ничего не соответствует» должен просто отображать страницу 404. - person Etki; 24.03.2015

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

if(file_exists(strtolower($controller).'.class.php') && class_exists ($controller, false))
{
   $obj = new $controller();
}

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

person NaijaProgrammer    schedule 24.03.2015
comment
Ну, это должно быть немного сложнее, так как для правильной реализации вам нужно будет вычислить путь включения и пространство имен, но да, вы можете проверить, существует ли файл. Но проблема в том, что он по-прежнему выдает ошибку, если класс не может быть найден в файле, что является ситуацией, в которой вы все равно хотите вернуть 404. - person Nikita240; 24.03.2015
comment
Лучше всего будет совместить проверку выше с тем, что предлагает @halloei. Я обновлю свой ответ, чтобы отразить то, что я имею в виду. - person NaijaProgrammer; 24.03.2015

Вот один из способов сделать это.

class_exists() предположительно пытается автоматически загрузить класс, если он еще не определен . Однако в настоящее время в php существует ошибка, из-за которой class_exists() выдает LogicException, если автозагрузчик не работает. Но мы можем просто перехватить это исключение, пока ошибка не будет исправлена!

<?php
spl_autoload_extensions('.class.php');
spl_autoload_register();

$controller = strtolower($_GET["controller"]);
$action = strtolower($_GET["action"]);

try {
    $class_exists = class_exists($controller);
}
catch(LogicException $e) {
    $class_exists = false;
}
finally {
    if(!$class_exists) {
        http_response_code(404);
        exit();
    }
}

$obj = new $controller();
$obj->{$action}();
person Nikita240    schedule 24.03.2015