Пользователь
0,0
рейтинг
11 февраля 2015 в 17:05

Разработка → PSR-7 в примерах перевод tutorial

Стандарт PSR-7 успешно завершён. На этой неделе были добавлены последние штрихи. И теперь версия 0.6.0 пакета http-message package готова к использованию. Попробуйте следовать этому стандарту в своих приложениях.

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

Для начала, я кратко расскажу о том, что регулирует этот стандарт.

HTTP сообщения


HTTP — достаточно простой протокол. Именно поэтому он успешно используется на протяжении многих лет. Сообщения в нём имеют следующую структуру:
<message line>
Header: value
Another-Header: value

Message body

Заголовки представляют собой пары ключ–значение. Ключи являются чувствительными к регистру. Значения представляют собой строки. Один заголовок может иметь несколько значений. В этом случае обычно значения представлены списком, разделённым запятыми.
Тело сообщения (Message body) – это строка. Хотя обычно оно обрабатывается сервером и клиентом как поток, чтобы уменьшить потребление памяти и снизить нагрузку при обработке. Это чрезвычайно важно, когда передаются большие наборы данных, и особенно, когда передаются файлы. Например, PHP «из коробки» представляет входящее тело запроса как поток php://input и использует выходной буфер (тоже, формально, поток), чтобы вернуть ответ.
Строка сообщения (message line) – это место, которое отличает HTTP запрос от ответа.
Строка сообщения у запроса (далее строка запроса) имеет следующий формат:
METHOD request-target HTTP/VERSION

Где метод (“METHOD”) определяет вид запроса: GET, POST, PUT, PATCH, DELETE, OPTIONS, HEAD и так далее, версия протокола (“VERSION”) обычно 1.0 или 1.1 (чаще 1.1 у современных веб клиентов). А на цели запроса (“request-target”) остановимся подробнее.
Цель запроса может быть представлена следующим образом:
  • В стандартной форме, которая представляет собой путь и строку запроса (если представлена), т.е. URI (Universal Resource Identifier).
  • В абсолютной форме, которая представляет собой абсолютный URI .
  • В форме для авторизации, которая представляет собой часть URI необходимую для авторизации (информация о пользователе, если представлена; хост; и порт, если не стандартный).
  • В виде *, то есть строка, состоящая из символа «*».

Обычно, данные для авторизации HTTP клиент передаёт только с первым запросом, чтобы соединиться с HTTP сервером. Потом в качестве цели запроса отправляется просто относительный (или абсолютный) путь к ресурсу (URI без данных для авторизации). Таким образом данные для авторизации передаются только для запроса на соединение (метод CONNECT ), который обычно выполняется, когда идет работа с прокси сервером. Символ «*» используется с методом OPTIONS, чтобы получить общие сведения о веб сервере.
Короче говоря, есть много вариантов использования цели запроса.
Теперь, чтобы окончательно вас запутать, рассмотрим URI. Мы видим следующее:
<scheme>://<authority>[/<path>][?<query string>]

“scheme” в http запросе будет или http, или https. “path” – это тоже всем понятная часть. Но что такое “authority”?
[user-info@]host[:port]

“authority” всегда содержит хост, который может быть доменным именем или IP адресом. Порт является опциональным и необходим только в том случае, когда не является стандартным для данной схемы (или если схема неизвестна). Информация о пользователе представляется в виде
user[:pass]

где пароль является необязательным. Фактически, в существующих спецификациях, рекомендуется вообще не использовать пароль в URI. Лучше принудительно запросить пароль у клиента.
Строка запроса – это набор пар ключ-значение, разделённых амперсандами:
?foo=bar&baz&quz=1

В зависимости от языка реализации, она так же может моделировать списки или массивы:
?sort[]=ASC&sort[]=date&filter[product]=name

PHP преобразует данную строку в двумерный массив:
[
    'sort' => [
        'ASC',
        'date'
    ],
    'filter' => [
        'product' => 'name'
    ],
]

Итак, если бы гибкости в формировании цели запроса нам оказалось бы недостаточно, URI предоставил бы свою.
К счастью, ответы HTTP сервера проще. Строка ответа выглядит следующим образом:
HTTP/VERSION <status>[ <reason>]

