PHP Classes

File: readme.md

Recommend this page to a friend!
  Classes of Rodrigo Faustino   FastAPI   readme.md   Download  
File: readme.md
Role: Documentation
Content type: text/markdown
Description: Documentation
Class: FastAPI
Create an API from controllers' PHPDoc comments
Author: By
Last change:
Date: 17 hours ago
Size: 12,758 bytes
 

Contents

Class file image Download

FastApi - Projeto Backend

O FastApi é um projeto de backend que facilita a inclusão de rotas em uma aplicação REST, permitindo a criação rápida de endpoints CRUD com operações básicas. O projeto utiliza anotações nos comentários (DocBlocks) dos controladores para definir as rotas, inspirado no estilo do FastAPI em Python. O banco de dados utilizado é o SQLite para facilitar os testes e a configuração inicial. Este projeto não possui frontend, focando apenas no backend.

Requisitos

  • PHP 7.4+ (recomendado PHP 8.0+)
  • Composer
  • Banco de dados SQLite
  • Postman ou Thunder Client (ou qualquer cliente de API REST)

Configuração

Clone o repositório:

git clone https://github.com/faustinopsy/fastapi.git

Instale as dependências do Composer:

composer install

Navegue até a pasta src (cd src) e inicie o servidor:

php -S localhost:8000

as classes controllers criadas precisam ter os comentários como o modelo:

<?php
namespace Fast\Api\Controllers;

use Fast\Api\Models\User;
use Fast\Api\Repositories\UserRepository;

class UserController {
    private $userRepository;

    public function __construct() {
        $this->userRepository = new UserRepository();
    }

    /
     * @GET("/users")
     */
    public function getAllUsers() {
        // Retorna todos os usuários
    }

    /
     * @GET("/users/{id}")
     */
    public function getUserById($id) {
        // Retorna um usuário pelo ID
    }

    /
     * @POST("/users")
     */
    public function createUser() {
        // Cria um novo usuário
    }

    /
     * @PUT("/users/{id}")
     */
    public function updateUser($id) {
        // Atualiza um usuário existente
    }

    /
     * @DELETE("/users/{id}")
     */
    public function deleteUser($id) {
        // Deleta um usuário existente
    }
}

Endpoints Disponíveis

O projeto inclui sete operações CRUD nas mesmas rotas, variando apenas o método HTTP. Veja abaixo os endpoints disponíveis:

  • GET /users - Retorna todos os usuários
  • GET /users/{id} - Retorna um usuário específico pelo ID
  • GET /users/nome/rodrigo - Retorna um usuário específico pelo nome
  • GET /users/data/2024-01-10/2024-03-15 -pode receber dois argumentos para relatórios
  • POST /users - Cria um novo usuário
  • PUT /users/{id} - Atualiza um usuário existente pelo ID
  • DELETE /users/{id} - Deleta um usuário existente pelo ID

Exemplo de Requisição com JSON

Para testar os endpoints, você pode usar o Postman ou Thunder Client.

Exemplo para criar um novo usuário (POST /users):

  • Método: POST URL: http://localhost:8000/users

Body (raw JSON):

{
  "nome": "seunome",
  "email": "seunome@gmail.com",
  "senha": "1234"
}

Exemplo de Atualização de Usuário (PUT /users/{id}):

Método: PUT

URL: http://localhost:8000/users/1

Body (raw JSON):

{
  "nome": "nomeatualizado",
  "email": "emailatualizado@gmail.com",
  "senha": "nova_senha"
}

Exemplo de Deleção de Usuário (DELETE /users/{id}):

Método: DELETE

URL: http://localhost:8000/users/1

Como Funciona o Roteamento com Anotações

As rotas são definidas diretamente nos métodos dos controladores usando anotações nos DocBlocks. O DocBlockRouter é responsável por ler essas anotações e registrar as rotas. Não é necessário registrar manualmente os controladores; o sistema carrega automaticamente todos os controladores presentes na pasta /Controllers. As rotas podem conter parâmetros, como {id}, que serão passados como argumentos para os métodos.

Exemplo de Parâmetros na Rota

/
 * @GET("/users/{id}")
 */
public function getUserById($id) {
    // $id será o valor capturado da URL
}

