v1.0
Acessar api.vupi.us

Estrutura Completa do Módulo

Entenda cada arquivo gerado pelo scaffold e como usá-los para construir módulos robustos.

Visão geral

Ao criar um módulo com scaffold, a IDE gera 16 arquivos organizados em camadas. Você tem total liberdade para adicionar, remover ou modificar qualquer arquivo.

MeuModulo/
├── Controllers/MeuModuloController.php   ← Endpoints HTTP (CRUD)
├── Services/MeuModuloService.php         ← Lógica de negócio
├── Repositories/MeuModuloRepository.php  ← Acesso ao banco (PDO)
├── Entities/MeuModulo.php                ← Entidade de domínio
├── DTOs/
│   ├── CreateMeuModuloDTO.php            ← Dados de entrada (criação)
│   └── UpdateMeuModuloDTO.php            ← Dados de entrada (atualização)
├── Middlewares/MeuModuloMiddleware.php   ← Middleware próprio
├── Validators/MeuModuloValidator.php     ← Validação e sanitização
├── Exceptions/MeuModuloException.php     ← Exceções de domínio
├── Helpers/MeuModuloHelper.php           ← Funções utilitárias
├── Config/config.php                     ← Configurações do módulo
├── Database/
│   ├── connection.php                    ← Seleção de banco
│   ├── Migrations/...                    ← Criação de tabelas
│   └── Seeders/MeuModuloSeeder.php       ← Dados iniciais
├── Routes/web.php                        ← Definição de rotas
└── README.md                             ← Documentação

Controller — Endpoints HTTP

O controller recebe a requisição, valida os dados e retorna a resposta. Dependências são injetadas automaticamente pelo container.

final class ProdutoController
{
    public function __construct(
        private readonly ProdutoService $service
    ) {}

    public function listar(Request $request): Response
    {
        $page = max(1, (int) ($request->query['page'] ?? 1));
        return Response::json($this->service->listar($page, 20));
    }

    public function criar(Request $request): Response
    {
        $erro = ProdutoValidator::validarCriacao($request->body);
        if ($erro !== null) {
            return Response::json(['error' => $erro], 422);
        }
        $item = $this->service->criar(ProdutoValidator::sanitizar($request->body));
        return Response::json(['produto' => $item], 201);
    }
}

Service — Lógica de negócio

O service contém as regras de negócio e orquestra o repository. Nunca acessa o banco diretamente.

final class ProdutoService
{
    public function __construct(
        private readonly ProdutoRepository $repository
    ) {}

    public function listar(int $page = 1, int $perPage = 20): array
    {
        $total = $this->repository->count();
        $items = $this->repository->findPaginated($page, $perPage);
        return ['data' => $items, 'total' => $total, 'page' => $page];
    }

    public function criar(array $data): array
    {
        return $this->repository->create($data);
    }
}

Repository — Acesso ao banco

O repository encapsula todas as queries SQL. Recebe PDO via injeção de dependência.

final class ProdutoRepository
{
    private string $table = 'produtos';

    public function __construct(private readonly PDO $pdo) {}

    public function findPaginated(int $page, int $perPage): array
    {
        $stmt = $this->pdo->prepare(
            "SELECT * FROM {$this->table} ORDER BY criado_em DESC LIMIT ? OFFSET ?"
        );
        $stmt->execute([$perPage, ($page - 1) * $perPage]);
        return $stmt->fetchAll(PDO::FETCH_ASSOC);
    }

    public function create(array $data): array
    {
        $id = bin2hex(random_bytes(16));
        $this->pdo->prepare(
            "INSERT INTO {$this->table} (id, nome, preco) VALUES (?, ?, ?)"
        )->execute([$id, $data['nome'], $data['preco']]);
        return $this->findById($id) ?? ['id' => $id];
    }
}

Validator — Validação e sanitização

final class ProdutoValidator
{
    public static function validarCriacao(array $data): ?string
    {
        if (empty(trim($data['nome'] ?? ''))) return 'Nome é obrigatório.';
        if (!isset($data['preco']) || $data['preco'] <= 0) return 'Preço deve ser positivo.';
        return null;
    }

    public static function sanitizar(array $data): array
    {
        return [
            'nome'  => trim(strip_tags($data['nome'] ?? '')),
            'preco' => (float) ($data['preco'] ?? 0),
        ];
    }
}

Exception — Erros de domínio

final class ProdutoException extends \DomainException
{
    private int $statusCode;

    public function __construct(string $message, int $statusCode = 400)
    {
        parent::__construct($message);
        $this->statusCode = $statusCode;
    }

    public function getStatusCode(): int { return $this->statusCode; }

