OpenCart 2+ описание процесса загрузки приложения

Для того, чтобы хорошо разбираться в том, с чем работаешь, необходимо знать процесс "изнутри". В данной статье я распишу пошагово процесс работы ядра фреймворка или CMS (кому как нравится) OpenCart версии 2+. Тем более, что OpenCart построен с учетом концепции MVC (модель-вод-контроллер), а значит имеет довольно понятную структуру с которой удобно работать и удобно расширять. Чего пока не хватает - так это документации и русскоязычных статей в интернете.

Файл index.php

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

  • Устанавливается константа с версией OC
define('VERSION', '2.3.0.2.2');

 

  • При наличии файла config.php в корневой папке, он подключается и устанавливает ряд констант, устанавливающих пути к служебным (system) и рабочим (catalog) папкам, а так же константы доступа к базе данных.
if (is_file('config.php')) {
    require_once('config.php');
}

 

  • В случае отсутствия файла config.php в корневой папке, константа DIR_APPLICATION оказывается не установленной. Тогда отсылается заголовок на переадресацию в папку install, которая запускает процесс установки OC и завершает на этом этапе работу скрипта
if (!defined('DIR_APPLICATION')) {
    header('Location: install/index.php');
    exit;
}

 

  • Если дошли до этой строки - конфигурация загружена, подключается файл из папки system/startup.php
require_once(DIR_SYSTEM . 'startup.php');

 

  • Последняя строка файла index.php
start('catalog'); 

выполнится лишь после подключения файла 'startup.php', о чем ниже.


Файл system/startup.php

  • Устанавливается протоколирование всех ошибок
error_reporting(E_ALL);

 

  • Проверяется версия php, должна быть не ниже 5.4
if (version_compare(phpversion(), '5.4.0', '<') == true) {
    exit('PHP5.4+ Required');
}

 

  • Устанавливается временная зона по-умолчанию
if (!ini_get('date.timezone')) {
    date_default_timezone_set('UTC');
}

 

  • Далее устанавливаются значения суперглобального массива $_SERVER


$_SERVER['DOCUMENT_ROOT'] - содержит путь к корневой директории сервера, если скрипт выполняется в виртуальном хосте, в данном элементе указывается путь к корневой директории виртуального хоста.
$_SERVER['REQUEST_URI'] - содержит имя скрипта, начиная от корневой директории виртуального хоста и параметры. Например: /index.php?route=extension/feed/google_sitemapy
$_SERVER['HTTP_HOST'] - доменное имя сайта.
$_SERVER['HTTPS'] - сохраняет в массив признак защищенного соединения HTTPS

  • Объявление функции modification(), которая используется для получения пути к

модифицированным файлам CMS (например при подключении дополнений).

function modification($filename) {…}


При этом в начале функции прописаны проверки на область видимости от куда выполняется скрипт – админка, папка install или catalog. Каждая из этих областей видимости определяет свои константы, на основе их и построены данные проверки.
В переменную $file сохраняется путь к файлу начиная от папки system/storage/modification. Если переданный функции файл там найден, то возвращается данный путь. Если нет – значит файл не модифицировался и возвращается тот же путь к файлу, который передан функции в качестве аргумента.

  • Подключение файла autoload.php, которого нет по-умолчанию в OpenCart 2.3.
// Autoloader
if (is_file(DIR_SYSTEM . '../../vendor/autoload.php')) {
    require_once(DIR_SYSTEM . '../../vendor/autoload.php');
}

В данном файле можно подключить сторонние автозагрузчики или что-то еще.

  • Далее код автозагрузки системных классов OC.

Функция spl_autoload_register() при каждом запросе неизвестного класса вызывает функцию library(), которая ищет и загружает запрашиваемый класс из папки system/library или же system/storage/modification/system/library если есть модификация запрашиваемого файла класса, т.к. используется функция modification() для такой проверки.

  • Подключается ряд важных системных классов, нужных для работы CMS из папки system/engine.

Экземпляры данных классов будут созданы чуть позже в главном файле фреймворка system/framework.php (описано ниже) и сохранены в реестр для доступа к ним из любого места приложения через загрузчик engine/loader.php.
Предварительно наличие файла проверяется среди модифицированных с помощью функции modification()

