С помощью Phalcon можно создавать приложения по типу “Микрофреймворк”. Для этого, необходимо написать всего лишь несколько строк кода.

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

<?php

use Phalcon\Mvc\Micro;

$app = new Micro();

$app->get(
    '/orders/display/{name}',
    function ($name) {
        echo "<h1>Это заказ: {$name}!</h1>";
    }
);

$app->handle();

Создание микроприложения

Класс Phalcon\Mvc\Micro является ответственным за создание микроприложения.

<?php

use Phalcon\Mvc\Micro;

$app = new Micro();

Маршрутизация (роутинг)

Определение маршрутов в приложении Phalcon\Mvc\Micro очень просто. Маршруты определяются следующим образом:

Приложение -> (метод/глагол) -> (url адрес/регулярное выражение, вызываемая функция PHP)

Настройка

Маршрутизация обрабатывается объектом Phalcon\Mvc\Router. [Info]

Обычно, / является стартовым маршрутом приложения, и в большинстве случаев осуществляется через метод GET протокола HTTP:

<?php

// Это стартовый маршрут
$app->get(
    '/',
    function () {
        echo '<h1>Welcome!</h1>';
    }
);

Объект Application

Маршруты можно задать используя экземпляр объекта-приложения Phalcon\Mvc\Micro следующим образом:

use Phalcon\Mvc\Micro;

$app = new Micro();

// Соответствия для GET запроса
$app->get(
    '/orders/display/{name}',
    function ($name) {
        echo "<h1>This is order: {$name}!</h1>";
    }
);

Объект Router

Вы также можете задать маршруты создав экземпляр объекта Phalcon\Mvc\Router, настроив с его помощью маршруты, а затем добавив его в контейнер зависимостей (инъекция).

use Phalcon\Mvc\Micro;
use Phalcon\Mvc\Router;

$router = new Router();

$router->addGet(
    '/orders/display/{name}',
    'OrdersClass::display';
    }
);

$app = new Micro();
$app->setService('router', $router, true);

Настройка ваших маршрутов с помощью методов глаголов (get, post и т.д.) объекта-приложения Phalcon\Mvc\Micro гораздо проще, чем создание объекта маршрутизатора с соответствующими маршрутами и затем инъекцией в приложение.

Каждый метод имеет свои преимущества и недостатки. Все зависит от дизайна и требований приложения.

Rewrite правила

Для того чтобы маршруты работали необходимо также внести изменения в конфигурацию, вашего, веб-сервера для, вашего, конкретного сайта.

Эти изменения описаны в Apache Rewrite Rules и NGINX Rewrite Rules.

Обработчики

Обработчик — это вызываемая часть кода, которая "привязана" к маршруту. При совпадении с маршрутом, его обработчик выполняется с заданными параметрами. Обработчиком может быть любая вызываемая часть кода, которая существует в PHP.

Определения

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

Анонимная Функция

Мы можем использовать анонимные функции (как показано ниже) для обработки запроса

$app->get(
    '/orders/display/{name}',
    function ($name) {
        echo "<h1>Это заказ: {$name}!</h1>";
    }
);

Доступ к объекту $app, внутри анонимной функции может достигаться путем инъекций переменой следующим образом:

$app->get(
    '/orders/display/{name}',
    function ($name) use ($app) {
        $content = "<h1>Это заказ: {$name}!</h1>";
        $app->response->setContext($content);
        $app->response->send();
    }
);

Функция

Мы можем определить функцию как обработчик и прикрепить её к определенному маршруту.

// Определение функции
function order_display($name) {
    echo "<h1>Это заказа: {$name}!</h1>";
}

$app->get(
    '/orders/display/{name}',
    'orders_display'
);

Статический метод

Также возможно использовать статический метод класса в качестве обработчика:

class OrdersClass
{
    public static function display($name) {
        echo "<h1>Это заказ: {$name}!</h1>";
    }
}

$app->get(
    '/orders/display/{name}',
    'OrdersClass::display'
);

Метод объекта

Мы также можем использовать метод экземпляра объекта:

class OrdersClass
{
    public function display($name) {
        echo "<h1>Это заказ: {$name}!</h1>";
    }
}

$orders = new OrdersClass();
$app->get(
    '/orders/display/{name}',
    [
        $orders,
        'display',
    ]
);

Контроллеры

С Phalcon\Mvc\Micro вы можете создавать как микроприложения, так и средне-сложные приложения. Средне-сложные приложения используют микро-архитектуру, но расширяют её, таким образом являясь компромиссом между микро- и полноценными приложениями.

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

<?php

use Phalcon\Mvc\Micro\Collection as MicroCollection;

$orders = new MicroCollection();

// Установка главного обработчика, т.е. экземпляра контроллера
$orders->setHandler(new OrdersController());

