3 заметки с тегом

Конспект

Роутинг для PSR 7 Response/Request. Часть 2: Проектируем Router

Содержание:

Добавление правил

Придумываем объект маршрутизации и его метод для добавления правила маршрутизации, в который передаем наш путь, HTTP метод и анонимную функцию:

Поиск нужного правила

Чтобы понять, какой обработчик к нашему запросу относится — придумываем метод, в который мы передаем наш Request, а он сам будет производить проверку по правилам выше и возвращать результат, из которого мы сможем извлекать экшн (анонимная функция) и атрибуты — далее мы должны найденные атрибутты в правилах передать в экшн, точнее в объект Request.
По итогу это должно выглядеть так:

Дополнительные методы для добавления правил**

Прежде чем создавать код для объекта Router — думаем над тем, что можно упростить.
Для того, чтобы не указывать HTTP методы в параметрах, мы можем создать методы объекта Router с названием этих HTTP методов:

Это более удобно, не нужно заморачиваться, также будет работать автоподстановка.

Проблема — много методов

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

Для решения этого — роутер разделяем на 2 части: на коллекции роутов и сам роутер.
https://gist.github.com/Maksclub/e09b1f4ad82b53b813ceb8a3043a878b

Помимо упрощения использования — данное разделение позволяет его использовать надежнее, например потом в контроллере, используя объект Router, нельзя будет добавить маршрут, так как у него нет таких методов.

Генерация адресов

Помимо разбора адресов при разработке и поддержке проектов необходимо адреса генерировать.
Конечно в самих представлениях мы можем выводить жестко:

<a href="/blog/<?= $post->id?>">
    <?= htmlspecialchars($post->title)?>
</a>

Но если нам нужно поменять роуты с /blog/ на /post/ или в магазине /product/ на еще какой-нибудь, то придется в ручную менять во всех местах, что может вылезти косяками в работе проекта.

Тогда придумываем метод generate() для нашего объект Router, с помощью которого мы и будем выводить адреса по названию маршрута:

<a href="<?= $router->generate('название_маршрута', ['id' => $post->id]) ?>">
    <?= htmlspecialchars($post->title)?>
</a>

Как видите нам необходимо имя маршрута. В принципе мы можем использовать путь, который указывали в правилах, но в случае регулярки при ее изменении нам придется менять ее во многих местах, потому для правил добавим еще один параметр с названием роута.

Укрощаем регулярное выражение в правилах

Громоздкие регулярные выражения не удобно записывать, а также метод генерации адреса с регуляркой не работает.

Тогда вместо:

$router->get('blog_show', '/blog/(?P<id>\d+)', ...);

Можно будет упрощенно использовать так:

$router->get('blog_show', '/blog/{id:\d+}', ...);

Но тогда мы не сможем использовать в данном случае сложные правила, например указать число символов в регулярном выражении, так как фигурные скобки нам усложнят парсинг адресов.

Как мы можем поступить:

  • Сам параметр указать чистым, а регулярное выражение передать отдельным параметром, например:
$router->get('blog_show', '/blog/{id}', ..., ['id' => '\d+']);
  • Сделать систему с необязательными параметрами, а сами параметры передавать как токены и задавать значения по умолчанию:
$router->get('blog_show', '/blog[/{page}]', ..., 
     [
         'tokens' => ['page' => '\d+'], 
         'default' => ['page' => 1],
]);

Будет делать именно второй вариант.

2017   Конспект   Роутинг

Роутинг для PSR 7 Response/Request. Часть 1: Обзор

Сухая выжимка из видео Дмитрия Елисеева PSR-7 фреймворк 2/6: Контроллеры и маршрутизация.

Вводные данные

У нас уже есть входная точка для нашего приложения и подключена библиотека для работы с HTTP PSR 7  — в примерах будет Zend Diactoros.

В итоге наш index.php выглядит так:


<\?php

use Zend\Diactoros\Response\HtmlResponse;
use Zend\Diactoros\Response\SapiEmitter;
use Zend\Diactoros\ServerRequestFactory;

chdir(dirname(__DIR__));
require 'vendor/autoload.php';

### Initialization
$request = ServerRequestFactory::fromGlobals();

### Action
$name = $request->getQueryParams()['name'] ?? 'Guest';
$response = new HtmlResponse('Hello, ' . $name . '!');

### Postprocessing
$response = $response->withHeader('X-Developer', 'maksfedorov.ru');

### Sending
$emitter = new SapiEmitter();
$emitter->emit($response);


По коду:

  1. Подключаем фабрику Zend Diactoros, которая конвертирует глобальные переменные в объекты Request и Response
  2. Создаем объект Response и передаем в него значение GET параметра name или строку ’Guest’
  3. Добавляем свой пользовательский заголовок к объекту Response
  4. Отправляем на вывод, добавляя код ответа и тип контента

Первый роутинг

