Контроллеры и SkyGate - energy-coresky/air GitHub Wiki

В SKY-приложениях, классический роутинг отсутствует. Первых два параметра адреса запроса, определяют соответственно активный контроллер и действие в нем (метод контроллера). Если нужно, эту простую схему, можно изменить под любые требования системы адресации с помощью реврайтов Coresky. Для фильтрации, подготовки и ограничений внешних входных данных (в том числе postfields и "сырых" входящих данных приложений), используются небесные врата - SkyGate. Управление вратами производится с помощью удобной визуальной утилиты. Файлы контроллеров приложений компилируются также как и файлы представлений, и исполняются из папки var/gate. Компилированные файлы содержат оригинальный код контроллеров (код программиста из папки mvc), дополненный наследниками класса Guard (для врат), код в которых генерируется автоматически из данных полученных на этапе визуального редактирования.

The gate

Контроллеры и URL

Части семантических URL и query string, доступны в SKY приложениях сплошным потоком из $sky->_0, $sky->_1 и т.д., смотрите рисунок:

URI parts

Часть URL Описание
1. указатель языка и/или мобильной версии присутствуют при необходимости
2. субдомен то-же самое
3. домен имеется всегда, главная часть общего адреса запроса
4. путь к проекту константа PATH на продакшн обычно имеет значение "/", т.е. эта часть URL не используется. Но на DEV удобно помещать проекты в под-папки одного виртуального сервера, поэтому PATH обычно содержит имя проекта и папку www, ту которая подразумевается корнем HTTP сервера на продакшн. Она также часто называется public или public_html. В SKY-приложениях, ее можно переименовать как угодно.
5. семантическая часть URL $sky->surl, если не FALSE, то содержит части семантического URL. Его первые части могут определять контроллер, и возможно, действие (если не первый ключ query string). Для URL на рисунке выше, можно создать мастер контроллер c_page.php и действие в нем a_act().
6. строка запроса Query String, все что после знака вопроса, заполняет массив $_GET в ядре PHP. При использовании семантических URL, может отсутствовать. Если отсутствует семантическая часть URL, то первые ключ-значение из $_GET, определяют соответственно контроллер и действие.

В Coresky, любая обработка web-запроса обычно задействует два контроллера: мастер и общий. Хотя использование общего контроллера можно отключить совсем, если определить пустые методы head_y(..) и tail_y(..) в мастер-контроллере. Класс любого контроллера всегда наследует абстрактный класс Controller:

<?php

class c_page extends Controller
{
    function head_y($action) {
    }
    . . .

    function tail_y() {
    }
}

Большинство имен контроллеров в Coresky приложениях, являются прото-именами данных из $sky->_0 с префиксом c_. Необходимо учитывать, что в случае использования rewrite, данные в $sky->_0 могут быть изменены в сравнении с внешним адресом запроса. Уточнение относительно семантического URL или query-string выполняется в SkyGate: для первого варианта установите чекбокс "Address has semantic part" и/или оставьте пустым поле "значение ключа". Контроллеры могут иметь три типа действий. Любые такие действия могут подготавливать переменные для шаблонов Jet или выполнять echo в STDOUT:

  • top-view - мастер действия, выполнение которых активируется непосредственно ядром, они всегда запускаются первыми. Такие действия не имеют смысла в общем контроллере. Только для этих действий настраиваются врата и только в них передаются внешние данные запроса из SkyGate.
  • sub-view - дополнительные действия, активация которых происходит с помощью функции view(..) или директивы Jet @view(..). Возможно использовать вложенные вызовы.
  • blk-view - действия активируются при использовании функционала блоков Jet, имеющих указатели на хендлеры. Эти действия не создают объект класса MVC, в отличие от двух предыдущих.

Для реализации web-интерфейса "Development tools", код Coresky содержит контроллер w2/dev_c.php, который может быть задействован только при DEV==true. Этот контроллер не использует реврайты или SkyGate, но использует трейт use HOOK_D;. Код этого трейта дополняет код контроллера стандартными действиями. Например для отображения страницы "crash", если произошло фатальное завершение работы скрипта.