// Установка основного префикса для всех маршрутов
$orders->setPrefix('/orders');

// Использовать метод 'index' контроллера OrdersController
$orders->get('/', 'index');

// Использовать метод 'show' контроллера OrdersController
$orders->get('/display/{slug}', 'show');

$app->mount($orders);

Контроллер OrdersController может выглядеть следующим образом:

<?php

use Phalcon\Mvc\Controller;

class OrdersController extends Controller
{
    public function index()
    {
        // ...
    }

    public function show($name)
    {
        // ...
    }
}

Поскольку наши контроллеры расширяют Phalcon\Mvc\Controller, все службы внедрения зависимостей доступны с соответствующими регистрационными именами. Например:

<?php

use Phalcon\Mvc\Controller;

class OrdersController extends Controller
{
    public function index()
    {
        // ...
    }

    public function show($name)
    {
        $content = "<h1>Это заказ: {$name}!</h1>";
        $this->response->setContext($content);

        return $this->response;
    }
}

Загрузка по требованию

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

Отложенная загрузка может быть легко реализована если использовать Phalcon\Mvc\Micro\Collection для настройки обработчиков:

$orders->setHandler('OrdersController', true);
$orders->setHandler('Blog\Controllers\OrdersController', true);

Пример использования

Предположим мы занимаемся разработкой API для интернет-магазина. Конечные точки /users, /orders и /products. Каждая из этих конечных точек зарегистрирована с использованием обработчиков, каждый обработчик это контроллер с соответствующими действиями.

Контроллеры, которые мы используем, как обработчики, выглядят следующим образом:

<?php

use Phalcon\Mvc\Controller;

class UsersController extends Controller
{
    public function get($id)
    {
        // ...
    }

    public function add($payload)
    {
        // ...
    }
}

class OrdersController extends Controller
{
    public function get($id)
    {
        // ...
    }

    public function add($payload)
    {
        // ...
    }
}

class ProductsController extends Controller
{
    public function get($id)
    {
        // ...
    }

    public function add($payload)
    {
        // ...
    }
}

Мы зарегистрировать обработчики:

<?php

use Phalcon\Mvc\Micro\Collection as MicroCollection;

// Обработчик Users
$users = new MicroCollection();
$users->setHandler(new UsersController());
$users->setPrefix('/users');
$users->get('/get/{id}', 'get');
$users->get('/add/{payload}', 'add');
$app->mount($users);

// Обработчик Orders
$orders = new MicroCollection();
$orders->setHandler(new OrdersController());
$orders->setPrefix('/users');
$orders->get('/get/{id}', 'get');
$orders->get('/add/{payload}', 'add');
$app->mount($orders);

// Обработчик Products
$products = new MicroCollection();
$products->setHandler(new ProductsController());
$products->setPrefix('/products');
$products->get('/get/{id}', 'get');
$products->get('/add/{payload}', 'add');
$app->mount($products);

Эта реализация загружает каждый обработчик по очереди и монтирует их в наш объект приложения. Проблема этого подхода в том, что каждый запрос приведет только в одну конечную точку, и будет выполнен только один метод класса. Остальные методы/обработчики просто останутся в памяти и не будут использованы.

Используя загрузку по запросу мы уменьшаем количество загруженных в память объектов, и в результате наше приложение использует меньше памяти.

Реализация выше изменяется, если мы желаем использовать загрузку по запросу, и будет выглядеть следующим образом:

<?php

use Phalcon\Mvc\Micro\Collection as MicroCollection;

// Обработчик Users
$users = new MicroCollection();
$users->setHandler('UsersController', true);
$users->setPrefix('/users');
$users->get('/get/{id}', 'get');
$users->get('/add/{payload}', 'add');
$app->mount($users);

// Обработчик Orders
$orders = new MicroCollection();
$orders->setHandler('OrdersController', true);
$orders->setPrefix('/users');
$orders->get('/get/{id}', 'get');
$orders->get('/add/{payload}', 'add');
$app->mount($orders);

// Обработчик Products
$products = new MicroCollection();
$products->setHandler('ProductsController', true);
$products->setPrefix('/products');
$products->get('/get/{id}', 'get');
$products->get('/add/{payload}', 'add');
$app->mount($products);

Используя это простое изменение в реализации, все обработчики остаются не созданными, пока не будут запрошены вызывающим. Поэтому когда вызывающий запрашивает /orders/get/2, наше приложение создаст экземпляр OrdersController и вызовет в нем метод get. Теперь наше приложение использует меньше ресурсов, чем прежде.

Не найдено (404)