Получим наш пользовательский путь (после исполняющего скрипта, до параметров). На основе этого пути зададим свой объект Response и передадим в каждый соответствующий контент.

Помимо простых путей, мы можем добавить сюда более гибкие пути, например для блога в виде JSON:


<\?php

### Action
$path = $request->getUri()->getPath();

if ($path === '/') {
    $name = $request->getQueryParams()['name'] ?? 'Guest';
    $response = new HtmlResponse('Hello, ' . $name . '!');
} elseif ($path === '/about') {
    $response = new HtmlResponse('I am a simple site');
} else {
    $response = new HtmlResponse('Undefined page', 404);
}

# ...


и его отдельных статей, проверяя путь каждой статьи через регулярное выражение:


<\?php



Это и есть простейший роутинг, но как понимаете — если контент будет сколь заметным или путей будет много — работать с таким кодом будет крайне не удобно.

В самом конце мы разнесем по разным файлам, но пока мы будет делать это постепенно, шаг за шагом показывая, как это будет происходить.

Оптимизация

Чтобы в условиях для каждого роута наши переменные не зассоряли наш эфир и глобальные переменные — поместим этот код в анонимные функции, присвоим их результат переменной $action и после условия выведем результат подходящей условию переменной:

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

Но мы можем воспользоваться методом withAttribute() объекта Request, тем самым изменяя сам объект Request, который мы и передаем в анонимную функцию, в итоге выглядет это так:


<\?php



ADR (Action Domain Response)

Action Domain Response — частный и упрощенный случай MVC.

Суть этого подхода для нас — нам нужно сделать систему маршрутизации, при которой в зависимости от разных адресов мы возвращаем нужную анонимную функцию.

Простейший вариант:


<\?php



Но для учета HTTP методов (например для REST API) это будет иметь такой вид:


<\?php



Теперь мы получили представление о том, что нам нужно получить и как это должно работать, в следующей части разберем принцип построения системы маршрутизации перейти к созданию маршрутизации→

2017   Конспект   Роутинг

Общий принцип оплаты в интернет-магазине через платежную систему (HTTP)

Черновик — есть неточности

Если вам нужно будет организовать оплату на своем сайте и под ваш фреймворк или ЦМС нет готовых решений — его не сложно написать самому.

Общий принцип работы

Все платежные системы работают примерно по одной схеме — у них есть API, который работает в частности через HTTP. При оплате товара магазин отправляет запрос на нужный URL платежки с параметрами заказа.

Нужно несколько параметров, например некий ID заказа, стоимость (price), вспомогательные параметры (например lang(язык), currency (валюта) и т. д...)

Наша цель — редиректить наш заказ на этот адрес, например в контроллере при оплате:

return $this->redirect('https://merchant.ru/payment.aspx?' . http_puild_query([
    'id' => $order->id,
    'price' => $order->price,
]))

Когда сервер платежки получает запрос и пользователь оплачивает — происходят два варианта события: Success или  Fail

Success — значит все отлично и происходит простой редирект назад в магазин.

При регистрации в системе своего проекта мы укзаываем successUrl — именно на него платежка и редиректит, например http://shop.dev/payment/success

Fail — происходит аналогично

URL для проверки заказа

Но при этом есть самый главный URL для взаимодействия — processUrl, этот URL интернет-магазина запрашивается «тайком» платежной системой тогда, когда осуществляется платеж.

Порядок работы:

  1. Оплата → (redirect) → merchant.ru/payment.aspx?id=123&price=1000
  2. Пользователь оплачивает
  3. Когда он нажимает оплатить, платежная система идет с запросом на processUrl, например на /payment/process (GET или POST), передавая параметры — чтобы проверить заказ.
  4. Наш сайт отвечает OK или не OK
  5. Если ОК → платежка списывает с человека деньги, и выводит примерно текст «Перейи на сайт магазина» (которая ведет на successUrl)

Сигнатура

Но если бы такая схема работала, то человек мог подменить к примеру цену, занизив ее, потому передают секретный ключ в параметре signature

https://merchant.ru/payment.aspx?id=123&price=1000&signature=dfg7fh6f54jhfgjh8ол65рро6

Например это мог бы быть некий хэш параметров:

md5($id . ':' . $price);

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

Платежки выдают пару ключей:

$password1= 'gfg5fh7677hjf';
$password2= '454545';

Почему нельзя обойтись одним — первый используется при кодировании нашых параметров, то есть
вместо md5($id . ’:’ . $price);
делают md5($id . ’:’ . $price . ’:’ . $password1);

После того как платеж оплачен во время редиректа платежка отправляет в запросе параметр сигнатуры на основе второго пароля, а мы проверяем.

Грубо говоря, через password1 мы подписываем запрос, а на сервере платежки идет проверка, когда платежка отправляет ответ — она подписывает его через password2. Так как данные ключи есть и у клиента и у платежки — это происходит безопасно и удобно.