Обзор

Phalcon\Loader позволяет загружать классы проекта автоматически, на основе некоторых предопределенных правил. Поскольку этот компонент написан на C, он обеспечивает наименьшие накладные расходы при чтении и интерпретации внешних файлов PHP.

Поведение этого компонента основано на возможностях PHP автозагрузки классов. Если класс, который еще не существует, используется в какой-либо части кода, его попытается загрузить специальный обработчик. Специальный обработчик для этой операции-Phalcon\Loader. Загружая классы по мере необходимости, общая производительность увеличивается, так как чтение файлов выполняется только для необходимых файлов. Этот метод называется отложенной инициализацией.

С помощью этого компонента вы можете загружать файлы из других проектов или поставщиков, этот автозагрузчик совместим с PSR-0 и PSR-4.

Компонент Phalcon\Loader предлагает опции для загрузки файлов на основе их класса, имени файла, каталогов в вашей файловой системе, а также расширений файлов.

Регистрация

Обычно мы используем spl_autoload_register() для регистрации пользовательского загрузчика для нашего приложения. Phalcon\Loader скрывает эту сложность. После определения всех ваших пространств имен, классов, каталогов и файлов вам нужно будет вызвать функцию register(), и загрузчик будет готов к использованию.

<?php

use Phalcon\Loader;

$loader = new Loader();

$loader->registerNamespaces(
    [
       'MyApp'        => 'app/library',
       'MyApp\Models' => 'app/models',
    ]
);

$loader->register();

register() использует spl_autoload_register() внутренне. В результате он принимает также принимает логический параметр prepend. Если указано значение true, то загрузчик будет добавлен в очередь автоматической загрузки вместо добавления (поведение по умолчанию).

Вы всегда можете вызвать метод isRegistered() , чтобы проверить, зарегистрирован ли ваш загрузчик или нет.

Примечание: если есть ошибка в регистрации автозапуска, компонент выдаст исключение.

<?php

use Phalcon\Loader;

$loader = new Loader();

$loader->registerNamespaces(
    [
       'MyApp'        => 'app/library',
       'MyApp\Models' => 'app/models',
    ]
);

$loader->register();

echo $loader->isRegistered(); // true

Отменить регистрацию автозапуска так же просто. Все, что вам нужно сделать, это вызвать unregister().

<?php

use Phalcon\Loader;

$loader = new Loader();

$loader->registerNamespaces(
    [
       'MyApp'        => 'app/library',
       'MyApp\Models' => 'app/models',
    ]
);

$loader->register();

if (true === $loader->isRegistered()) {
    $loader->unregister();
}

Уровень безопасности.

Phalcon\Loader предлагает уровень безопасности, очищающий имена классов по умолчанию, избегая возможного включения несанкционированных файлов. Рассмотрим следующий пример:

<?php

// Basic autoloader
spl_autoload_register(
    function ($className) {
        $filepath = $className . '.php';

        if (file_exists($filepath)) {
            require $filepath;
        }
    }
);

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

<?php

// Эта переменная не фильтруется и поступает из небезопасного источника
$className = '../processes/important-process';

// Проверяет, существует ли класс, запускающий автозагрузчик
if (class_exists($className)) {
    // ...
}

В приведенном выше фрагменте кода, если ../processes/important-process.php -это допустимый файл, который мог быть загружен хакером или из не очень осторожного процесса загрузки, тогда внешний пользователь мог бы выполнить код без какой-либо авторизации и впоследствии получить доступ ко всему приложению, если не к серверу.

Чтобы избежать этих или самых сложных атак, Phalcon\Loaderудаляет недопустимые символы из имени класса, уменьшая вероятность атаки.

Регистрация Пространств Имен

Использование пространств имен имеет некоторые последствия при загрузке соответствующего контроллера. Для настройки поведения фреймворка в пространствах имен Необходимо выполнить одну или все из следующих задач:

Очень популярным способом организации вашего приложения являются каталоги, каждый из которых представляет определенное пространство имен.  Phalcon\Loader  может зарегистрировать эти пространства имен для сопоставления каталогов и пройти через эти каталоги для поиска файла, который требуется вашему приложению.

Пространства имен можно использовать, чтобы избежать конфликтов имен классов. это означает, что при наличии двух контроллеров в приложении с одинаковым именем пространство имен можно использовать для их различения. Пространства имен также полезны для создания пакетов или модулей.

Метод  registerNamespaces() принимает массив, где ключи-это пространства имен, а значения-это фактические каталоги в файловой системе. Разделитель пространства имен будет заменен разделителем каталогов, когда загрузчик попытается найти классы.

 

<?php

use Phalcon\Loader;

$loader = new Loader();