Любой маршрут, который не сопоставлен в нашем Phalcon\Mvc\Micro приложении, приведет к попытке выполнить обработчик определенный с помощью метода notFound. Аналогично для других методов/действий (get, post и т. д.), Вы можете зарегистрировать обработчик с помощью метода notFound, который может быть любой вызываемой PHP функцией.

<?php

$app->notFound(
    function () use ($app) {
        $app->response->setStatusCode(404, 'Не найдено');
        $app->response->sendHeaders();

        $message = 'Не на что здесь смотреть. Продолжаем....';
        $app->response->setContent($message);
        $app->response->send();
    }
);

Также, вы можете обрабатывать маршруты, которые не были сопоставлены (404) с помощью middleware, обсуждаемых ниже.

Методы-глаголы

Объект-приложение Phalcon\Mvc\Micro предоставляет следующий перечень методов для связки HTTP-метода с маршрутом.

delete

Пример ниже сработает если HTTP-метод будет DELETE и маршрут соответствовать /api/products/delete/{id}

$app->delete(
        '/api/products/delete/{id}',
        'delete_product'
    );

get

Пример ниже сработает если HTTP-метод будет GET, а маршрут равен /api/products

$app->get(
        '/api/products',
        'get_products'
    );

head

Сработает, если HTTP метод будет HEAD, а маршурт /api/products

$app->head(
        '/api/products',
        'get_products'
    );

map

Map позволяет назначить одну и ту же конечную точку для более чем одного HTTP метода. Пример ниже сработает, если HTTP метод будет GET или POST, а маршрут /repos/store/refs

$app
        ->map(
            '/repos/store/refs',
            'action_product'
        )
        ->via(
            [
                'GET',
                'POST',
            ]
        );

options

Сработает, если HTTP метод будет OPTIONS, а маршрут /api/products/options

$app->options(
        '/api/products/options',
        'info_product'
    );

patch

Сработает, если HTTP методбудет PATCH, а маршрут /api/products/update/{id}

$app->patch(
        '/api/products/update/{id}',
        'update_product'
    );

post

Сработает, есди HTTP метод будет POST, а маршрут /api/products/add

$app->post(
        '/api/products',
        'add_product'
    );

put

Сработает, если HTTP метод будет PUT, а маршрут/api/products/update/{id}

$app->put(
        '/api/products/update/{id}',
        'update_product'
    );

Коллекции

Коллекции — это удобный способ группирования коллекций, прикрепленных к обработчику, и общего префикса (при необходимости). Для гипотетической конечной точки /orders у нас могут быть следующие конечные точки:

/orders/get/{id}
/orders/add/{payload}
/orders/update/{id}
/orders/delete/{id}

Все эти маршруты обрабатываются нашим OrdersController. Мы создали наши маршруты с коллекцией следующим образом:

<?php

use Phalcon\Mvc\Micro\Collection as MicroCollection;

$orders = new MicroCollection();
$orders->setHandler(new OrdersController());

$orders->setPrefix('/orders');

$orders->get('/get/{id}', 'displayAction');
$orders->get('/add/{payload}', 'addAction');
$orders->get('/update/{id}', 'updateAction');
$orders->get('/delete/{id}', 'deleteAction');

$app->mount($orders);

Параметры

Мы кратко видели выше, как параметры определяются в маршрутах. Параметры задаются в строке маршрута, заключая имя параметра в фигурные скобки.

$app->get(
    '/orders/display/{name}',
    function ($name) {
        echo "<h1>Это заказ: {$name}!</h1>";
    }
);

Мы также можем применять определенные правила для каждого параметра с помощью регулярных выражений. Регулярное выражение задается после имени параметра, разделяя его с :.

// Сопоставить order id
$app->get(
    '/orders/display/{id:[0-9]+}',
    function ($id) {
        echo "<h1>This is order: #{$id}!</h1>";
    }
);

// Сопоставить год (4 числа) и заголовок (буквы)
$app->get(
    '/posts/{year:[0-9][4]}/{title:[a-zA-Z\-]+}',
    function ($year, $title) {
        echo '<h1>Заголовок: $title</h1>';
        echo '<h2>Год: $year</h2>';
    }
);

Дополнительно: Phalcon\Mvc\Router. [Подробнее]

Перенаправления

Вы можете перенаправить один маршрут в другой с помощью объекта Phalcon\Http\Response, также, как в полном приложении.

$app->post('/old/url',
    function () use ($app) {
        $app->response->redirect('new/url');
        $app->response->sendHeaders();
    }
);

$app->post('/new/welcome',
    function () use ($app) {
        echo 'This is the new Welcome';
    }
);

Примечание мы должны передать объект $app в нашу анонимную функцию, чтобы иметь доступ к объекту request.

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

<?php

use Phalcon\Mvc\Controller;

class UsersController extends Controller
{
    public function oldget($id)
    {
        return $this->response->redirect('users/get/' . $id);
    }

