Для початківців “велосипедистів” іль просто цікавих…

Дана стаття не заклик до дії, а лише невеличка замальовка на тему “Як би я це зробив”. На даний момент у мене у відділі активно використовується Zend Framework, і саме з ним я найкраще знайомий, тому не лякайтеся паралелей, це не реклама, адже більшість фреймворків в рівній мірі поєднують в собі плюси і мінуси, а нам потрібні лише переваги…

Правила

Почав би з регламентування правил:

  • Стандарти кодування – краще скористатися існуючими, раджу стандарти Zend Framework’а
  • Процес додавання коду у репозиторій (навіть якщо ви самі в проекті – це буде добре дисциплінувати), тільки не перегинайте палицю, інакше це сповільнить розвиток проекту

Не виробивши даних правил, ви ризикуєте перетворити фреймворк в смітник. Так само, настійно рекомендую писати юніт тести допоможуть заощадити багато часу.

Архітектура

Сподіваюся більшість читачів вже знайома з патерном MVC (Model-View-Controller) – так давайте на ньому базувати наш фреймворк, використання чогось іншого, боюся, буде відлякувати користувачів (тут я маю на увазі програмістів 🙂 ).

Model

У типовому проекті модель зав’язується на одну таблицю в базі даних, але винятків вистачає, тому не слід приймати дане твердження за аксіому. Наша модель повинна з легкістю працювати з різними сховищами даних, будь то БД, файли, або пам’ять.

Давайте уявімо як ми будемо користуватися такою моделлю:

// модель User використовує в якості сховища БД
class Model_User extends Framework_Model_Database
{
$_table = “users”;
$_pkey = “id”;
function getByLogin($login) { /*…*/ }
function getByEmail($e) { /*…*/ }
}
// модель MainConfig використовує в якості сховища ini файл
class Model_MainConfig extends Framework_Model_Ini
{
protected $_file = “application.ini”;
function setOption($key) { /*…*/ }
function getOption($key) { /*…*/ }
}
// модель Registry використовує в якості сховища пам’ять – якась альтернатива глобальним змінним
class Model_Registry extends Framework_Model_Memory
{
function setOption($key) { /*…*/ }
function getOption($key) { /*…*/ }
}
// модель Session використовує в якості сховища файли сесії
class Model_Session extends Framework_Model_Session
{
protected $_namespace = “global”;
function setOption($key) { /*…*/ }
function getOption($key) { /*…*/ }
}

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

View

Які нині вимоги до шаблонизатору? Особисто для мене нативний синтаксис PHP, підтримка різного роду хелперів і фільтрів. Так само повинен бути реалізований патерн “двохетапного подання” (Two Step View pattern), ZF для цього служать два компоненти – Zend_View і Zend_Layout.

Наведу приклад такого подання:

books): ?>

Author
Title

books as $key => $val): ?>

escape($val[‘author’]) ?> escape($val[‘title’]) ?>

Немає книг для відображення.

Приклад використання layout’ів (взято з документації по Zend_Layout):

Так, в Zend Framework’е вдала реалізація подання, вона мені подобається, звичайно, не без дрібних нарікань, але в цілому – це п’ять.

Controller

Контролер повинен виконувати свої обов’язки – обробляти запит, штовхати модель і подання – щоб користувач отримав бажаний результат.

Давайте спробуємо зреагувати на запит користувача наступного виду:

http://example.com/?controller=users&action=profile&id=16

Так, проведемо розбір – у нас просять показати профайл користувача з id=16. Відповідно напрошується існування контролера users з методом profile, який би зміг отримати в якості параметра якийсь id:

// назва контролера повинно містити префікс – аби чого не наплутати
class Controller_Users extends Framework_Controller_Action
{
public function actionProfile()
{
// отримуємо дані з запиту
$id = $this->request->get(‘id’);
// штовхаємо модель
$user = new Model_User();
$user -> getById($id);
// закидаємо дані подання
$this->view->user = $user;
}
}

Природно, на плечі контролера так само лягає обов’язок змінювати формат представлення, тобто якщо нам треба повернути дані у форматі JSON, то ніякого іншого висновку бути не повинно (це і так мається на увазі самим MVC, але варто зайвий раз нагадати).

Хто уважніше побачить у даному прикладі поява якогось Request’a – це об’єкт який займається розбором вхідного запиту. Навіщо він потрібен – про це трохи далі.

Routers

Тепер трохи про вимоги з боку кінцевих користувачів, зокрема про ЧПУ. Порівняйте наступні варіанти посилань:

http://example.com/?controller=users&action=profile&id=16
http://example.com/users/profile/id/16/ // стандартна схема побудови ДО a в ZF
http://example.com/users/profile/16/ // CodeIgniter
http://example.com/profile/16/ // можливе побажання замовника, і його потрібно виконувати

Для генерації/розбору такого вхідного запиту в ZF використовуються роутери – за фактом – це правила побудови url’ов, нам так само доведеться їх реалізувати – і з цим складно посперечатися.

Мені більше по душі передача именнованых параметрів – такий URL легше читаємо, порівняйте:

http://example.com/users/list/page/2/limit/20/filter/active

і

http://example.com/users/list/2/20/active