$loader->registerNamespaces(
    [
       'MyApp'             => 'app/library',
       'MyApp\Controllers' => 'app/controllers',
       'MyApp\Models'      => 'app/models',
    ]
);

$loader->register();

В приведенном выше примере всякий раз, когда мы ссылаемся на контроллер, загрузчик будет искать его в  app/controllers и его подкаталогах. Аналогично для модели поиск будет происходить в app/models.

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

Так, например, приведенный выше пример определяет наше пространство имен MyApp , чтобы указать на app/library. Если у нас есть файл:

/app/library/Components/Mail.php

 который имеет пространство имен:

MyApp\Components

затем погрузчику, как указано выше, не нужно знать о местоположении пространства имен  MyApp\Components или иметь его в декларации registerNamespaces().

Если компонент, на который ссылается код, является MyApp\Components\Mail, он будет считать, что он является подприхотиемом пространства корневых имен. Однако, так как мы указали другое место для  MyApp\Controllers и MyApp\Models пространства имен, погрузчик будет искать эти пространства имен в указанных каталогах.

Метод registerNamespaces() также принимает второй параметр merge. по умолчанию он равен false. Однако вы можете установить его в true при наличии нескольких вызовов registerNamespaces() , чтобы определения пространства имен были объединены.

<?php

use Phalcon\Loader;

$loader = new Loader();

$loader->registerNamespaces(
    [
       'MyApp'             => 'app/library',
    ]
);

$loader->registerNamespaces(
    [
       'MyApp\Controllers' => 'app/controllers',
       'MyApp\Models'      => 'app/models',
    ],
    true
);

$loader->register();

Приведенный выше пример объединяет второе объявление registerNamespaces() с предыдущим.

Если вам нужно проверить, какие классы зарегистрированы в автозагрузчике, вы можете использовать геттер getNamespaces() , который возвращает массив зарегистрированных пространств имен. В приведенном выше примере функция getNamespaces() возвращает:

[
   'MyApp'             => 'app/library',
   'MyApp\Controllers' => 'app/controllers',
   'MyApp\Models'      => 'app/models',
]

Регистрация каталогов

Еще один способ сообщить Phalcon\Loader, где находятся файлы вашего приложения, - это зарегистрировать каталоги. Когда файл должен быть востребован приложением, загрузчик автоматически сканирует зарегистрированные каталоги, чтобы найти файл, на который ссылается приложение, чтобы оно могло его запросить.

Метод registerDirs() принимает массив, каждый элемент которого является каталогом в файловой системе, содержащим файлы, которые потребуются приложению.

Этот тип регистрации не рекомендуется с точки зрения производительности. Кроме того, порядок объявленных каталогов имеет значение, так как загрузчик пытается найти файлы, последовательно ища каталоги. В результате сначала должен быть объявлен каталог, содержащий наибольшее количество ссылочных файлов, и т. д.

?php

use Phalcon\Loader;

// Создает автозагрузчик
$loader = new Loader();

// Зарегистрируйте несколько каталогов
$loader->registerDirs(
    [
        'library/MyComponent',
        'library/OtherComponent/Other',
        'vendor/example/adapters',
        'vendor/example',
    ]
);

// Зарегистрировать автозагрузчик
$loader->register();

// Обязательный класс будет автоматически включать файл
// из первого каталога, где он был расположен,
// то есть: library/OtherComponent/Other/Some.php
$some = new \Some(); 

Метод registerDirs() также принимает второй параметр merge. по умолчанию он равен false. Однако вы можете установить его в true при наличии нескольких вызовов  registerDirs() , чтобы определения классов были объединены.

<?php

use Phalcon\Loader;

$loader = new Loader();

$loader->registerDirs(
    [
        'app/functions',
    ]
);

$loader->registerDirs(
    [
        'app/controllers',
        'app/models',
    ],
    true
);

$loader->register();

Приведенный выше пример объединяет второе объявление registerDirs() с предыдущим.

Если вам нужно проверить, какие классы зарегистрированы в автозагрузчике, вы можете использовать геттер getDirs() , который возвращает массив зарегистрированных классов. В приведенном выше примере функция, getDirs() возвращает:

[
    'app/functions',
    'app/controllers',
    'app/models',
]

Регистрация классов

Еще один способ сообщить  Phalcon\Loader, где находятся компоненты/классы ваших классов, чтобы загрузчик мог правильно загрузить их, - это использовать registerClasses().

Метод принимает массив, где ключом является класс пространства имен, а значением-расположение файла, содержащего этот класс. Как и ожидалось, это самый быстрый способ автоматической загрузки класса, так как загрузчику не нужно выполнять сканирование файлов или статистику, чтобы найти ссылки на файлы.