    public function get($id)
    {
        // ...
    }
}

Наконец, вы можете выполнять перенаправления в Middleware (если вы его используете). Пример приведен ниже в соответствующем разделе.

URL-адреса для маршрутов

Другая возможность маршрутизации — настройка именованных маршрутов и генерация URL-адресов для этих маршрутов. Это двухшаговый процесс.

  • Сначала нам нужно назвать наш маршрут. Это может быть достигнуто с помощью метода setName(), который доступен из методов-глаголов в нашем приложении (get, post, и т. д.);
// Задать маршрут с именем 'show-order'
$app
    ->get(
        '/orders/display/{id}',
        function ($id) use ($app) {
            // ... Найти заказ и показать его
        }
    )
    ->setName('show-order');
  •  Нам нужно использовать компонент Phalcon\Mvc\Url для получения URL-адреса на основе определенных маршрутов.
// Используем именованный маршрут и создаем URL от него
$app->get(
    '/',
    function () use ($app) {
        $url = sprintf(
            '<a href="/%s">Show the order</a>',
            $app->url->get(
                [
                    'for' => 'show-order',
                    'id'  => 1234,
                ]
            )
        );

        echo $url;
    }
);

Внедрение зависимостей

Когда микро приложение создано, неявно создается контейнер сервисов Phalcon\Di\FactoryDefault.

<?php

use Phalcon\Mvc\Micro;
$app = new Micro();

$app->get(
    '/',
    function () use ($app) {
        $app->response->setContent('Hello!!');
        $app->response->send();
    }
);

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

use Phalcon\Mvc\Micro;
use Phalcon\Di\FactoryDefault;
use Phalcon\Config\Adapter\Ini as IniConfig;

$di = new FactoryDefault();

$di->set(
    'config',
    function () {
        return new IniConfig('config.ini');
    }
);

$app = new Micro();

$app->setDI($di);

$app->get(
    '/',
    function () use ($app) {
        // Читаем настройки из конфига
        echo $app->config->app_name;
    }
);

$app->post(
    '/contact',
    function () use ($app) {
        $app->flash->success('Что ты делаешь Дэйв?');
    }
);

Также вы можете использовать синтаксис массивов, чтобы зарегистрировать сервисы в контейнере зависимостей из объекта приложения:

<?php

use Phalcon\Mvc\Micro;
use Phalcon\Db\Adapter\Pdo\Mysql as MysqlAdapter;

$app = new Micro();

// Настройка сервиса базы данных
$app['db'] = function () {
    return new MysqlAdapter(
        [
            'host'     => 'localhost',
            'username' => 'root',
            'password' => 'secret',
            'dbname'   => 'test_db',
        ]
    );
};

$app->get(
    '/blog',
    function () use ($app) {
        $news = $app['db']->query('SELECT * FROM news');

        foreach ($news as $new) {
            echo $new->title;
        }
    }
);

Ответы

Микро-приложение может возвращать множество различных типов ответов. Прямой выход, использовать шаблонизатор, расчетные данные, представление данных, JSON и т. д.

Обработчики могут возвращать raw ответ используя plain text, объект Phalcon\Http\Response или пользовательский компонент реализующий интерфейс Phalcon\Http\ResponseInterface.

Прямой вывод

$app->get(
    '/orders/display/{name}',
    function ($name) {
        echo "<h1>Это заказ: {$name}!</h1>";
    }
);

Подключение внешнего файла

$app->get(
    '/orders/display/{name}',
    function ($name) {
        require 'views/results.php';
    }
);

Прямой вывод JSON

$app->get(
    '/orders/display/{name}',
    function ($name) {
        echo json_encode(
            [
                'code' => 200,
                'name' => $name,
            ]
        );
    }
);

Новый объект Response

Вы можете использовать метод setContent объекта Response, чтобы вернуть ответ:

$app->get(
    '/show/data',
    function () {
        // Создание ответа
        $response = new Phalcon\Http\Response();

        // Установка заголовка Content-Type
        $response->setContentType('text/plain');

        // Передача контента из файла
        $response->setContent(file_get_contents('data.txt'));

        // Возврат ответа
        return $response;
    }
);

Ответ приложения

Можно также использовать объект Phalcon\Http\Response для возврата ответов вызывающему объекту. Объект Response имеет много полезных методов, которые делают возврат ответов намного проще.

$app->get(
    '/show/data',
    function () use ($app) {
        // Установка заголовка Content-Type
        $app->response->setContentType('text/plain');
        $app->response->sendHeaders();

        // Вывод файла
        readfile('data.txt');
    }
);

Возврат ответа приложения

Другим подходом возврата данных обратно вызывающей стороне является возвращения объекта Response напрямую из приложения. Когда ответы возвращаются обработчиками они автоматически отправляются приложением.