    public static function naoEncontrado(): self
    {
        return new self('Produto não encontrado.', 404);
    }
}

Entity — Modelo de domínio

A Entity representa um objeto de negócio com comportamento e regras. Diferente de um array, ela encapsula lógica.

final class Produto
{
    public function __construct(
        private string $id,
        private string $nome,
        private float $preco,
        private bool $ativo = true
    ) {}

    public function getId(): string { return $this->id; }
    public function getNome(): string { return $this->nome; }
    public function getPreco(): float { return $this->preco; }
    public function isAtivo(): bool { return $this->ativo; }

    public function aplicarDesconto(float $percentual): void
    {
        if ($percentual < 0 || $percentual > 100) {
            throw new \InvalidArgumentException('Desconto inválido.');
        }
        $this->preco = $this->preco * (1 - $percentual / 100);
    }

    public function desativar(): void
    {
        $this->ativo = false;
    }
}

DTO — Transferência de dados

DTOs são objetos simples para transportar dados entre camadas. Não têm comportamento, apenas propriedades.

final class CreateProdutoDTO
{
    public function __construct(
        public readonly string $nome,
        public readonly float $preco,
        public readonly ?string $descricao = null
    ) {}

    public static function fromArray(array $data): self
    {
        return new self(
            nome: $data['nome'] ?? '',
            preco: (float) ($data['preco'] ?? 0),
            descricao: $data['descricao'] ?? null
        );
    }

    public function toArray(): array
    {
        return [
            'nome' => $this->nome,
            'preco' => $this->preco,
            'descricao' => $this->descricao,
        ];
    }
}

Routes — Definição de rotas

O arquivo Routes/web.php registra os endpoints do módulo. Middlewares são aplicados por rota.

<?php

use Src\Modules\Produto\Controllers\ProdutoController;
use Src\Kernel\Middlewares\AuthHybridMiddleware;

/** @var \Src\Kernel\Contracts\RouterInterface $router */

$auth = [AuthHybridMiddleware::class];

$router->get('/api/produtos',       [ProdutoController::class, 'listar']);
$router->post('/api/produtos',      [ProdutoController::class, 'criar'],     $auth);
$router->get('/api/produtos/{id}',  [ProdutoController::class, 'buscar']);
$router->put('/api/produtos/{id}',  [ProdutoController::class, 'atualizar'], $auth);
$router->delete('/api/produtos/{id}', [ProdutoController::class, 'deletar'], $auth);

Config — Configurações do módulo

Centralize configurações específicas do módulo em Config/config.php.

<?php

return [
    'cache_ttl' => 3600,
    'max_items_per_page' => 50,
    'allowed_file_types' => ['jpg', 'png', 'pdf'],
    'api_timeout' => 30,
];

Helpers — Funções utilitárias

Helpers contêm funções auxiliares reutilizáveis em todo o módulo.

final class ProdutoHelper
{
    public static function formatarPreco(float $preco): string
    {
        return 'R$ ' . number_format($preco, 2, ',', '.');
    }

    public static function gerarSlug(string $nome): string
    {
        $slug = strtolower($nome);
        $slug = preg_replace('/[^a-z0-9]+/', '-', $slug);
        return trim($slug, '-');
    }

    public static function calcularImpostos(float $valor): float
    {
        return $valor * 0.15; // 15% de impostos
    }
}

Database/connection.php — Escolha do banco

Define qual banco de dados o módulo usa. O sistema prioriza conexões personalizadas configuradas na IDE.

<?php

// Retorna 'core', 'modules' ou 'auto'
// 'core'    → usa DB_* do .env (banco principal)
// 'modules' → usa DB2_* do .env (banco secundário)
// 'auto'    → o Kernel decide baseado na origem do módulo

return 'auto';
Banco de dados personalizado na IDE

Quando você cria um módulo na IDE e tem uma conexão de banco de dados personalizada ativa, o arquivo connection.php é gerado com 'auto'. O sistema prioriza automaticamente sua conexão personalizada, ignorando as configurações do .env.

Isso permite que cada desenvolvedor use seu próprio banco (MySQL, PostgreSQL, etc.) sem conflitar com outros usuários da plataforma.

Quando usar 'modules' (banco secundário)?

Use 'modules' quando quiser isolar dados de módulos externos do banco principal. Útil para multi-tenancy ou quando módulos de terceiros não devem acessar tabelas do sistema. Requer configuração de DB2_* no .env.

Liberdade total

Você pode criar quantas classes, pastas e arquivos quiser dentro do módulo. O scaffold é apenas um ponto de partida. Adicione Events, Observers, Jobs, Caches, ou qualquer padrão que seu projeto precisar.

Próximos Passos