“VERSION”, как говорилось ранее, — это обычно 1.0 или, чаще, 1.1. “status” – это число от 100 до 599 включительно; «reason» — пояснение к статусу, стандартное для каждого статуса.
Итак, это был беглый обзор HTTP сообщений. Давайте теперь посмотрим, как PSR-7 моделирует их.

Заголовки сообщений


Названия заголовков сообщений изначально регистронезависимы. К сожалению, большинство языков и библиотек приводят их к одному регистру. Как пример, PHP хранит их в массиве $_SERVER в верхнем регистре, с префиксом HTTP_, и подставляя _for – (это для соответствия с Common Gateway Interface (CGI) спецификацией).
PSR-7 упрощает доступ к заголовкам, предоставляя объектно-ориентированный слой над ними
// Вернёт null, если не найдено:
$header = $message->getHeader('Accept');

// Проверить, указан ли заголовок:
if (! $message->hasHeader('Accept')) {
}

// Если у заголовка несколько значений,
// то вернётся массив:
$values = $message->getHeaderLines('X-Foo');

Вся вышеуказанная логика не зависит от того, как указан заголовок; accept, ACCEEPT или даже aCCePt будут корректными именами заголовка и вернут одинаковый результат.
PSR-7 предполагает, что разбор всех заголовков вернёт структуру в виде массива:
/* Returns the following structure:
    [
        'Header' => [
            'value1'
            'value2'
        ]
    ]
 */
foreach ($message->getAllHeaders() as $header => $values) {
}

Когда структура определена, пользователи точно будут знать, что получат и могут обрабатывать заголовки удобным им способом – вне зависимости от реализации.
Но что если вы захотите добавить заголовки к сообщению – например, чтобы создать запрос и передать его HTTP клиенту?
Сообщения в PSR-7 смоделированы в виде объектов-значений; это означает, что любое изменение в состоянии – это, фактически, другое значение. Таким образом, определение нового заголовка создаст в результате новый объект сообщения.
$new = $message->withHeader('Location', 'http://example.com');

Если вам нужно просто обновить значение, вы можете просто переопределить его:
$message = $message->withHeader('Location', 'http://example.com');

Если вы хотите добавить другое значение к уже существующему заголовку, вы можете поступить следующим образом:
$message = $message->withAddedHeader('X-Foo', 'bar');

Или даже удалить заголовок:
$message = $message->withoutHeader('X-Foo');


Тело сообщения


Как было указано выше, тело сообщения обычно обрабатывается как поток для улучшения производительности. Это особенно важно, когда вы передаёте файлы, используя HTTP. Если только вы не собираетесь использовать всю доступную память на текущий процесс. Большинство реализаций HTTP сообщений, которые я просмотрел, забывают об этом или пытаются изменить поведение по факту (да, даже ZF2 этим грешит!). Если вы хотите подробнее прочитать о преимуществах данного подхода, прочитайте статью Майкла Доулинга. Он написал в своём блоге об использовании потоков в PSR-7 прошлым летом.
Итак, тело сообщения в PSR-7 смоделировано как поток.
«Но это слишком сложно для 80% случаев, где можно обойтись строками!» — наиболее частый аргумент в числе тех, что критикуют данную реализацию обработки тела сообщения. Хорошо, давайте рассмотрим следующее:
$body = new Stream('php://temp');
$body->write('Here is the content for my message!');

Данный пример, и все последующие примеры работы с HTTP сообщениями в данном посте будут использовать phly/http – библиотеку, написанную мной и отражающую развитие PSR-7. В данном случае Stream реализует StreamableInterface.
По существу, вы получаете тонкий, объектно-ориентированный интерфейс для взаимодействия с телом сообщения, который позволяет добавить к нему информацию, прочитать её и многое другое. Хотите изменить сообщение? Создайте новое тело сообщения:
$message = $message->withBody(new Stream('php://temp'));