Dependências

O projeto utiliza a seguinte dependência:

phpdocumentor/reflection-docblock: Utilizada para interpretar as anotações nos DocBlocks. - Instalação via Composer:

composer require phpdocumentor/reflection-docblock

Autoloading com Composer

Certifique-se de que o composer.json está configurado para o autoloading das classes:

{
    "autoload": {
        "psr-4": {
            "Fast\\Api\\": "src/"
        }
    }
}

Estrutura do index.php

O arquivo index.php é responsável por inicializar o roteamento e processar as requisições:

<?php
namespace Fast\Api;

use Fast\Api\Http\HttpHeader;
use Fast\Api\Rotas\DocBlockRouter;

require_once '../vendor/autoload.php';

// Define os cabeçalhos HTTP padrão
HttpHeader::setCabecalhosPadrao();

// Trata requisições OPTIONS para CORS
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
    http_response_code(204);
    exit();
}

// Obtém o método HTTP e a URI da requisição
$metodoHttp = $_SERVER['REQUEST_METHOD'];
$uri = $_SERVER['REQUEST_URI'];

// Cria uma instância do roteador
$roteador = new DocBlockRouter();

// Define o caminho base e o namespace para os controladores
$caminhoControladores = __DIR__ . '/../src/Controllers';
$namespaceBase = 'Fast\\Api\\Controllers';

// Obtém todas as classes de controladores dinamicamente
$classesControladoras = obterClassesControladoras($caminhoControladores, $namespaceBase);

// Passa todas as classes de controladores para o roteador
foreach ($classesControladoras as $classeControladora) {
    $roteador->passaControlador($classeControladora);
}

// Resolve a rota com base no método HTTP e URI
$roteador->resolve($metodoHttp, $uri);

// Função para obter todas as classes de controladores
function obterClassesControladoras($caminhoControladores, $namespaceBase) {
    $classesControladoras = [];

    $iterador = new \RecursiveIteratorIterator(
        new \RecursiveDirectoryIterator($caminhoControladores)
    );

    foreach ($iterador as $arquivo) {
        if ($arquivo->isFile() && $arquivo->getExtension() === 'php') {
            // Obtém o caminho do arquivo relativo ao caminho dos controladores
            $caminhoRelativo = substr($arquivo->getPathname(), strlen($caminhoControladores));
            // Remove os separadores de diretório iniciais
            $caminhoRelativo = ltrim($caminhoRelativo, DIRECTORY_SEPARATOR);
            // Remove a extensão '.php' do arquivo
            $caminhoRelativo = substr($caminhoRelativo, 0, -4);
            // Substitui os separadores de diretório pelos separadores de namespace
            $parteNomeClasse = str_replace(DIRECTORY_SEPARATOR, '\\', $caminhoRelativo);
            // Monta o nome completo da classe, incluindo o namespace
            $nomeClasse = $namespaceBase . '\\' . $parteNomeClasse;

            // Verifica se a classe existe (autoload)
            if (!class_exists($nomeClasse)) {
                // Inclui o arquivo para carregar a classe
                require_once $arquivo->getPathname();
            }

            // Se a classe existe após inclusão, adiciona à lista de classes controladoras
            if (class_exists($nomeClasse)) {
                $classesControladoras[] = $nomeClasse;
            }
        }
    }

    return $classesControladoras;
}

Arquivo: Rotas/DocBlockRouter.php (comentado em detalhes)

este arquivo é dependente do Reflection e do phpDocumentor https://phpdoc.org/ https://www.php.net/manual/en/book.reflection.php

<?php
namespace Fast\Api\Rotas;

