Symfony і основи HTTP

01/04/2015 0 symfony, розробка, інструкція, фронт-контролер

Вітаємо! Ви зробили правильний вибір, почавши вивчати Symfony, бо завдяки цьому ви зможете стати більш продуктивним, всебічно розвинутим і популярним веб розробником (щодо останнього твердження, тут все уже залежить від вас самих). Symfony створено для того, щоб надавати базові, низькорівневі інструменти для пришвидшення веб-розробки. При цьому ваші веб-додатки будуть надійними і стабільними. Symfony взяв найкраще від різних технологій — інструменти і концепції, які ви будете вивчати, створювались завдяки зусиллям тисяч людей протягом багатьох років. Таким чином, ви не просто вивчаєте Symfony. Ви відкриваєте основи web, найкращі практики розробки і способи застосування цілої низки чудових РНР-бібліотек у складі Symfony чи поза ним. Отож, приготуйтесь!

Згідно з філософією Symfony, ця глава починається з роз’яснення основної концепції, на якій побудована уся веб-розробка: НТТР. Тому її обов’язково потрібно прочитати кожному, незважаючи на попередній досвід чи улюблену технологію.

HTTP — це просто

HTTP (Hypertext Transfer Protocol або просто Протокол Передачі Гіпертексту) — це текстова мова, що дозволяє двом комп’ютерам обмінюватись інформацією між собою. Отак просто! Тобто, коли ми хочемо переглянути свіжий комікс xkcd, відбувається приблизно такий “діалог”:

../_images/http-xkcd.png

 

І хоча насправді мова, якою користуються комп'ютери, значно формальніша, за суттю вона все ж залишається дуже простою. А НТТР — термін для опису цієї мови. Незалежно від того, як саме ви ведете веб-розробку, ціллю вашого сервера завжди залишається розуміння простого текстового запиту і видача простої текстової відповіді на цей запит.

Symfony з самих початків будувалася в таких реаліях . Усвідомлюєте ви це, чи ні,  ви щодня використовуєте НТТР. А за допомогою Symfony навчитеся керувати ним!

Крок 1: Клієнт надсилає запит

Будь-який діалог в мережі починається із запиту. Запит — це текстове повідомлення, яке створює клієнт (наприклад, браузер, додаток iPhone і т.д.) у форматі, відомому як НТТР. Клієнт надсилає запит серверу і чекає на відповідь.

На мові НТТР цей запит буде виглядати приблизно так:

GET / HTTP/1.1
Host: xkcd.com
Accept: text/html
User-Agent: Mozilla/5.0 (Macintosh)

Це просте повідомлення містить всю необхідну інформацію стосовно ресурсу, який потрібен клієнту. Найбільш важливим є перший рядок запиту, що містить два компоненти: URI запиту і НТТР-метод.

 
URI (наприклад /, /contact і т.д.) — унікальна адреса, або місце знаходження, яке визначає запитуваний клієнтом ресурс. НТТР-метод (наприклад, GET) визначає, що саме ви хочете зробити з цим ресурсом. НТТР-методи — це дієслова у запиті, що визначають типовий  шлях взаємодії клієнта із запитуваним ресурсом:

GET — отримати ресурс з сервера;

POST — створити ресурс на сервері;

PUT — оновити ресурс на сервері;

DELETE — видалити ресурс з сервера.

Тепер, коли ви знаєте про ці НТТР-методи та їх функції, можна собі уявити, як виглядатиме запит для видалення конкретного запису в блозі:

DELETE /blog/15 HTTP/1.1

Насправді існує 9 методів, ще їх називають дієсловами (verbs), визначених в специфікації протокола НТТР, але більшість з них не дуже поширені або не скрізь підтримуються. Насправді, більшість браузерів підтримують лише методи POST та GET у HTML формах. Інші ж, підтримуються у XMLHttpRequests, так самояк і маршрутизатором Symfony.

На додачу до першого рядка, НТТР-запит містить також декілька інформаційних рядків, які називаються заголовками (headers). Ці заголовки можуть надавати різну інформацію: запитуваний Host, формати відповіді, які підтримує клієнт (Accept), i додаток, який використовує клієнт для виконання запиту (User-Agent). Існують й інші заголовки, повний список яких ви можете знайти у Вікіпедії на сторінці "Список заголовків HTTP".

