Middlewares no Slim

Olá pessoas,

Vamos ver como usar middlewares no Slim.

Middlewares são códigos que rodam antes e/ou depois do Slim manipular a requisição e resposta.

Aqui tem um pequeno gráfico mostrando os middlewares:

Middlewares.

Para mais informações você pode dar uma olhada na documentação do Slim

Então nos vamos começar com o nosso template inicial:

Slim Miníno

Você pode ver o código aqui: https://github.com/GrupoToneladas/Blog-Slim-Middleware. (Para ter acesso ao código nesse estado use a tag “inicio” desse repositório git checkout inicio).

Nós temos três actions, respondendo por três seções do sistema. Iremos criar middlewares para verificar se o usuário possui acesso a aquela seção.

Primeiramente, para deixar mais simples o post, vamos fazer um “login” fake. Inclua a seguinte classe na pasta app/src/Model (a pasta não existe, por tanto, crie ela), com o nome UserLoginModel.php:

<?php

namespace App\Model;

class UserLoginModel
{
    public function checkLogin($usuario, $senha)
    {
        if ($usuario == 'admin' && $senha == 'admin') {
            $_SESSION = [
                'logado' => true,
                'acessos' => [
                    'admin',
                    'usuarios',
                ]
            ];
        } elseif ($usuario == 'usuarios' && $senha == 'usuarios') {
            $_SESSION = [
                'logado' => true,
                'acessos' => [
                    'usuarios'
                ]
            ];
        }

        return true;
    }
}

via GIPHY

Por favor, NUNCA faça isso em um sistema em produção. Usuário e senha DEVEM estar num banco de dados e com a senha criptografada (password_hash e password_verify estão aí pra isso).

Continuando…

O usuário “admin” tem acesso a todo sistema e o usuário “usuarios” tem acesso somente a seção de “/usuarios”. E se não estiver logado, terá acesso somente a “home”.

Primeiro vamos adicionar a sessão do PHP no Twig. Edite o arquivo de dependencia e adicione as linhas a seguir, antes do return da view:

    $env = $view->getEnvironment();
    $env->addGlobal('session', $_SESSION);

Ficando assim:

<?php

$container = $app->getContainer();

$container['view'] = function ($container) {
    $settings = $container->get('settings')['view'];
    $view = new \Slim\Views\Twig($settings['template_path'], [
        'cache' => $settings['cache_path']
    ]);

    $view->addExtension(new \Slim\Views\TwigExtension(
        $container['router'],
        $container['request']->getUri()
    ));

    $env = $view->getEnvironment();
    $env->addGlobal('session', $_SESSION);

    return $view;
};

# Actions
$container[App\Action\HomeAction::class] = function ($container) {
    return new App\Action\HomeAction($container->get('view'));
};

$container[App\Action\AdminAction::class] = function ($container) {
    return new App\Action\AdminAction($container->get('view'));
};

$container[App\Action\UsuariosAction::class] = function ($container) {
    return new App\Action\UsuariosAction($container->get('view'));
};

E adicione o inicio de sessão no arquivo public/index.php:

session_start();

Ficando assim:

<?php

session_start();

include __DIR__ . "/../vendor/autoload.php";
$settings = include __DIR__ . "/../app/config/settings.php";

$app = new \Slim\App($settings);

include __DIR__ . "/../app/config/dependencies.php";
include __DIR__ . "/../app/config/routes.php";

$app->run();

Vamos adicionar o formulário de login, a rota e o action que vai responder por esse formulário.