Однако использование этого метода может помешать обслуживанию вашего приложения. Чем больше растет ваше приложение, тем больше файлов добавляется, тем легче становится ошибиться при сохранении списка файлов, используемых в registerClasses()

<?php

use Phalcon\Loader;

$loader = new Loader();

$loader->registerClasses(
    [
        'MyApp\Components\Mail'             => 'app/library/Components/Mail.php',
        'MyApp\Controllers\IndexController' => 'app/controllers/IndexController.php',
        'MyApp\Controllers\AdminController' => 'app/controllers/AdminController.php',
        'MyApp\Models\Invoices'             => 'app/models/Invoices.php',
        'MyApp\Models\Users'                => 'app/models/Users.php',
    ]
);

$loader->register();

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

Метод registerClasses() также принимает второй параметр merge. по умолчанию он равен false.  Однако вы можете установить его в true при наличии нескольких вызовов registerClasses(), чтобы определения классов были объединены.

<?php

use Phalcon\Loader;

$loader = new Loader();

$loader->registerClasses(
    [
        'MyApp\Components\Mail'             => 'app/library/Components/Mail.php',
        'MyApp\Controllers\IndexController' => 'app/controllers/IndexController.php',
        'MyApp\Controllers\AdminController' => 'app/controllers/AdminController.php',
    ]
);

$loader->registerClasses(
    [
        'MyApp\Models\Invoices'             => 'app/models/Invoices.php',
        'MyApp\Models\Users'                => 'app/models/Users.php',
    ],
    true
);

$loader->register();

Приведенный выше пример объединяет второе объявление registerNamespaces() с предыдущим.

Если вам нужно проверить, какие классы зарегистрированы в автозагрузчике, вы можете использовать геттер getClasses() , который возвращает массив зарегистрированных классов. В приведенном выше примере функция getClasses() возвращает:

[
    'MyApp\Components\Mail'             => 'app/library/Components/Mail.php',
    'MyApp\Controllers\IndexController' => 'app/controllers/IndexController.php',
    'MyApp\Controllers\AdminController' => 'app/controllers/AdminController.php',
    'MyApp\Models\Invoices'             => 'app/models/Invoices.php',
    'MyApp\Models\Users'                => 'app/models/Users.php',
]

Регистрация файлов

Бывают случаи, когда вам может потребоваться  require конкретный файл, содержащий класс без пространства имен, или файл, содержащий некоторый код, который вам нужен. Примером может служить файл, содержащий удобные функции отладки.

Phalcon\Loader предлагает registerFiles() который используется для запроса таких файлов. Он принимает массив, содержащий имя файла и расположение каждого файла.

<?php

use Phalcon\Loader;

$loader = new Loader();

$loader->registerFiles(
    [
        'functions.php',
        'arrayFunctions.php',
    ]
);

$loader->register();

Эти файлы автоматически загружаются при вызове метода register().

Метод registerFiles() также принимает второй параметр merge. по умолчанию он равен false. Однако вы можете установить его в true при наличии нескольких вызовов registerFiles() , чтобы определения файлов были объединены.

<?php

use Phalcon\Loader;

$loader = new Loader();

$loader->registerFiles(
    [
        'app/functions/functions.php',
    ]
);

$loader->registerFiles(
    [
        'app/functions/debug.php',
    ],
    true
);

$loader->register();

Приведенный выше пример объединяет второе объявление registerFiles() с предыдущим.

Если вам нужно проверить, какие классы зарегистрированы в автозагрузчике, вы можете использовать геттер getFiles() , который возвращает массив зарегистрированных файлов. В приведенном выше примере функция getFiles() возвращает:

[
    'app/functions/functions.php',
    'app/functions/debug.php',
]

У вас также есть доступ к методу loadFiles() , который будет пересекать все зарегистрированные файлы, и если они существуют, то он потребует их. Этот метод автоматически вызывается при вызове функции register().

Расширения файлов

Когда вы используете registerNamespaces() и registerDirs()Phalcon\Loader автоматически предполагает, что ваши файлы будут иметь расширение .php. Это поведение можно изменить с помощью метода setExtensions(). Метод принимает массив, где каждый элемент является проверяемым расширением (без него .):

<?php

use Phalcon\Loader;

$loader = new Loader();

$loader->setExtensions(
    [
        'php',
        'inc',
        'phb',
    ]
);

$loader->registerDirs(
    [
        'app/functions',
    ]
);

В приведенном выше примере при ссылке на файл Mail, загрузчик будет искать в app/functions следующие файлы:

  • Mail.php
  • Mail.inc
  • Mail.phb

Файлы проверяются в том порядке, в котором определено каждое расширение.

 

