Обзор

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

Этот компонент опирается на возможности PHP по автоматической загрузке классов. Если класс, определенный в коде, еще не был включен, специальный обработчик попытается загрузить его. 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 (string $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, где находятся ваши классы - компоненты/классы расположены так, чтобы автозагрузчик мог их правильно загрузить, - это использование 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',
]

Файлы

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

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().

Каталоги

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

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

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

<?php

use Phalcon\Loader;

$loader = new Loader();

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

$loader->register();

Метод 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',
]

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

Когда вы используете 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

use Phalcon\Loader;

$loader = new Loader();

$loader->setFileCheckingCallback("is_file");

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

<?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);

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

События

Компонент [Events Manager][events] предлагает крючки, которые могут быть реализованы для наблюдения или расширения функциональности загрузчика. Phalcon\Loader реализует интерфейс Phalcon\Events\EventsAwareInterface, и поэтому доступны методы getEventsManager() и  setEventsManager().

Доступны следующие события:

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

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

<?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
    ) {
        echo $loader->getCheckedPath();
    }
);

$loader->setEventsManager($eventsManager);

$loader->register();

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

getCheckedPath() hсодержит путь, который сканируется во время каждой итерации внутреннего цикла. Также вы можете использовать метод 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, он будет использоваться для получения запрошенного файла (выполняется неявное кэширование файла)