PHP Classes

How to Use a PHP FastAPI Implementation to Create APIs Using a Router Class From the PackageFastAPI: Create an API from controllers' PHPDoc comments

Recommend this page to a friend!
  Info   Example   Screenshots   View files Files   Install with Composer Install with Composer   Download Download   Reputation   Support forum   Blog    
Last Updated Ratings Unique User Downloads Download Rankings
2024-10-27 (7 days ago) RSS 2.0 feedNot enough user ratingsTotal: 28 This week: 28All time: 11,171 This week: 2Up
Version License PHP version Categories
fastapi 1.0.0MIT/X Consortium ...5PHP 5, Web services, Language
Description 

Author

This package can create an API from controllers' PHPDoc comments.

It provides a class that can parse controller classes to find PHPDoc comments that define API endpoints implemented by controller class functions.

The class checks the URL of each API request to determine which controller class function will be called to generate the API call response.

In Portuguese:

O projeto utiliza anotações nos comentários (DocBlocks) dos controladores para definir as rotas, inspirado no estilo do FastAPI em Python.

Innovation Award
PHP Programming Innovation award nominee
October 2024
Nominee
Vote
Many developers are creating APIs to provide integration of mobile applications or serve the needs of partners of Web site owners.

Creating an API from scratch is a complex process.

This package can simplify the API creation process by letting API developers define the API endpoints in PHPDoc comments on the controller classes that will provide the service of each API function.

The package provides a PHPDoc Router class that processes the API request and calls the respective controller class to generate the API response.

Manuel Lemos
Picture of Rodrigo Faustino
  Performance   Level  
Name: Rodrigo Faustino <contact>
Classes: 29 packages by
Country: Brazil Brazil
Age: 41
All time rank: 2464169 in Brazil Brazil
Week rank: 2 Up2 in Brazil Brazil Up
Innovation award
Innovation award
Nominee: 19x

Winner: 2x

Example

<?php
namespace Fast\Api;

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

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

HttpHeader::adicionarCabecalhos();

if (
$_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
   
http_response_code(204);
    exit();
}

$metodoHttp = $_SERVER['REQUEST_METHOD'];
$uri = $_SERVER['REQUEST_URI'];

$roteador = new DocBlockRouter();

$caminhoControladores = __DIR__ . '/../src/Controllers';
$namespaceBase = 'Fast\\Api\\Controllers';

$classesControladoras = obterClassesControladoras($caminhoControladores, $namespaceBase);

foreach (
$classesControladoras as $classeControladora) {
   
$roteador->passaControlador($classeControladora);
}

$roteador->resolve($metodoHttp, $uri);

/**
 * Função para obter todas as classes de controladores no diretório Controllers
 *
 * @param string $caminhoControladores Caminho para o diretório de controladores
 * @param string $namespaceBase Namespace base dos controladores
 * @return array Lista de nomes completos das classes de controladores
 */
   
function obterClassesControladoras($caminhoControladores, $namespaceBase) {
       
$classesControladoras = [];

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

        foreach (
$iterador as $arquivo) {
            if (
$arquivo->isFile() && $arquivo->getExtension() === 'php') {
               
$caminhoRelativo = substr($arquivo->getPathname(), strlen($caminhoControladores));
               
$caminhoRelativo = ltrim($caminhoRelativo, DIRECTORY_SEPARATOR);
               
$caminhoRelativo = substr($caminhoRelativo, 0, -4);
               
$parteNomeClasse = str_replace(DIRECTORY_SEPARATOR, '\\', $caminhoRelativo);
               
$nomeClasse = $namespaceBase . '\\' . $parteNomeClasse;

            if (!
class_exists($nomeClasse)) {
                require_once
$arquivo->getPathname();
            }
            if (
class_exists($nomeClasse)) {
               
$classesControladoras[] = $nomeClasse;
            }
        }
    }

    return
$classesControladoras;
}


Details

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.


Screenshots (7)  
  • img/img-1.png
  • img/img-2.png
  • img/img-3.png
  • img/img-4.png
  • img/img-5.png
  • img/img-6.png
  • img/img-7.png
  Files folder image Files (18)  
File Role Description
Files folder imagesrc (1 file, 6 directories)
Accessible without login Plain text file composer.json Data Auxiliary data
Accessible without login Plain text file composer.lock Data Auxiliary data
Accessible without login Plain text file readme.md Doc. Documentation

  Files folder image Files (18)  /  src  
File Role Description
Files folder imageControllers (1 file)
Files folder imageDatabase (2 files)
Files folder imageHttp (1 file)
Files folder imageModels (1 file)
Files folder imageRepositories (1 file)
Files folder imageRotas (1 file)
  Accessible without login Plain text file index.php Example Example script

  Files folder image Files (18)  /  src  /  Controllers  
File Role Description
  Plain text file UserController.php Class Class source

  Files folder image Files (18)  /  src  /  Database  
File Role Description
  Plain text file config.php Class Class source
  Plain text file Database.php Class Class source

  Files folder image Files (18)  /  src  /  Http  
File Role Description
  Plain text file HttpHeader.php Class Class source

  Files folder image Files (18)  /  src  /  Models  
File Role Description
  Plain text file User.php Class Class source

  Files folder image Files (18)  /  src  /  Repositories  
File Role Description
  Plain text file UserRepository.php Class Class source

  Files folder image Files (18)  /  src  /  Rotas  
File Role Description
  Plain text file DocBlockRouter.php Class Class source

The PHP Classes site has supported package installation using the Composer tool since 2013, as you may verify by reading this instructions page.
Install with Composer Install with Composer
 Version Control Unique User Downloads Download Rankings  
 100%
Total:28
This week:28
All time:11,171
This week:2Up