Adicione o seguinte trecho no arquivo app/templates/home.html, logo após a tag de h1:

    {% if session.logado == false %}
    <div class="row" style="margin-top: 70px">
      <div class="col-md-offset-2 col-md-8">
        <div class="panel panel-primary">
          <div class="panel-heading">Acesso ao sistema</div>
          <div class="panel-body">
            <form class="form-horizontal" action="/logar" method="POST">
              <div class="form-group">
                <label class="control-label col-sm-2" for="usuario">Usuário</label>
                <div class="col-sm-8"><input id="usuario" class="form-control" type="text" name="usuario" placeholder="Usuário"></div>
              </div>
              <div class="form-group">
                <label class="control-label col-sm-2" for="senha">Senha</label>
                <div class="col-sm-8"><input id="senha" class="form-control" type="password" name="senha" placeholder="Senha"></div>
              </div>
              <div class="form-group">
                <div class="col-sm-offset-2 col-sm-6"><input class="form-control btn btn-primary" type="submit" value="Entrar"></div>
              </div>
            </form>
          </div>
        </div>
      </div>
    </div>
    {% endif %}

Ficando assim:

{% extends "base.html" %}
{% block content %}
    <h1>Usando o Slim Middleware!</h1>
    {% if session.logado == false %}
    <div class="row" style="margin-top: 70px">
      <div class="col-md-offset-2 col-md-8">
        <div class="panel panel-primary">
          <div class="panel-heading">Acesso ao sistema</div>
          <div class="panel-body">
            <form class="form-horizontal" action="/logar" method="POST">
              <div class="form-group">
                <label class="control-label col-sm-2" for="usuario">Usuário</label>
                <div class="col-sm-8"><input id="usuario" class="form-control" type="text" name="usuario" placeholder="Usuário"></div>
              </div>
              <div class="form-group">
                <label class="control-label col-sm-2" for="senha">Senha</label>
                <div class="col-sm-8"><input id="senha" class="form-control" type="password" name="senha" placeholder="Senha"></div>
              </div>
              <div class="form-group">
                <div class="col-sm-offset-2 col-sm-6"><input class="form-control btn btn-primary" type="submit" value="Entrar"></div>
              </div>
            </form>
          </div>
        </div>
      </div>
    </div>
    {% endif %}
{% endblock %}

Agora adicione a rota de “/logar”:

$app->post('/logar', App\Action\AcessoLogin::class)
    ->setName('acesso/logar');

E a rota de deslogar:

$app->get('/deslogar', function ($request, $response, $args) {
    session_destroy();

    return $response->withStatus(302)->withHeader('Location', '/');
});

Ficando assim:

<?php

$app->get('/', App\Action\HomeAction::class)
    ->setName('home');

$app->get('/admin', App\Action\AdminAction::class)
    ->setName('admin');

$app->get('/usuarios', App\Action\UsuariosAction::class)
    ->setName('usuarios');

$app->post('/logar', App\Action\AcessoLoginAction::class)
    ->setName('acesso/logar');


$app->get('/deslogar', function ($request, $response, $args) {
    session_destroy();

    return $response->withStatus(302)->withHeader('Location', '/');
});

Adicione o Action de acesso na pasta app/src/Action com o nome AcessoLogin.php:

<?php

namespace App\Action;

use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Message\ResponseInterface as Response;
use App\Model\UserLoginModel;

class AcessoLoginAction
{
    private $model;

    public function __construct(UserLoginModel $model)
    {
        $this->model = $model;
    }

    public function __invoke(Request $request, Response $response, $args)
    {
        $dados = $request->getParsedBody();

        $this->model->checkLogin($dados['usuario'], $dados['senha']);

        return $response->withStatus(302)->withHeader('Location', '/');
    }
}

Perceba que pegamos os dados vindo do formulário com o metodo getParsedBody do $request e não passamos nenhuma mensagem se o login foi feito com sucesso ou não, somente mandados para a página inicial com os metodos withStatus e withHeader. Para passar essa mensagens use a biblioteca Slim/Flash, você pode ver um tutorial nosso aqui: Usando mensagens flash no Slim.

Agora vamos adicionar essa classe nas nossas dependencias:

$container[App\Action\AcessoLoginAction::class] = function ($container) {
    return new App\Action\AcessoLoginAction($container->get('Model\UserLogin'));
};