require_once(modification(DIR_SYSTEM . 'engine/action.php'));
…

 

  • Из папки system/helper подключается несколько файлов содержащие функции-помошники
  1. general.php – Файл для обеспечения безопастности. Содержит функцию token() для генерации случайной строки (токена), функцию hash_equals() для сравнения хеш-строк
  2. utf8.php – устанавливает кодировку
  3. json.php – кодирует/раскодирует данные в формате json

 

  • В конце данного файла объявление функции start(), которая должна подключить главный файл фреймворка- framework.php, реализующий дальнейшую работу приложения согласно модели MVC.
function start($application_config) {
    require_once(DIR_SYSTEM . 'framework.php');    
}


После этого, выполнение скрипта возвращается к файлу index.php в котором выполняется последняя строка, запускающая подключение к фалу framework.php
В качестве аргумента, функции передается указатель на рабочий каталог- backend(admin) или frontend(catalog)

start('catalog');




Файл system/framework.php

Не забываем, что все классы в данном файле загружаются с помощью автозагрузчика, с проверкой на модификацию (сначала поиск в папке system/storage/modification/system/…)

  • Первой строкой создается объект класса Registry. Файл system\engine\registry.php
$registry = new Registry();


Это самый важный объект приложения. Данный класс используется для хранения(получения, проверки на наличие) в массиве $data служебных объектов (Config, Event, Loader, Request, Response, Database, Session, Cache, Url, Language, Document). Далее в данном файле (framework.php) создаются эти самые служебные объекты и с помощью метода set() сохраняются в закрытой переменной $data класса Registry. Так же, многие из этих объектов, получают Registry в качестве аргумента конструктора, при их создании, для получения доступа из них к реестру, а из него к другим служебным объектам.

Пример.
Любой контроллер наследует от абстрактного класса Controller, который получает объект Registry при создании:

abstract class Controller {
    protected $registry;
    public function __construct($registry) {
        $this->registry = $registry;
    }
...

так же есть магические методы

public function __get($key) {
    return $this->registry->get($key);
}
public function __set($key, $value) {
    $this->registry->set($key, $value);
}


Таким образом, при запросе свойства класса, которое у него отсутствует, идет передача вызова на объект Registry (который был передан в protected $registry).
Таким образом, при вызове в контроллере

$this->load->model('...');

после того, как не будет найдено свойство load контроллера, будет вызвано $registry->load.

То есть, объект Registry используется, в последствии, многими другими объектами для получения доступа к хранимым в нем служебным объектам (их свойствам и методам).

  • Config. Файл system\library\config.php

Данный объект собирает массив настроек (private $data) для различных частей приложения. Он имеет три метода для сохранения, получения и проверки наличия определенной настройки.
Сразу после создания объекта, вызывается метод загрузки конфигурации из файла system\config\default.php

$config->load('default');

в данном файле хранятся настройки по умолчанию касающиеся БД, эл.почты, вывода ошибок, сессии, кэша, кодировки и др. (см. файл system\config\default.php)

Следующей строкой в массив настроек добавляются настройки из файла
- system\config\catalog.php (если фреймворк запущен из frontend – в index.php строкой start('catalog');)
или
- system\config\admin.php (для админки)
Затем, экземпляр класса Config сохраняется в массив $data класса Registry
Данные одного из этих файлов (который загрузится) дополняет и где-то меняет настройки по-умолчанию из файла default.php

  • Event. Файл system\engine\event.php

Объект данного класса работает с событиями OC. События используются для вставки своего кода в существующий на определенном этапе его выполнения.
Строкой

$event = new Event($registry);

передается объект Registry в конструктор класса Event, для использования в методе trigger() при вызове нужных функций.

- Event Register. Регистрируюся все события, которые были получены объектом Config и сохранены в массиве action_event. Они записываются в массив data[$trigger] объекта Event.

if ($config->has('action_event')) {
    foreach ($config->get('action_event') as $key => $value) {
        $event->register($key, new Action($value));
    }
}

 

  • Loader. Файл system\engine\loader.php

Объект позволяет загрузить нужные классы controller, model, view, language, а так же служебные классы из папок library, helper, config
Экземпляр класса Loader сохраняется в массив объекта Registry с ключем 'load', поэтому доступен как load.
Пример загрузки из класса контроллера:

$this->load->model('account/custom_field');

Делает он это, опять таки, с помощью переданного ему, в качестве аргумента для конструктора, объекта Registry, хранящему все служебные объекты с их свойствами (читать выше).