Крок 2: Сервер надає відповідь

З моменту, коли сервер отримує запит, він точно знає, який ресурс потрібен клієнту (завдяки URI) і що клієнт хоче з ним робити (на основі НТТР-методу). Наприклад, при отриманні запиту GET сервер знайде запитуваний ресурс і поверне його у вигляді НТТР-відповіді.  

У форматі НТТР відправлена назад до браузера відповідь виглядатиме так:

HTTP/1.1 200 OK
Date: Sat, 02 Apr 2011 21:05:05 GMT
Server: lighttpd/1.4.19
Content-Type: text/html

<html>
  <!-- ... HTML for the xkcd comic -->
</html>

НТТР-відповідь містить запитуваний ресурс (в даному випадку — HTML-код сторінки), а також додаткові дані про цю відповідь. Перший рядок найбільш важливий, адже містить НТТР статус-код (в даному випадку це 200). Статус-код повідомляє про результат виконання відправленого запиту. Чи був цей запит успішним? Чи виникали помилки в процесі його виконання? Одні статус-коди позначають успішні запити, інші — запити з помилками, а треті повідомляють дію, очікувану від клієнта (наприклад, перенаправлення на іншу сторінку). Повний їх список можна знайти у Вікіпедії на сторінці "Список кодів стану HTTP"

Як і запит, НТТР-відповідь містить додаткову інформацію у вигляді НТТР-заголовків. Одним із найважливіших є заголовок Content-Type. Тіло одного і того ж ресурсу може бути передано у різних форматах, включаючи HTML, XML або JSON. Заголовок Content-Type, використовуючи такі типи Інтернет медіа, як text/html, повідомляє клієнту, який саме формат використовується у відповіді. Список широко використовуваних типів медіа читайте у статті Вікіпедії: "Список типових медіа типів".

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

Запити, відповіді та Web-розробка

Обмін запитами-відповідями — це фундаментальний процес, рушійна сила всіх комунікацій у всесвітній мережі. І наскільки важливий цей процес, настільки він простий.

А найважливіше тут ось що: незалежно від мови програмування, яку ви використовуєте, від додатку, який створюєте (web, мобільний, JSON API) і навіть від філософії, якій слідуєте при розробці ПО, кінцевою ціллю додатку залишається прийом та інтерпретація запиту і створення належної відповіді.

Symfony було спроектовано на основі цих реалій.

Для того, щоб більше дізнатися про специфікацію НТТР, прочитайте оригінал HTTP 1.1 RFC або HTTP Bis (ініціатива по роз’ясненню оригінальної специфікації).Хорошим інструментом для перевірки заголовків запиту і відповіді є розширення для Firefox під назвою Live HTTP Headers.

Запити і відповіді в PHP

А як відсилати запити і обробляти відповіді, якщо використовується РНР? РНР насправді дещо абстрагує вас від самого процесу:

$uri = $_SERVER['REQUEST_URI'];
$foo = $_GET['foo'];

header('Content-Type: text/html');
echo 'The URI requested is: '.$uri;
echo 'The value of the "foo" parameter is: '.$foo;

Як би це дивно не звучало, саме цей невеличкий додаток отримує інформацію із НТТР-запиту і використовує її для створення НТТР-відповіді. Замість того, щоб парсити необроблений НТТР-запит, РНР готує суперглобальні змінні (типу $_SERVER i $_GET), які містять всю інформацію про запит. По аналогії, замість того, щоб відсилати назад текст відповіді з форматуванням за правилами НТТР, ви можете використовувати функцію header() для створення заголовків відповіді та просто вивести основний контент, який буде розпізнаватись як контентний блок відповіді. Як результат, РНР сформує правильну НТТР-відповідь і видасть її клієнту:

HTTP/1.1 200 OK
Date: Sat, 03 Apr 2011 02:14:33 GMT
Server: Apache/2.2.17 (Unix)
Content-Type: text/html

The URI requested is: /testing?foo=symfony
The value of the "foo" parameter is: symfony

Запити і відповіді в Symfony

Symfony являє собою альтернативу традиційному лінійному підходу РНР завдяки двом класам, які дозволяють легко взаємодіяти з НТТР-запитами і відповідями.

