Microestrutura fina

Em 25 de abril de 2019, a nova versão alpha principal do microframework Slim viu a luz do dia e, em 18 de maio, passou a beta . Sugiro que você se familiarize com a nova versão.


Sob o corte:


  • Sobre as inovações da estrutura
  • Escrevendo um aplicativo simples no Slim-4
  • Sobre a Amizade Slim e PhpStorm

Novo no Slim 4


Principais inovações em comparação com a versão 3:


  • A versão mínima do PHP é 7.1;
  • Suporte para PSR-15 (Middleware);
  • Removida a implementação de mensagens http. Instale qualquer biblioteca compatível com PSR-7 e use;
  • Removida a dependência do Pimple . Instale seu recipiente compatível com PSR-11 favorito e use-o;
  • A capacidade de usar seu roteador (anteriormente, não era possível abandonar o FastRoute );
  • Mudou a implementação do tratamento de erros;
  • Implementação alterada da saída da resposta;
  • Fábrica adicionada para criar uma instância do aplicativo;
  • Configurações removidas;
  • O Slim não define mais default_mimetype como uma string vazia; portanto, você precisa instalá-lo no php.ini ou no aplicativo usando ini_set('default_mimetype', '') ;
  • O manipulador de solicitação de aplicativo agora aceita apenas o objeto de solicitação (na versão antiga, aceitava objetos de solicitação e resposta).

Como criar um aplicativo agora?


Na terceira versão, a criação do aplicativo era mais ou menos assim:


 <?php use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Message\ResponseInterface; use Slim\App; require 'vendor/autoload.php'; $settings = [ 'addContentLengthHeader' => false, ]; $app = new App(['settings' => $settings]); $app->get('/hello/{name}', function (ServerRequestInterface $request, ResponseInterface $response, array $args) { $name = $args['name']; $response->getBody()->write("Hello, $name"); return $response; }); $app->run(); 

Agora o construtor do aplicativo aceita os seguintes parâmetros:


ParâmetroTipoObrigatórioDescrição do produto
$ responseFactoryPsr\Http\Message\ResponseFactoryInterfacesimFábrica de pedidos http do servidor compatível com PSR-17
$ container\Psr\Container\ContainerInterfacenãoContêiner de dependência
$ callableResolver\Slim\Interfaces\CallableResolverInterfacenãoManipulador de retorno de chamada
$ routeCollector\Slim\Interfaces\RouteCollectorInterfacenãoRoteador
$ routeResolver\Slim\Interfaces\RouteResolverInterfacenãoManipulador de resultados de roteamento

Agora também você pode usar o método de create estático da fábrica de aplicativos \Slim\Factory\AppFactory .
Este método aceita os mesmos parâmetros que a entrada, apenas todos eles são opcionais.


 <?php use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Message\ResponseInterface; use Slim\Factory\AppFactory; require 'vendor/autoload.php'; $app = AppFactory::create(); $app->get('/hello/{name}', function (ServerRequestInterface $request, ResponseInterface $response) { $name = $request->getAttribute('name'); $response->getBody()->write("Hello, $name"); return $response; }); $app->run(); 

Devolva-me 404 erros!


Se tentarmos abrir uma página inexistente, obteremos um código de resposta 500 , não 404 . Para que os erros sejam processados ​​corretamente, você precisa conectar \Slim\Middleware\ErrorMiddleware


 <?php use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Message\ResponseInterface; use Slim\Factory\AppFactory; use Slim\Middleware\ErrorMiddleware; require 'vendor/autoload.php'; $app = AppFactory::create(); $middleware = new ErrorMiddleware( $app->getCallableResolver(), $app->getResponseFactory(), false, false, false ); $app->add($middleware); $app->get('/hello/{name}', function (ServerRequestInterface $request, ResponseInterface $response) { $name = $request->getAttribute('name'); $response->getBody()->write("Hello, $name"); return $response; }); $app->run(); 

Middleware