<?php

use Phalcon\Mvc\Micro;
use Phalcon\Http\Response;

$app = new Micro();

// Возврат объекта Response
$app->get(
    '/welcome/index',
    function () {
        $response = new Response();

        $response->setStatusCode(401, 'Не авторизован');
        $response->setContent('Доступ не авторизован');

        return $response;
    }
);

JSON

При использовании Phalcon\Http\Response, JSON-ответ может быть сформирован с такой же лёгкостью:

$app->get(
    '/welcome/index',
    function () use ($app) {

        $data = [
            'code'    => 401,
            'status'  => 'error',
            'message' => 'Не авторизированный доступ',
            'payload' => [],
        ];

        $response->setJsonContent($data);

        return $response;
    }
);

События

Phalcon\Mvc\Micro приложение тесно работает с Phalcon\Events\Manager, если он представлен, чтобы вызывать события, которые могут быть использованы на протяжении работы нашего приложения. Тип этих событий — micro. Эти события вызываются в нашем приложение и могут быть прикреплены к соответствующим обработчикам, которые будут выполнять действия необходимые нашему приложению.

Доступные события

Поддерживаются следующие события:

Название событияСрабатываетМожет остановить операцию?
beforeHandleRoute Основной метод вызван; Маршруты ее не проверены Да
beforeExecuteRoute Маршрут сопоставлен, обработчик верный, но еще не выполнен Да
afterExecuteRoute Обработчик только что закончил работать Нет
beforeNotFound Маршрут не найден Да
afterHandleRoute Маршрут только что закончил выполнение Да
afterBinding Срабатывает после того, как модели связаны, но перед выполнением обработчика Да

Пример: Аутентификация

Вы легко можете проверить был ли пользователь аутентифицирован или нет используя событие beforeExecuteRoute. В следующем примере, мы рассмотрим как контролировать безопасность приложения используя событийную модель:

<?php

use Phalcon\Mvc\Micro;
use Phalcon\Events\Event;
use Phalcon\Events\Manager as EventsManager;

// Создаем менеджер событий
$eventsManager = new EventsManager();

$eventsManager->attach(
    'micro:beforeExecuteRoute',
    function (Event $event, $app) {
        if ($app->session->get('auth') === false) {
            $app->flashSession->error("The user isn't authenticated");

            $app->response->redirect('/');
            $app->response->sendHeaders();

            // Возврат (false) останавливает операцию
            return false;
        }
    }
);

$app = new Micro();

// Привязываем менеджер событий к приложению
$app->setEventsManager($eventsManager);

Пример: Страница не найдена

Другим встроенным событием, которое можно подписаться на реализацию бизнес-логики, является beforeNotFound. В следующем примере показан один из способов обработки запросов на несуществующий адрес:

<?php

use Phalcon\Mvc\Micro;
use Phalcon\Events\Event;
use Phalcon\Events\Manager as EventsManager;

// Создаем менеджер событий
$eventsManager = new EventsManager();

$eventsManager->attach(
    'micro:beforeNotFound',
    function (Event $event, $app) {
        $app->response->redirect('/404');
        $app->response->sendHeaders();

        return $app->response;
    }
);

$app = new Micro();

// Привязываем менеджер событий к приложению
$app->setEventsManager($eventsManager);

Middleware.

Middleware — это классы, которые могут быть прикреплены к вашему приложению, с целью ввести ещё один слой, для обработки бизнес-логики. Они запускаются последовательно, в соответствии с порядком регистрации и не только улучшают гибкость, путем инкапсуляции специфичной функциональности, но также повышают производительность. Middleware может остановить выполнение, когда конкретное бизнес-правило не было удовлетворенно. Это дает возможность приложению завершить работу на ранней стадии, без выполнения полного цикла запроса.

Наличие Phalcon\Events\Manager необходимо для работы middleware, поэтому он должен быть зарегистрирован в нашем Di контейнере.

Прикреплённые события

Middleware может быть присоединен к микро приложения в 3 разных событий. Это:

СобытиеОписание
before Перед выполнением обработчика
after После выполнения обработчика
final После того, как был отправлен ответ вызывающему

before

Это событие идеально для остановки выполнения приложения, когда определенный критерий не соблюден. В примере ниже, мы проверяем был ли пользователь авторизован и останавливаем выполнение с необходимым перенаправлением.

<?php

$app = new Phalcon\Mvc\Micro();

// Вызывается перед выполнением каждого маршрута
// Возврат false отменяет выполнение маршрута
$app->before(
    function () use ($app) {
        if (false === $app['session']->get('auth')) {
            $app['flashSession']->error("Пользователь не авторизован");

            $app['response']->redirect('/error');

            // Возврат false останавливает нормальное выполнение
            return false;
        }

        return true;
    }
);