Клас Request простою об’єктно орієнтованою репрезентацію відповіді HTTP. З його допомогою, уся інформація про запит знаходиться у ваших руках:

use Symfony\Component\HttpFoundation\Request;

$request = Request::createFromGlobals();

// the URI being requested (e.g. /about) minus any query parameters
$request->getPathInfo();

// retrieve GET and POST variables respectively
$request->query->get('foo');
$request->request->get('bar', 'default value if bar does not exist');

// retrieve SERVER variables
$request->server->get('HTTP_HOST');

// retrieves an instance of UploadedFile identified by foo
$request->files->get('foo');

// retrieve a COOKIE value
$request->cookies->get('PHPSESSID');

// retrieve an HTTP request header, with normalized, lowercase keys
$request->headers->get('host');
$request->headers->get('content_type');

$request->getMethod();    // GET, POST, PUT, DELETE, HEAD
$request->getLanguages(); // an array of languages the client accepts

Клас Request також виконує багато роботи в фоновому режимі, тому вам не доведеться забивати собі ними голову. Наприклад, метод isSecure() звертається до трьох різних значень в РНР, які визначають, чи користувач здійснює підключення по захищеному протоколу (https).

ParameterBag та Request Attribute

Як ми уже бачили вище, змінні $_GET і $_POST доступні через відповідні загальні властивості query і request. Кожен з цих об'єктів виступає об'єктом ParameterBag, що має методи get(), has(), all() та багато інших. Насправді ж, кожна з відкритих властивостей з попереднього прикладу є, певною мірою, зразком ParameterBag. 

Клас запиту також має відкриту властивість attributes, що містить особливі дані стосовно того, як ваш додаток працює "зсередини". У випадку із фреймворком Symfonу attributes містить ті властивості, які повертає поставлений у відповідність маршрут, скажімо, _controller, id (якщо у вас є метасимвол {id}), або навіть ім'я поставленого у відповідність маршруту (_route). Властивість attributes існує цілком і повністю для того, аби ви могли готувати та зберігати конкретні дані про певний запит.

В Symfony також є клас Response: проста РНР-інтерпретація НТТР-відповіді. Завдяки цьому ваш додаток зможе використовувати об’єктно-орієнтований інтерфейс для генерації відповіді, яку потрібно відправити назад клієнту: 

use Symfony\Component\HttpFoundation\Response;

$response = new Response();

$response->setContent('<html><body><h1>Hello world!</h1></body></html>');
$response->setStatusCode(Response::HTTP_OK);
$response->headers->set('Content-Type', 'text/html');

// prints the HTTP headers followed by the content
$response->send();

Якби в Symfony цього не було, вам довелося б самостійно шукати зручні інструменти для перегляду інформації з запиту і конструювання відповіді. І навіть коли ви засвоїте більш складні можливості Symfony, завжди варто пам‘ятати, що ціль вашого додатку полягає в інтерпретації запиту і генеруванні відповідної до нього відповіді на основі логіки додатку.

Класи Request i Response є частинами самостійного, включеного до Symfony, компонента HttpFoundation, який, однак, можна використовувати незалежно від Symfony. Він включає в себе класи для роботи з сесіями і для завантаження файлів. 

Шлях від запиту до відповіді

Об’єкти Request i Response досить прості, як, власне, і сам НТТР-протокол. Найскладніше завдання у розробці додатку — написання процесів, які відбуваютсья в інтервалі між отриманням запиту і надсиланням відповіді. Тобто, дійсно попрацювати доведеться над написанням коду, який буде інтерпретувати інформацію з запиту і створювати відповідь.

Ваш додаток може мати багато функцій: відправка email’ів, обробка відправлених форм, збереження інформації в базу даних, відображення HTML-сторінок, захист контенту правилами безпеки тощо. Як же впоратися з усім цим і підтримувати код організованим?

Symfony розроблено спеціально для вирішення таких проблем, тому вам не доведеться робити це самостійно.

Фронт-контролер

Раніше додатки створювалися таким чином, що кожна сторінка мала свій окремий файл:

index.php
contact.php
blog.php

Для такого підходу характерна ціла низка проблем, наприклад, негнучкі URL (а раптом вам потрібно буде замінити blog.php на news.php і зберегти при цьому всі ваші посилання?), а також необхідність включати в кожен файл безліч інших, щоб безпека з’єднання з базою даних та вигляд сайту були узгодженими.  

