Автозагрузчик классов

 

Доброго всем времени суток. Сегодня мы с Вами продолжаем собирать ядро для 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. В нем содержатся начало папок в которых необходимо искать не подключенный класс. Поиск будет производится по папкам проекта, движка и модулем этих двух папок. То есть в нашем примере произойдет следующее.

  • Возьмется первый элемент массива classes, а именно /???/rs-mini.
  • Пристыкуется переменная className и получим вот такую строку: /???/rs-mini/conf/test.class.php
  • Далее с помощью функции file_exists() проверится есть ли такой файл. Если нет цикл повторится. В нашем примере нет такого файла.
  • Берется второй элемент массива, а именно /???/project
  • Повторится пристыковка, в следствие чего получаем вот такую строку: /???/project/conf/test.class.php
  • С помощью функции file_exists() подтвердится что такой файл есть, после чего отработает функция require_once() которая подключит данный класс, предотвратив вывод ошибки о том что класс не найден.

Тест

Давайте посмотрим как это работает. Подправим нашу тестовую точку входа (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)

Всего Вам наилучшего, у меня все!

Прикрепленные файлы

 

Возможно Вам будут интересны следующие заметки

Комментарии (9)

Ваше имя *
Сайт
Ваш E-mail *
Ваше сообщение *
 
Вы не подтвердили условия политики конфиденциальности.
dlegame, 15 Июля 2015 г. 05:49 пишет:
Читатель
Грубая ошибка, с этим:
if(!defined('RS-MINI')) die();
Зачем if? если сама функция defined(); проверяет все это дело, просто укажите:
defined('RS-MINI') or die();
Дмитрий, 15 Июля 2015 г. 10:05 пишет:
Гость
Не сказал бы что это "Грубая" ошибка)
Ответ для пользователя: dlegame
Алексей, 15 Июля 2015 г. 10:07 пишет:
Автор
А в чем разница, кроме как написания?
Ответ для пользователя: dlegame
Кирпичик, 16 Июля 2015 г. 19:41 пишет:
Гость
может быть из за таких "грубых" ошибок, система потом будет работать на 5 сек медленее(((
Ответ для пользователя: Алексей
Алексей, 16 Июля 2015 г. 19:56 пишет:
Автор
Если только на 0.00005.
Ответ для пользователя: Кирпичик
CWTeam, 19 Июля 2015 г. 15:56 пишет:
Читатель
Вставлю и тут свое словечко.
Не пробу было взять готовый вариант по стандарту 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
Алексей, 19 Июля 2015 г. 22:24 пишет:
Автор
Можно было, но хочется свое, с покером и куртизанками =)
Ответ для пользователя: CWTeam
Кирпчик, 29 Июля 2015 г. 21:18 пишет:
Гость
loader::register();
А как это работает?( что означают точки??
Алексей, 30 Июля 2015 г. 00:18 пишет:
Автор

У класса есть методы статичные и обычные. Обычные методы можно вызвать только в том случае если был сделан объект класса

<?php

class a {
    public function getName($name)
    {
        return $name;
    }
}

$a = new a();
echo $a->getName('Rio-Shaman');

метод getName() из объекта $a (класс a) вызывается через стрелку.

Статичные методы можно вызывать не делая объект класса

<?php

class a {
    public static function getName($name)
    {
        return $name;
    }
}

echo a::getName('Rio-Shaman');

метод getName() уже вызывается не из объекта, а прямо указывая класс. И вызов происходит не через стрелку, а через два двоеточия

Ответ для пользователя: Кирпчик