after

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

$app->map(
    '/api/robots',
    function () {
        return [
            'status' => 'OK',
        ];
    }
);

$app->after(
    function () use ($app) {
        // Выполняется после выполнения маршрута
        echo json_encode($app->getReturnedValue());
    }
);

finish

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

$app->finish(
    function () use ($app) {
        if (true === file_exists('/tmp/processing.cache')) {
            unlink('/tmp/processing.cache');
        }
    }
);

Настройка

Прикрепление middleware к вашему приложению происходит очень легко, как показано выше, с помощью вызова методов before, after и finish.

$app->before(
    function () use ($app) {
        if (false === $app['session']->get('auth')) {
            $app['flashSession']->error("Пользователь не авторизован");

            $app['response']->redirect('/error');

            // Возврат false останавливает нормальное выполнение
            return false;
        }

        return true;
    }
);

$app->after(
    function () use ($app) {
        // Выполняется после выполнения маршрута
        echo json_encode($app->getReturnedValue());
    }
);

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

<?php

use Phalcon\Events\Manager;
use Phalcon\Mvc\Micro;

use Website\Middleware\CacheMiddleware;
use Website\Middleware\NotFoundMiddleware;
use Website\Middleware\ResponseMiddleware;

/**
 * Create a new Events Manager.
 */
$eventsManager = new Manager();
$application   = new Micro();

/**
 * Прикрепляем middleware к менеджеру событий и приложению 
*/
$eventsManager->attach('micro', new CacheMiddleware());
$application->before(new CacheMiddleware());

$eventsManager->attach('micro', new NotFoundMiddleware());
$application->before(new NotFoundMiddleware());

/**
 * Этот нужен, чтобы слушать событие `after`
 */
$eventsManager->attach('micro', new ResponseMiddleware());
$application->after(new ResponseMiddleware());

/**
 * Убедимся, что наш менеджер событий теперь находится в DI контейнере
 */
$application->setEventsManager($eventsManager);

Нам нужен объект Phalcon\Events\Manager. Это может быть вновь созданный экземпляр, или мы можем получить один из существующих в нашем DI контейнере (если вы использовали FactoryDefault).

Мы прикрепляем каждый класс middleware в micro хуке в менеджере событий. Мы могли бы также быть немного более конкретными и прикрепить его, на вызов события micro:beforeExecuteRoute.

Затем мы прикрепляем класс middleware в наше приложение на одно из трех прослушиваемых событий, описанных выше (before, after, finish).

Реализация

Middleware может быть любой вызываемой PHP функцией. Вы можете организовать код любым путем, который вам нравится, для реализации middleware. Если вы решили использовать классы для middleware, Вам понадобится реализовать интерфейс Phalcon\Mvc\Micro\MiddlewareInterface

<?php

use Phalcon\Mvc\Micro;
use Phalcon\Mvc\Micro\MiddlewareInterface;

/**
 * CacheMiddleware
 *
 * Кэширует страницы, чтобы сократить обработку
 */
class CacheMiddleware implements MiddlewareInterface
{
    /**
     * Вызывает middleware
     *
     * @param Micro $application
     *
     * @returns bool
     */
    public function call(Micro $application)
    {
        $cache  = $application['cache'];
        $router = $application['router'];

        $key = preg_replace('/^[a-zA-Z0-9]/', '', $router->getRewriteUri());

        // Проверяет закэширован ли запрос
        if ($cache->exists($key)) {
            echo $cache->get($key);

            return false;
        }

        return true;
    }
}

События в Middleware

События, вызываемые для нашего приложения, также вызываются внутри класса реализующего интерфейс Phalcon\Mvc\Micro\MiddlewareInterface. Это обеспечивает большую гибкость и более широкие возможности для разработчиков, поскольку можем взаимодействовать с обработкой запроса.

Пример API

Предположим, у нас есть API, которое мы реализовали с помощью Micro приложения. Нам необходимо прикрепить различные Middleware классы к приложению, чтобы иметь возможность лучше контролировать его выполнение.

Middleware , которое мы будем использовать:

  • Брандмауэр
  • Не найти
  • Переадресовывать
  • CORS
  • Запрос
  • Ответ

Брандмауэр

Этот middleware прикрепляется к событию before нашего микроприложения. Цель этого middleware проверить, кто вызывает наше API, и опираясь на белый список, разрешает ему продолжить или нет

<?php

use Phalcon\Events\Event;
use Phalcon\Mvc\Micro;
use Phalcon\Mvc\Micro\MiddlewareInterface;

/**
 * FirewallMiddleware
 *
 * Проверяет белый список и допускает клиентов или нет
 */
