Может войдёшь?
Черновики Написать статью Профиль

Расширение классов Request и Response в Laravel 4

перевод

  1. 1. Наследование класса PHPResponse
  2. 2. Наследование класса PHPRequest
    1. 2.1. Композиция вместо наследования

При наследовании Illuminate-класса PHPRequest и класса PHPResponse возникает некоторая путаница.

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

Наследование класса PHPResponse

Сначала я опишу наследование класса PHPResponse, так как оно проще.

Обычно в Illuminate-библиотеках есть класс, который содержит основную массу функциональных возможностей этой библиотеки. Этот класс часто имеет соответствующий фасад, который предоставляет разработчикам «простой» доступ к его методам. Поэтому мы можем просто вызывать PHPAuth::guest() или PHPRedirect::route().

Illuminate-класс Response — PHPIlluminate\Http\Response. Но его фасад PHPIlluminate\Support\Facades\Response отличается от остальных. Вместо наследования PHPFacade он представляет собой собственный маленький одинокий класс.

То есть это не «лицо» PHPIlluminate\Http\Response, а скорее он просто использует класс PHPResponse:

PHP
# Файл Illuminate/Support/Facades/Response.php

class Response {

/**
 * Вернуть новый объект ответа приложения.
 *
 * @param  string  $content
 * @param  int     $status
 * @param  array   $headers
 * @return Symfony\Component\HttpFoundation\Response
 */
public static function make($content ''$status 200, array $headers = array())
{
    return new \
Illuminate\Http\Response($content$status$headers);
}

Примечание: фасад класса Response в Laravel не использует PHPIlluminate\Http\Response для всех своих ответов. Он использует классы Symfony для ответов JSON, Stream и Download.

Итак, как нам наследовать этот класс? К счастью, это не так сложно! Оказывается, нам вообще не надо иметь дело с фасадами, что делает всё ещё проще.

Во-первых, зададим для класса наследование «фасада» PHPResponse:

PHP
<?php namespace Fideloper\Example\Facades;

use 
Illuminate\Support\Facades\Response as BaseResponse;

class 
Response extends BaseResponse {

    public static function 
doSomething()
    {
        return new \
Symfony\Component\HttpFoundation\JsonResponse(['message' => 'yay!']);
    }

}

Во-вторых, заменим фасад Response в Laravel на свой.

Учитывая, что ваша автозагрузка будет обрабатывать этот класс (см. мою статью Установка библиотеки приложения Laravel), ваш следующий шаг — просто заменить фасад PHPResponse Laravel вашим собственным.

Зайдите в app/config/app.php и добавьте ваш класс PHPResponse вместо того, что указан в Laravel по умолчанию:

PHP
# Файл: app/config/app.php

'aliases' => array(

    ... 
прочие алиасы ...
    
'Redis'           => 'Illuminate\Support\Facades\Redis',
    
'Request'         => 'Illuminate\Support\Facades\Request',
    
//'Response'      => 'Illuminate\Support\Facades\Response',
    
'Response'        => 'Fideloper\Example\Facades\Response',

    ... 
больше алиасов ...

)

И вуаля! Вы можете использовать PHPResponse::DoSomething() как пожелаете!

Наследование класса PHPRequest

С классом PHPRequest сложнее. Его наследование не так изящно и просто. Сложность в том, что он используется в самом начале выполнения Laravel. Давайте посмотрим.

Откройте public/index.php

PHP
// Комментарии удалены:
require __DIR__.'/../bootstrap/autoload.php';
$app = require_once __DIR__.'/../bootstrap/start.php';
$app->run();
$app->shutdown();

Обратите внимание, что вторым вызывается файл bootstrap/start.php. Давайте посмотрим на этот файл:

PHP
// Комментарии удалены:
$app = new Illuminate\Foundation\Application;

$env $app->detectEnvironment(array(

    
'local' => array('your-machine-name'),

));

... 
Больше кода ...

Сначала создается класс приложения PHPApplication. Затем в Illuminate\Foundation\Application происходит следующее:

PHP
public function __construct(Request $request null)
{
    
$this['request'] = $this->createRequest($request);

    ... 
Больше кода ...
}

protected function 
createRequest(Request $request null)
{
    return 
$request ?: Request::createFromGlobals();
}

Таким образом, в классе PHPApplication первым происходит создание (и «заполнение») объекта PHPRequest. Мы видим, что объект PHPRequest создается в самом начале обработки запроса клиента. Он не только создается, но и заполняется информацией HTTP-запросов (заголовками, параметрами и т.д.).

Это происходит до того, как вызываются какие-либо события (PHPEvents), и регистрируются какие-либо сервис-провайдеры или псевдонимы (фасады). Это означает, что если мы перепишем классы ядра Laravel с помощью обычных инструментов (обычно через сервис-провайдер), то мы просто заменим объект приложения PHPRequest на пустой.

Я допустил эту ошибку — я использовал сервис-провайдер и просто заменил старый объект PHPRequest своим собственным. Я даже вызывал PHPRequest::createFromGlobals(), чтобы мой объект мог так же получить информацию о запросе. Однако это не работало. В конечном счёте объект запроса создавался дважды (один в классе PHPApplication, другой в моём сервис-провайдере). Когда запрос создается в первый раз, то он, по-видимому, сбрасывает часть информации о самом запросе (в целях безопасности?).

Поэтому один из вариантов для нас — отредактировать PHPbootstrap/start.php и передать наш собственный класс PHPRequest.

Во-первых, создадим класс PHPRequest:

PHP
<?php namespace Fideloper\Example\Http;

class 
Request extends \Illuminate\Http\Request {

    public function 
doSomething()
    {
        echo 
'Делаем что-либо.';
    {

}

Во-вторых, внедрим наш PHPRequest в PHPApplication при его создании.

Вы могли заметить, что метод PHPApplication::__construct может принять PHPRequest в качестве параметра. Мы это и используем. Откройте bootstrap/start.php.

Замените это:

PHP
$app = new Illuminate\Foundation\Application;

На это (используйте любое подходящее имя класса):

PHP
// Обратате внимание на вызов createFromGlobals()
$request Fideloper\Example\Http\Request::createFromGlobals();
$app = new Illuminate\Foundation\Application$request );

Недостаток такого подхода в том, что он не удобен для пакетов. Вы не сможете устанавливать пакеты, которые будут наследовать класс PHPRequest, не требуя при этом от разработчика редактирования bootstrap/start.php. Может это и небольшое неудобство, но PHP (к счастью) движется в сторону устанавливаемых пакетов, и это может стать препятствием на этом пути.

Композиция вместо наследования

Мартин Фаулер как-то здорово сказал: «выбирайте композицию (composition) вместо наследования». Это значит, что лучше использовать классы библиотек внутри ваших собственных классов вместо наследования и расширения классов этих библиотек.

Если говорить о приведенных выше примерах, то вместо наследования PHPRequest и PHPResponse подумайте о создании нового класса и использования базовых PHPRequest и PHPResponse внутри (с помощью внедрения зависимостей или используя старое доброе ключевое слово new).

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

Ниже приведен пример кода. Я создаю интерфейс для моих новых классов PHPRequest и PHPResponse, а затем создаю специфическую для Laravel реализацию каждого из них. Позднее вы можете создать реализации для других фреймворков, если захотите, и ваш пакет станет независимым от фреймворков!

PHP
# Файл: ResponseInterface.php
<?php namespace Fideloper\Example\Http;

interface 
ResponseInterface {

    public function 
doSomething();

}

# Файл: LaravelResponse.php
<?php namespace Fideloper\Example\Http;

class 
LaravelResponse implements ResponseInterface {

    public function 
doSomething()
    {
        return \
Response::make('Делаем что-то');
    }

}

# Файл: RequestInterface.php
<?php namespace Fideloper\Example\Http;

interface 
RequestInterface {

    public function 
doSomething();

}

# Файл: LaravelRequest.php - использует DI
<?php namespace Fideloper\Example\Http;

use 
Symfony\Component\HttpFoundation\Request;

class 
LaravelRequest implements RequestInterface {

    public function 
__construct(Request $request)
    {
        
$this->request $request;
    }

    public function 
doSomething()
    {
        
$somethingHeader $this->request->header('нечто');

        return 
$somethingHeader;
    }

}

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

Как уже упоминалось, приведенный код использует классы PHPRequest/PHPResponse Laravel, а не наследует их.

Использование интерфейсов может сделать их независимыми от фреймворка, позволяя вам реализовать класс для любой ситуации (использовать в других фреймворках или библиотеках). Если вы придерживаетесь только Laravel, то можете вообще не использовать интерфейсы.

Как вы считаете, полезен ли этот материал? Да Нет

Написать комментарий

Разметка: ? ?

Авторизуйся, чтобы прокомментировать.