Проверка файлов обратного вызова

Можно ускорить загрузчик, установив другой метод обратного вызова проверки файлов с помощью метода setFileCheckingCallback.

Поведение по умолчанию использует is_file. Однако вы также можете использовать null, который не будет проверять, существует ли файл или нет, прежде чем загружать его, или вы можете использовать stream_resolve_include_path, который намного быстрее, чем is_file, но вызовет проблемы, если целевой файл удаляется из файловой системы.

<?php

// Поведение по умолчанию.
$loader->setFileCheckingCallback("is_file");

// Быстрее, чем `is_file()`, но подразумевает некоторые вопросы, если
// файл удаляется из файловой системы.
$loader->setFileCheckingCallback("stream_resolve_include_path");

// Не проверять наличие файлов.
$loader->setFileCheckingCallback(null); 

Поведение по умолчанию

<?php

use Phalcon\Loader;

$loader = new Loader();

$loader->setFileCheckingCallback("stream_resolve_include_path");

Быстрее, чем is_file(),  но создает проблемы, если файл удаляется из файловой системы.

<?php

use Phalcon\Loader;

$loader = new Loader();

$loader->setFileCheckingCallback(null);

Не проверяйте наличие файла.

Изменение текущих стратегий

Дополнительные данные автоматической загрузки можно добавить к существующим значениям, передав true в качестве второго параметра:

<?php

// Добавление дополнительных каталогов
$loader->registerDirs(
    [
        '../app/library',
        '../app/plugins',
    ],
    true
); 

События Автозагрузки

В следующем примере EventsManager работает с загрузчиком классов, что позволяет получить отладочную информацию о потоке операций:

<?php

use Phalcon\Events\Event;
use Phalcon\Events\Manager as EventsManager;
use Phalcon\Loader;

$eventsManager = new EventsManager();

$loader = new Loader();

$loader->registerNamespaces(
    [
        'Example\Base'    => 'vendor/example/base',
        'Example\Adapter' => 'vendor/example/adapter',
        'Example'         => 'vendor/example',
    ]
);

// Прослушать все события загрузчика
$eventsManager->attach(
    'loader:beforeCheckPath',
    function (Event $event, Loader $loader) {
        echo $loader->getCheckedPath();
    }
);

$loader->setEventsManager($eventsManager);

$loader->register();

Некоторые события при возврате boolean false могут остановить активную операцию. Поддерживаются следующие события:

Имя событияВызванныйМожет остановить работу?
afterCheckClass Срабатывает в конце процесса автоматической загрузки, когда класс не найден. Нет
beforeCheckClass Срабатывает в начале процесса автоматической загрузки, перед проверкой наличия класса. Да
beforeCheckPath Срабатывает перед проверкой каталога на наличие файла класса. Да
pathFound Срабатывает, когда загрузчик находит файл класса или файл в зарегистрированном каталоге Да

В приведенном выше примере мы создаем новый объект Events Manager, прикрепляем метод к событию loader:beforeCheckPath и затем устанавливаем его в нашем автозагрузчике. Каждый раз, когда загрузчик зацикливается и ищет определенный файл в определенном пути, путь будет напечатан на экране.

getCheckedPath() содержит путь, который сканируется во время каждой итерации внутреннего цикла. Также вы можете использовать метод getfoundPath() , который удерживает путь найденного файла во время внутреннего цикла.

Для событий, которые могут остановить работу, все, что вам нужно будет сделать, это вернуть false в методе, который прикреплен к конкретному событию:

<?php

use Phalcon\Events\Event;
use Phalcon\Events\Manager;
use Phalcon\Loader;

$eventsManager = new Manager();
$loader        = new Loader();

$loader->registerNamespaces(
    [
       'MyApp'        => 'app/library',
       'MyApp\Models' => 'app/models',
    ]
);

$eventsManager->attach(
    'loader:beforeCheckPath',
    function (
        Event $event, 
        Loader $loader
    ) {
        if ('app/models' === $loader->getCheckedPath()) {
            return false;
        }
    }
);

$loader->setEventsManager($eventsManager);

$loader->register();

 В приведенном выше примере, когда загрузчик начнет сканирование папки app/models для пространства имен MyApp\Models , он остановит операцию.

Устранение неисправностей

Некоторые вещи, которые следует иметь в виду при использовании универсального автозагрузчика:

  • Процесс автоматической загрузки чувствителен к регистру, класс будет загружен так, как написано в коде.
  • Стратегии на основе пространств имен / префиксов быстрее, чем стратегии каталогов.
  • Если байт-код кэша, такой как APC установлен, это будет использоваться для извлечения запрошенного файла (выполняется неявное кэширование файла)