class FirewallMiddleware implements MiddlewareInterface
{
    /**
     * Before anything happens
     *
     * @param Event $event
     * @param Micro $application
     *
     * @returns bool
     */
    public function beforeHandleRoute(Event $event, Micro $application)
    {
        $whitelist = [
            '10.4.6.1',
            '10.4.6.2',
            '10.4.6.3',
            '10.4.6.4',
        ];
        $ipAddress = $application->request->getClientAddress();

        if (true !== array_key_exists($ipAddress, $whitelist)) {
            $this->response->redirect('/401');
            $this->response->send();

            return false;
        }

        return true;
    }

    /**
     * Вызывает middleware
     *
     * @param Micro $application
     *
     * @returns bool
     */
    public function call(Micro $application)
    {
        return true;
    }
}

Не найти

Когда этот middleware обработан, это означает, что запрашивающему IP разрешен доступ к нашему приложению. Приложение попытается сопоставить маршрут, и если он не будет найден сработает событие beforeNotFound. Тогда мы прекратим обработку и вернем пользователю соответствующий ответ 404. Этот middleware прикреплен к событию before нашего Micro приложения

<?php

use Phalcon\Mvc\Micro;
use Phalcon\Mvc\Micro\MiddlewareInterface;

/**
 * NotFoundMiddleware
 *
 * Обрабатывает 404
 */
class NotFoundMiddleware implements MiddlewareInterface
{
    /**
     * Маршрут не был найден
     *
     * @returns bool
     */
    public function beforeNotFound()
    {
        $this->response->redirect('/404');
        $this->response->send();

        return false;
    }

    /**
     * Вызывает middleware
     *
     * @param Micro $application
     *
     * @returns bool
     */
    public function call(Micro $application)
    {
        return true;
    }
}

Перенаправление

Мы прикрепляем этот middleware снова к событию before нашего Micro приложения, потому что мы не хотим, чтобы запрос продолжал обрабатываться, если запрашиваемая конечная точка должна быть перенаправлена.

<?php

use Phalcon\Events\Event;
use Phalcon\Mvc\Micro;
use Phalcon\Mvc\Micro\MiddlewareInterface;

/**
 * RedirectMiddleware
 *
 * Проверяет запрос и перенаправляет пользователя куда-нибудь еще, если необходимо
 */
class RedirectMiddleware implements MiddlewareInterface
{
    /**
     * Прежде чем что-то произойдет
     *
     * @param Event $event
     * @param Micro $application
     *
     * @returns bool
     */
    public function beforeHandleRoute(Event $event, Micro $application)
    {
        if ('github' === $application->request->getURI()) {
            $application->response->redirect('https://github.com');
            $application->response->send();

            return false;
        }

        return true;
    }

    /**
     * Вызывает middleware
     *
     * @param Micro $application
     *
     * @returns bool
     */
    public function call(Micro $application)
    {
        return true;
    }
}

CORS

Опять же это middleware прилагается к событию before нашего микро-приложения. Мы должны убедиться, что он стреляет прежде, чем что-нибудь случится с нашим приложением

<?php

use Phalcon\Events\Event;
use Phalcon\Mvc\Micro;
use Phalcon\Mvc\Micro\MiddlewareInterface;

/**
 * CORSMiddleware
 *
 * Проверка CORS
 */
class CORSMiddleware implements MiddlewareInterface
{
    /**
     * Прежде, чем что-нибудь случится
     *
     * @param Event $event
     * @param Micro $application
     *
     * @returns bool
     */
    public function beforeHandleRoute(Event $event, Micro $application)
    {
        if ($application->request->getHeader('ORIGIN')) {
            $origin = $application->request->getHeader('ORIGIN');
        } else {
            $origin = '*';
        }

        $application
            ->response
            ->setHeader('Access-Control-Allow-Origin', $origin)
            ->setHeader(
                'Access-Control-Allow-Methods',
                'GET,PUT,POST,DELETE,OPTIONS'
            )
            ->setHeader(
                'Access-Control-Allow-Headers',
                'Origin, X-Requested-With, Content-Range, ' .
                'Content-Disposition, Content-Type, Authorization'
            )
            ->setHeader('Access-Control-Allow-Credentials', 'true');

        return true;
    }

    /**
     * Calls the middleware
     *
     * @param Micro $application
     *
     * @returns bool
     */
    public function call(Micro $application)
    {
        return true;
    }

Запрос

Это middleware получает  данные JSON и проверяет их. Если  данные JSON недопустимы, выполнение будет остановлено.

<?php

use Phalcon\Events\Event;
use Phalcon\Mvc\Micro;
use Phalcon\Mvc\Micro\MiddlewareInterface;

/**
 * RequestMiddleware
 *
 * Проверяет входящие данные
 */
class RequestMiddleware implements MiddlewareInterface
{
    /**
     * Перед выполнением маршрута
     *
     * @param Event $event
     * @param Micro $application
     *
     * @returns bool
     */
    public function beforeExecuteRoute(Event $event, Micro $application)
    {
        json_decode($application->request->getRawBody());
        if (JSON_ERROR_NONE !== json_last_error()) {
            $application->response->redirect('/malformed');
            $application->response->send();

            return false;
        }

        return true;

    }