Моё мнение состоит в том, что несмотря на то, что представление тела сообщения в виде потока кажется сложным, на деле реализация и использование достаточно просты и понятны.
Выгода использования StreamableInterface в PSR-7 состоит в том, что он предоставляет гибкость, упрощающую реализацию различных шаблонов проектирования. Например, вы можете реализовать “callback” функцию, которая при вызове метода read() или getContents() возвращает содержание сообщения (Drupal, в частности, использует этот шаблон). Или «Iterator», реализация которого использует любой «Traversable», чтобы вернуть или объединить контент. Смысл в том, что такой интерфейс даёт вам широкий простор реализации множества шаблонов для работы с телом сообщения. А не ограничивает просто строками или файлами.
StreamableInterface предлагает набор методов, которые чаще всего используются при работе с телом HTTP сообщения. Это не означает, что он предусматривает абсолютно всё, но покрывает большой набор потенциально необходимых операций.
Лично я люблю использовать потоки php://temp, потому как они находятся в памяти до тех пор, пока не станут достаточно большими (в этом случае они записываются во временный файл на диске). Метод может быть достаточно эффективным.

Ответы


До сих пор мы рассматривали функции, общие для любых сообщений. Теперь я собираюсь остановиться на ответах в частности.
У ответа есть статус код и пояснительная фраза:
$status = $response->getStatusCode();
$reason = $response->getReasonPhrase();

Это легко запомнить. Теперь, что если мы сами формируем ответ?
Пояснительная фраза считается необязательной (но в тоже время стандартной для каждого статус кода). Для неё интерфейс предусматривает специфичный для ответа мутатор withStatus():
$response = $response->withStatus(418, "I’m a teapot");

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

Запросы


Запросы содержат следующее:
  • Метод.
  • URI / цель запроса.

Последний немного сложен для моделирования. Вероятно в 99% случаев, мы увидим в качестве цели запроса стандартный URI. Но это не отменяет того факта, что необходимо предусмотреть и другие типы целей запроса. Таким образом, интерфейс запроса выполняет следующее:
  • Составляет экземпляр UriInterface, который моделирует URI запроса.
  • Предоставляет два метода для цели запроса: getRequestTarget(), который возвращает цель запроса, и вычисляет её, если не представлено (используя предлагаемый URI, чтобы вернуть исходную форму или чтобы вернуть «/», если URI не предоставлен или не содержит путь); и withRequestTarget(), чтобы создавать новый экземпляр со специфической целью запроса.

Это в дальнейшем позволит нам адресовать запросы с произвольной целью запроса, когда это необходимо (например, используя информацию об URI в запросе для установки соединения с HTTP клиентом).
Давайте получим метод и URI с запроса:
$method = $request->getMethod();
$uri    = $request->getUri();

$uri в данном случае будет экземпляром UriInterface, и позволит вам использовать URI:
// части URI:
$scheme    = $uri->getScheme();
$userInfo  = $uri->getUserInfo();
$host      = $uri->getHost();
$port      = $uri->getPort();
$path      = $uri->getPath();
$query     = $uri->getQuery();     // строка запроса
$authority = $uri->getAuthority(); // [user-info@]host[:port]

Точно так же, как и HTTP сообщения, URI представлены в виде объектов-значений, и изменение любой части URI меняет его значение, мутирующие методы возвращают новый экземпляр:
$uri = $uri
    ->withScheme('http')
    ->withHost('example.com')
    ->withPath('/foo/bar')
    ->withQuery('?baz=bat');

Так как изменение URI означает создание нового экземпляра, то если вы хотите, чтобы изменение отразились в вашем запросе, вам необходимо сообщить об этих изменениях объекту запроса; и, как и с любым сообщением, если необходимо изменить метод или URI в конкретном экземпляре, следует использовать следующие методы:
$request = $request
    ->withMethod('POST')
    ->withUri($uri->withPath('/api/user'));


Серверные запросы


Серверные запросы имеют немного другие задачи, нежели стандартные сообщения HTTP запросов. Родной для PHP Server API (SAPI) предоставляет нам набор обычных, как для PHP разработчиков, функций:
  • Десериализация аргументов в строке запроса ($_GET).
  • Десериализация закодированных данных переданных методом POST ($_POST).
  • Десериализация куки ($_COOKIE).
  • Индикация и обработка загруженных файлов ($_FILES).
  • Инкапсуляция параметров CGI/SAPI ($_SERVER).