Тому краще використовувати фронт-контролер, єдиний РНР-файл, відповідальний за кожен запит до вашого додатку. Приклад:

       /index.php   звертається до index.php

       /index.php/contact   звертається до index.php

       /index.php/blog   звертається до index.php

Використовуючи модуль mod_rewrite для Apache (або його еквівалента для інших веб серверів), URL-и можна почистити від будь-яких згадок за фронт-контролер, тобто зробити так, аби лишилися тільки /, /contact i /blog.

Тепер кожен запит обробляється за типовою схемою. Замість того, щоб створювати окремий файл для кожної сторінки вашого додатку, завжди виконується фронт-контролер і за допомогою маршрутизатора звертається до різних частин вашого додатку, в залежності від URL. Це вирішує більшість проблем, характерних для стандартного підходу. Практично всі сучасні додатки так працюють, наприклад, WordPress.

Залишайтесь організованими

Розберемось із фронт-контролером зсередини. Припустімо, нам треба з'ясувати, яка саме частина коду виконається і що відобразиться в результаті цієї маніпуляції. Для цього треба перевірити вхідний URI і виконати необхідну операцію залежно від отриманого значення. Це можна зробити швидко, але не дуже гарно:

// index.php
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;

$request = Request::createFromGlobals();
$path = $request->getPathInfo(); // the URI path being requested

if (in_array($path, array('', '/'))) {
    $response = new Response('Welcome to the homepage.');
} elseif ('/contact' === $path) {
    $response = new Response('Contact us');
} else {
    $response = new Response('Page not found.', Response::HTTP_NOT_FOUND);
}
$response->send();

Рішення у даній ситуації може виявитися непростим. На щастя, Symfony й було створено саме для таких випадків.

Що становить додаток на Symfony

Довірте обробку запитів Symfony — і життя стане простішим. Обробка кожного запиту відбувається за однією й тією ж схемою:

Вхідні запити інтерпретуються маршрутизатором і передаються до функції-контролера, яка і повертає об’єкт Response.

Кожна сторінка вашого сайту має визначатися в конфігурації маршрутизатора для того, щоб можна було коректно перенаправляти конкретні URL до потрібних РНР функцій. Кожна з цих функцій (їх називають контролерами) повинна створити і відправити об’єкт Response, використовуючи інформацію із запиту та інші інструменти, які надає Symfony. Іншими словами, ваш код направляється саме у контролер: там ви розпізнаєте запит і створюєте на нього відповідь.

Це не так складно, як може здатись:

1) кожен запит обробляється фронт-контролером;

2) система маршрутизації визначає, яку саме функцію PHP треба виконати на основі тієї інформації, яку ми отримали у запиті, та на основі налаштувань марштуризатора;

3) виконується необхідна PHP функція, в ході виконання якої написаний вами код генерує і видає об'єкт Response відповідно до логіки додатку.

Symfony Request у дії

Давайте розглянемо цей процес, не заглиблюючись у зайві деталі. Припустімо, ви хочете додати сторінку /contact до вашого додатку на Symfony. Спочатку потрібно /contact URI прописати в конфігурації маршрутизатора:

YAML

# app/config/routing.yml
contact:
    path:     /contact
    defaults: { _controller: AppBundle:Main:contact }

XML

<!-- app/config/routing.xml -->
<?xml version="1.0" encoding="UTF-8" ?>
<routes xmlns="http://symfony.com/schema/routing"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://symfony.com/schema/routing
        http://symfony.com/schema/routing/routing-1.0.xsd">

    <route id="contact" path="/contact">
        <default key="_controller">AppBundle:Main:contact</default>
    </route>
</routes>

PHP

// app/config/routing.php
use Symfony\Component\Routing\Route;
use Symfony\Component\Routing\RouteCollection;

$collection = new RouteCollection();
$collection->add('contact', new Route('/contact', array(
    '_controller' => 'AppBundle:Main:contact',
)));

return $collection;

