Мы прерываем нашу регулярно запланированную серию, чтобы представить вам эту статью о передаче представительских состояний, более известную как REST. Да, это серия статей Дэвида Ширея на эту тему, но он любезно предложил мне написать этот выпуск и поделиться некоторыми соображениями по поводу поддержки запросов REST в PHP.
В первой статье своей серии Дэвид объяснил, что REST — это больше, чем архитектурный паттерн. Это набор руководящих принципов, которые, если им следовать, могут помочь вам создавать масштабируемые и надежные приложения. В следующих статьях Дэвид возобновит обсуждение, взглянув на REST со стороны клиента уравнения. В этой статье я бы хотел сосредоточиться на стороне сервера. Вы узнаете, как переключить свое мышление с ориентированного на действия мышления, которое преобладает в веб-разработке сегодня, на более RESTful, ориентированный на ресурсы подход, и увидите один способ структурировать свой код и направлять запросы URI в PHP.
Изменение вашего мышления
Первое, что вы должны сделать после принятия решения о разработке приложения в соответствии с принципами REST, — это провести мозговой штурм по списку ресурсов, которые будут доступны.
Общий подход к веб-приложениям сосредоточен на функциональности. Например, система рейтинга ресторанов, написанная на PHP, может иметь скрипт editRestaurant.php
который представляет форму для обновления записи, а форма может отправляться в скрипт с именем saveRestaurant.php
. Обратите внимание, как действия, выполняемые сценариями, отображаются как часть имен их файлов. REST, с другой стороны, сосредотачивается на ресурсах … в этом случае сам ресторан.
Пользователь по-прежнему сможет создавать, просматривать и обновлять записи ресторана в системе рейтингов на основе REST, но только через ссылки, например example.com/viewRestaurant.php?id=42
. Вместо этого сообщение будет создано путем отправки запроса HTTP POST
на example.com/restaurant
, просмотренного HTTP GET
на example.com/restaurant/42
и обновленного HTTP PUT
на example.com/restaurant/42
. Независимо от того, являются ли они приложениями толстого клиента, веб-приложениями, мобильными приложениями или чем-либо еще, все программы позволяют пользователям выполнять задачи (действия). Приложения RESTful ничем не отличаются, это лишь основные действия, заранее определенные используемым протоколом. А так как действия уже определены, вы можете сосредоточиться на том, каким будет целевой ресурс этих действий (то есть ресторан).
На этом этапе вам следует только проводить мозговой штурм в отношении ресурсов, предоставляемых пользователю приложения, но не обязательно объектов, составляющих ваше приложение. Продолжая пример с оценкой, система может иметь объект User
для аутентификации пользователей приложения, объекты Restaurant
и Comment
для ресторана и предоставленные пользователем комментарии, и, возможно, даже контейнерный объект RestApp
со статическими RestApp
методами (Restaurant App, REST App, увидеть мой каламбур там?). Пользователь сам не будет взаимодействовать с этими объектами, хотя они не относятся к категории ресурсов.
Обслуживающий персонал ресторана хочет написать информацию о столовой и одобрить все замечательные комментарии, оставленные пользователями; читатель хочет прочитать отзывы и оставить комментарии. Если вы магазин Agile Development, то, возможно, вы сможете получить список ресурсов прямо из ваших пользовательских историй. Хотите угадать, какие ресурсы будут? Да … вы уже догадались! Ресторан и комментарий!
Сопоставление ресурсов: от URI до кода
Большинство REST-ориентированного кода, вероятно, будет использовать какое-то отображение ресурсов. Это позволяет вам иметь чистые URL ресурсов и скрывать фактическую архитектуру и реализацию от ваших конечных пользователей, что действительно очень хорошо!
Обычная практика — направлять все запросы в центральный файл индекса или контроллера, а затем вызывать оттуда соответствующую логику кода в зависимости от запрашиваемого ресурса и любых параметров или свойств запроса. Если вы используете стек LAMP, перенаправлять запросы в одну точку входа так же просто, как включить mod_rewrite, разрешить переопределения htaccess и поместить файл .htaccess
в свой веб-каталог с помощью следующего:
ПереписатьEngine на RewriteCond% {REQUEST_FILENAME}! -F RewriteCond% {REQUEST_FILENAME}! -D RewriteRule ^ (. *) $ /Index.php
Все ресурсы, которые отображаются в существующий файл (например, изображения, файлы CSS и т. Д.), Обслуживаются непосредственно Apache. В противном случае будет использован файл index.php
.
Для иллюстрации предположим, что у вас есть index.php
css/style.css
index.php
, css/style.css
и img/logo.png
. Apache будет обслуживать css/style.css
в ответ на запрос example.com/css/style.css
; img/logo.png
будет обслуживаться для example.com/img/logo.png
; и Apache будет вызывать index.php
для обработки запроса на example.com/restaurant
и example.com/restaurant/42
.
Файл index.php
должен знать, через какой URL он был получен, знать, что на самом деле запрашивал пользователь, какой код PHP в вашем приложении должен выполняться для конкретного запроса и что делать, когда запрос не может быть удовлетворен.
Естественная «иерархия», которую я до сих пор тонко использовал в своих примерных URL-адресах (например, www.example.com/restaurant/42
), следует довольно распространенному шаблону, который вы увидите в REST-приложениях в сети. Путь URL ( /restaurant/42
) легко разбирается и понимается как запрос ресурса ресторана, в частности, экземпляра ресурса ресторана, идентифицированного идентификатором 42. Таким образом, кодовая база может иметь класс Restaurant
, соответствующий экземпляр может быть создан и обработан соответствующим образом. Например:
<?php // assume autoloader available and configured $path = parse_url($_SERVER["REQUEST_URI"], PHP_URL_PATH); $path = trim($path, "/"); @list($resource, $params) = explode("/", $path, 2); $resource = ucfirst(strtolower($resource)); $method = strtolower($_SERVER["REQUEST_METHOD"]); $params = !empty($params) ? explode("/", $params) : array(); if (class_exists($resource)) { try { $resource = new $resource($params); $resource->{$method}(); } catch (Exception $e) { header("HTTP/1.1 500 Internal Server Error"); } } else { header("HTTP/1.1 404 File Not Found"); }
Этот простой пример иллюстрирует один из способов синтаксического анализа пути URL-адреса в ресурсе и его возможных параметров, а затем использования этой информации для создания экземпляра класса с тем же именем, что и у ресурса. Параметры из пути вводятся в объект через его конструктор, и затем ответственность за обработку запроса передается методу объекта с тем же именем, что и метод HTTP, используемый для его запроса.
Однако одним из недостатков приведенного выше кода является то, что вы ограничены мелкой иерархией пространства имен объектов. Если вы хотите использовать такой механизм динамической диспетчеризации, я предлагаю проанализировать URI справа налево и проверить, существует ли подходящий класс. Это должно позволить вам иметь больше глубины в иерархии.
Представление ресурсов как классов
Выбранная вами стратегия маршрутизации будет влиять на структуру объектов ресурсов. В предыдущем коде маршрутизации предполагается, что в классе будут определены методы post()
, get()
, put()
, head()
и т. Д. Поскольку все возможные объекты ресурсов должны следовать этому соглашению, было бы неплохо определить главный интерфейс, контракт, который обещает, что объект предложит определенный API (и, следовательно, мы надеемся, определенную функциональность).
Вы также можете написать абстрактный класс с частичной реализацией стандартных методов. Абстрактный класс не может быть создан сам по себе и должен быть расширен конкретным классом, но он помогает вам избежать повторяющихся определений методов. Немного подумав, можно создать очень мощную и надежную основу для других объектов ресурсов.
<?php abstract class Resource { protected static $httpMethods = array("GET", "POST", "HEAD", "PUT", "OPTIONS", "DELETE", "TRACE", "CONNECT"); protected $params; public function __construct(array $params) { $this->params = $params; } protected function allowedHttpMethods() { $myMethods = array(); $r = new ReflectionClass($this); foreach ($r->getMethods(ReflectionMethod::IS_PUBLIC) as $rm) { $myMethods[] = strtoupper($rm->name); } return array_intersect(self::$httpMethods, $myMethods); } public function __call($method, $arguments) { header("HTTP/1.1 405 Method Not Allowed", true, 405); header("Allow: " . join($this->allowedHttpMethods(), ", ")); } }
Спецификация HTTP / 1.1 говорит, что код 405 должен быть возвращен клиенту, если используется неподдерживаемый метод запроса, и что должен быть возвращен также список допустимых методов. Предположим, что ресурс example.com/restaurant
должен поддерживать параметры GET
и POST
, но, очевидно, не такие, как DELETE
. Ответ на запрос DELETE
должен выглядеть следующим образом:
HTTP / 1.1 405 метод не разрешен Разрешить: ПОЛУЧИТЬ, ПОЧТУ
Абстрактный класс использует магический метод __call()
для перехвата любых неопределенных методов объекта, а затем использует отражение для определения списка методов HTTP, поддерживаемых конкретным классом. Конкретный класс должен иметь открытый метод с тем же именем, что и метод HTTP, который он обрабатывает, чтобы он работал… приемлемое соглашение. Классы должны реализовывать такие функции / методы только для тех методов HTTP-запросов, которые они намерены поддерживать, а обработка ошибок для неподдерживаемых запросов оставлена на усмотрение функций, инкапсулированных в абстрактном родительском объекте.
Пример класса Restaurant
который расширяет Resource
может выглядеть так:
<?php class Restaurant extends Resource { public function __construct($params) { parent::__construct($params); } public function get() { // logic to handle an HTTP GET request goes here // ... } public function post() { // logic to handle an HTTP POST request goes here // ... } }
Заключительные замечания
Несмотря на то, что REST — это круто, подход, основанный исключительно на REST, может оказаться непрактичным, учитывая ожидания пользователей в Интернете. Мир вырос с принудительной парадигмой RPC-стиля поверх HTTP, и мы застряли с этим сейчас; это то, что люди ожидают. Им нужна красивая страница входа, функциональность входа / выхода и т. Д., Что не имеет смысла в мире REST. Помните, что вся информация, необходимая серверу для удовлетворения запроса, должна сопровождать этот запрос; Сеансы на стороне сервера и отслеживание входов / выходов не соответствуют парадигме REST.
Это не значит, что ваши основанные на REST проекты должны быть небезопасными; Существуют стратегии, такие как HTTP AUTH, DIGEST AUTH и т. д., которые позволяют учетным данным пользователя сопровождать запрос. Я просто говорю, что лучше понять преимущества и ограничения REST и применить его к проектам, которые имеют наибольшее значение. Вместо веб-сайтов REST-полные системы, как правило, представляют собой веб-сервисы, такие как протокол публикации Atom для публикации постов в блогах, или API-интерфейсы, подобные предоставляемым CouchDB.
Теперь мы вернемся к вашей регулярно запланированной серии, которая уже идет. 🙂
Изображение через littlesam / Shutterstock