SKY-приложения, обычно имеют два контроллера с фиксированными именами и постфиксом:

  • c_$sky->_0.php - мастер контроллеры с префиксом;
  • default_c.php - мастер контроллер с фиксированным именем, всегда использует трейт use HOOK_D;
  • common_c.php - общий контроллер, всегда использует трейт use HOOK_C; Его методы могут быть вызваны перед (и после) вызовом любого мастер действия top-view.

Необходимость специального мастер контроллера default_c.php обусловлена тремя причинами:

  • действие default_c::empty_a определяет главную страницу, которая имеется в любом web-приложении;
  • этот контроллер использует use HOOK_D;, который дополняет код стандартными действиями. Наличие которых необходимо и достаточно для большинства приложений;
  • прото-имена top-view действий в этом контроллере соответствуют $sky->_0. Представьте, что необходимо простое действие для адреса http://example.net/test. Чтобы не делать отдельный маленький контроллер c_test.php, можно создать действие default_c::a_test. Наличие этого контроллера даёт возможность для выбора: создавать или нет отдельный контроллер для действия. Также, действие default_c::default_a() возможно определить только в этом контроллере.

Если, в общем случае, прото-имена контроллеров соответствуют $sky->_0, то прото-имена действий соответствуют $sky->_1 (кроме действий в default_c и dev_c). Также имеются действия с фиксированными именами и постфиксом а не префиксом:

  • **a_**$sky->_1 и **j_**$sky->_1 - мастер действия с префиксом.
  • empty_a и empty_j - активируются, когда $sky->_1 равно пустой строке
  • default_a и default_j - здесь "default" означает "всё остальное". Если необходимое действие не определено в контролере, но определено дефолтное, то оно будет задействовано.

Если определено действие default_c::default_a(), то оно будет активировано для всех (любых) не CSN-AJAX запросов, для которых не будет найдено действие среди других определенных действий в других существующих контроллерах. Если имеется контроллер c_list.php и действие default_c::a_list(), то последнее никогда не будет активировано. Ядро, всегда приоритетно пытается задействовать контроллер с префиксом.

В примерах Coresky приложений, часто используется реврайт, который задействует контроллер c_main.php с методом empty_a() для главной страницы приложения. В этом контроллере, также рекомендуется размещать CSN-AJAX действия, которые должны быть доступны с различных страниц приложения (там где явно требуется указать контроллер, а не относительно, использовать "свой" контроллер).

Общий контроллер common_c.php, в отличие от других, не компилируется и исполняется из плана приложения, папки main/mvc, а не var/gate. Этот контроллер не содержит actions с пре/постфиксами "a", "j", но содержит методы head_y($action), error_y(), tail_y(), генерирующие переменные для layout (кроме error_y()). Объект этого контроллера находится в MVC::$cc, а объект мастер контроллера в MVC::$mc. Вышеуказанные три метода, могут присутствовать и в мастер контроллерах, и из них можно вызвать соответствующие методы общего контроллера с помощью parent::. С помощью метода error_y(), можно настроить представление мягких ошибок (таких как 404) для всего приложения в общем контроллере или части приложения в мастер контроллерах. Также, в общем контроллере, удобно размещать sub-view и blk-view действия, которые должны быть доступны из разных мастер контроллеров.

Запуск sub-view и blk-view, всегда производится с помощью метода MVC::handle(..), как и поиск custom-директив для компилятора Jet. Этот метод, вначале пытается найти и запустить действие в "своём" мастер контроллере, в том, где был произведён запуск top-view. Если действие не найдено, то поиск продолжается в общем контроллере.

Специальные методы контроллеров

Контроллеры SKY-приложений содержат различные методы с однобуквенными префиксами и постфиксами, которые имеют специальное значение. Но только для top-view действий, использующих "a" и "j" будет сгенерирован код SkyGate в компилированных контроллерах.