  • Request (запрос браузера на сервер). Файл system\library\request.php

Данный класс сохраняет в своих свойствах элементы глобальных массивов (_GET _POST _REQUEST _COOKIE _FILES _SERVER), пропуская, предварительно, ключи и значения через метод clean(), которая преобразует специальные символы в HTML-сущности.
С помощью данного класса, обращаться к элементам суперглобальных массивов можно так:

$this->request->post['address_id']

в результате будет получено значение массива $_POST по ключу 'address_id'.

  • Response (ответ сервера браузеру). Файл system\library\response.php

После создания объекта Response, следующей строкой, создается заголовок с кодировкой по-умолчанию.

$response->addHeader('Content-Type: text/html; charset=utf-8');


В данный объект можно сохранять свои заголовки методом addHeader($header), которые будут добавляться в private $headers.
Есть так же метод для переадресации redirect($url, $status = 302), метод для сжатия страницы сервером перед отправкой браузеру compress().
Метод setOutput($output) вызывается во многих контроллерах для сохранения страницы (после формирования файла представления) в private $output объекта Response, для последующего вывода:

$this->response->setOutput($this->load->view('checkout/checkout', $data));

Метод output() – передает браузеру для вывода заголовки и все содержимое страницы. Он выполняется в самом конце файла framework.php

