Na primeira parte desta série sobre Rotas MVC em PHP 8, criamos a estrutura básica e implementamos um roteador inicial. Agora, nesta segunda parte, vamos configurar o servidor para suportar URLs amigáveis e aprimorar as classes Request e Dispatcher. Caso você não tenha acompanhado a primeira parte, recomendo antes revisitar o artigos anterior da série para uma compreensão completa:
Sistema de Rotas MVC em PHP 8 – Parte 1
Configurando o Servidor Web
Para configurar o servidor e permitir o uso de URLs amigáveis, você pode optar pelo Apache ou NGINX. Primeiramente, vamos ver como fazer isso no Apache.
1. Configuração no Apache
Se você utiliza o Apache, especialmente em um ambiente local como WAMP ou em um servidor Linux, será necessário habilitar o módulo mod_rewrite
para suportar URLs amigáveis.
Passo a passo para habilitar o mod_rewrite no Linux:
Para começar, execute os seguintes comandos para habilitar o módulo rewrite e configurar as permissões:
sudo a2enmod rewrite
sudo nano /etc/apache2/apache2.conf
Em seguida, dentro do arquivo de configuração, modifique a diretiva para permitir o uso de .htaccess
:
<Directory /var/www/>
AllowOverride All
</Directory>
Finalmente, após fazer a alteração, reinicie o Apache:
sudo systemctl restart apache2
No Windows (WAMP):
Se você utiliza o WAMP, então precisa habilitar o mod_rewrite
editando o arquivo httpd.conf
dessa maneira:
- primeiro, abra o arquivo de configuração do Apache, localizado em
httpd.conf
, e habilite o módulomod_rewrite
:
LoadModule rewrite_module modules/mod_rewrite.so
- e por fim, após salvar o arquivo, reinicie o servidor Apache pelo painel de controle do WAMP.
Criando o Arquivo .htaccess
(para Apache)
Um detalhe importante para, quando você utilizar o Apache, será criar o arquivo .htaccess
no diretório public/
do seu projeto. Então, crie, este arquivo para redirecionar as requisições para o index.php e garantir que as URLs amigáveis funcionem corretamente.
Então, crie o arquivo .htaccess
com o seguinte conteúdo:
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /rotas/public
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^([A-Za-z0-9_/\\-]+)$ index.php?uri=$1 [QSA,L]
</IfModule>
Essa configuração redirecionará todas as requisições para o arquivo index.php
, a menos que um arquivo ou diretório real seja encontrado.
Agora que já abordamos o Apache, vamos ver como configurar URLs amigáveis no NGINX.
2. Configuração no NGINX
No Linux (NGINX):
- primeiramente edite o arquivo de configuração do NGINX dessa forma:
sudo nano /etc/nginx/sites-available/default
- depois, eentro do bloco
server {}
, adicione as seguintes configurações:
server {
listen 80;
server_name your_domain.com;
root /caminho/para/seu/projeto/public;
index index.php index.html index.htm;
location / {
try_files $uri $uri/ /index.php?uri=$uri;
}
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/var/run/php/php8.3-fpm.sock;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
location ~ /\.ht {
deny all;
}
}
Finalmente, reinicie o NGINX para aplicar as novas configurações utilizando esse comando:
sudo systemctl restart nginx
No NGINX, o arquivo
.htaccess
, que configuramos para apache, não é necessário porque todas as regras de redirecionamento e manipulação de URLs são centralizadas no próprio arquivo de configuração do servidor.
Medidas de Segurança
Agora que as configurações principais foram feitas, vamos adicionar uma camada extra de segurança, impedindo a indexação de diretórios.
No Apache:
Crie um arquivo .htaccess
no diretório raiz do projeto e adicione a linha abaixo:
Options -Indexes
No NGINX:
Adicione a seguinte configuração ao arquivo do servidor:
location ~ /\.ht {
deny all;
}
Tanto no Apache quanto no NGINX, essas medidas garantem que os diretórios e arquivos ocultos, como .htaccess
, não sejam acessíveis.
Refatorando a Classe Request
Agora que o servidor está configurado corretamente, então podemos avançar e partir para as refatorações. Vamos começar com a classe Request. Essa classe precisa capturar adequadamente os dados enviados via requisição, então ajuste-a da seguinte maneira:
declare(strict_types=1);
namespace Src;
class Request
{
private string $uri;
private string $method;
private array $data = [];
private array $files = [];
public function __construct()
{
$this->uri = trim(parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH), '/');
$this->method = strtolower($_SERVER['REQUEST_METHOD']);
$this->setData();
$this->setFiles();
}
private function setData(): void
{
$this->data = match ($this->method) {
'post' => $_POST,
'get' => $_GET,
'put', 'delete', 'patch' => json_decode(file_get_contents('php://input'), true) ?? [],
default => []
};
}
private function setFiles(): void
{
$this->files = $_FILES ?? [];
}
public function uri(): string
{
return $this->uri;
}
public function method(): string
{
return $this->method;
}
public function all(): array
{
return $this->data;
}
public function hasFile(string $key): bool
{
return isset($this->files[$key]);
}
public function file(string $key): array|null
{
return $this->files[$key] ?? null;
}
}
Observe que agora essa classe captura e organiza os dados enviados com a requisição, sejam eles via POST, GET, ou arquivos, e trata outros métodos HTTP como PUT e DELETE.
Refatorando a Classe Dispatcher
A classe Dispacher deve serr inteligente o suficiente para lidar com os controladores de maneira correta. Então, nessa etapa, vamos refatorar a classe Dispatcher para garantir que o controlador correto seja chamado com base na rota definida.
declare(strict_types=1);
namespace Src;
use Exception;
class Dispatcher
{
public function dispatch(string|callable $callback, array $params = [], string $namespace = "App\\"): mixed
{
if (is_callable($callback)) {
return call_user_func_array($callback, array_values($params));
}
[$controller, $method] = explode('@', $callback);
$controller = $namespace . $controller;
if (!class_exists($controller)) {
throw new Exception("Controller $controller não encontrado.");
}
$controllerInstance = new $controller;
if (!method_exists($controllerInstance, $method)) {
throw new Exception("Método $method não encontrado em $controller.");
}
return call_user_func_array([$controllerInstance, $method], array_values($params));
}
}
Finalizando essa classe, agora ele trata as rotas que chamam métodos de controladores, verificando se o método e a classe existem antes de invocá-los.
Atualizando Router
A classe de roteamento é o coração do nosso projeto e por isso, precisamos adicionar uma melhoria na resolução de rotas e o despacho das solicitações ao controlador correto na classe Router. Então, faça o seguinte ajuste:
declare(strict_types=1);
namespace Src;
use Src\Request;
use Src\Dispatcher;
class Router
{
public function __construct(
private RouteCollection $routes,
private Dispatcher $dispatcher
) {}
public function get(string $pattern, callable|string $callback): void
{
$this->routes->add('GET', $pattern, $callback);
}
public function post(string $pattern, callable|string $callback): void
{
$this->routes->add('POST', $pattern, $callback);
}
private function notFound(): void
{
header("HTTP/1.0 404 Not Found");
echo "404 - Página não encontrada";
}
public function resolve(Request $request): void
{
$route = $this->routes->match($request->method(), $request->uri());
if ($route === null) {
$this->notFound();
return;
}
$this->dispatch($route);
}
protected function dispatch(object $route, string $namespace = "App\\"): mixed
{
return $this->dispatcher->dispatch($route->callback, $route->uri, $namespace);
}
}
Pronto! Mas agora, será necessário garantir a intanciação dos objetos de forma correta. Faça também esse ajuste no arquivo bootstrap.php para isso:
declare(strict_types=1);
require __DIR__ . '/vendor/autoload.php';
use Src\Dispatcher;
use Src\RouteCollection;
use Src\Router;
$routes = new RouteCollection;
$dispatcher = new Dispatcher;
$router = new Router($routes, $dispatcher);
require __DIR__ . '/routes/routes.php';
Testando o Sistema de Rotas
Veja que o sistema de rotas foi atualizado, então agora você precisa editar o arquivo index.php
em public/
para resolver as rotas da maneira correta.
declare(strict_types=1);
require __DIR__ . '/../bootstrap.php';
use Src\Request;
$request = new Request;
$router->resolve($request);
Por fim, crie algumas rotas no arquivo routes.php
:
declare(strict_types=1);
global $router;
$router->get('', fn() => print('Página Inicial'));
$router->get('sobre', fn() => print('Página Sobre'));
$router->get('/contatos/telefones', 'Controller@getTelefones');
$router->post('/contatos/store', 'Controller@store');
Você pode observar que agora quando uma rota nova é invocada e o controller não existe ou não possui o método, uma exceção deve ser lançada. Mas se o método for adicionado no controller apenas com finalidade de teste, o controller deve responder corretamente.
Para finalizar, adicione o controller de exemplo:
declare(strict_types=1);
namespace App;
class Controller
{
public function getTelefones(): void
{
echo json_encode([
'telefones' => [
'11-4321-1234'
]
]);
}
}
Conclusão
Com essas melhorias, agora você terá um Sistema de Rotas MVC em PHP 8, mais robusto e que será capaz de suportar URLs amigáveis, invocar controladores e métodos de forma dinâmica. Vamos parar por aqui por hora mas no próximo artigo, vamos integrar controladores reais e trabalhar com rotas mais complexas. Continue acompanhando!