Префикс или постфикс Описание
a top-view: обычные action, какие имеются в большинстве популярных фреймворк, в том числе Laravel. Могут обрабатывать, в том числе, ajax запросы (но не CSN-AJAX)
j top-view: специальные action для обработки CSN-AJAX, в котором имеется много дополнительного функционала для упрощения разработки. $sky-fly == 1 для этого случая. Помимо того, что должны присутствовать HTTP заголовки X_Action_J и X_Requested_With == xmlhttprequest, в запросах, всегда отсутствует семантическая часть адреса, и метод HTTP запроса всегда POST.
x sub-view: методы с префиксом x предназначены для действий sub-представлений. Префикс x назначается по умолчанию, если явно не указан никакой другой. Такие действия могут быть определены и в мастер и в общем контроллерах.
r sub-view или block действия, включающие механизм RED-LABEL.
y методы head_y($action), tail_y(), error_y() могут быть определены и в мастер и в общем контроллерах. Методы head_y($action), tail_y() выполняются соответственно до и после мастер-действия. Код в этих методах является общим для всех действий контроллера или всех действий приложения. Переменные в возвращенном массиве из этих методов, будут присвоены MVC::$_y и отражены, как правило, в layout. Если однобуквенный префикс явно не указан, в компилированном шаблоне Jet будет применен префикс $y_.
b blk-view: рекомендуемый префикс для действий блоков Jet. Но для блоков, как и для sub-view, могут использоваться и другие буквы латинского алфавита. Для блоков нельзя использовать буквы, которые используются для действий top-view. Нельзя также использовать буквы "e" и "k", - первая используется для переменных итераторов-eVar, а вторая для переменных из SKY::$vars. Таким образом, доступные для блоков и sub-представлений буквы: b, c, d, f, g, h, i, l, m, n, o, p, q, r, s, t, u, v, w, x, z

Шпаргалка для быстрого ориентирования, top-view flow:

MVC flow

Выполнение кода начинается с передачи управления методу ::head_y(..) мастер контроллера. Если этот метод не определен, то родительский метод Controller::head_y(..) запустит аналогичный метод общего контроллера. После этого запустится код SkyGate и только, если не было ошибок, управление будет передано мастер действию, смотрите код MVC::top(). Такая архитектура нивелирует понятие "MiddleWare" и позволяет очень гибко корректировать протекание запроса на всех уровнях. В common_c::head_y(..), можно инициализировать класс USER, произвести проверку CSRF и выполнить прочие подобные действия. А в common_c::tail_y() в некоторой позиции обычно написано:

    if (!MVC::$layout)
        return;
    // далее следует основная генерация переменных для layout

Для мастер действия c_somecontr::a_someact(..), ядро автоматически предустановит шаблон представления somecontr.someact, это подразумевает имя файла шаблона "_somecontr.jet" и маркер .someact, читайте подробнее в документации о шаблонизаторе Jet. Предустановка файла layout обычно выполняется в common_c::head_y(..) кода приложения. В SKY-приложениях, довольно редко приходится изменять предустановленные layout и body шаблоны Jet, так как возможно создавать алиасы для маркеров. Но изменить предустановленный layout можно с помощью MVC::$layout и body с помощью MVC::body(..)

С помощью действий sub-view, можно изолировать части визуализаций, с целью их использования на разных страницах веб-приложения или повторения на одной странице. А мощная система манипулирования блоками кода представлений шаблонизатора Jet, позволяет производить специальные действия blk-view для визуализации блоков.

Трейты HOOK_C и HOOK_D

Как упоминалось ранее, трейт HOOK_D содержит стандартные действия. Их можно переопределить, но они могут быть полезны для большинства веб-приложений:

  • ::a_crash() - страница для визуализации фатальных ошибок. Используется в том случае, если без редиректа корректно отобразить ошибку невозможно из-за наличия непредвиденных данных в STDOUT;
  • ::a_etc(..) - позволяет загружать файлы под управлением реврайтов. Активно используется в "dev-tools", но метод полезен и на продакшн;
  • ::j_init(..) - после создания новой сессии, Coresky пытается сделать CSN-AJAX запрос, подтверждая, что клиент обрабатывает javascript. Выполняя эту цель, попутно на backend передается разрешение монитора и Timezone из браузера клиента. Если, после создания сессии произошел вызов этого действия, то можно предполагать что мы имеем дело с реальным человеком. Если это бот, то по крайней мере не простой, а такой, который умеет обрабатывать javascript.