  • DB. Класс для работы с базой данных. Файл system\library\db.php

При создании объекта проверяется существование класса, отвечающего за работу с указанным расширением (указывается в конфиге, в массиве «db_type» - например mysqli). Если класс найден, на основании других данных из конфига создается соединение с БД.
Данный объект используется в моделях:

$this->db->query("INSERT INTO…

 

  • Session. Класс для работы с сессиями. Файл system\library\session.php

В настройках проверяется нужно ли сразу стартовать сессию (для каталога admin стартует сразу)

В конструкторе класса Session есть строка определяющая параметры для куки сессии
session_set_cookie_params(0, '/');
ноль значит, что при закрытии браузера куки удалятся (например товары из корзины). Поэтому можно установить свое значение в секундах.
Так же с сессиями работают:

  1. класс Session\DB из файла system\library\session\db.php который сохраняет определенные данные в БД устанавливая в одно из полей идентификатор текущей сессии (нужно предварительно раскомментировать код создания таблицы session в начале файла);
  2. класс Session\File из файла system\library\session\file.php который создает файл имеющий название '/sess_' + идентификатор сессии и сохраняет в него данные.

Идентификатор сессии, данные классы, получают из класса Session с помощью метода getId()

  • Cache. Класс для работы с кэшем. Файл system\library\cache.php

Данный класс подключает один из файлов, который определяет место хранения кэша. Место хранения передается конструктору при создании объекта в качестве аргумента $adaptor.
Возможные способы хранения кэша:

  1. APC (system\library\cache\apc.php)
  2. Файл (system\library\cache\file.php)
  3. Оперативная память (system\library\cache\mem.php)

По умолчанию в настройках стоит хранение кэша в файле и срок 3600 (1 час)
Методы работы с кэшем get() set() delete(), в зависимости от указанного места хранения, перенаправляются к одному из соответствующих классов.

  • Url. Объект данного класса генерирует URL (ссылки). Файл system\library\url.php

Методу link() передаются параметры из которых он формирует и возвращает полный URL.

$data['customlink'] = $this->url->link('custom/custom');

 

  • Language. Файл подключающий языковые файлы и получающий из них запрашиваемую строку. Файл system\library\language.php

Язык используемый по-умолчанию, прописывается в файле конфигурации system\config\default.php:

$_['language_default'] = 'en-gb';


Метод load данного класса, принимает, в качестве первого аргумента путь к файлу перевода, который подключается:

$this->load->language('checkout/checkout');

В данной строке, load является не методом класса language, а объектом класса Loader, который содержит метод language(), вызывающий (через регистратор объектов- Registry) метод load уже объекта language, передавая ему параметры в скобках.

Получение данных из массива подключаемого языкового файла осуществляется с помощью метода get():

$this->language->get('heading_title')

 

  • Document. Объект данного класса содержит данные (собирает и возвращает) выводимые в основном в шапке HTML документа, такие как заголовок страницы, описание, ключевые слова, стили, js скрипты и др. Файл system\library\document.php

Методы класса состоят из get – получающих значение элемента и set(add) устанавливающих значение или массив значений. Таким образом можно, например добавить свой файл стилей в массив:

$this->document->addStyle('catalog/view/mystyle.css');

далее, в контроллере получаем все стили и передаем в представление (header.tpl), где в цикле они извлекаются.

  • Config Autoload. Производится автозагрузка конфигурационных файлов, которые можно добавить в system\config\admin.php

Данные файлы нужно указать в system\config\default.php в массиве

// Autoload Configs
$_['config_autoload'] = array();

 

  • Language Autoload. Загружает автоматически языковые файлы, указанные в system\config\default.php
$_['language_autoload'] = array('en-gb');

 

  • Library Autoload. Позволяет автоматически загрузить указанные классы библиотек. Например для frontend загружается дополнительно библиотека openbay, указанная в файле system\config\catalog.php
$_['library_autoload'] = array(
    'openbay'
);

 

  • Model Autoload. Позволяет автоматически загрузить модель из списка указанном в конфигурационных файлах, например в system\config\default.php
$_['model_autoload'] = array();

 

  • Front Controller. Объект используется для выполнения переданных ему действий (вызов определенного метода нужного контроллера) до выполнения метода контроллера переданного в route (полученного из URL).
$controller = new Front($registry);

Создается объект класса Front хранящий обработчики событий в массиве (private $pre_action) и вызывающий для их обработки метод execute объекта Action. Файл system\engine\front.php

Метод addPreAction() формирует массив объектов класса Action для последующего выполнения.
Метод dispatch() сначала выполняет все элементы из массива $pre_action, а затем переданный в качестве аргумента Action $action. Для выполнения он передает объекты методу execute(), где вызывается одноименный метод объекта Action, который и выполняет метод (по-умолчанию index) переданного ему контроллера.

  • Pre Actions. В данном блоке, в фронт-контроллер устанавливаются действия, которые должны выполняться до выполнения любых других действий.
if ($config->has('action_pre_action')) {
    foreach ($config->get('action_pre_action') as $value) {
        $controller->addPreAction(new Action($value));
    }
}


В конфигурационном файле проверяется наличие действий (контроллеров), методы (index) которых нужно выполнить сразу же. Например в system\config\catalog.php:

$_['action_pre_action'] = array(
    'startup/session',
    'startup/startup',
    'startup/error',
    'startup/event',
    'startup/maintenance',
        'startup/'.$seo_type
);

В цикле, данные массива передаются в метод addPreAction() объекта Front, где они сохраняются в массив $pre_action для дальнейшего выполнения.

  • Dispatch. Данная строка кода:
$controller->dispatch(new Action($config->get('action_router')), new Action($config->get('action_error')));

отправляет на выполнение объект Action с аргументом из конфигурационного файла (startup/router.php) и вторым аргументом объект с указанием файла который должен обработать ошибки, если будут. В данном случае это «error/not_found»

То есть вызов метода $controller->dispatch() в OC начинает процесс работы контроллера/метода указанных в route в данный момент (из URL), перед этим выполнив методы (index по-умолчанию если не указаны другие) данных классов (из 'action_pre_action'):

'startup/session',
'startup/startup',
'startup/error',
'startup/event',
'startup/maintenance',
'startup/'.$seo_type

Указанные контроллеры находятся в папке catalog\controller\startup

Выполнив предварительные методы из списка, последним в списке вызывается catalog\controller\startup\router.php
он из URL отбирает только значение «route»- контроллер/действие и создает объект класса Action передав ему их в качестве аргумента для конструктора. Далее вызывается метод execute() объекта Action, который и выполняет переданные ему контроллер/метод. Остальные GET параметры используются уже в этих контроллерах.

  • Далее
$response->setCompression($config->get('config_compression'));

Считывается из конфигурации и сохраняется в свойстве объекта Response уровень сжатия для функции gzencode(). По умолчанию – 0 (без сжатия).
Завершающая строка выполняемая фреймворком

$response->output();

Вызывает метод output() объекта Response, в котором производится вызов метода сжатия compress() если до этого был выбран уровень сжатия, затем отсылаются заголовки и выводится контент строкой echo $output;


Стоит еще вспомнить класс Action, который был подключен в файле system/startup.php. Файл system\engine\action.php
Объект его создается в основном в данном файле (framework.php) когда нужно выполнить какой-то метод определенного контроллера. При создании его объекта, конструктору передается $route – путь к выполняемому контроллеру/метод. Если метод не передан – будет выполняться метод index.
В файле framework.php объект класса Action передается
- для выполнения методов служебных объектов (предзагрузки) массива 'action_pre_action' файла конфигурации
- методу dispatch() объекта Front Controller, который сначала в цикле выполняет действия добавленные в предзагрузку и затем действие(контроллер) startup/router, который разбирает текущий URL и выполняет указанный там контроллер/действие.