Aproveitamos para adicionar o model nas dependencias também:

# Model
$container['Model\UserLogin'] = function ($container) {
    return new App\Model\UserLoginModel();
};

Ficando assim:

<?php

$container = $app->getContainer();

$container['view'] = function ($container) {
    $settings = $container->get('settings')['view'];
    $view = new \Slim\Views\Twig($settings['template_path'], [
        'cache' => $settings['cache_path']
    ]);

    $view->addExtension(new \Slim\Views\TwigExtension(
        $container['router'],
        $container['request']->getUri()
    ));

    return $view;
};

# Actions
$container[App\Action\HomeAction::class] = function ($container) {
    return new App\Action\HomeAction($container->get('view'));
};

$container[App\Action\AdminAction::class] = function ($container) {
    return new App\Action\AdminAction($container->get('view'));
};

$container[App\Action\UsuariosAction::class] = function ($container) {
    return new App\Action\UsuariosAction($container->get('view'));
};

$container[App\Action\AcessoLoginAction::class] = function ($container) {
    return new App\Action\AcessoLoginAction($container->get('Model\UserLogin'));
};

# Model
$container['Model\UserLogin'] = function ($container) {
    return new App\Model\UserLoginModel();
};

Agora temos um sistema de login BEM rudimentar, vamos criar o middleware que vai verificar a sessão.

Adicione a seguinte pasta em app/src/Middleware/.

Adicione o middleware Admin.php, com o seguinte conteudo:

<?php

namespace App\Middleware;

use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Message\ResponseInterface as Response;

class Admin
{
    public function __invoke(Request $request, Response $response, $next)
    {
        $response = $next($request, $response);

        if (!isset($_SESSION['logado']) || !(in_array('admin', $_SESSION['acessos']))) {
            $response = $response->withStatus(302)->withHeader('Location', '/');
        }

        return $response;
    }
}

Adicione a classe nas dependencias:

#Middleware
$container[App\Middleware\Admin::class] = function () {
    return new App\Middleware\Admin();
};

Ficando assim:

<?php

$container = $app->getContainer();

$container['view'] = function ($container) {
    $settings = $container->get('settings')['view'];
    $view = new \Slim\Views\Twig($settings['template_path'], [
        'cache' => $settings['cache_path']
    ]);

    $view->addExtension(new \Slim\Views\TwigExtension(
        $container['router'],
        $container['request']->getUri()
    ));

    $env = $view->getEnvironment();
    $env->addGlobal('session', $_SESSION);

    return $view;
};

# Actions
$container[App\Action\HomeAction::class] = function ($container) {
    return new App\Action\HomeAction($container->get('view'));
};

$container[App\Action\AdminAction::class] = function ($container) {
    return new App\Action\AdminAction($container->get('view'));
};

$container[App\Action\UsuariosAction::class] = function ($container) {
    return new App\Action\UsuariosAction($container->get('view'));
};

$container[App\Action\AcessoLoginAction::class] = function ($container) {
    return new App\Action\AcessoLoginAction($container->get('Model\UserLogin'));
};

# Model
$container['Model\UserLogin'] = function ($container) {
    return new App\Model\UserLoginModel();
};

#Middleware
$container[App\Middleware\Admin::class] = function () {
    return new App\Middleware\Admin();
};

E adicione nas rotas o middleware, para isso adicione o metodo add() na rota que deseja ter o middleware:

$app->get('/admin', App\Action\AdminAction::class)
    ->setName('admin')
    ->add(App\Middleware\Admin::class);

Assim, onde estiver o middleware App\Middleware\Admin somente quem tem login de admin pode acessar.

Deixo o desafio de adicionar o middleware de usuarios e fazer com que usuários com esse login acessem somente a seção de usuarios. A resposta está no nosso Github.

Até a próxima.

comments powered by Disqus