O middleware agora deve ser uma implementação do PSR-15. Como exceção, você pode transmitir funções, mas a assinatura deve corresponder ao método process() da \Psr\Http\Server\MiddlewareInterface


 <?php use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Message\ResponseInterface; use Psr\Http\Server\RequestHandlerInterface; use Slim\Factory\AppFactory; require 'vendor/autoload.php'; $app = AppFactory::create(); $app->add(function (ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface { $response = $handler->handle($request); return $response->withHeader('Content-Type', 'application/json'); }); // ...     $app->run(); 

A assinatura ($request, $response, $next) não ($request, $response, $next) mais suportada


Como viver sem configurações?


Você pode viver sem configurações. As ferramentas fornecidas nos ajudarão com isso.


httpVersion e responseChunkSize


A httpVersion foi responsável pela saída da versão do protocolo na resposta.
A configuração responseChunkSize determinou o tamanho de cada pedaço lido do corpo da resposta quando enviado ao navegador.


Agora, essas funções podem ser atribuídas ao emissor da resposta.


Escrever emissor


 <?php // /src/ResponseEmitter.php namespace App; use Psr\Http\Message\ResponseInterface; use Slim\ResponseEmitter as SlimResponseEmitter; class ResponseEmitter extends SlimResponseEmitter { private $protocolVersion; public function __construct(string $protocolVersion = '1.1', int $responseChunkSize = 4096) { $this->protocolVersion = $protocolVersion; parent::__construct($responseChunkSize); } public function emit(ResponseInterface $response) : void{ parent::emit($response->withProtocolVersion($this->protocolVersion)); } } 

Nós nos conectamos ao aplicativo


 <?php use App\ResponseEmitter; use Slim\Factory\AppFactory; require 'vendor/autoload.php'; $app = AppFactory::create(); $serverRequestFactory = \Slim\Factory\ServerRequestCreatorFactory::create(); $request = $serverRequestFactory->createServerRequestFromGlobals(); // ...     $response = $app->handle($request); $emitter = new ResponseEmitter('2.0', 4096); $emitter->emit($response); 

outputBuffering


Essa configuração permitiu ativar / desativar o buffer de saída. Definir valores:


  • false buffer false está desativado (todas as chamadas para echo , print instruções de print são ignoradas).
  • 'append' - todas as chamadas para echo , instruções de print são adicionadas após o corpo da resposta
  • 'prepend' - todas as chamadas para echo , instruções de print são adicionadas antes do corpo da resposta

\Slim\Middleware\OutputBufferingMiddleware desenvolvedores de \Slim\Middleware\OutputBufferingMiddleware propõem a substituição dessa opção pelo \Slim\Middleware\OutputBufferingMiddleware , no construtor do qual é transmitida uma fábrica de fluxo compatível com PSR-17 e um modo que pode ser igual a append ou append


 <?php use Slim\Factory\AppFactory; use Slim\Factory\Psr17\SlimPsr17Factory; use Slim\Middleware\OutputBufferingMiddleware; require 'vendor/autoload.php'; $app = AppFactory::create(); $middleware = new OutputBufferingMiddleware(SlimPsr17Factory::getStreamFactory(), OutputBufferingMiddleware::APPEND); $app->add($middleware); // ...     $app->run(); 

determineRouteBeforeAppMiddleware


Essa configuração tornou possível obter a rota atual do objeto de solicitação no middleware


É fornecida uma substituição \Slim\Middleware\RoutingMiddleware


 <?php use Slim\Factory\AppFactory; use Slim\Middleware\RoutingMiddleware; require 'vendor/autoload.php'; $app = AppFactory::create(); $middleware = new RoutingMiddleware($app->getRouteResolver()); $app->add($middleware); // ...     $app->run(); 

displayErrorDetails


A configuração permitiu exibir detalhes do erro. Ao depurar, facilita a vida.


Lembre-se de \Slim\Middleware\ErrorMiddleware ? Aqui nos ajudará!


 <?php use Slim\Factory\AppFactory; use Slim\Middleware\ErrorMiddleware; require 'vendor/autoload.php'; $app = AppFactory::create(); $middleware = new ErrorMiddleware( $app->getCallableResolver(), $app->getResponseFactory(), true, //        false, //   false //    ); $app->add($middleware); // ...     $app->run(); 

addContentLengthHeader


Essa configuração permitiu ativar / desativar a adição automática do cabeçalho Content-Length com o valor do volume de dados no corpo da resposta


Substitui a opção de middleware \Slim\Middleware\ContentLengthMiddleware


 <?php use Slim\Factory\AppFactory; use Slim\Middleware\ContentLengthMiddleware; require 'vendor/autoload.php'; $app = AppFactory::create(); $middleware = new ContentLengthMiddleware(); $app->add($middleware); // ...     $app->run(); 

routerCacheFile


Agora você pode instalar diretamente o arquivo de cache do roteador


 <?php use Slim\Factory\AppFactory; require 'vendor/autoload.php'; $app = AppFactory::create(); $app->getRouteCollector()->setCacheFile('/path/to/cache/router.php'); // ...     $app->run(); 

Criando um aplicativo no Slim-4


Para dar uma olhada mais de perto na estrutura, escreveremos um pequeno aplicativo.


O aplicativo terá as seguintes rotas:


  • /hello/{name} - página de boas-vindas;
  • / - redirecionar para a página /hello/world
  • Outras rotas retornarão uma página personalizada com erros 404.

A lógica estará nos controladores, renderizaremos a página através do mecanismo de modelo Twig
Como bônus, adicione um aplicativo de console baseado no componente Symfony Console com um comando que exibe uma lista de rotas


Etapa 0. Instalando Dependências


Vamos precisar de:



Selecionei ultra-lite / container como o container de dependência, como leve, conciso e compatível com o padrão.
Os desenvolvedores PSR-7 e PSR-17 Slim fornecem slim / psr7 em um pacote. Vamos usá-lo


Supõe-se que o gerenciador de pacotes do Composer já esteja instalado.

Criamos uma pasta para o projeto ( /path/to/project será usado como exemplo) e vamos para ele.


Adicione o arquivo composer.json ao projeto com o seguinte conteúdo:


 { "require": { "php": ">=7.1", "slim/slim": "4.0.0-beta", "slim/psr7": "~0.3", "ultra-lite/container": "^6.2", "symfony/console": "^4.2", "twig/twig": "^2.10" }, "autoload": { "psr-4": { "App\\": "app" } } } 

e execute o comando


 composer install 

Agora temos todos os pacotes necessários e o carregador automático de classes está configurado.


Se trabalharmos com o git , adicione o arquivo .gitignore e o diretório do vendor (e o diretório do seu IDE, se necessário)


 /.idea/* /vendor/* 

Estou usando o PhpStorm IDE e orgulhoso disso . Para um desenvolvimento confortável, é hora de fazer amizade com o contêiner e o IDE.
Na raiz do projeto, crie o arquivo .phpstorm.meta.php e escreva este código:


 <?php // .phpstorm.meta.php namespace PHPSTORM_META { override( \Psr\Container\ContainerInterface::get(0), map([ '' => '@', ]) ); } 

Esse código informa ao IDE que, para um objeto que implementa a \Psr\Container\ContainerInterface , o método get() retornará um objeto de classe ou implementação de interface cujo nome é passado no parâmetro


Etapa 1. Estrutura do Aplicativo


Adicione os diretórios:


  • app - código do aplicativo. Vamos conectar nosso namespace para o carregador automático de classes a ele;
  • bin - diretório para o utilitário do console;
  • config - aqui serão os arquivos de configuração do aplicativo;
  • public - um diretório aberto na web (ponto de entrada do aplicativo, estilos, js, imagens etc.);
  • diretório template - template ;
  • var é o diretório para vários arquivos. Logs, cache, armazenamento local, etc.

E os arquivos:


  • config/app.ini - a principal configuração do aplicativo;
  • config/app.local.ini - configuração para ambiente local ;
  • app/Support/CommandMap.php - mapeamento de comandos de aplicativos de console para carregamento lento.
  • app/Support/Config.php - Classe de configuração (para que o IDE saiba quais configurações temos).
  • app/Support/NotFoundHandler.php - classe de manipulador de erros 404.
  • app/Support/ServiceProviderInterface.php - A interface do provedor de serviços.
  • app/Provider/AppProvider.php - O principal provedor do aplicativo.
  • bootstrap.php - montagem de contêiner;
  • bin/console - ponto de entrada do aplicativo de console;
  • public/index.php - ponto de entrada do aplicativo da web.

config / app.ini
 ;   slim.debug=Off ;   templates.dir=template ;   templates.cache=var/cache/template 

config / app.local.ini
 ;        .       ;        slim.debug=On ;       templates.cache= 

Sim, ainda é bom excluir configurações de ambiente do repositório. Afinal, pode haver aparências / senhas. O cache também é excluído.


.gitignore
 /.idea/* /config/* /vendor/* /var/cache/* !/config/app.ini !/var/cache/.gitkeep 

app / Support / CommandMap.php
 <?php // app/Support/CommandMap.php namespace App\Support; class CommandMap { /** *  .   =>    * @var string[] */ private $map = []; public function set(string $name, string $value) { $this->map[$name] = $value; } /** * @return string[] */ public function getMap() { return $this->map; } } 

app / Suporte / Config.php
 <?php // app/Support/Config.php namespace App\Support; class Config { /** * @var string[] */ private $config = []; public function __construct(string $dir, string $env, string $root) { if (!is_dir($dir)) return; /* *    */ $config = (array)parse_ini_file($dir . DIRECTORY_SEPARATOR . 'app.ini', false); /* *      */ $environmentConfigFile = $dir . DIRECTORY_SEPARATOR . 'app.' . $env . '.ini'; if (is_readable($environmentConfigFile)) { $config = array_replace_recursive($config, (array)parse_ini_file($environmentConfigFile, false)); } /* * ,      */ $dirs = ['templates.dir', 'templates.cache']; foreach ($config as $name=>$value) { $this->config[$name] = $value; } /* *      */ foreach ($dirs as $parameter) { $value = $config[$parameter]; if (mb_strpos($value, '/') === 0) { continue; } if (empty($value)) { $this->config[$parameter] = null; continue; } $this->config[$parameter] = $root . DIRECTORY_SEPARATOR . $value; } } public function get(string $name) { return array_key_exists($name, $this->config) ? $this->config[$name] : null; } } 

app / Suporte / NotFoundHandler.php
 <?php // app/Support/NotFoundHandler.php namespace App\Support; use Psr\Http\Message\ResponseFactoryInterface; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use Slim\Interfaces\ErrorHandlerInterface; use Throwable; class NotFoundHandler implements ErrorHandlerInterface { private $factory; public function __construct(ResponseFactoryInterface $factory) { $this->factory = $factory; } /** * @param ServerRequestInterface $request * @param Throwable $exception * @param bool $displayErrorDetails * @param bool $logErrors * @param bool $logErrorDetails * @return ResponseInterface */ public function __invoke(ServerRequestInterface $request, Throwable $exception, bool $displayErrorDetails, bool $logErrors, bool $logErrorDetails): ResponseInterface { $response = $this->factory->createResponse(404); return $response; } } 

Agora você pode ensinar o PhpStorm a entender quais teclas possuem chaves e que tipo elas são.


.phpstorm.meta.php
 <?php // .phpstorm.meta.php namespace PHPSTORM_META { override( \Psr\Container\ContainerInterface::get(0), map([ '' => '@', ]) ); override( \App\Support\Config::get(0), map([ 'slim.debug' => 'bool', 'templates.dir' => 'string|false', 'templates.cache' => 'string|false', ]) ); } 

app / Support / ServiceProviderInterface.php
 <?php // app/Support/ServiceProviderInterface.php namespace App\Support; use UltraLite\Container\Container; interface ServiceProviderInterface { public function register(Container $container); } 

app / Provider / AppProvider.php
 <?php // app/Provider/AppProvider.php namespace App\Provider; use App\Support\CommandMap; use App\Support\Config; use App\Support\NotFoundHandler; use App\Support\ServiceProviderInterface; use Psr\Container\ContainerInterface; use Psr\Http\Message\ResponseFactoryInterface; use Slim\CallableResolver; use Slim\Exception\HttpNotFoundException; use Slim\Interfaces\CallableResolverInterface; use Slim\Interfaces\RouteCollectorInterface; use Slim\Interfaces\RouteResolverInterface; use Slim\Middleware\ErrorMiddleware; use Slim\Middleware\RoutingMiddleware; use Slim\Psr7\Factory\ResponseFactory; use Slim\Routing\RouteCollector; use Slim\Routing\RouteResolver; use UltraLite\Container\Container; class AppProvider implements ServiceProviderInterface { public function register(Container $container) { /* *     */ $container->set(CommandMap::class, function () { return new CommandMap(); }); /* *   http- */ $container->set(ResponseFactory::class, function () { return new ResponseFactory(); }); /* *    http-   */ $container->set(ResponseFactoryInterface::class, function (ContainerInterface $container) { return $container->get(ResponseFactory::class); }); /* *     */ $container->set(CallableResolver::class, function (ContainerInterface $container) { return new CallableResolver($container); }); /* *        */ $container->set(CallableResolverInterface::class, function (ContainerInterface $container) { return $container->get(CallableResolver::class); }); /* *   */ $container->set(RouteCollector::class, function (ContainerInterface $container) { $router = new RouteCollector( $container->get(ResponseFactoryInterface::class), $container->get(CallableResolverInterface::class), $container ); return $router; }); /* *      */ $container->set(RouteCollectorInterface::class, function (ContainerInterface $container) { return $container->get(RouteCollector::class); }); /* *     */ $container->set(RouteResolver::class, function (ContainerInterface $container) { return new RouteResolver($container->get(RouteCollectorInterface::class)); }); /* *        */ $container->set(RouteResolverInterface::class, function (ContainerInterface $container) { return $container->get(RouteResolver::class); }); /* *    404 */ $container->set(NotFoundHandler::class, function (ContainerInterface $container) { return new NotFoundHandler($container->get(ResponseFactoryInterface::class)); }); /* *  middleware   */ $container->set(ErrorMiddleware::class, function (ContainerInterface $container) { $middleware = new ErrorMiddleware( $container->get(CallableResolverInterface::class), $container->get(ResponseFactoryInterface::class), $container->get(Config::class)->get('slim.debug'), true, true); $middleware->setErrorHandler(HttpNotFoundException::class, $container->get(NotFoundHandler::class)); return $middleware; }); /* *  middleware  */ $container->set(RoutingMiddleware::class, function (ContainerInterface $container) { return new RoutingMiddleware($container->get(RouteResolverInterface::class)); }); } } 

Movemos o roteamento para o contêiner para podermos trabalhar com ele sem inicializar o objeto \Slim\App .


bootstrap.php
 <?php // bootstrap.php require_once __DIR__ . DIRECTORY_SEPARATOR . 'vendor' . DIRECTORY_SEPARATOR . 'autoload.php'; use App\Support\ServiceProviderInterface; use App\Provider\AppProvider; use App\Support\Config; use UltraLite\Container\Container; /* *   */ $env = getenv('APP_ENV'); if (!$env) $env = 'local'; /* *   */ $config = new Config(__DIR__ . DIRECTORY_SEPARATOR . 'config', $env, __DIR__); /* *  - */ $providers = [ AppProvider::class, ]; /* *    */ $container = new Container([ Config::class => function () use ($config) { return $config;}, ]); /* *   */ foreach ($providers as $className) { if (!class_exists($className)) { /** @noinspection PhpUnhandledExceptionInspection */ throw new Exception('Provider ' . $className . ' not found'); } $provider = new $className; if (!$provider instanceof ServiceProviderInterface) { /** @noinspection PhpUnhandledExceptionInspection */ throw new Exception($className . ' has not provider'); } $provider->register($container); } /* *   */ return $container; 

bin / console
 #!/usr/bin/env php <?php // bin/console use App\Support\CommandMap; use Psr\Container\ContainerInterface; use Symfony\Component\Console\Application; use Symfony\Component\Console\CommandLoader\ContainerCommandLoader; use Symfony\Component\Console\Input\ArgvInput; use Symfony\Component\Console\Output\ConsoleOutput; /** @var ContainerInterface $container */ $container = require dirname(__DIR__) . DIRECTORY_SEPARATOR . 'bootstrap.php'; $loader = new ContainerCommandLoader($container, $container->get(CommandMap::class)->getMap()); $app = new Application(); $app->setCommandLoader($loader); /** @noinspection PhpUnhandledExceptionInspection */ $app->run(new ArgvInput(), new ConsoleOutput()); 

É aconselhável dar permissão a este arquivo para executar


 chmod +x ./bin/console 

public / index.php
 <?php // public/index.php use Psr\Container\ContainerInterface; use Psr\Http\Message\ResponseFactoryInterface; use Slim\App; use Slim\Interfaces\CallableResolverInterface; use Slim\Interfaces\RouteCollectorInterface; use Slim\Interfaces\RouteResolverInterface; use Slim\Middleware\ErrorMiddleware; use Slim\Middleware\RoutingMiddleware; use Slim\Psr7\Factory\ServerRequestFactory; /** @var ContainerInterface $container */ $container = require dirname(__DIR__) . DIRECTORY_SEPARATOR . 'bootstrap.php'; $request = ServerRequestFactory::createFromGlobals(); Slim\Factory\AppFactory::create(); $app = new App( $container->get(ResponseFactoryInterface::class), $container, $container->get(CallableResolverInterface::class), $container->get(RouteCollectorInterface::class), $container->get(RouteResolverInterface::class) ); $app->add($container->get(RoutingMiddleware::class)); $app->add($container->get(ErrorMiddleware::class)); $app->run($request); 

Verificação
Inicie o aplicativo de console:


 ./bin/console 

Em resposta, a janela de boas-vindas do componente symfony/console deve aparecer com dois comandos disponíveis - help e list .


 Console Tool Usage: command [options] [arguments] Options: -h, --help Display this help message -q, --quiet Do not output any message -V, --version Display this application version --ansi Force ANSI output --no-ansi Disable ANSI output -n, --no-interaction Do not ask any interactive question -v|vv|vvv, --verbose Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug Available commands: help Displays help for a command list Lists commands 

Agora inicie o servidor web.


 php -S localhost:8080 -t public public/index.php 

E abra qualquer URL no localhost: 8080.
Todas as solicitações devem retornar uma resposta com o código 404 e um corpo vazio.
Isso acontece porque não temos rotas listadas.


Resta conectar o render, escrever modelos, controladores e definir rotas.


Etapa 2. Renderizar


Adicione o modelo template/layout.twig . Este é o modelo básico para todas as páginas.


template / layout.twig
 {# template/layout.twig #} <!DOCTYPE html> <html lang="en"> <head> <title>{% block title %}Slim demo{% endblock %}</title> </head> <body> {% block content %}{% endblock %} </body> </html> 

Adicione um template/hello.twig página de boas-vindas template/hello.twig


template / hello.twig
 {# template/hello.twig #} {% extends 'layout.twig' %} {% block title %}Slim demo::hello, {{ name }}{% endblock %} {% block content %} <h1>Welcome!</h1> <p>Hello, {{ name }}!</p> {% endblock %} 

E o template/err404.twig página de erro template/err404.twig


template / err404.twig
 {# template/err404.twig #} {% extends 'layout.twig' %} {% block title %}Slim demo::not found{% endblock %} {% block content %} <h1>Error!</h1> <p>Page not found =(</p> {% endblock %} 

Adicione o app/Provider/RenderProvider.php provedor de renderização app/Provider/RenderProvider.php


app / Provider / RenderProvider.php
 <?php // app/Provider/RenderProvider.php namespace App\Provider; use App\Support\Config; use App\Support\ServiceProviderInterface; use Psr\Container\ContainerInterface; use Twig\Environment; use Twig\Loader\FilesystemLoader; use UltraLite\Container\Container; class RenderProvider implements ServiceProviderInterface { public function register(Container $container) { $container->set(Environment::class, function (ContainerInterface $container) { $config = $container->get(Config::class); $loader = new FilesystemLoader($config->get('templates.dir')); $cache = $config->get('templates.cache'); $options = [ 'cache' => empty($cache) ? false : $cache, ]; $twig = new Environment($loader, $options); return $twig; }); } } 

Ligue o provedor no bootstrap


bootstrap.php
 <?php // bootstrap.php // ... use App\Provider\RenderProvider; // ... $providers = [ // ... RenderProvider::class, // ... ]; // ... 

Adicionar uma renderização ao manipulador de erros 404


app / Support / NotFoundHandler.php (DIFF)
 --- a/app/Support/NotFoundHandler.php +++ b/app/Support/NotFoundHandler.php @@ -8,15 +8,22 @@ use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use Slim\Interfaces\ErrorHandlerInterface; use Throwable; +use Twig\Environment; +use Twig\Error\LoaderError; +use Twig\Error\RuntimeError; +use Twig\Error\SyntaxError; class NotFoundHandler implements ErrorHandlerInterface { private $factory; - public function __construct(ResponseFactoryInterface $factory) + private $render; + + public function __construct(ResponseFactoryInterface $factory, Environment $render) { $this->factory = $factory; + $this->render = $render; } /** @@ -26,10 +33,14 @@ class NotFoundHandler implements ErrorHandlerInterface * @param bool $logErrors * @param bool $logErrorDetails * @return ResponseInterface + * @throws LoaderError + * @throws RuntimeError + * @throws SyntaxError */ public function __invoke(ServerRequestInterface $request, Throwable $exception, bool $displayErrorDetails, bool $logErrors, bool $logErrorDetails): ResponseInterface { $response = $this->factory->createResponse(404); + $response->getBody()->write($this->render->render('err404.twig')); return $response; } } 

app / Provider / AppProvider.php (DIFF)
 --- a/app/Provider/AppProvider.php +++ b/app/Provider/AppProvider.php @@ -19,6 +19,7 @@ use Slim\Middleware\RoutingMiddleware; use Slim\Psr7\Factory\ResponseFactory; use Slim\Routing\RouteCollector; use Slim\Routing\RouteResolver; +use Twig\Environment; use UltraLite\Container\Container; class AppProvider implements ServiceProviderInterface @@ -99,7 +100,7 @@ class AppProvider implements ServiceProviderInterface *    404 */ $container->set(NotFoundHandler::class, function (ContainerInterface $container) { - return new NotFoundHandler($container->get(ResponseFactoryInterface::class)); + return new NotFoundHandler($container->get(ResponseFactoryInterface::class), $container->get(Environment::class)); }); /* 

404 .


3.



2:


  • app/Controller/HomeController.php
  • app/Controller/HelloController.php

( URL ), — ( html)


app/Controller/HomeController.php
 <?php // app/Controller/HomeController.php namespace App\Controller; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use Slim\Interfaces\RouteParserInterface; class HomeController { /** * @var RouteParserInterface */ private $router; public function __construct(RouteParserInterface $router) { $this->router = $router; } public function index(ServerRequestInterface $request, ResponseInterface $response) { $uri = $this->router->fullUrlFor($request->getUri(), 'hello', ['name' => 'world']); return $response ->withStatus(301) ->withHeader('location', $uri); } } 

app/Controller/HelloController.php
 <?php // app/Controller/HelloController.php namespace App\Controller; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use Twig\Environment as Render; use Twig\Error\LoaderError; use Twig\Error\RuntimeError; use Twig\Error\SyntaxError; class HelloController { /** * @var Render */ private $render; public function __construct(Render $render) { $this->render = $render; } /** * @param ServerRequestInterface $request * @param ResponseInterface $response * @return ResponseInterface * @throws LoaderError * @throws RuntimeError * @throws SyntaxError */ public function show(ServerRequestInterface $request, ResponseInterface $response) { $response->getBody()->write($this->render->render('hello.twig', ['name' => $request->getAttribute('name')])); return $response; } } 

,


app/Provider/WebProvider.php
 <?php // app/Provider/WebProvider.php namespace App\Provider; use App\Controller\HelloController; use App\Controller\HomeController; use App\Support\ServiceProviderInterface; use Psr\Container\ContainerInterface; use Slim\Interfaces\RouteCollectorInterface; use Slim\Interfaces\RouteCollectorProxyInterface; use Twig\Environment; use UltraLite\Container\Container; class WebProvider implements ServiceProviderInterface { public function register(Container $container) { /* *   */ $container->set(HomeController::class, function (ContainerInterface $container) { return new HomeController($container->get(RouteCollectorInterface::class)->getRouteParser()); }); $container->set(HelloController::class, function (ContainerInterface $container) { return new HelloController($container->get(Environment::class)); }); /* *   */ $router = $container->get(RouteCollectorInterface::class); $router->group('/', function(RouteCollectorProxyInterface $router) { $router->get('', HomeController::class . ':index')->setName('index'); $router->get('hello/{name}', HelloController::class . ':show')->setName('hello'); }); } } 


bootstrap.php
 <?php // bootstrap.php // ... use App\Provider\WebProvider; // ... $providers = [ // ... WebProvider::class, // ... ]; // ... 

- ( )...


 php -S localhost:8080 -t public public/index.php 

http://localhost:8080 , http://localhost:8080/hello/world


world'.
http://localhost:8080/hello/ivan ivan'.


, , http://localhost:8080/helo/world 404 .


4.


route:list


app/Command/RouteListCommand.php
 <?php // app/Command/RouteListCommand.php namespace App\Command; use Slim\Interfaces\RouteCollectorInterface; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; class RouteListCommand extends Command { /* *    ,        */ const NAME = 'route:list'; /** * @var RouteCollectorInterface */ private $router; public function __construct(RouteCollectorInterface $router) { $this->router = $router; parent::__construct(); } protected function configure() { $this->setName(self::NAME) ->setDescription('List of routes.') ->setHelp('List of routes.') ; } protected function execute(InputInterface $input, OutputInterface $output) { $io = new SymfonyStyle($input, $output); $io->title('Routes'); $rows = []; $routes = $this->router->getRoutes(); if (!$routes) { $io->text('Routes list is empty'); return 0; } foreach ($routes as $route) { $rows[] = [ 'path' => $route->getPattern(), 'methods' => implode(', ', $route->getMethods()), 'name' => $route->getName(), 'handler' => $route->getCallable(), ]; } $io->table( ['Route', 'Methods', 'Name', 'Handler'], $rows ); return 0; } } 

,


app/Provider/CommandProvider.php
 <?php // app/Provider/CommandProvider.php namespace App\Provider; use App\Command\RouteListCommand; use App\Support\CommandMap; use App\Support\ServiceProviderInterface; use Psr\Container\ContainerInterface; use Slim\Interfaces\RouteCollectorInterface; use UltraLite\Container\Container; class CommandProvider implements ServiceProviderInterface { public function register(Container $container) { /* *       */ $container->set(RouteListCommand::class, function (ContainerInterface $container) { return new RouteListCommand($container->get(RouteCollectorInterface::class)); }); /* *        */ $container->get(CommandMap::class)->set(RouteListCommand::NAME, RouteListCommand::class); } } 


bootstrap.php
 <?php // bootstrap.php // ... use App\Provider\CommandProvider; // ... $providers = [ // ... CommandProvider::class, // ... ]; // ... 

...


 ./bin/console route:list 

… :


 Routes ====== --------------- --------- ------- ------------------------------------- Route Methods Name Handler --------------- --------- ------- ------------------------------------- / GET index App\Controller\HomeController:index /hello/{name} GET hello App\Controller\HelloController:show --------------- --------- ------- ------------------------------------- 

, , !


, Slim — routes.php ( ), . — , , .


Source: https://habr.com/ru/post/pt452834/


All Articles