Коли хтось заходить на сторінку /contact, URI співпадає з маршрутом і виконується вказаний нами контроллер. Про це ви дізнаєтесь у розділі "Маршрутизація", рядок  AppBundle:Main:contact — коротка форма запису, яка вказує на специфічний метод PHP, contactAction, визначений в класі MainController: 

 // src/AppBundle/Controller/MainController.php
namespace AppBundle\Controller;

use Symfony\Component\HttpFoundation\Response;

class MainController
{
    public function contactAction()
    {
        return new Response('<h1>Contact us!</h1>');
    }
}

В цьому простому прикладі контролер створює об’єкт Response, який містить простий HTML-код “<h1>Contact us!</h1>”. В розділі "Контролер" ви дізнаєтеся, як контролер може відображати шаблони, відокремлюючи представлення від коду в файлах шаблонів. Це дає можливість контролеру зосередитися на "важчій" роботі: інтеграція з базами даних, обробка відправлених користувачем даних чи відправка email повідомлень.

Symfony: працюйте над додатком, а не над інструментами

Тепер вам відомо, що кінцева ціль нашого додатку — інтерпретація вхідних запитів і створення відповіді. Але з часом додаток росте і стає все складніше підтримувати код впорядкованим. Знову і знову потрібно буде виконувати ті ж завдання: збереження даних в базу, відображення і повторне використання шаблонів, обробка форм, розсилання email повідомлень, валідація даних, які вводить користувач і питання безпеки.

Ці проблеми не унікальні. Symfony — це фреймворк (структура, своєрідний каркас), що надає вам всі необхідні інструменти для створення власного додатку, а не інших інструментів. Symfony ні до чого вас не зобов’язує: ви можете використовувати фреймворк повністю, або тільки його частину.

Автономні інструменти: компоненти Symfony

Що ж являє собою Symfony? В першу чергу це набір більш як 20-ти незалежних бібліотек, які можна використовувати в будь-якому РНР-проекті. Ці бібліотеки називаються Symfony Components і вони містять корисні методи для роботи практично з будь-яким проектом, незалежно від процесу його розвитку.Наведемо кілька прикладів:

1) HttpFoundation — містить класи Request i Response, а також класи для роботи з сесіями і завантаженням файлів;

2) Routing — потужна система маршрутизації, яка дозволяє поставити у відповідність до певного URI (наприклад /contact) інформацію про те, як цей запит обробляти (наприклад, за допомогою методу contactAction());

3) Form — багатофункціональний і гнучкий фреймворк для створення форм і обробки результатів їх заповнення;

4) Validator — система, призначена для створення правил для даних та їх валідації (перевірки, чи відповідають ті дані, які ввів користувач, установленим правилам);

5) Templating — набір інструментів для відображення шаблонів, підтримки наслідування шаблонів (наприклад, коли ваш шаблон оформлено через layout), а також інші типові для шаблонів операції;

6) Security — потужна бібліотека для підтримання всіх видів безпеки всередині додатку;

7) Translation — фреймворк для підтримки перекладів у вашому додатку.

Кожен із наведених компонентів є роз’єднаними, і їх можна використовувати самостійно у будь-якому РНР-проекті, незалежно від того використовуєте ви Symfony Framework чи ні. Кожна частина зроблена для користування і при потребі їх можна замінити.

Комплексне рішення: фреймворк Symfony

То що ж таке фреймворк Symfony? Це — РНР бібліотека, яка забезпечує рішення двох різних задач:

1) Надає у розпорядження ряд вибраних компонентів (Symfony Components) і сторонніх бібліотек (наприклад, Swift Mailer для відправки пошти);

2) Забезпечує розумною конфігурацією за замовчуванням, а також “склеює” все у бібліотеку, роблячи ваш додаток єдиним цілим.

Ціль фреймворка — інтеграція незалежних інструментів і забезпечення їх спільної роботи, для забезпечення хорошого досвіду розробникам. Сам фреймворк являє собою Symfony Bundle (плагін), який можна конфігурувати, або навіть повністю замінити.

Symfony надає чудовий набір інструментів для швидкої розробки веб-додатків, але нічого не нав’язує безпосередньо вашому додатку. Розробник може швидко почати роботу, використовуючи дистрибутив Symfony (надає “скелет” додатку з типовими налаштуваннями). А для тих, хто хоче сам у всьому розібратись, ніяких обмежень просто не існує :)

Поділитися