Middlewares
Os middlewares são geralmente usados para interceptar solicitações ou respostas. Por exemplo, realizar a verificação unificada da identidade do usuário antes de executar o controlador, redirecionar para a página de login se o usuário não estiver logado, ou adicionar um cabeçalho específico à resposta. Outro exemplo seria o acompanhamento da proporção de solicitações para uma URI específica, e assim por diante.
Modelo de Cebola dos Middlewares
┌──────────────────────────────────────────────────────┐
│ middleware1 │
│ ┌──────────────────────────────────────────┐ │
│ │ middleware2 │ │
│ │ ┌──────────────────────────────┐ │ │
│ │ │ middleware3 │ │ │
│ │ │ ┌──────────────────┐ │ │ │
│ │ │ │ │ │ │ │
── Request ───────────────────────> Controller ─ Response ───────────────────────────> Cliente
│ │ │ │ │ │ │ │
│ │ │ └──────────────────┘ │ │ │
│ │ │ │ │ │
│ │ └──────────────────────────────┘ │ │
│ │ │ │
│ └──────────────────────────────────────────┘ │
│ │
└──────────────────────────────────────────────────────┘
Os middlewares e os controladores compõem um modelo clássico de cebola, onde os middlewares são como as camadas externas da cebola e o controlador é o núcleo da cebola. Como mostrado no gráfico, a solicitação atravessa os middlewares 1, 2 e 3 para chegar ao controlador. O controlador retorna uma resposta, que então atravessa os middlewares na ordem 3, 2, 1 antes de ser enviada para o cliente. Ou seja, em cada middleware podemos obter tanto a solicitação quanto a resposta.
Interceptação de Solicitações
Às vezes, não queremos que uma solicitação atinja a camada do controlador. Por exemplo, se descobrirmos, no middleware 2, que o usuário atual não está logado, podemos interceptar a solicitação e retornar uma resposta de login. Nesse caso, o fluxo se parece com o exemplo abaixo:
┌────────────────────────────────────────────────────────────┐
│ middleware1 │
│ ┌────────────────────────────────────────────────┐ │
│ │ middleware2 │ │
│ │ ┌──────────────────────────────┐ │ │
│ │ │ middleware3 │ │ │
│ │ │ ┌──────────────────┐ │ │ │
│ │ │ │ │ │ │ │
── Request ─────────┐ │ │ Controller │ │ │ │
│ │ Response │ │ │ │ │ │
<───────────────────┘ │ └──────────────────┘ │ │ │
│ │ │ │ │ │
│ │ └──────────────────────────────┘ │ │
│ │ │ │
│ └────────────────────────────────────────────────┘ │
│ │
└────────────────────────────────────────────────────────────┘
Como mostrado no gráfico, a solicitação atinge o middleware 2 e gera uma resposta de login, que atravessa o middleware 1 e retorna para o cliente.
Interface do Middleware
Os middlewares devem implementar a interface Webman\MiddlewareInterface
.
interface MiddlewareInterface
{
/**
* Processa uma solicitação recebida pelo servidor.
*
* Processa uma solicitação recebida pelo servidor para produzir uma resposta.
* Se não puder produzir a resposta por si só, poderá delegar ao manipulador de solicitações fornecido para fazê-lo.
*/
public function process(Request $request, callable $handler): Response;
}
Isso significa que os middlewares devem implementar o método process
, que deve retornar um objeto support\Response
. Por padrão, este objeto é gerado por $handler($request)
(a solicitação continua a cruzar as camadas da cebola). Também pode ser uma resposta gerada por funções auxiliares como response()
, json()
, xml()
ou redirect()
(a solicitação para de atravessar as camadas da cebola).
Obter solicitação e resposta no middleware
No middleware, podemos obter a solicitação e também a resposta após o controle ser executado, portanto, o middleware é dividido em três partes internas.
- Fase de passagem da solicitação, isto é, antes do processamento da solicitação
- Fase de processamento do controlador, isto é, durante o processamento da solicitação
- Fase de saída da resposta, isto é, após o processamento da solicitação
A representação dessas três fases no middleware é a seguinte:
<?php
namespace app\middleware;
use Webman\MiddlewareInterface;
use Webman\Http\Response;
use Webman\Http\Request;
class Test implements MiddlewareInterface
{
public function process(Request $request, callable $handler) : Response
{
echo 'Esta é a fase de passagem da solicitação, ou seja, antes do processamento da solicitação';
$response = $handler($request); // Continua a passagem até o núcleo da cebola, até que seja executado o controlador e se obtenha a resposta
echo 'Esta é a fase de saída da resposta, ou seja, após o processamento da solicitação';
return $response;
}
}
Exemplo: Middleware de autenticação
Crie o arquivo app/middleware/AuthCheckTest.php
(se o diretório não existir, crie-o) da seguinte forma:
<?php
namespace app\middleware;
use ReflectionClass;
use Webman\MiddlewareInterface;
use Webman\Http\Response;
use Webman\Http\Request;
class AuthCheckTest implements MiddlewareInterface
{
public function process(Request $request, callable $handler) : Response
{
if (session('user')) {
// Já autenticado, a solicitação continua a passagem
return $handler($request);
}
// Obter os métodos do controlador que não exigem autenticação via reflexão
$controller = new ReflectionClass($request->controller);
$noNeedLogin = $controller->getDefaultProperties()['noNeedLogin'] ?? [];
// O método acessado requer autenticação
if (!in_array($request->action, $noNeedLogin)) {
// Interceptar a solicitação e retornar uma resposta de redirecionamento, parando a passagem da solicitação
return redirect('/user/login');
}
// Não requer autenticação, a solicitação continua a passagem
return $handler($request);
}
}
Crie o controlador app/controller/UserController.php
da seguinte forma:
<?php
namespace app\controller;
use support\Request;
class UserController
{
/**
* Métodos que não exigem autenticação
*/
protected $noNeedLogin = ['login'];
public function login(Request $request)
{
$request->session()->set('user', ['id' => 10, 'name' => 'webman']);
return json(['code' => 0, 'msg' => 'login ok']);
}
public function info()
{
return json(['code' => 0, 'msg' => 'ok', 'data' => session('user')]);
}
}
Observação
$noNeedLogin
registra os métodos no controlador que podem ser acessados sem autenticação
Adicione o middleware de autenticação global no arquivo config/middleware.php
da seguinte forma:
return [
// Middleware global
'' => [
// ... outros middlewares
app\middleware\AuthCheckTest::class,
]
];
Com o middleware de autenticação, podemos nos concentrar em escrever o código de negócios na camada do controlador, sem se preocupar se o usuário está autenticado.
Exemplo: Middleware de solicitação de recursos
Crie o arquivo app/middleware/AccessControlTest.php
(se o diretório não existir, crie-o) da seguinte forma:
<?php
namespace app\middleware;
use Webman\MiddlewareInterface;
use Webman\Http\Response;
use Webman\Http\Request;
class AccessControlTest implements MiddlewareInterface
{
public function process(Request $request, callable $handler) : Response
{
// Se for uma solicitação de opções, retornar uma resposta vazia; caso contrário, continuar a passagem e obter uma resposta
$response = $request->method() == 'OPTIONS' ? response('') : $handler($request);
// Adicionar cabeçalhos http relacionados com o controle de recursos à resposta
$response->withHeaders([
'Access-Control-Allow-Credentials' => 'true',
'Access-Control-Allow-Origin' => $request->header('origin', '*'),
'Access-Control-Allow-Methods' => $request->header('access-control-request-method', '*'),
'Access-Control-Allow-Headers' => $request->header('access-control-request-headers', '*'),
]);
return $response;
}
}
Nota
As solicitações de recursos podem gerar solicitações de opções. Não queremos que as solicitações de opções cheguem ao controlador, portanto, retornamos uma resposta vazia (response('')
) para interceptar a solicitação. Se a sua interface requer configuração de rota, useRoute::any(..)
orRoute::add(['POST', 'OPTIONS'], ..)
para configurar.
Adicione o middleware de solicitação de recursos global no arquivo config/middleware.php
da seguinte forma:
return [
// Middleware global
'' => [
// ... outros middlewares
app\middleware\AccessControlTest::class,
]
];
Observação
Se a solicitação ajax personalizou cabeçalhos, é necessário adicionar este cabeçalho personalizado ao campoAccess-Control-Allow-Headers
do middleware. Caso contrário, ocorrerá o erroRequest header field XXXX is not allowed by Access-Control-Allow-Headers in preflight response.
Observações
- O middleware é dividido em middleware global, middleware da aplicação (o middleware da aplicação só é válido no modo de várias aplicações, consulte Múltiplas Aplicações) e middleware de rota
- Atualmente não é compatível com middleware de um único controlador (mas é possível implementar algo semelhante ao middleware do controlador por meio do julgamento de
$request->controller
no middleware) - O arquivo de configuração do middleware está localizado em
config/middleware.php
- A configuração do middleware global está sob a chave
''
- A configuração do middleware da aplicação está sob o nome da aplicação específica, por exemplo:
return [
// Middleware global
'' => [
app\middleware\AuthCheckTest::class,
app\middleware\AccessControlTest::class,
],
// Middleware da aplicação "api" (o middleware da aplicação só é válido no modo de várias aplicações)
'api' => [
app\middleware\ApiOnly::class,
]
];
Middleware de rota
Podemos configurar um ou um grupo de rotas com middleware específico.
Por exemplo, adicione a seguinte configuração no arquivo config/route.php
:
<?php
use support\Request;
use Webman\Route;
Route::any('/admin', [app\admin\controller\IndexController::class, 'index'])->middleware([
app\middleware\MiddlewareA::class,
app\middleware\MiddlewareB::class,
]);
Route::group('/blog', function () {
Route::any('/create', function () {return response('create');});
Route::any('/edit', function () {return response('edit');});
Route::any('/view/{id}', function ($r, $id) {response("view $id");});
})->middleware([
app\middleware\MiddlewareA::class,
app\middleware\MiddlewareB::class,
]);
Construtor de Middleware com Parâmetros
Observação
Esta função requer webman-framework >= 1.4.8
A partir da versão 1.4.8, o arquivo de configuração suporta a instanciação direta de middleware ou funções anônimas. Isso permite passar parâmetros para o middleware por meio de construtores.
Por exemplo, a configuração em config/middleware.php
também pode ser feita da seguinte maneira:
return [
// Middleware global
'' => [
new app\middleware\AuthCheckTest($param1, $param2, ...),
function(){
return new app\middleware\AccessControlTest($param1, $param2, ...);
},
],
// Middleware da aplicação API (somente válido no modo de múltiplas aplicações)
'api' => [
app\middleware\ApiOnly::class,
]
];
Da mesma forma, os middlewares de rotas também podem passar parâmetros por meio do construtor, como mostrado em config/route.php
:
Route::any('/admin', [app\admin\controller\IndexController::class, 'index'])->middleware([
new app\middleware\MiddlewareA($param1, $param2, ...),
function(){
return new app\middleware\MiddlewareB($param1, $param2, ...);
},
]);
Ordem de Execução do Middleware
- A ordem de execução do middleware é
middleware global
->middleware da aplicação
->middleware de rota
. - Com vários middlewares globais, a execução segue a ordem de configuração dos middlewares (o mesmo vale para os middlewares de aplicação e de rota).
- Requisições 404 não acionam nenhum middleware, incluindo os middlewares globais.
Passagem de Parâmetros para o Middleware (route->setParams)
Configuração de Rota config/route.php
<?php
use support\Request;
use Webman\Route;
Route::any('/test', [app\controller\IndexController::class, 'index'])->setParams(['some_key' =>'some value']);
Middleware (supondo ser um middleware global)
<?php
namespace app\middleware;
use Webman\MiddlewareInterface;
use Webman\Http\Response;
use Webman\Http\Request;
class Hello implements MiddlewareInterface
{
public function process(Request $request, callable $handler) : Response
{
// Como o $request->route padrão é null, é necessário verificar se está vazio
if ($route = $request->route) {
$value = $route->param('some_key');
var_export($value);
}
return $handler($request);
}
}
Passagem de Parâmetros do Middleware para o Controlador
Às vezes, o controlador precisa usar dados gerados pelo middleware, nesse caso, podemos passar parâmetros para o controlador adicionando propriedades ao objeto $request
. Por exemplo:
Middleware
<?php
namespace app\middleware;
use Webman\MiddlewareInterface;
use Webman\Http\Response;
use Webman\Http\Request;
class Hello implements MiddlewareInterface
{
public function process(Request $request, callable $handler) : Response
{
$request->data = 'some value';
return $handler($request);
}
}
Controlador:
<?php
namespace app\controller;
use support\Request;
class FooController
{
public function index(Request $request)
{
return response($request->data);
}
}
Middleware Obtendo Informações de Rota Atual da Requisição
Observação
Requer webman-framework >= 1.3.2
Podemos usar $request->route
para obter o objeto de rota, e chamando os métodos correspondentes, obter as informações desejadas.
Configuração de Rota
<?php
use support\Request;
use Webman\Route;
Route::any('/user/{uid}', [app\controller\UserController::class, 'view']);
Middleware
<?php
namespace app\middleware;
use Webman\MiddlewareInterface;
use Webman\Http\Response;
use Webman\Http\Request;
class Hello implements MiddlewareInterface
{
public function process(Request $request, callable $handler) : Response
{
$route = $request->route;
// Se a requisição não corresponder a nenhuma rota (exceto a rota padrão), $request->route será null.
// Supondo que o navegador acesse o endereço /user/111, o seguinte será mostrado
if ($route) {
var_export($route->getPath()); // /user/{uid}
var_export($route->getMethods()); // ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD','OPTIONS']
var_export($route->getName()); // user_view
var_export($route->getMiddleware()); // []
var_export($route->getCallback()); // ['app\\controller\\UserController', 'view']
var_export($route->param()); // ['uid'=>111]
var_export($route->param('uid')); // 111
}
return $handler($request);
}
}
Observação
O método$route->param()
requer webman-framework >= 1.3.16
Middleware Obtendo Exceções
Observação
Requer webman-framework >= 1.3.15
Durante o processamento de uma requisição, pode ocorrer uma exceção. No middleware, podemos usar $response->exception()
para obtê-la.
Configuração de Rota
<?php
use support\Request;
use Webman\Route;
Route::any('/user/{uid}', function (Request $request, $uid) {
throw new \Exception('exception test');
});
Middleware:
<?php
namespace app\middleware;
use Webman\MiddlewareInterface;
use Webman\Http\Response;
use Webman\Http\Request;
class Hello implements MiddlewareInterface
{
public function process(Request $request, callable $handler) : Response
{
$response = $handler($request);
$exception = $response->exception();
if ($exception) {
echo $exception->getMessage();
}
return $response;
}
}
Middleware Global
Observação
Esta função requer webman-framework >= 1.5.16
Os middlewares globais do projeto principal afetam apenas o projeto principal e não têm impacto nos aplicativos de plugin. Às vezes, podemos querer adicionar um middleware que afete todos os plug-ins, incluindo o projeto principal. Nesse caso, podemos usar o middleware global. No arquivo config/middleware.php
, a configuração seria a seguinte:
return [
'@' => [ // Adiciona um middleware global ao projeto principal e a todos os plug-ins
app\middleware\MiddlewareGlobl::class,
],
'' => [], // Adiciona um middleware global apenas ao projeto principal
];
Observação
O middleware global@
pode ser configurado não apenas no projeto principal, mas também em algum plug-in. Por exemplo, configurando o middleware global@
em um arquivoplugin/ai/config/middleware.php
, ele afetaria o projeto principal e todos os plug-ins.
Adição de Middleware a um Plugin Específico
Observação
Esta função requer webman-framework >= 1.5.16
Às vezes, queremos adicionar um middleware a um aplicativo de plugin, sem modificar o código do próprio plug-in (uma vez que as alterações seriam substituídas durante uma atualização). Nesse caso, podemos configurar o middleware no projeto principal.
No arquivo config/middleware.php
, a configuração para adicionar middleware a um plug-in específico seria a seguinte:
return [
'plugin.ai' => [], // Adiciona um middleware ao plug-in ai
'plugin.ai.admin' => [], // Adiciona um middleware ao módulo admin do plug-in ai
];
Observação
Da mesma forma, também é possível configurar um arquivo semelhante em um plug-in para afetar outros plug-ins. Por exemplo, adicionando essa configuração a um arquivoplugin/foo/config/middleware.php
, ela afetaria o plug-in ai.