3. Універсальний контролер
3.1. Вступ
У попередньому методі передбачалося, що нам потрібно написати контролер із назвою main.php. Маючи трохи досвіду, ми розуміємо, що цей контролер часто виконує однакові дії, і тоді виникає спокуса написати загальний контролер, який можна використовувати в більшості веб-додатків. Код такого контролера може виглядати так:
<?php
// загальний контролер
// читання конфігурації
include 'config.php';
// підключення бібліотек
for($i=0;$i<count($dConfig['includes']);$i++){
include($dConfig['includes'][$i]);
}//for
// запуск або відновлення сесії
session_start();
$dSession=$_SESSION["session"];
if($dSession) $dSession=unserialize($dSession);
// отримання дії, яку потрібно виконати
$sAction=$_GET['action'] ? strtolower($_GET['action']) : 'init';
$sAction=strtolower($_SERVER['REQUEST_METHOD']).":$sAction";
// чи є послідовність дій нормальною?
if( ! enchainementOK($dConfig,$dSession,$sAction)){
// послідовність ненормальна
$sAction='enchainementinvalide';
}//if
// обробка дії
$scriptAction=$dConfig['actions'][$sAction] ?
$dConfig['actions'][$sAction]['url'] :
$dConfig['actions']['actionInvalide']['url'];
include $scriptAction;
// відправлення відповіді (перегляду) клієнту
$sEtat=$dSession['etat']['principal'];
$scriptVue=$dConfig['etats'][$sEtat]['vue'];
include $scriptVue;
// кінець скрипта — сюди не слід потрапляти, якщо тільки це не помилка
trace ("Erreur de configuration.");
trace("Action=[$sAction]");
trace("scriptAction=[$scriptAction]");
trace("Etat=[$sEtat]");
trace("scriptVue=[$scriptVue]");
trace ("Vérifiez que les script existent et que le script [$scriptVue] se termine par l'appel à finSession.");
exit(0);
// ---------------------------------------------------------------
function finSession(&$dConfig,&$dReponse,&$dSession){
// $dConfig: словник конфігурації
// $dSession: словник, що містить інформацію про сесію
// $dReponse: словник аргументів сторінки відповіді
// запис сеансу
if(isset($dSession)){
// параметри запиту заносяться в сесію
$dSession['requete']=strtolower($_SERVER['REQUEST_METHOD'])=='get' ? $_GET :
strtolower($_SERVER['REQUEST_METHOD'])=='post' ? $_POST : array();
$_SESSION['session']=serialize($dSession);
session_write_close();
}else{
// сесія відсутня
session_destroy();
}
// відображення відповіді
include $dConfig['vuesReponse'][$dReponse['vuereponse']]['url'];
// кінець скрипта
exit(0);
}//закінчення сесії
//--------------------------------------------------------------------
function enchainementOK(&$dConfig,&$dSession,$sAction){
// перевіряє, чи дозволена поточна дія з огляду на попередній стан
$etat=$dSession['etat']['principal'];
if(! isset($etat)) $etat='sansetat';
// перевірка дії
$actionsautorisees=$dConfig['etats'][$etat]['actionsautorisees'];
$autorise= ! isset($actionsautorisees) || in_array($sAction,$actionsautorisees);
return $autorise;
}
//--------------------------------------------------------------------
function dump($dInfos){
// виводить словник інформації
while(list($clé,$valeur)=each($dInfos)){
echo "[$clé,$valeur]<br>\n";
}//while
}//відстеження
//--------------------------------------------------------------------
function trace($msg){
echo $msg."<br>\n";
}//відстеження
?>
3.2. Файл конфігурації додатка
Додаток налаштовується у скрипті, який обов’язково має назву config.php. Параметри додатка зберігаються у словнику під назвою $dConfig, який використовується як контролером, так і скриптами дій, моделями та елементарними поданнями.
3.3. Бібліотеки, які слід включити до контролера
Бібліотеки, які потрібно включити в код контролера, розміщуються в таблиці $dConfig['includes']. Контролер включає їх за допомогою такої послідовності коду:
<?php
...
// читання конфігурації
include "config.php";
// включення бібліотек
for($i=0;$i<count($dConfig['includes']);$i++){
include($dConfig['includes'][$i]);
}//for
3.4. Управління сесіями
Універсальний контролер автоматично керує сесією. Він зберігає та відновлює вміст сесії за допомогою словника $dSession. Цей словник може містити об’єкти, які необхідно серіалізувати, щоб згодом їх можна було правильно відновити. Ключ, пов’язаний із цим словником, — «session». Отже, відновлення сесії здійснюється за допомогою такого коду:
<?php
…
// запуск або відновлення сеансу
session_start();
$dSession=$_SESSION["session"];
if($dSession) $dSession=unserialize($dSession);
Якщо дія потребує збереження інформації в сесії, вона додає ключі та значення до словника $dSession. Оскільки всі дії використовують одну й ту саму сесію, існує ризик конфлікту ключів сесії, якщо додаток розробляється незалежно кількома особами. Це є певною складністю. Необхідно створити репозиторій, що містить перелік ключів сесії, який буде спільним для всіх. Ми побачимо, що кожна дія завершується викликом наступної функції finSession:
<?php
...
// ---------------------------------------------------------------
function finSession(&$dConfig,&$dReponse,&$dSession){
// $dConfig: словник конфігурації
// $dSession: словник, що містить інформацію про сесію
// $dReponse: словник аргументів сторінки відповіді
// запис сеансу
if(isset($dSession)){
// параметри запиту додаються до сесії
$dSession['requete']=strtolower($_SERVER['REQUEST_METHOD'])=='get' ? $_GET :
strtolower($_SERVER['REQUEST_METHOD'])=='post' ? $_POST : array();
$_SESSION['session']=serialize($dSession);
session_write_close();
}else{
// сесія відсутня
session_destroy();
}
// відображення відповіді
include $dConfig['vuesReponse'][$dReponse['vuereponse']]['url'];
// кінець скрипта
exit(0);
}//закінчення сесії
Процес може вирішити не продовжувати сеанс. Для цього достатньо не передавати значення параметру $dSession функції finSession, у цьому випадку сеанс видаляється (session_destroy). Якщо словник $dSession існує, він зберігається в сесії, яка потім записується (session_write_close). Отже, поточна операція може зберігати елементи в сесії, додаючи їх до словника $dSession. Слід зазначити, що контролер автоматично зберігає параметри поточного запиту в сесії. Це дозволить відновити їх у разі потреби для обробки наступного запиту.
3.5. Відправлення відповіді клієнту
Кінцевою метою функції finSession є надсилання відповіді користувачеві. Ми вже зазначали, що відповідь може мати різні шаблони сторінок. Вони задаються у файлі $dConfig['vuesReponse']. У додатку з двома шаблонами це може виглядати так:
<?php
…
$dConfig['vuesReponse']['modele1']=array('url'=>'m-modele1.php');
$dConfig['vuesReponse']['modele2']=array('url'=>'m-modele2.php');
Поточна операція вказує у $dReponse['vuereponse'] бажану модель. Контролер відображає її за допомогою команди:
<?php
…
// відображається відповідь
include $dConfig['vuesReponse'][$dReponse['vuereponse']]['url'];
Після відправлення цієї відповіді клієнту контролер завершує роботу (exit).
3.6. Виконання дій
Контролер очікує запитів із параметром action=XX. Якщо цей параметр відсутній у запиті, а сам запит має вигляд GET, дія приймає значення «init». Це стосується найпершого запиту, що надсилається до контролера, який має вигляд http://machine:port/chemin/main.php.
<?php
…..
// отримано дію, яку потрібно виконати
$sAction=$_GET['action'] ? strtolower($_GET['action']) : 'init';
За замовчуванням з кожною дією пов’язаний скрипт, який відповідає за її обробку. Наприклад:
<?php
...
// налаштування дій додатка
$dConfig['actions']['get:init']=array('url'=>'a-init.php');
$dConfig['actions']['post:calculerimpot']=array('url'=>'a-calculimpot.php');
$dConfig['actions']['get:retourformulaire']=array('url'=>'a-retourformulaire.php');
$dConfig['actions']['post:effacerformulaire']=array('url'=>'a-init.php');
$dConfig['actions']['enchainementinvalide']=array('url'=>'a-enchainementinvalide.php');
$dConfig['actions']['actionInvalide']=array('url'=>'a-actioninvalide.php');
Заздалегідь визначено дві дії:
у разі, якщо поточна дія не може слідувати за попередньою дією | |
випадок, коли запитувана дія відсутня у словнику дій |
Дії, властиві додатку, позначаються у формі «метод:дія», де «метод» — це метод GET або POST запиту, а «дія» — запитувана дія, у даному випадку: init, calculerimpot, retourformulaire, effacerformulaire. Слід зауважити, що дія отримується незалежно від методу передачі параметрів (GET або POST) за допомогою послідовності:
<?php
…
// отримано дію, яку потрібно виконати
$sAction=$_GET['action'] ? strtolower($_GET['action']) : 'init';
Адже навіть якщо форма відправлена, завжди можна написати:
Елементи форми будуть відправлені методом POST (method='post'). Проте запитувана URL-адреса буде такою: main.php?action=calculerimpot. Параметри цього URL будуть отримані зі словника $_GET, тоді як інші елементи форми будуть отримані зі словника $_POST.
Маючи словник дій, контролер виконує запитувану дію наступним чином:
<?php
...
// обробка дії
$scriptAction=$dConfig['actions'][$sAction] ?
$dConfig['actions'][$sAction]['url'] :
$dConfig['actions']['actionInvalide']['url'];
include $scriptAction;
Якщо запитувана дія відсутня у словнику дій, буде виконано скрипт, що відповідає недійсній дії. Після завантаження скрипта дії в контролер він виконується. Слід зауважити, що він має доступ до змінних контролера ($dConfig, $dSession), а також до суперглобальних словників PHP ($_GET, $_POST, $_SERVER, $_ENV, $_SESSION). У скрипті міститься логіка додатка та виклики бізнес-класів. У будь-якому випадку дія повинна
- заповнювати словник $dSession, якщо елементи потрібно зберегти в поточній сесії
- у $dReponse['vuereponse'] вказати ім’я шаблону відповіді, що має відображатися
- завершуватися викликом finSession($dConfig, $dReponse, $dSession). Якщо сесію потрібно знищити, дія просто завершиться викликом finSession($dConfig, $dReponse).
З метою забезпечення узгодженості дія може додати до словника $dReponse всю інформацію, необхідну для переглядів. Але це не є обов’язковим. Необхідним є лише значення $dReponse['vuereponse']. Слід зауважити, що будь-який скрипт дії закінчується викликом функції finSession, яка, у свою чергу, закінчується операцією exit. Отже, з скрипту дії повернення неможливе.
3.7. Послідовність дій
Веб-додаток можна розглядати як автомат з кінцевими станами. Різні стани додатка пов’язані з вікнами, що відображаються користувачеві. Користувач за допомогою посилання або кнопки переходить до іншого вікна. Веб-додаток змінив свій стан. Ми бачили, що дія ініціюється запитом типу http://machine:port/chemin/main.php?action=XX. Цей URL має походити з посилання, що міститься у вікні, яке відображається користувачеві. Адже ми хочемо уникнути ситуації, коли користувач безпосередньо вводить URL http://machine:port/chemin/main.php?action=XX, тим самим обходячи шлях, який для нього передбачила програма. Це також стосується випадків, коли клієнтом є програма.
Послідовність буде правильною, якщо запитуване URL є URL, яке можна запросити з останнього екрану, показаного користувачеві. Список таких екранів легко визначити. Він складається
- URL, що містяться у вікні або у вигляді посилань, або у вигляді цілей дій типу «submit»
- URL — це те, що користувач може ввести безпосередньо у своєму браузері, коли йому відображається цей вигляд.
Список станів додатка не обов’язково збігається зі списком переглядів. Розглянемо, наприклад, такий елементарний перегляд erreurs.php:
Les erreurs suivantes se sont produites :
<ul>
<?php
for($i=0;$i<count($dReponse["erreurs"]);$i++){
echo "<li class='erreur'>".$dReponse["erreurs"][$i]."</li>\n";
}//for
?>
</ul>
<div class="info"><?php echo $dReponse["info"] ?></div>
<a href="<?php echo $dReponse["href"] ?>"><?php echo $dReponse["lien"] ?></a>
Цей елементарний вигляд буде інтегрований у композицію елементарних виглядів, яка сформує відповідь. На цьому вигляді є посилання, яке може бути розміщене динамічно. Отже, представлення erreurs.php може відображатися з n різними посиланнями залежно від обставин. Це призведе до появи n різних станів додатка. У стані № i представлення erreurs.php відображатиметься з посиланням lieni. У цьому стані допустиме лише використання посилання lieni.
Перелік станів додатка та можливих дій у кожному стані буде зафіксовано у словнику $dConfig['etats']:
<?php
...
// налаштування станів додатка
$dConfig['etats']['e-formulaire']=array(
'actionsautorisees'=>array('post:calculerimpot','get:init','post:effacerformulaire'),
'vue'=>'e-formulaire2.php');
$dConfig['etats']['e-erreurs']=array(
'actionsautorisees'=>array('get:retourformulaire','get:init'),
'vue'=>'e-erreurs2.php');
$dConfig['etats']['sansetat']=array('actionsautorisees'=>array('get:init'));
Вищезазначена програма має два стани з назвами: «e-formulaire» та «e-erreurs». Додаємо стан під назвою «sansetat», який відповідає початковому запуску програми, коли вона ще не мала стану. У стані E перелік дозволених дій міститься в таблиці $dConfig['etats'][E]['actionsautorisees']. У ньому вказано дозволений для дії метод (get/post) та її назву. У наведеному вище прикладі є чотири можливі дії: get:init, post:alculerimpot, get:retourformulaire та post:effacerformulaire.
Маючи словник $dConfig['etats'], контролер може визначити, чи дозволена поточна дія $sAction у поточному стані додатка. Цей стан формується для кожної дії та зберігається в сесії у $dSession['etat']. Код контролера для перевірки, чи дозволена поточна дія, виглядає так:
<?php
.....
// чи є послідовність дій нормальною?
if( ! enchainementOK($dConfig,$dSession,$sAction)){
// послідовність ненормальна
$sAction='enchainementinvalide';
}//if
// обробка дії
$scriptAction=$dConfig['actions'][$sAction] ?
$dConfig['actions'][$sAction]['url'] :
$dConfig['actions']['actionInvalide']['url'];
include $scriptAction;
..........
//--------------------------------------------------------------------
function enchainementOK(&$dConfig,&$dSession,$sAction){
// перевіряє, чи дозволена поточна дія з огляду на попередній стан
$etat=$dSession['etat']['principal'];
if(! isset($etat)) $etat='sansetat';
// перевірка дії
$actionsautorisees=$dConfig['etats'][$etat]['actionsautorisees'];
$autorise= ! isset($actionsautorisees) || in_array($sAction,$actionsautorisees);
return $autorise;
}
Логіка така: дія $sAction дозволена, якщо вона міститься у списку $dConfig['etats'][$etat]['actionsautorisees'] або якщо цей список відсутній , що дозволяє виконати будь-яку дію. $etat — це стан додатка наприкінці попереднього циклу «запит клієнта/відповідь сервера». Цей стан було збережено в сесії, і його можна знайти саме там. Якщо виявляється, що запитувана дія є недопустимою, виконується скрипт $dConfig['actions']['enchainementInvalide']['url']. Цей скрипт відповідатиме за надсилання відповідної відповіді клієнту.
На етапі розробки словник $dConfig['etats'] можна не заповнювати. У цьому випадку будь-який стан дозволяє виконувати будь-яку дію. Словник можна буде доопрацювати після повного налагодження програми. Він захищатиме програму від несанкціонованих дій.
3.8. Налагодження
Контролер пропонує дві функції налагодження:
- функція trace дозволяє вивести повідомлення у потік HTML
- функція dump дозволяє вивести вміст словника в цей самий потік
Будь-який скрипт дії зможе використовувати ці дві функції. Оскільки код скрипта дії включено (include) у код контролера, функції trace та dump будуть доступні для скриптів.
3.9. Висновок
Універсальний контролер покликаний дати розробнику можливість зосередитися на діях та представленнях свого додатка. Він забезпечує для нього:
- управління сесією (відновлення, збереження)
- перевірку правильності запитуваних дій
- виконання скрипту, пов’язаного з дією
- надсилання клієнту відповіді, що відповідає результату виконання дії