Трейт HOOK_C содержит хуки для настройки ядра Coresky:

  • ::langs_h() - используется в многоязычных веб-приложениях;
  • ::rewrite_h(..) - запускается всякий раз перед вызовом MVC::top()
  • ::head_h() - используется совместно с HOOK_D::j_init(..);
  • ::user_h(..) - используется для создания объекта класса User;
  • ::dd_h(..) - вызывается всякий раз после создания соединений с реляционными базами данных;
  • ::make_h(..) - вызывается для генерации кода "first-run";

Хуки трейта HOOK_C позволяют перестраивать ядро Coresky таким образом, что имеется возможность создавать приложения произвольных типов: от микросервисов и API до сложных многоязычных веб-приложений. Подробная информация о методах трейтов имеется в документации, в соответствующих разделах.

Коротко о SkyGate

Визуальная утилита, сохраняет конфигурацию врат в хеш-массиве, в файле main/gate.php (который является частью кода приложения), а компилятор файлов контроллеров, подобно компилятору шаблонов Jet, использует эту информацию и дополняет код контроллеров автоматически сгенерированным кодом врат. Этот код, утилитой SkyGate, предоставляется "на лету", во время редактирования.

Если в SKY-приложении открыть страницу, для которой не существует действие, - будет показана 404 ошибка. Но если создать пустое действие и попытаться открыть такую страницу, будет показана ошибка SkyGate и предложено настроить врата (только в режиме DEV):

Gate error

Для действий j HTTP метод фиксирован POST и выбирается утилитой автоматически, они не могут содержать семантическую часть в адресе запроса.

Для действий a HTTP методы необходимо выбрать руками. Если семантическая часть адреса запроса, содержит две или более составляющих - просто не заполняйте ключи в параметрах или используйте чекбокс "address has semantic part".

Для переменных внешних данных, нужно использовать регулярные выражения. Такие параметры (но не постоянные) передаются из врат в мастер-действия через массив PHP. Для одного параметра используется одна переменная, но их можно объединить в массив или объект, если определить "key name / value name". Для частей запроса которые могут присутствовать, но могут и отсутствовать во входных данных, можно использовать чекбокс NS (Not Strict).

Преимущества SkyGate

  1. Для поиска роутера, подходящего к каждому HTTP запросу, всегда тратится процессорное время. SKY-Gate лишен этого недостатка, за исключением того, что подобное использование ресурсов, может быть необходимо для осуществления Coresky rewrites. Однако, часто, без реврайтов можно обойтись и алгоритм их работы более быстр.

  2. Роутеры могут определять доступные HTTP методы и осуществлять фильтрацию данных в адресной части запроса, но не для postfields. SKY-Gate способен делать фильтрацию postfields, а также, концептуально, осуществлять полный комплекс мер по подготовке, фильтрации и т.д. любых входных данных. Код SKY-Gate генерируется один раз автоматически, во время компиляции контроллеров приложений. Рутинное программирование заменено визуальной настройкой с помощью веб-интерфейса, это предпочтительно.

  3. Реврайты Coresky, которые являются логическим продолжением SkyGate, намного эффективнее решают сопутствующие проблемы адресаций в веб-приложениях (читайте ниже), чем это делается в системах с классическими роутерами. Хорошо, когда алгоритмы и процессор, "напрягаются" на DEV, а не на продакшн.

Red label

Этот механизм полностью игнорируется на продакшн, а в режиме DEV, имеется возможность отключить часть кода и вместо его представления, показать на странице блоки <DIV> с красным фоном и именем метки. Это бывает полезно во время разработки, когда мешает js-код, осуществляющий запросы на внешние ресурсы: счетчики, информеры, код Google analytics или в других подобных случаях.

Возвращаемые из контроллеров значения

Как упоминалось выше, возвращенный массив из методов head_y($action), tail_y(), будет присвоен MVC::$_y (переменные layout). Если массив возвращается из действий a, j или x (действия subview), он будет добавлен к $mvc_instance->_v (переменные body).

Если вернуть null - это не произведет никаких специальных действий.

Возвращенная строка из $mvc_instance->tail_y() установит новый MVC::$layout, а из других методов установит $mvc_instance->body.

Возвращенный integer, например return 404; активирует "мягкую" ошибку приложения.