use ReflectionClass;
use ReflectionMethod;
use phpDocumentor\Reflection\DocBlockFactory;
class DocBlockRouter {
    private array $rotas = [];
    public function passaControlador(string $classeControladora) {
        // Cria uma reflexão da classe controladora
        $reflexaoControladora = new ReflectionClass($classeControladora);
        // Obtém todos os métodos públicos da classe controladora
        $metodos = $reflexaoControladora->getMethods(ReflectionMethod::IS_PUBLIC);
        // Cria uma instância da fábrica de DocBlocks
        $fabricaDocBlock = DocBlockFactory::createInstance();
        // Percorre cada método público da classe controladora
        foreach ($metodos as $metodo) {
            // Obtém o comentário do DocBlock do método
            $comentarioDoc = $metodo->getDocComment();
            if ($comentarioDoc) {
                // Cria um objeto DocBlock a partir do comentário
                $docBlock = $fabricaDocBlock->create($comentarioDoc);
                $metodosHttp = ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'];
                // Verifica se o DocBlock possui alguma das tags HTTP
                foreach ($metodosHttp as $metodoHttp) {
                    if ($docBlock->hasTag($metodoHttp)) {
                        // Obtém a primeira tag correspondente ao método HTTP
                        $tagRota = $docBlock->getTagsByName($metodoHttp)[0];
                        // Obtém o conteúdo da descrição da tag (ex: '("/usuarios")')
                        $conteudo = $tagRota->getDescription()->render();
                        // Extrai o caminho da rota a partir do conteúdo da tag
                        preg_match('/\("(.*)"\)/', $conteudo, $correspondencias);
                        $caminho = $correspondencias[1] ?? '';
                        // Armazena a rota no array de rotas, associando o método HTTP, caminho e ação
                        $this->rotas[$metodoHttp][$caminho] = [$classeControladora, $metodo->getName()];
                    }
                }
            }
        }
    }
    public function resolve($metodoHttp, $uri) {
        // Verifica se existem rotas para o método HTTP solicitado
        if (!isset($this->rotas[$metodoHttp])) {
            http_response_code(405);
            echo json_encode(['status' => false, 'message' => 'Método não permitido']);
            exit();
        }
        // Obtém o caminho da URI (sem query strings)
        $uri = parse_url($uri, PHP_URL_PATH);
        // Percorre cada rota registrada para o método HTTP
        foreach ($this->rotas[$metodoHttp] as $rota => $acao) {
            // Substitui os parâmetros da rota por expressões regulares nomeadas
            $padrao = preg_replace_callback('/\{([a-zA-Z_][a-zA-Z0-9_]*)\}/', function ($matches) {
                $nomeParametro = $matches[1];
                // Para parâmetros genéricos, permite qualquer sequência que não contenha '/'
                return '(?P<' . $nomeParametro . '>[^/]+)';
            }, $rota);
            // Define o padrão como uma expressão regular completa
            $padrao = '#^' . $padrao . '$#u';
            // Verifica se a URI corresponde ao padrão da rota
            if (preg_match($padrao, $uri, $correspondencias)) {
                // Cria uma instância da classe controladora
                $instanciaControladora = new $acao[0]();
                $nomeMetodo = $acao[1];
                // Filtra os parâmetros nomeados capturados na expressão regular
                $parametros = array_filter(
                    $correspondencias,
                    fn($chave) => is_string($chave),
                    ARRAY_FILTER_USE_KEY
                );
                // Obtém os dados do corpo da requisição (se houver)
                $dados = json_decode(file_get_contents('php://input'), true);
                // Obtém os parâmetros esperados pelo método através da reflexão
                $metodoRefletido = new ReflectionMethod($instanciaControladora, $nomeMetodo);
                $parametrosMetodo = $metodoRefletido->getParameters();
                $argumentos = [];
                // Prepara os argumentos a serem passados para o método, na ordem correta
                foreach ($parametrosMetodo as $parametro) {
                    $nome = $parametro->getName();
                    if (isset($parametros[$nome])) {
                        $argumentos[] = $parametros[$nome];
                    } elseif ($nome === 'dados') {
                        $argumentos[] = $dados;
                    } else {
                        $argumentos[] = null;
                    }
                }
                // Chama o método da controladora com os argumentos obtidos
                return call_user_func_array([$instanciaControladora, $nomeMetodo], $argumentos);
            }
        }
        // Se nenhuma rota corresponder, retorna erro 404
        http_response_code(404);
        echo json_encode(['status' => false, 'message' => 'Rota não encontrada']);
        exit();
    }
}

busca por datas deletar usuario atualizar usuario criar usuario tentar criar usuario existente buscar usuario por id buscar todos

Licença

Este projeto está licenciado sob a MIT License.