Не получается загрузить класс через spl_autoload_register, как исправить?
Решил сделать свою MVC, но без использования Composer Namespace. Решил пойти по пути Zend'a и сделать неймспейсы в названиях классов. Проблема началась тогда, когда я захотел реализовать расширения для MVC, чтобы было легко добавлять "плагины" без влезания в основной код.
Позаимствовав extendClass у движка XenForo, я получил класс для расширений, но вот проблема в том, что при попытке сделать class_exists, я получаю ошибку, что нет такого класса, получается, spl_autoload_register не видит этот класс при попытке сделать class_alias с виртуальным классом.
Ошибка:
Could not find class MyAddon_Application_Controller_Pub_Index when attempting to extend Application_Controller_Pub_Index
Проблема еще заключается в том, что в Extension.php я делаю:
Application::autoload($outputClass); dump(Application::getAutoloader()->getLoadedClasses()); if (!class_exists($outputClass)) { throw new Exception("Could not find class $outputClass when attempting to extend $class"); } |
Application::autoload($outputClass); dump(Application::getAutoloader()->getLoadedClasses()); if (!class_exists($outputClass)) { throw new Exception("Could not find class $outputClass when attempting to extend $class"); }
Вывод дампа:
^ array:2 [▼ 0 => "Application_Mvc_Extension" 1 => "Application_Controller_Pub_AbstractController" ] |
^ array:2 [▼ 0 => "Application_Mvc_Extension" 1 => "Application_Controller_Pub_AbstractController" ]
По какой-то причине тут нет "MyAddon_Application_Controller_Pub_Index".
Очень надеюсь, что есть те, кто помогут. Потому что для меня это выглядит как какая-то магия
Структура проекта:
Код autoloader'a:
<?php class Application_Autoload { protected $loadedClasses; protected static $_instance; public function __construct() { spl_autoload_register([$this, 'autoloader']); } public static final function getInstance(): Application_Autoload { if (!self::$_instance) { self::$_instance = new self(); } return self::$_instance; } public function getLoadedClasses() { return $this->loadedClasses; } /** * @throws Exception */ public function autoloader($class, $type = null): bool { $this->loadedClasses[] = $class; if (!$class) { return false; } if (class_exists($class, false) || interface_exists($class, false)) { return true; } if (substr($class, 0, 5) == 'XFCP_') { throw new Exception('Cannot load class using XFCP. Load the class using the correct loader first.'); } $filename = $this->autoloaderClassToFile($class, $type); if (!$filename) { return false; } if (file_exists($filename)) { include($filename); return (class_exists($class, false) || interface_exists($class, false)); } return false; } public function autoloaderClassToFile($class, $type) { if (preg_match('#[^a-zA-Z0-9_\\]#', $class)) { return false; } $srcPath = $type ? '/src/addons/' : '/src/'; return str_replace(['_', '\'], '/', Application::getRootDirectory()) . $srcPath . str_replace(['_', '\'], '/', $class) . '.php'; } } |
<?php class Application_Autoload { protected $loadedClasses; protected static $_instance; public function __construct() { spl_autoload_register([$this, 'autoloader']); } public static final function getInstance(): Application_Autoload { if (!self::$_instance) { self::$_instance = new self(); } return self::$_instance; } public function getLoadedClasses() { return $this->loadedClasses; } /** * @throws Exception */ public function autoloader($class, $type = null): bool { $this->loadedClasses[] = $class; if (!$class) { return false; } if (class_exists($class, false) || interface_exists($class, false)) { return true; } if (substr($class, 0, 5) == 'XFCP_') { throw new Exception('Cannot load class using XFCP. Load the class using the correct loader first.'); } $filename = $this->autoloaderClassToFile($class, $type); if (!$filename) { return false; } if (file_exists($filename)) { include($filename); return (class_exists($class, false) || interface_exists($class, false)); } return false; } public function autoloaderClassToFile($class, $type) { if (preg_match('#[^a-zA-Z0-9_\\]#', $class)) { return false; } $srcPath = $type ? '/src/addons/' : '/src/'; return str_replace(['_', '\'], '/', Application::getRootDirectory()) . $srcPath . str_replace(['_', '\'], '/', $class) . '.php'; } }
Код Application.php(точка входа приложения, вызывается start() из index.php):
// ... public static function getExtensions(): array { return [ 'Application_Controller_Pub_Index' => [ 'MyAddon_Application_Controller_Pub_Index' ] ]; } /** * @throws Exception */ public static function start($rootDirectory): void { set_error_handler(['Application', 'errorHandler']); register_shutdown_function(['Application', 'fatalHandler']); set_exception_handler(['Application', 'exceptionHandler']); ini_set('display_errors', 0); ini_set('error_reporting', E_ALL); self::$time = time(); self::$rootDirectory = $rootDirectory; self::$autoloader = new Application_Autoload(); foreach (Application::getExtensions() as $baseClass => $fakeClass) { self::extendClass($baseClass, $fakeClass); } self::$router = new Application_Mvc_Router(); } /** * @throws Exception */ public static function extendClass($extendClass, $fakeClass = null) { $a = new Application_Mvc_Extension(self::getExtensions()); return $a->extendClass($extendClass, $fakeClass); } public static function autoload($class) { return Application_Autoload::getInstance()->autoloader($class); } // ... |
// ... public static function getExtensions(): array { return [ 'Application_Controller_Pub_Index' => [ 'MyAddon_Application_Controller_Pub_Index' ] ]; } /** * @throws Exception */ public static function start($rootDirectory): void { set_error_handler(['Application', 'errorHandler']); register_shutdown_function(['Application', 'fatalHandler']); set_exception_handler(['Application', 'exceptionHandler']); ini_set('display_errors', 0); ini_set('error_reporting', E_ALL); self::$time = time(); self::$rootDirectory = $rootDirectory; self::$autoloader = new Application_Autoload(); foreach (Application::getExtensions() as $baseClass => $fakeClass) { self::extendClass($baseClass, $fakeClass); } self::$router = new Application_Mvc_Router(); } /** * @throws Exception */ public static function extendClass($extendClass, $fakeClass = null) { $a = new Application_Mvc_Extension(self::getExtensions()); return $a->extendClass($extendClass, $fakeClass); } public static function autoload($class) { return Application_Autoload::getInstance()->autoloader($class); } // ...
Код Extension.php:
<?php class Application_Mvc_Extension { protected $classExtensions = []; public function __construct(array $classExtensions = []) { $this->classExtensions = $classExtensions; } /** * @throws Exception */ public function extendClass($class, $type = '', $fakeBase = false) { if (!$class) { return false; } $extensions = !empty($this->classExtensions[$class]) ? $this->classExtensions[$class] : []; if (!$extensions) { return $class; } $outputClass = $class; Application::autoload($outputClass); foreach ($extensions as $baseClass => $extendClass) { if (preg_match('/[;,$/#"'.()]/', $extendClass)) { continue; } $proxyClass = 'XFCP_' . $extendClass; class_alias($outputClass, $proxyClass); $outputClass = $extendClass; Application::autoload($outputClass); if (!class_exists($outputClass)) { throw new Exception("Could not find class $outputClass when attempting to extend $class"); } } $this->classExtensions[$class] = $outputClass; Application::autoload($outputClass); return $outputClass; } } |
<?php class Application_Mvc_Extension { protected $classExtensions = []; public function __construct(array $classExtensions = []) { $this->classExtensions = $classExtensions; } /** * @throws Exception */ public function extendClass($class, $type = '', $fakeBase = false) { if (!$class) { return false; } $extensions = !empty($this->classExtensions[$class]) ? $this->classExtensions[$class] : []; if (!$extensions) { return $class; } $outputClass = $class; Application::autoload($outputClass); foreach ($extensions as $baseClass => $extendClass) { if (preg_match('/[;,$/#"'.()]/', $extendClass)) { continue; } $proxyClass = 'XFCP_' . $extendClass; class_alias($outputClass, $proxyClass); $outputClass = $extendClass; Application::autoload($outputClass); if (!class_exists($outputClass)) { throw new Exception("Could not find class $outputClass when attempting to extend $class"); } } $this->classExtensions[$class] = $outputClass; Application::autoload($outputClass); return $outputClass; } }
src/addons/MyAddon/Application/Controller/Pub/Index.php:
<?php class MyAddon_Application_Controller_Pub_Index extends XFCP_Application_Controller_Pub_Index { public function __construct() { echo 'constructor'; } public function actionIndex($params) { echo 'extend class'; } } if (false) { class MyAddon_Application_Controller_Pub_Index extends Application_Controller_Pub_Index { } } |
<?php class MyAddon_Application_Controller_Pub_Index extends XFCP_Application_Controller_Pub_Index { public function __construct() { echo 'constructor'; } public function actionIndex($params) { echo 'extend class'; } } if (false) { class MyAddon_Application_Controller_Pub_Index extends Application_Controller_Pub_Index { } }
Дополнительно:
Вы походу в первый 3енд смотрите. Смотрите в третий, не изобретайте велосипед, ставьте psr4
Лучше переписать сейчас, чем потом просто с нуля пилить все, попробуйте нэймспейсы, 5.3 умер
Кажись, композер не отрабатывает загрузку классов из вне папки Application, потому что плагины находятся на уровень выше.
корень/src/addons/
корень/src/Application/
корень/src/composer.json:
{
"autoload": {
"psr-4": {
"Application\": "Application"
}
}
}
Не знаете, как сделать, чтобы загрузка шла из addons и из Application? Не хотелось бы переносить плагины в Application
1. /src/Application/Extension.php
2. /src/addons/Application/Controller/Pub/Index.php
В первом я пытался вызвать класс под номером 2, но он его не видит.
Всё прописано:
namespace MyAddonApplicationControllerPub;
class Index extends XFCP_Index
Может я что-то не так делаю? Эта проблема уже замучала, 3 дня колупаю, в чём загвоздка и почему он не видит класс. Что с неймспейсами, что с ручной регистрацией через spl_autoload_register - одна проблема
Решил проблему тем, что перешёл на неймспейсы и в своём extension.php отдельно(независимо от composer autoloader'a) подключал файлы плагинов.
spl_autoload_register(function ($class) { $path = str_replace('\', '/', Application::getRootDirectory() . '/src/addons/' . $class . '.php'); if (!file_exists($path)) { throw new Exception("Файл расширения {$class} по пути {$path} не найден!"); } else { require $path; } }); |
spl_autoload_register(function ($class) { $path = str_replace('\', '/', Application::getRootDirectory() . '/src/addons/' . $class . '.php'); if (!file_exists($path)) { throw new Exception("Файл расширения {$class} по пути {$path} не найден!"); } else { require $path; } });
Спасибо Дмитрий !
- Хм. А прописать единственную строку в composer.json не проще было?
Ваша функция, конечно, тоже не особо сложная, но одну строку прописать в секции автозагрузки все равно проще =)) - Сергей delphinpro, не подскажете как? Я пытался добавить так:
{
"autoload": {
"psr-4": {
"Application\": "Application",
"addons\": "addons"
}
},
"require-dev": {
"symfony/var-dumper": "^5.4"
},
"require": {
"ext-pdo": "*"
}
}Но не сработало. Если вы знаете, то подскажите, пожалуйста
- Aqulus, Подскажу
Для начала вот
PSR-0 или PSR-4, и как правильно построить структуру проекта?
PHP: автозагрузка классов. Объясните на пальцах на простом примере?Допустим имеется вот такая структура
src App Controllers AnyController.php Addons FirstAddon SomeClass.php
src App Controllers AnyController.php Addons FirstAddon SomeClass.php
App – это основное приложение. Все классы в этой папке будут начинаться с неймспейса App. Таким образом в файле AnyController.php будет класс class AppControllersAnyController {}
Addons - дополнения. Все классы в этой папке будут начинаться с неймспейса Addons. Таким образом в файле SomeClass.php будет класс class AddonsFirstAddonSomeClass {}
В composer.json пишется следующее
{ "autoload": { "psr-4": { "App\": "src/App", "Addons\": "src/Addons" } }, }
{ "autoload": { "psr-4": { "App\": "src/App", "Addons\": "src/Addons" } }, }
Выполняется команда composer du, подключается автозагрузчик из папки vendor include 'vendor/autoload.php'; и всё работает
- Корневые папки пространства имен, те что описываются в composer.json могут называться как угодно.
src dpr core Controllers AnyController.phpsrc dpr core Controllers AnyController.php
{ "autoload": { "psr-4": { "Delphinpro\App\": "src/dpr/core" } }, }
{ "autoload": { "psr-4": { "Delphinpro\App\": "src/dpr/core" } }, }
главное, что указан корневой неймспейс, и именно он будет использоваться
файл src/dpr/core/Controllers/AnyController.php содержит класс
class DelphinproAppControllersAnyController { }
class DelphinproAppControllersAnyController { }
src/dpr/core/Controllers/AnyController.php
class DelphinproAppControllersAnyController
Опишите проблему, и специалист поможет с настройкой, исправлением ошибки или доработкой сайта. Подберём понятный план работ без лишней переписки.
Пока нет других ответов. Будьте первым, кто поможет автору.
Ответить на вопрос

Проблема с загрузкой класса через spl_autoload_register может возникать по нескольким причинам. Вот несколько шагов, которые могут помочь исправить эту проблему:
1. Проверьте путь к файлу класса: Убедитесь, что путь к файлу класса указан правильно в функции spl_autoload_register. Если путь указан неверно, PHP не сможет найти файл и загрузить класс.
2. Проверьте правильность именования файлов: Убедитесь, что имя файла класса совпадает с именем класса и что расширение файла соответствует используемому языку программирования (например, .php для PHP).
3. Проверьте пространство имен и автозагрузку: Если вы используете пространства имен в своем коде, убедитесь, что они правильно настроены и соответствуют структуре каталогов. Также проверьте, что автозагрузчик корректно обрабатывает имена классов с учетом пространства имен.
4. Проверьте наличие файла с классом: Убедитесь, что файл с классом существует в указанном пути и что он доступен для чтения. Если файла нет или у вас нет прав на его чтение, PHP не сможет загрузить класс.
5. Проверьте ошибки и логи: Проверьте журнал ошибок PHP на наличие сообщений об ошибках при попытке загрузить класс. Это может помочь вам определить причину проблемы и исправить ее.
Если после выполнения этих шагов проблема с загрузкой класса через spl_autoload_register все еще не решена, попробуйте использовать другие методы автозагрузки классов, такие как Composer или простое подключение файлов с классами вручную.