return true; - присвоит пустую строку и body и layout. return false; - присвоит пустую строку только layout, смотрите код метода MVC::set(..).

Перед запуском действий в контроллерах, включается буферизация вывода в STDOUT с помощью ob_start() поэтому, если нужно выдать большой файл, делать это нужно по схеме:

    function a_action() {
        . . .
        ob_end_clean();
        readfile("bigfile.zip");
        throw new Stop;
    }

Coresky rewrites

Стандартный файл реврайтов, можно сгенерировать набрав в консоле: sky rewrite. Эта команда использует первые три реврайта из библиотеки.

Чтобы открыть визуальную утилиту для редактирования и тестирования реврайтов, кликните "Show map" в утилите SkyGate. Изменив реврайты, можно "поломать" работу dev-tools, поэтому будьте осторожны с R1 и R2:

Rewrites

При сохранении реврайтов, делается проверка на корректность PHP кода. Кроме того, чтобы актуализировать новые реврайты, нужно нажать "Drop All Cache".

Настроенные реврайты сохраняются в массиве, в файле main/rewrite.php. Этот файл, как и main/gate.php, main/wares.php является частью кода приложения, поэтому используется план main.app. Во время генерации файла var/cache/sky_plan.php, объединенный код реврайтов, записывается в SKY::$plans['main']['rewrite']. Этот код выполняется при каждом не консольном протекании скрипта, в конце конструктора HEAVEN, перед запуском MVC::top(). Для анализа внешнего, запрашиваемого адреса, можно использовать переменные:

$cnt # кол-во частей семантического URI
$surl # массив частей семантического URI
$uri # полный URI запроса, необходимо считать что $uri === URI (константа, только чтение)
$sky->_0, $sky->_1 и т.д. # сплошной поток частей запроса (только чтение)
$_GET # query string superglobal array

Вместо константы URI, необходимо использовать переменную $uri, так как код реврайтов запускается много раз (за один клик), с виртуально измененным адресом запроса для получения карты внешних URI на странице "CORESKY REWRITES MAP". Виртуальное изменение адреса запроса, также делается во время тестов:

Rewrite map

Чтобы сделать реврайт, для записи, необходимо использовать $_GET или $surl (по ссылке передаётся $sky->surl). Также, если реврайт изменил кол-во частей $surl - изменяйте $cnt, т.к. анализ этого значения, может понадобиться в последующих реврайтах.

Типы реврайтов

Реврайты бывают: терминальные, реверсивные, аддитивные, субстрактные и блокирующие.

Терминальные реврайты не осуществляют непосредственно реврайт, но прекращают обработку, так чтобы она не происходила в дальнейших реврайтах. Это позволяет упростить (оптимизировать) код других реврайтов. Пример:

if ('_' == $sky->_0[0]) # no rewrites for dev-tools
    return;

Реверсивные реврайты, делают такой реврайт, что при их двойном использовании, получаем первоначальный URI. Примеры:

пример 1: /1/2/three => /1/three/2 => /1/2/three

пример 2: /x/y/page.html => /x/y/page => /x/y/page.html

пример 3: /dash-surl/x/y => /nodash/x/y => /dash-surl/x/y

PHP код для примера 3:

$rw = [
    'dash-surl' => 'nodash',
];
if ($cnt) {
    $rw += array_flip($rw);
    $p =& $surl[0];
    empty($rw[$p]) or $p = $rw[$p];
}

Такие реврайты использовать предпочтительно, так как сохраняется каноничность системы адресации (активация действий, как и до реврайта, возможна только по одному URI). К тому же, получение внешнего URI, на странице "CORESKY REWRITES MAP", гарантировано происходит только в 2 шага и без использования теста.

Аддитивные реврайты позволяют активировать контроллер/действие более чем по одному URI:

if ($cnt && 'x' == $surl[0])
    $surl[0] = 'ctrl';
# can reach c_ctrl::... via two URI:
# /ctrl/... and /x/...