Ви напевно захочете відразу засунути даний функціонал безпосередньо в клас Request, але не поспішайте, адже нам ще потрібно генерувати правильні URL під View – а викликати там об’єкт Request – трохи не логічно, давайте таки залишимо це на совісті окремого класу, до якого може звертатися як Request так і View

Request & Response

З призначенням класу Request думаю проблем не виникає – в його функції входить не так багато:

  • обробка вхідних параметрів всіма правилами з Router’а
  • віддавати параметри контролер на вимогу

Але є ще Response – про нього я як то не згадував раніше, що ж він має робити:

  • формувати заголовок відповіді
  • формувати відповідь – тобто брати view, обертати в якийсь layout і на вихід

Мені не дуже подобається реалізація Response в ZF – занадто багато зайвого в ньому

Modules

Фреймворк повинен бути модульним, тобто написавши якийсь модуль (блог, форум, тощо) ви зможете з легкістю використовувати даний код в інших додатках. Для цього нам знадобиться лише відокремити MVC кожного модуля в свою директорію, при цьому якийсь модуль залишиться за головного.

Core

Тепер варто перейти до найсмачнішого – безпосередньо до ядра системи, його функціонал ми практично вже описали, варто лише підвести риску:

  • При ініціалізації вхідний запит повинен бути оброблений всіма правилами Router’ів, щоб об’єкт Request міг повернути нам запитувана значення по ключу
  • Об’єкт Request так само повинен знати, який модуль/контролер/екшен запитується
  • Ядро має довантажити необхідний контролер і викликати запитуваний екшен (метод контролера)
  • Після відпрацювання контролера викликається Response і ставить крапку
  • Для гнучкості у систему варто додати або хуки, або плагіни на кожен з перерахованих етапів.

    Допоміжний класи

    Якщо ви захочете потренуватися в написанні “велосипедів”, то можете почати звідси:

    • Робота з БД – необхідна підтримка MySQL, SQLite, PostgreSQL (це мінімум), а в цілому варто приділити цим пунктом багато уваги, оскільки він один може залучити безліч користувачів
    • Валідатори – необхідна річ, для економії часу при написанні форм (див. Zend_Validate)
    • Транслятор – для реалізації багатомовність в системі, можливо вистачить і gettext’a, але не варто на це сподіватися
    • Пошта – можна обійтися лише функцією mail, але це якось не по-дорослому
    • Пагинатор – для вирішення тривіальної задачі – розбиття по сторінках (див. Zend_Paginator)
    • Навігатор – побудова меню, карти сайту і “хлібних крихт” (див. Zend_Navigation)
    • Кешування – без нього нікуди (див. Zend_Cache)
    • Конфігураційні файли – Zend_Config занадто великий для того, щоб обробляти лише один ini файл, тут можете попрактикуватися, але все ж таки поглядали на Zend_Config_Ini
    • Автозавантажувач – дуже корисна річ, і головне зручне – Zend_Loader
    • ACL – можливо буде потрібно, принаймні, розподіл прав за запитом модуль/контролер/екшен краще нехай буде зашитий в системі

    Я не випадково наводжу посилання на пакети Zend Framewrok’а – вони цілком адекватні і самостійні, можуть бути використані самі по собі, тобто ніхто ж не мtшает вам побудувати свій фреймворк з кубиків Zend’a (і ось приклад: ZYM engine)

    Тривіальні завдання

    У фреймворку повинен бути закладений функціонал для вирішення таких тривіальних завдань (дрібних і не дуже):

    • Redirect – самий звичайний, викликається з контролера
    • Forward – це пересилання з одного модуль/контролер/екшен на інший без перезавантаження сторінки
    • Messages – різні повідомлення, з можливістю отримання їх після перезавантаження сторінки
    • Scaffold – швидкий спосіб побудови програми для редагування записів в базі даних (перебільшено)

    Ще краще, якщо з фреймворком буде поставлятися готова до використання CMS система – вона дозволить популяризувати ваше дітище, і можливо приверне сторонніх розробників.

    Можливо чого забув з “тривіального” – пишіть…

    Структура каталогу

    І так, що у нас виходить, якщо поглянути на файлову систему (в document_root повинна лежати лише папка public):

    project
    |– application
    | |– configs
    | |– layouts
    | |– controllers
    | |– models
    | |– views
    | `– modules
    | `–
    | |– layouts
    | |– controllers
    | |– models
    | `– views
    |– data
    | |– cache
    | |– logs
    | `– sessions
    | library–
    | `– Framework
    |– public
    | |– styles
    | | scripts–
    | |– images
    | |– uploads
    | |– .htaccess
    | `– index.php
    `– tests

    Висновок

    To Be Or Not To Be – вирішувати вам, як по мені – можна змиритися з недоліками якогось одного фреймворку, і насолоджуватися його перевагами. Можливо, ви спробуєте написати своє рішення або схрестити існуючі, але не забуваєте – написання такого роду програми тягне за собою відповідальність за його підтримки.

    P. S. Для всіх моїх читачів – RSS каналу доступний за адресою http://anton.shevchuk.name/feed/ (якщо Ви використовуєте який інший – виправте).
    P. P. S. Ще я досить активно зависаю на твітері, так що йдіть за мною…