Пишемо свій PHP Framework

  1. Правила
  2. архітектура
  3. Model
  4. View
  5. Controller
  6. Routers
  7. Request & Response
  8. Modules
  9. Core
  10. допоміжний класи
  11. тривіальні завдання
  12. структура каталогу
  13. висновок

Для початківців велосипедистів иль просто цікавих
Для початківців "велосипедистів" иль просто цікавих ...

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

Правила

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

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

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

архітектура

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

Model

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

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

// модель User використовує в якості сховища БД class Model_User extends Framework_Model_Database {$ _table = & quot; users & quot ;; $ _Pkey = & quot; id & quot ;; function getByLogin ($ login) {/*...*/} function getByEmail ($ email) {/*...*/}} // модель MainConfig використовує в якості сховища ini файл class Model_MainConfig extends Framework_Model_Ini {protected $ _file = & quot; application.ini & quot ;; 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 = & quot; global & quot ;; function setOption ($ key) {/*...*/} function getOption ($ key) {/*...*/}}

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

View

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

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

<? Php if ($ this-> books):?> <! - Таблиця з декількох книг. -&gt; <table> <tr> <th> Author </ th> <th> Title </ th> </ tr> <? Php foreach ($ this-> books as $ key => $ val):?&gt; <tr> <td> <? php echo $ this-> escape ($ val [ 'author'])?&gt; </ td> <td> <? php echo $ this-> escape ($ val [ 'title'] )?&gt; </ td> </ tr> <? php endforeach; ?&gt; </ Table> <? Php else:?> <P> Ні книг для відображення. </ P> <? Php endif; ?>

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

Приклад використання 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/ // стандартна схема побудови URL'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

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

  1. При ініціалізації вхідний запит повинен бути оброблений всіма правилами Router'ов, щоб об'єкт Request міг повернути нам запитувана значення по ключу
  2. Об'єкт Request так само повинен знати, який модуль / контролер / екшен запитується
  3. Ядро має довантажити необхідний контролер і викликати запитуваний екшен (метод контролера)
  4. Після відпрацювання контролера викликається 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 | `- <module_name> | | - 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 - вирішувати вам, як на мене - можна змиритися з недоліками якогось одного фреймворка, і насолоджуватися його перевагами. Можливо, ви спробуєте написати своє рішення або схрестити існуючі, але не забуваєте - написання такого роду додатки тягне за собою відповідальність за його підтримки.

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

Php if ($ this-> books):?
Gt; <table> <tr> <th> Author </ th> <th> Title </ th> </ tr> <?
Php foreach ($ this-> books as $ key => $ val):?
Gt; <tr> <td> <?
Php echo $ this-> escape ($ val [ 'author'])?
Gt; </ td> <td> <?
Php echo $ this-> escape ($ val [ 'title'] )?
Gt; </ td> </ tr> <?
Php endforeach; ?
Gt; </ Table> <?