Чтобы получить внешние URI на странице "CORESKY REWRITES MAP", для таких реврайтов, нужно верно указать тест-подсказку. Для переменных частей URI, в тестах, нужно указывать {цифра} (где "цифра" - номер переменного параметра) или записывать только начало URI (определяющее контроллер), смотрите примеры в библиотеке. Отсутствие нужного внешнего URI на странице "CORESKY REWRITES MAP" не влияет на работоспособность кода приложения. Но все внешние URI, которые показаны - проходят проверку на реальных реврайтах, с виртуально измененным адресом запроса.

Субстрактные реврайты, в основном, выбирают информацию из URI не для адресных целей. Такие реврайты, так-же как и аддитивные, увеличивают количество вариантов URI для активации действий. Примеры PHP кода, можно посмотреть в библиотеке реврайтов в утилите SkyGate:

# take language from URI:
/en/ctrl/action => /ctrl/action

# take page number from URI:
/ctrl/page-2/action => /ctrl/action

Все тесты должны начинаться с символа "/". Если указать пустую строку - тест не будет использоваться для поиска внешних URI. Если тестов не хватает - можно пустую строку ввести в поле реврайта, но заполнить тест. Это никоем образом не уменьшит производительность на продакшн.

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

if ($cnt && 'ctrl' == $surl[0])
    return $surl[0] = '404';
# cannot reach c_ctrl::... via /ctrl/... URI

Реврайты для production-продуктов и для i18n

Реврайты удобно использовать для wares, чтобы разрешать возможные коллизии в URI и именах контроллеров. Представьте ситуацию: имеется Coresky-приложение с множеством контроллеров. Разработчик захотел присоединить production-продукт "Forum" с ещё десятью контроллерами. Контроллер c_topic.php имеется и в приложении и в форуме. Нет проблем: если production-продукт умеет работать с изменяемой адресацией, реврайты можно сделать так:

/variable-forum-key/topic/action => /topic/action

Трейт HOOK_C, имеет статические свойства HOOK_C::$lg, HOOK_C::$page, HOOK_C::$ware. В эти переменные, необходимо заносить дополнительную информацию, полученную реврайтами для языка страниц, номера страницы pagination и ware соответственно. Код для получения языка из сематического URL:

common_c::langs_h();
if ($cnt && in_array($surl[0], $sky->langs)) {
    common_c::$lg = array_shift($surl);
    $cnt--;
}

Пагинация

Функция ядра pagination(..), может помочь сделать пагинацию для любого типа адресации. Если, номер страницы необходимо указать в семантической части url, то нужно применить примерно такой реврайт:

if ($cnt > 2 && preg_match("/^page\-\d+$/", $surl[1])) {
    common_c::$page = (int)substr($surl[1], 5);
    array_splice($surl, 1, 1);
    $cnt--;
}

Код выше модифицирует массив $surl и заносит номер страницы в common_c::$page. Если это проделано для страницы, где пагинация не используется, то код backend должен дать ответ 404. То-же самое, если пагинация используется, но указан несуществующий номер страницы. В будущем, этот функционал будет внесен в SkyGate, а сейчас это можно проделать разными способами. Например, сделать проверку так:

<?php

class common_c extends Controller
{
    function head_y($action) {
        . . . # when pagination do not used for the action
        if (false !== self::$page && !$this->test_pagination($action))
            return 404;
    }
    . . .
    function tail_y() {
        . . .
        if (false !== self::$page) # when page not exist
            return 404;
    }
}

Заметим, что код в tail_y(), необходимо применять для любого типа адресации, например, когда номер страницы указывается в query string. Альтернативно, такую проверку можно делать непосредственно в действии, где используется функция pagination(..).

Папки assets на DEV

В режиме DEV, файлы статики (*.css и *.js), могут реально не находиться в папке public. А файлы статики DEV-wares никогда там не находятся, но браузер делает запросы в папку public. Если файл отсутствует, то запрос перехватится index.php, и с помощью реврайтов, Coresky вернет содержимое этих файлов браузеру из папок assets.

В самом начале трассировки, всегда можно увидеть урл реального запроса и как он трактуется кодом Coresky после реврайта.

При разработке Coresky, одна копия кода, подключается к нескольким приложениям. Это касается и файлов sky.js, dev.js и т.д. Поэтому, на DEV, удобно использовать реврайты для файлов статики. По похожим причинам, удобно делать подобную обработку и для файлов статики DEV-продуктов.