Аргументы из строки запроса, данные из тела запроса и куки могут быть получены с разных частей запроса, но было бы удобно, чтобы это было реализовано за нас. Бывают случаи, когда нам может потребоваться работать с этими значениями:
  • Для API, данные могут быть в формате XML или JSON и могут быть переданы не только методом POST. То есть мы должны расшифровать данные и потом снова внедрить их в запрос.
  • Многие фреймворки сейчас шифруют куки, и это означает, что их нужно расшифровать и внедрить в запрос.

Итак, PSR-7 предоставляет специальный интерфейс, ServerRequestInterface, который расширяет базовый RequestInterface, описывающий функции работы с подобными данными:
$query   = $request->getQueryParams();
$body    = $request->getBodyParams();
$cookies = $request->getCookieParams();
$files   = $request->getFileParams();
$server  = $request->getServerParams();

Представим, что вы пишете API и хотите принимать запросы в формате JSON; выполнение этого может выглядеть следующим образом:
$accept = $request->getHeader('Accept');
if (! $accept || ! preg_match('#^application/([^+\s]+\+)?json#', $accept)) {
    $response->getBody()->write(json_encode([
        'status' => 405,
        'detail' => 'This API can only provide JSON representations',
    ]));
    emit($response
        ->withStatus(405, 'Not Acceptable')
        ->withHeader('Content-Type', 'application/problem+json')
    );
    exit();
}

$body = (string) $request->getBody();
$request = $request
    ->withBodyParams(json_decode($body));

Пример выше демонстрирует несколько функций. Во-первых, он показывает извлечение заголовка из запроса, и ветвление логики, основанное на этом заголовке. Во-вторых, он показывает формирование объекта запроса в случае ошибки (функция emit() является гипотетической, она принимает объект запроса и отдаёт заголовки и тело запроса). Наконец, пример демонстрирует получение тела запроса, десериализацию и внедрение его снова в запрос.

Атрибуты


