Доброго всем времени суток. Сегодня мы с Вами продолжаем собирать ядро для CMS RS-MINI. По плану у Нас создания автозагрузчика классов. Зачем он нужен?
Очень плохая практика писать весь код в одном файле. Да и если Ваш проект что-то посложнее вывода «привет мир» на экран, у Вас просто не получится уместить логику в одном месте. Вы попытаетесь распихать код по разным папкам, и склеить все в одно с помощью include или require_once (предпочтительнее второе)
Если глянуть на старые проекты, то можно увидеть где-нибудь в системных папках файл в котором написана портянка из множеств require_once. А в большинстве случаев данные портянки можно наблюдать прям в классах каких-нибудь прикладных модулей.
В общем не важно как, кто и где подключает необходимые библиотеки. Эти подходы имеют право на жизнь. Но в нашем случае (я имею ввиду в случае движка RS-MINI) хочется обращаться к классам по неймспейсу, и не думать, подключен ли класс или нет.
Это возможно сделать используя специальный стек. Смысл такой, в php есть возможность вызвать функцию или класс перед тем моментом как php сгенерирует ошибку о том что вызываемый класс не найден. То есть у нас есть шанс подключить нужный класс до того как php отругает нас. Нам лишь осталось написать такой класс, и зарегистрировать его в этом специальном стеке.
Так как до реального использования автозагрузчика нам еще далеко давайте для начала подготовим специальный файл, в котором мы протестируем работу класса. Я предлагаю скопировать точку входа (только без echo) и назвать ее test.php (положить тоже в корень сайта) В этом файле будет подключен класса автозагрузки, запущен метод регистрации в стеке и запуск автоподключение тестового класса
<?php
/*
* @package RS-MINI
* @copyright (c) 2015 Alexey Glumov aka Rio-Shaman (support@rio-shaman.ru)
* @license GNU General Public License version 2; see LICENSE.txt
*
*/
define('RS-MINI', TRUE); // константа безопасности
ini_set('display_errors', 1); // показывать ли ошибки на экране
error_reporting(E_ALL | E_STRICT); // ур-нь ошибок
mb_internal_encoding("UTF-8"); // кодировка
date_default_timezone_set('Europe/Moscow'); // временая зона
define('SHORT_DIR_RSMINI', '/rs-mini'); // короткий путь к движку (от корня пользовательской папки)
define('SHORT_DIR_PROJECT', '/project'); // короткий путь к проекту (от корня пользовательской папки)
define('DIR_RSMINI', __DIR__ . SHORT_DIR_RSMINI); // каталог где лежит система (полный путь)
define('DIR_PROJECT', __DIR__ . SHORT_DIR_PROJECT); // каталог где лежит проект (полный путь)
Так же нам понадобится тестовый класс который мы будем подключать. Его я предлагаю поместить в папку /project/conf/ и назвать test.class.php. Почему именно в такую папку? Я хочу что-бы тест был приближен к реальности, а в реальности в этой папке будет лежать файл конфигурации движка. К тому же метод автозагрузчика будет мониторить только системные папки, а это именно системная папка
Вот код тестового класса
<?php
/*
* @package RS-MINI
* @copyright (c) 2015 Alexey Glumov aka Rio-Shaman (support@rio-shaman.ru)
* @license GNU General Public License version 2; see LICENSE.txt
*
*/
namespace conf;
if(!defined('RS-MINI')) die();
/*
* класс для проверки автозагрузчика
*/
class test
{
// метод выведит нам привет мир
public static function getTest()
{
return "Привет мир из папки /project/conf/";
}
}
Обратите внимание, что я указал неймспейс conf. Почему именно такое наименование? Неймспейс поможет автозагрузчику понять в какой папке ему искать вызываемый класс. Точный путь до класса будет /project/conf/test.class.php. Папка project будет прибита гвоздями в самом автозагрузчике, а папка conf возьмется из неймспейса.
Настало время предъявить код автозагрузчика. Вот он (файл называется loader.class.php и лежит в папке /rs-mini/core/)
<?php
/*
* @package RS-MINI
* @copyright (c) 2015 Alexey Glumov aka Rio-Shaman (support@rio-shaman.ru)
* @license GNU General Public License version 2; see LICENSE.txt
*
*/
if(!defined('RS-MINI')) die();
/*
* Класс автозагрузки классов
*/
class loader
{
/*
* метод подключения классов
*
* @access - private
*
* @param string className - имя вызываемого класса
*/
private static function loadClasses($className)
{
$classPath = str_replace('\\', '/', $className).'.class.php';
// папки modules присутсвуют в стеке отдельно для того что бы
// наймспейс был вида configurator\main\controller а не modules\configurator\main\controller
// немного сокращаем запись так как неймспейсы контроллеров и шаблонов
// бывают иногда очень длинными
$classes = array(
DIR_RSMINI, // система
DIR_PROJECT, // проект
DIR_RSMINI . '/modules', // модули системы
DIR_PROJECT . '/modules' // модули проекта
);
foreach($classes as $class) {
$filepath = $class . '/' . $classPath;
if (file_exists($filepath)) {
require_once($filepath);
return;
}
}
}
/*
* регистрирует класс и метод в стэке метода __autoload
*
* @access - public
*/
public static function register()
{
spl_autoload_register(array('loader', 'loadClasses'));
}
}
Давайте поясню методы данного класса. Ну начнем пожалуй с register(). Обратите внимание данный метод статичный, а стало быть создавать объект для вызова этого метода нет нужды (мне следовало бы написать небольшую заметку по поводу статичных методов, но попробую внедрить информацию о таких методах по ходу дела). В теле этого метода используется тот самый инструмент для регистрации метода класса в специальном стеке. Именно функция spl_autoload_register() позволяет нам определить какой код будет отрабатывать перед тем как php ругнется на нас. В параметры этой функции был послан массив. Первый элемент массива это имя класса автозагрузчика. Второй элемент массива — имя метода в этом классе.
Второй метод называется loadClasses(). Он тоже является статичным. Это необходимо иначе функция spl_autoload_register() не сможет его вызвать. В теле данного метода мы обрабатываем параметр className. Этот параметр будет объявлен функцией spl_autoload_register(). Содержит имя вызываемого не подключенного класса.
В имени содержится не просто имя класса, но и неймспейс. То есть если Вы в коде напишете вот так:
$obj = new \conf\test();
то в переменной className будет содержаться вот такая стока:
conf\test
Первая строка метода loadClasses() заменяет обратные слэши на обычные слэши и пристыковывает окончание .class.php, тем самым мы получаем вот такую строку:
conf/test.class.php
Далее формируется массив classes. В нем содержатся начало папок в которых необходимо искать не подключенный класс. Поиск будет производится по папкам проекта, движка и модулем этих двух папок. То есть в нашем примере произойдет следующее.
Давайте посмотрим как это работает. Подправим нашу тестовую точку входа (test.php) вот таким образом
<?php
/*
* @package RS-MINI
* @copyright (c) 2015 Alexey Glumov aka Rio-Shaman (support@rio-shaman.ru)
* @license GNU General Public License version 2; see LICENSE.txt
*
*/
define('RS-MINI', TRUE); // константа безопасности
ini_set('display_errors', 1); // показывать ли ошибки на экране
error_reporting(E_ALL | E_STRICT); // ур-нь ошибок
mb_internal_encoding("UTF-8"); // кодировка
date_default_timezone_set('Europe/Moscow'); // временая зона
define('SHORT_DIR_RSMINI', '/rs-mini'); // короткий путь к движку (от корня пользовательской папки)
define('SHORT_DIR_PROJECT', '/project'); // короткий путь к проекту (от корня пользовательской папки)
define('DIR_RSMINI', __DIR__ . SHORT_DIR_RSMINI); // каталог где лежит система (полный путь)
define('DIR_PROJECT', __DIR__ . SHORT_DIR_PROJECT); // каталог где лежит проект (полный путь)
// подключаем автозагрузчик
require_once(DIR_RSMINI . '/core/loader.class.php');
// регистрируем автозагрузчик в стеке
loader::register();
// а теперь давайте вызовем наш тестовый метод тестового класса
echo \conf\test::getTest();
Результат виден на скриншоте
Архив с примером вы можете скачать в конце статьи.
И вот еще что, все тестовые файлы которые мы сегодня делали можно смело удалять (тестовые это test.php и /project/conf/test.class.php)
Всего Вам наилучшего, у меня все!
Не пробу было взять готовый вариант по стандарту PSR-0 https://gist.github.com/jwage/221634 или PSR-4 https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-4-autoloader-examples.md
У класса есть методы статичные и обычные. Обычные методы можно вызвать только в том случае если был сделан объект класса
метод getName() из объекта $a (класс a) вызывается через стрелку.
Статичные методы можно вызывать не делая объект класса
метод getName() уже вызывается не из объекта, а прямо указывая класс. И вызов происходит не через стрелку, а через два двоеточия