    /**
     * Вызывает middleware
     *
     * @param Micro $application
     *
     * @returns bool
     */
    public function call(Micro $application)
    {
        return true;
    }
}

Ответ

Этот middleware отвечает за изменение нашего ответа и его отправку вызывающему в виде JSON строки. Следовательно, мы должны прикрепить его к событию after нашего Micro приложения.

<?php

use Phalcon\Mvc\Micro;
use Phalcon\Mvc\Micro\MiddlewareInterface;

/**
* ResponseMiddleware
*
* Изменяет ответ
*/
class ResponseMiddleware implements MiddlewareInterface
{
     /**
      * Прежде, чем что-нибудь случится
      *
      * @param Micro $application
      *
      * @returns bool
      */
    public function call(Micro $application)
    {
        $payload = [
            'code'    => 200,
            'status'  => 'success',
            'message' => '',
            'payload' => $application->getReturnedValue(),
        ];

        $application->response->setJsonContent($payload);
        $application->response->send();

        return true;
    }
}

Модели

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

<?php

$loader = new \Phalcon\Loader();
$loader
    ->registerDirs(
        [
            __DIR__ . '/models/',
        ]
    )
    ->register();

$app = new \Phalcon\Mvc\Micro();

$app->get(
    '/products/find',
    function () {
        $products = \MyModels\Products::find();

        foreach ($products as $product) {
            echo $product->name, '<br>';
        }
    }
);

$app->handle();

Внедрение моделей

Используя класс Phalcon\Mvc\Model\Binder Вы можете внедрять экземпляры моделей в Ваши маршруты:

<?php

$loader = new \Phalcon\Loader();

$loader->registerDirs(
    [
        __DIR__ . '/models/',
    ]
)->register();

$app = new \Phalcon\Mvc\Micro();
$app->setModelBinder(new \Phalcon\Mvc\Model\Binder());

$app->get(
    "/products/{product:[0-9]+}",
    function (Products $product) {
        // делаем что-нибудь с объектом $product
    }
);

$app->handle();

Так как объект Binder использует внутренний Reflection Api, который может быть тяжелым, есть возможность установить кэш, чтобы ускорить этот процесс. Это можно выполнить используя второй аргумент метода setModelBinder(), который также может принимать имя сервиса, или просто передав экземпляр кэша в конструктор класса Binder.

В настоящее время связыватель будет использовать только первичный ключ модели для выполнения findFirst(). Пример маршрута для вышеупомянутого будет /products/1.

Представления

Phalcon\Mvc\Micro по своей сути не имеет службы представлений. Однако мы можем использовать компонент Phalcon\Mvc\View\Simple для визуализации представлений.

<?php

$app = new Phalcon\Mvc\Micro();

$app['view'] = function () {
    $view = new \Phalcon\Mvc\View\Simple();

    $view->setViewsDir('app/views/');

    return $view;
};

// Вернуть отображенный вид
$app->get(
    '/products/show',
    function () use ($app) {
        // Отобразить app/views/products/show.phtml с передачей некоторых переменных
        echo $app['view']->render(
            'products/show',
            [
                'id'   => 100,
                'name' => 'Artichoke',
            ]
        );
    }
);

 

<?php

$app = new Phalcon\Mvc\Micro();

$app['view'] = function () {
    $view = new \Phalcon\Mvc\View();

    $view->setViewsDir('app/views/');

    return $view;
};

// Return a rendered view
$app->get(
    '/products/show',
    function () use ($app) {
        // Render app/views/products/show.phtml passing some variables
        echo $app['view']->start()->render(
            'products',
            'show',
            [
                'id'   => 100,
                'name' => 'Artichoke',
            ]
        )->finish()->getContent();
    }
);

Обработка ошибок

Также, Phalcon\Mvc\Micro приложение имеет метод error, который может быть использован для перехвата ошибок порождённых исключениями. Следующий фрагмент кода показывает основное использование этой возможности:

<?php

$app = new Phalcon\Mvc\Micro();

$app->get(
    '/',
    function () {
        throw new \Exception('Произошла ошибка', 401);
    }
);

$app->error(
    function ($exception) {
        echo json_encode(
            [
                'code'    => $exception->getCode(),
                'status'  => 'error',
                'message' => $exception->getMessage(),
            ]
        );
    }
);