Другая особенность серверных запросов – это атрибуты. Они предназначены для хранения значений, которые получены из текущего запроса. Частый пример использования – это хранение результатов маршрутизации (разделение URI на пары ключ/значение).
Работа с атрибутами состоит из следующих методов:
  • getAttribute($name, $default = null) для получения определённого атрибута и возврата значения по умолчанию, если атрибут не найден.
  • getAttributes() получение всех атрибутов.
  • withAttribute($name, $value) для возврата нового экземпляра ServerRequestInterface, который содержит данный атрибут.
  • withoutAttribute(($name) для возврата экземпляра ServerRequestInterface без указанного атрибута.

В качестве примера давайте рассмотрим Aura Router с нашим экземпляром запроса:
use Aura\Router\Generator;
use Aura\Router\RouteCollection;
use Aura\Router\RouteFactory;
use Aura\Router\Router;

$router = new Router(
    new RouteCollection(new RouteFactory()),
    new Generator()
);

$path  = $request->getUri()->getPath();
$route = $router->match($path, $request->getServerParams());
foreach ($route->params as $param => $value) {
    $request = $request->withAttribute($param, $value);
}

Экземпляр запроса в данном случае используется для упорядочивания данных и передачи маршруту. Затем результаты маршрутизации используются для создания экземпляра ответа.

Варианты использования


Теперь после быстрой экскурсии по различным компонентам PSR-7, давайте вернёмся к конкретным примерам использования.

Клиенты


Для меня главным создателем стандарта PSR-7 является Майкл Доулинг, автор популярного HTTP клиента Guzzle. Поэтому совершенно очевидно, что PSR-7 принесёт улучшения HTTP клиентам. Давайте обсудим как.
Во-первых, это означает, что разработчики будут иметь уникальный интерфейс сообщений для выполнения запросов; они могут отправлять объект запроса по стандарту PSR-7 клиенту и получать обратно объект ответа по тому же стандарту.
$response = $client->send($request);

Благодаря тому, что сообщения и URI смоделированы как объекты-значения, это так же означает, что разработчики могут создавать базовые экземпляры запросов и URI и создавать раздельные запросы и URI из них:
$baseUri     = new Uri('https://api.example.com');
$baseRequest = (new Request())
    ->withUri($baseUri)
    ->withHeader('Authorization', $apiToken);

while ($action = $queue->dequeue()) {
    // Новый объект запроса! Содержит только
    // URI и заголовок с авторизацией с базового.
    $request = $baseRequest
        ->withMethod($action->method)
        ->withUri($baseUri->withPath($action->path)); // новый URI!

    foreach ($action->headers as $header => $value) {
        // Базовый запрос НЕ получит данные заголовки, что обеспечит последующим
        // запросам содержать только необходимые заголовки!
        $request = $request->withHeader($header, $value);
    }
    
    $response = $client->send($request);
    $status   = $response->getStatusCode();
    if (! in_array($status, range(200, 204))) {
        // Запрос провален!
        break;
    }

    // Получить данные!
    $data->enqueue(json_decode((string) $response->getBody()));
}

Что PSR-7 предлагает, так это стандартный способ взаимодействия с запросами, которые вы отправляете клиентом, и ответами, которые получаете. Реализуя объекты-значения, мы открываем возможность некоторым интересным вариантам использования с прицелом на упрощение шаблона «сброс запроса» — изменение запроса всегда даёт в результате новый экземпляр, позволяя нам иметь базовый экземпляр с известным состоянием, который мы всегда можем расширить.

Связующее звено


Я не буду долго на этом останавливаться, т.к. уже делал это в статье. Основная идея, если кратко, состоит в следующем:
function (
    ServerRequestInterface $request,
    ResponseInterface $response,
    callable $next = null
) {
}

Функция принимает два HTTP сообщения, и выполняет некоторые преобразования с ними (которые могут включать делегирование к следующему доступному связующему звену). Обычно эти звенья возвращают объект ответа.
Другой вариант, который часто используется – это лямбда выражения (спасибо Ларри Гарфилду, который выслал мне на почту этот термин!):
/* response = */ function (ServerRequestInterface $request) {
    /* ... */
    return $response;
}

В лямбда звене вы составляете одно в другом:
$inner = function (ServerRequestInterface $request) {
    /* ... */
    return $response;
};
$outer = function (ServerRequestInterface $request) use ($inner) {
    /* ... */
    $response = $inner($request);
    /* ... */
    return $response;
};
$response = $outer($request);

И наконец, есть метод, продвигаемый Rack и WSGI, в котором каждое звено является объектом и проходит к выходу:
class Command
{
    private $wrapped;

    public function __construct(callable $wrapped)
    {
        $this->wrapped = $wrapped;
    }

    public function __invoke(
        ServerRequestInterface $request,
        ResponseInterface $response
    ) {
        //возможное управление запросом
        $new = $request->withAttribute('foo', 'bar');

        // делегирование звену, которое мы определили:
        $result = ($this->wrapped)($new, $response);

        // смотрим, получился ли в результате ответ
        if ($result instanceof ResponseInterface) {
            $response = $result;
        }

        // управление ответом перед его возвращением
        return $reponse->withHeader('X-Foo', 'Bar');
    }
}

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

Фреймворки


Одна вещь, которую фреймворки предлагают на протяжении многих лет – это … слой абстракции над HTTP сообщениями. Цель PSR-7 предоставить общий набор интерфейсов для фреймворков, чтобы последние могли использовать одинаковые абстракции. Это позволит разработчикам писать переиспользуемый, независимый от фреймворка код или, по крайней мере, это то, что я хотел бы увидеть!
Рассмотрим Zend Framework 2. Он определяет интерфейс Zend\Stdlib\DispatchableInterface, который является базовым для любого контроллера, который вы собираетесь использовать в фреймворке:
use Zend\Http\RequestInterface;
use Zend\Http\ResponseInterface;

interface DispatchableInterface
{
    public function dispatch(
        RequestInterface $request,
        ResponseInterface $response
    );
}

Это как раз описанное нами выше промежуточное звено; одно единственное различие состоит в том, что оно использует специфичные для данного фреймворка реализации HTTP сообщений. Что если вместо этого оно будет поддерживать PSR-7?
Большинство реализаций HTTP сообщений в фреймворках построено таким образом, что вы можете изменить состояние сообщения в любое время. Иногда это может быть не совсем верно, особенно если допустить, что состояние сообщения может быть уже недействительно. Но это, пожалуй, единственный недостаток данного метода.
Сообщения по стандарту PSR-7 являются объектами-значениями. Таким образом, вам не потребуется сообщать приложению каким-либо образом о любом изменении в сообщениях. Это делает реализацию более явной и простой для отслеживания в вашем коде (и пошагово в отладчике, и с использованием статических анализаторов кода).
В качестве примера, если ZF2 будет обновлён в соответствии с PSR-7, разработчики не будут обязаны сообщать MvcEvent о любых изменениях, которые они хотят передать сдедующим клиентам:
// Внутри контроллера
$request  = $request->withAttribute('foo', 'bar');
$response = $response->withHeader('X-Foo', 'bar');

$event = $this->getEvent();
$event->setRequest($request)
      ->setResponse($response);

Приведённый выше код ясно показывает, что мы меняем состояние приложения.
Использование объектов-значений делает более простой одну особенную практику: распределение подзапросов или реализация Hierarchical MVC (HMVC). В этом случае вы можете создавать новые запросы, основанные на текущем, без информирования об этом приложения, и будучи уверенными, что состояние приложения не изменится.
В общем, для большинства фреймворков, использование PSR-7 сообщений переведёт к переносимой абстракции над HTTP сообщениями. Это даст возможность реализовать универсальное промежуточное звено. Адаптация сообщений, однако, потребует незначительных изменений. Разработчикам необходимо обновить код, отвечающий за отслеживание состояния приложения.

Источники


Надеюсь, вы увидите преимущество, которое предоставляет стандарт PSR-7: унифицированную, полную абстракцию над HTTP сообщениями. В дальнейшем, эта абстракция может быть использована для каждой части HTTP транзакции (где вы отправляете запросы через HTTP клиент, или разбираете серверный запрос).
PSR-7 спецификация ещё не завершена полностью. Но то, что я обозначил выше, не подвергнется значительным изменениям без голосования. Более детально ознакомиться со спецификацией можно по ссылке:

Я также рекомендую вам прочитать «пояснительную записку», так как она описывает идеи, разработанные решения и результаты (бесконечных) споров на протяжении двух лет:

Последние обновления опубликованы в пакете psr/http-message, который вы можете установить через composer. Это всегда самые последние обновлённые предложения.
Я создал библиотеку, phly/http, которая предлагает конкретную реализацию предложенных интерфейсов. Её так же можно установить через composer.
Наконец, если вы хотите поэкспериментировать с промежуточным звеном, основанным на PSR-7, предлагаю следующие варианты:
  • phly/conduit, портированная с Sencha библиотека Connect, изпользующая phly/http и psr/http-message в своей основе.
  • Stacker, StackPHP-подобная реализация, написанная Ларри Гарфилдом.

Я вижу будущее разработки с PSR-7. И верю, что он породит совершенно новое поколение PHP приложений.
Перевод: Matthew Weier O'Phinney
Максим Васильев @codru
карма
10,0
рейтинг 0,0
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама

Самое читаемое Разработка

Комментарии (32)

  • +2
    Стандарт PSR-7 успешно завершён.
    Да не стандарт это. Просто рекомендация (PHP Standard Recommendation).
    • –2
      Всё-таки здесь используется слово «стандарт».
    • +2
      Это одновременно и стандарт, и рекомендация.
      Даже если вы не будете им следовать, ваш код все равно будет запускаться.
    • –1
      Прикиньте RFC тоже не стандарт ;)
  • +2
    Блин, снова вместо древних стандартных set/unset имеем незнакомые with/without :(
    • –2
      По смыслу больше подходит для такого стиля.
      Запрос->c_заголовком(1)->без_заголовка(2)->отправить();
      В то время, как код с set и unset представляется как
      Запрос->установить_заголовок(1);
      Запрос->убрать_заголовок(2);
      Запрос->отправить();
    • 0
      Спросил как мог github.com/php-fig/fig-standards/issues/431 (может кто чего добавит)
    • +3
      в аннотации к Message интерфейсу написано:
      > Messages are considered immutable
      то есть сообщение нельзя менять, поэтому как уже говорил ilyaplot set/unset тут не подходит — эти методы создают новое сообщение
      • –1
        Почему? sectus чуть ниже очень правильно напомнил про clone и множество безымянных объектов.
        • +1
          я отвечал на конкретную ветку про set/unset, почему сообщение сделано неизменяемым это уже другой вопрос
          к этому стандарту есть «пояснительная записка» в которой отражены мысли по этому поводу:
          github.com/php-fig/fig-standards/blob/master/proposed/http-message-meta.md#why-value-objects
          • 0
            Тоже самое можно реализовать с использование свойства readonly, которое позволит избавиться от промежуточных объектов и заблокирует изменения после отправки (хотя еще вопрос насколько это нужно).
            • +1
              не я автор этого стандарта, из документа вытекает такой смысл:
              Интерфейс внутри сервера для работы с запросом будет подразумевать явное его изменение, таким образом избавляются от случайных изменений по сути глобального объекта, также благодаря этому приложение может отреагировать на это изменение, именно как цельного набора значений, а не отдельных свойств
      • 0
        Угу, и там же говориться что в большинстве популярных библиотек сообщения mutable и много кому нужно их менять — нахрена такой «стандарт»? Это даже не говоря о том что без цепочек новый объект весьма проблематично создать (а оно нужно если те же заголовки добавляются в зависимости от внешних условий)
        • 0
          Вы забываете про то что mutable объекты плохо согласуются с такими вещями как многопоточность и т.д. VO тут подходит лучше. Все же этот стандарт будут применять при реализации мидлвэров, серверов и т.д. и сценарий при котором мутабл объекты это плохо более чем реальны.

          Да и вообще, можете привести пример в коде когда вас это парит?
          • 0
            Мне кажется это вы забыли что в PHP многопоточности вообще НЕТ и маловероятно что она когда нибудь будет. + еще есть java/c# и много других языков где она есть, но проблем с изменяемыми объектами нет (есть сложности и особенности, но не проблемы).

            Запросто:

            $myObjectWithSuperName = new Object();

            if ($veryLongSettingName1 == 1) {
            $myObjectWithSuperName = $myObjectWithSuperName->with(1);
            }

            if ($veryLongSettingName2 == 2) {
            $myObjectWithSuperName = $myObjectWithSuperName->with(2);
            }
  • 0
    Вы очень вовремя. Если честно, я не все понял из поста после того, как работал с кодом весь день.
    Скажите, а насколько это правильно использовать PHP для API сервера? Не будут ли меня пинать, когда узнают, что мой сервер работает на PHP?
    • +4
      Полагаю, что если качество работы сервера не будет вызывать нареканий, то никто вас «пинать» не будет.
      • 0
        Я просто привык к тому, что все не любят php программистов. Правда, ни разу никто не объяснил причину такого отношения :)
        • +1
          ну объективно надо разделять нелюбовь к языку php и к программистам на php. И то и то имеет место быть, но имеет разные причины.

          PHP популярный язык программирования с низким порогом входа, поэтому и программистов на нем гораздо больше чем например на C, и сосредоточенны они достаточно кучно — так или иначе рядом с вебом, и даже если % хороших программистов не зависит от ЯП, то в абсолютных цифрах % оставшихся все равно больше (а если взять порог входа, то и сам процент не в пользу php).

          У самого языка/реализации тоже есть проблемы, кроме разнородных неймингов стандартной либы, не очевидных поведений и достаточной забаговановасти ядра (где-то в 5.5-5.6 мне более менее перестали попадаться всякие неожиданные сегфолты, которые без ковыряния в коде ядра, которое просто жутко как написано, не починишь) много ограничений дает «PHP создан, чтобы умирать» — да можно писать демоны и на php но это будут скорее какие-то воркеры, чем сервер для обработки клиентских запросов, потомучно в PHP все работает исходя из этого постулата, ни GC ни стандартные библиотеки, ни уж тем более сторонние, не создавались для того чтобы обслужить больше 1 запроса. А уж отсутсвие JIT в 2015 году это я даже не знаю как назвать. (да какой JIT даже отсутствие встроенного кешера байт-кода до 5.5)
          Да и от языка скриптования шаблонов он начал отходить относительно недавно, а порядок в нем начали наводить так вообще не больше 2-3 лет назад.
          Очень хороший пинок дали Composer и Symfony Components, а также появление альтернативных реализация рантайма.
          В общем ждем PHP 7 надеемся и верим…
    • +1
      Если вам нужно просто предоставить API для HTTP-запросов — за что пинать? А вот если вам, например, нужны вебсокеты или ещё что-нибудь выходящее за рамки отлаженных кейсов работы с PHP — тогда лучше воздержаться. Есть всякие штуки типа ReactPHP, но всё же это пока слабо распространено.
  • 0
    Сколько сталкиваюсь с этим PSR-7 — вроде полезно должно быть, но не пойму в чем отличие от того же Symfony\HttpFoundation кроме «стандартизации»
    • 0
      Ну раз уж в PHP-FIG собрались ребята знакомые не только с Symfony, то для этого стандарта, по идее, они выбрали лучшее, что было в уже существующих реализациях.
    • +2
      смысл стандартов как раз в стандартизации. Тот же HttpFoundation и HttpKernel будет раздроблен на более мелкие компоненты.
  • 0
    Я как-то не понял этой радости от «объект-значение». В примерах статьи чаще всего не нужна новая копия. Как-то неприятно смотреть, что в процессе изменения нескольих свойств «рождаются» и тут же «умирают» несколько объектов. И хочется спросить, а чем clone не удовлетворил?
    • 0
      Спросил как мог github.com/php-fig/fig-standards/issues/432 (может кто чего добавит)
      • 0
        Вот эти два пункта, которые Вы оформили в виде задач, связаны. В первом случае мы создаём новый объект без этого свойства — without. Во втором случае мы удаляём свойство из объекта — unset.
        • 0
          Изначально хотел всё в одну запихнуть, но потом подумал что лучше раздельно… Или все-таки надо было в одну?
    • 0
      Возможно здесь будет ответ на ваш вопрос.
      • 0
        Если честно, то не совсем я понял этот ответ. Основной посыл, который я понял: если изменять состояние, то может случиться что-то страшное.
  • 0
    Имхо, но PSR-7 не лучший стандарт PSR.

    Он предоставляет интерфейсы, которые должны реализовать фреймворки.
    Т.е. ещё одна зависимость в композере от пакета php-fig/http-message
    Итак уже в папке vendor множество всяких пакетов… И тут будет ещё один.

    Для чего это? Чтобы облегчить перевод приложения из одного фреймворка в другой?
    Фреймворки должны реализовать группу интерфейсов PSR-7, но никто не мешает авторам фреймворков добавить собственные методы. Это не нарушает принципы ООП. А эти методы могут быть очень полезными.
    Т.е. встаёт выбор, использовать интерфейс PSR-7, и иметь независимость от фреймворка.
    Или использовать интерфейс фреймворка(который также реализует PSR-7), но. получить зависимость от фреймворка и удобные методы.
    Я выберу второе.

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

    Серьёзно, в чём плюс этого PSR-7?
    • 0
      Плюс? Ну как я вижу, возможность создавать PSR-7 совместимые мидлвары (авторизация, CORS, кеширование, много чего) и сервера (ReactPHP http сервер какой-нибудь). Опять же вас никто не принуждает использовать этот стандарт.
    • +1
      Абсолютно аналогична необходимость этого psr к тому что о логгировании. Сейчас есть кучка пакетов, все работают с psr-log. И не нужно задумываться, как и что нужно сделать, чтобы _____ (новая либа, нужная в проект), начала писать логи своей активности прямо сейчас. Ровно также и с http.
      Вы, видимо, забываете, что из php не только обрабатываются входящие запросы, но и посылаются исходящие. Скорее всего guzzle и buzz реализуют эти интерфейсы, и тогда разработчики оберток над api будут требовать не guzzle/buzz/curl, а php-fig/http-message.

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