# PHP-MVC-Framework2
**Repository Path**: wfdaj/php-mvc-framework2
## Basic Information
- **Project Name**: PHP-MVC-Framework2
- **Description**: No description available
- **Primary Language**: PHP
- **License**: Not specified
- **Default Branch**: main
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 0
- **Forks**: 0
- **Created**: 2026-05-05
- **Last Updated**: 2026-05-21
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
# Modelo MVC Genérico em PHP
Um micro-framework MVC moderno, leve e modular, construído com PHP puro. Projetado para servir como um _template base_ ágil para iniciar novos projetos, oferecendo recursos de segurança e arquitetura vistos em frameworks robustos (como Laravel), porém mantendo a simplicidade.
## Recursos Integrados
- **Arquitetura Modular:** Separação limpa de responsabilidades (ex: `app/Site`, `app/Auth`).
- **Roteamento Dinâmico:** Suporte a métodos HTTP, rotas por arquivo e injeção de parâmetros de URL.
- **Middlewares:** Proteja suas rotas e execute lógicas antes do Controller.
- **Classe Request:** Abstração completa de globais (`$_POST`, `$_GET`, etc.).
- **Proteção CSRF Nativa:** Formulários seguros por padrão via Middleware Global.
- **Sessões e Flash Messages:** Gerenciamento elegante de feedback para o usuário.
- **Herança de Views:** Sistema inteligente de layouts globais ou específicos de módulo.
---
## 📁 Estrutura de Diretórios
```text
├── app/ # Seus módulos (Ex: Site, Auth, Painel)
│ ├── Auth/
│ │ ├── Controllers/
│ │ ├── Middlewares/
│ │ ├── Models/
│ │ └── Views/
│ ├── Shared/ # Arquivos compartilhados (Layouts globais, Middlewares globais)
│ └── Site/
├── core/ # Núcleo do Framework (NÃO EDITAR)
│ ├── Database/ # Acesso a Dados (Database.php, Model.php)
│ ├── Http/ # Request, Router, Session, Middleware
│ ├── Utils/ # Ferramentas auxiliares (Env, Funcoes, etc)
│ ├── View/ # Motor de Renderização
│ ├── Application.php # Inicializador Principal
│ └── Controller.php # Classe Base dos Controllers
├── public/ # Ponto de entrada (Document Root)
│ └── index.php
└── routes/ # Arquivos de Rotas (*.php carregados automaticamente)
```
---
## 🚀 Como Usar
### 1. Criando Rotas
As rotas são definidas dentro de arquivos `.php` na pasta `/routes`. Todos os arquivos nessa pasta são carregados automaticamente.
```php
// routes/site.php
get('/', HomeController::class, 'index');
// Rota POST com parâmetros na URL (ex: /produto/15)
$router->post('/produto/{id}', HomeController::class, 'atualizar');
// Rota Protegida por Middleware
$router->get('/painel', HomeController::class, 'painel')->middleware(AuthMiddleware::class);
```
### 2. Criando Controllers
Todo controller deve herdar de `Core\Controller`. O objeto `Request` é injetado automaticamente nas funções para capturar dados.
```php
namespace App\Site\Controllers;
use Core\Controller;
use Core\Http\Request;
class HomeController extends Controller
{
public function index(Request $request)
{
// Resgatando dados (Substitui $_GET e $_POST)
$termo = $request->get('busca');
$email = $request->post('email');
// Retornando uma View
$this->render('Site', 'home/index', ['title' => 'Página Inicial']);
}
}
```
**Métodos úteis do Controller:**
- `$this->render('Modulo', 'caminho/view', $dados)`: Renderiza tela html.
- `$this->json($array, $status)`: Retorna uma resposta JSON (ideal para APIs REST). Ex: `$this->json(['ok' => true], 200)`.
- `$this->redirect('/url')`: Redireciona o usuário.
- `$this->flash('chave', 'mensagem')`: Define uma notificação Flash temporária.
### 3. Criando Views e Layouts
As views ficam em `app/Modulo/Views`. Por padrão, a função `$this->render()` sempre tenta injetar a view dentro do layout global localizado em `app/Shared/Views/layouts/default.php`.
Para imprimir variáveis, use o PHP puro. E para proteger os seus formulários POST, sempre use a função injetada `$csrf_field()`:
```php
Login
```
### 4. Flash Messages (Notificações)
Para exibir mensagens de sucesso ou erro (ex: após um login falhar), faça no controller:
```php
$this->flash('error', 'Credenciais inválidas!');
$this->redirect('/login');
```
E as exiba no seu layout (`default.php`):
```php
```
### 5. Middlewares e Agrupamento de Rotas
Middlewares funcionam como "guarda-costas" das rotas.
O **CsrfMiddleware** já vem ativado globalmente pelo roteador para todas as rotas de alteração (`POST`, `PUT`, `DELETE`).
**Middleware em rota individual:**
```php
$router->get('/dashboard', DashboardController::class, 'index')
->middleware(\App\Auth\Middlewares\AuthMiddleware::class);
```
**Middleware no grupo (aplicado automaticamente a todas as rotas do grupo):**
```php
$router->group(['prefix' => '/admin', 'middleware' => AuthMiddleware::class], function ($router) {
$router->get('/painel', AdminController::class, 'index'); // tem AuthMiddleware
$router->get('/usuarios', AdminController::class, 'users'); // tem AuthMiddleware
});
```
**Combinando: middleware do grupo + middleware extra por rota:**
```php
$router->group(['prefix' => '/admin', 'middleware' => AuthMiddleware::class], function ($router) {
// Essa rota tem AuthMiddleware (herdado do grupo)
$router->get('/painel', AdminController::class, 'index');
// Essa rota tem AuthMiddleware (grupo) + AdminOnlyMiddleware (extra individual)
$router->delete('/usuario/{id}', AdminController::class, 'destroy')
->middleware(AdminOnlyMiddleware::class);
});
```
> 💡 A ordem de execução é: **CSRF Global → Middlewares do Grupo → Middleware Individual → Controller**.
### 6. Validação de Dados (Request Validator)
O objeto `Request` possui um validador embutido. Basta passar as regras. Se falhar, o sistema armazena os erros na sessão e volta para a página anterior automaticamente.
**Regras Suportadas Atualmente:**
- `required`: O campo não pode vir vazio.
- `email`: O campo deve ser um endereço de e-mail válido.
- `min:X`: O campo deve ter no mínimo X caracteres (ex: `min:6`).
- `max:X`: O campo deve ter no máximo X caracteres.
- `numeric`: O campo deve ser numérico (aceita decimais).
- `integer`: O campo deve ser um número inteiro.
- `alpha_num`: O campo deve conter apenas letras e números.
**Exemplo de uso no Controller:**
```php
public function salvar(Request $request) {
$dados = $request->validate([
'nome' => 'required',
'email' => 'required|email',
'senha' => 'required|min:6'
]);
// O código abaixo só executa se a validação passar
$this->model->create($dados);
}
```
### 7. Tratamento de Erros e Exceções
O framework possui um `Handler` global (registrado em `public/index.php`).
Se uma exceção ou erro fatal ocorrer:
- Em ambiente local (`APP_ENV=local`), o erro completo (Stack Trace) será exibido na tela.
- Em produção (`APP_ENV=production`), uma página 500 genérica será exibida, e o erro detalhado será salvo no arquivo `storage/logs/app.log`.
---
## 🚀 Iniciando um Novo Projeto
A forma mais rápida de iniciar um novo projeto a partir deste template é via linha de comando:
1. Clone o repositório sem o histórico de commits (mais leve):
```bash
git clone --depth=1 https://github.com/FabioSouzaF/PHP-MVC-Framework NovoProjeto
cd NovoProjeto
```
2. Inicialize o banco de dados e as configurações:
```bash
php console init
```
_O comando `init` irá copiar automaticamente o `.env.example` para `.env`, criar o banco de dados especificado e rodar as Migrations iniciais._
---
## 🔧 Configurando Banco de Dados (Migrations)
O framework agora possui um sistema CLI de **Migrations** próprio para estruturar o banco de dados sem precisar ficar importando arquivos `.sql`.
As credenciais do banco devem ser inseridas no arquivo `.env` na raiz do projeto:
```env
DB_HOST=localhost
DB_NAME=modelo_mvc
DB_USER=root
DB_PASS=
APP_ENV=local # production
```
### Criando o Banco (Executando Migrations)
Após configurar o `.env` e criar o database (o database vazio deve existir no SGBD), rode o seguinte comando no terminal na raiz do projeto:
```bash
php console migrate
```
Ele vai ler a pasta `database/migrations/` e criar as tabelas faltantes.
### Criando novas Migrations
Para criar uma nova tabela, use:
```bash
php console make:migration nome_da_sua_tabela
```
Isso gerará um arquivo na pasta `database/migrations/`. Abra-o e digite o SQL de criação dentro do método `up()`.
### Gerando Código pela CLI
O `console` também automatiza a criação de **Testes Unitários** e **DTOs**, evitando o trabalho manual de criar pastas e boilerplate.
**Criar um Teste Unitário:**
```bash
# Gera: tests/Unit/Core/ValidatorTest.php
php console make:test Core/ValidatorTest
# Gera: tests/Unit/App/DTOs/PedidoDTOTest.php
php console make:test App/DTOs/PedidoDTOTest
```
**Criar um DTO:**
```bash
# Gera: app/Auth/DTOs/UserDTO.php
php console make:dto Auth/UserDTO
```
**Criar um Model:**
```bash
# Model tradicional (SQL puro)
php console make:model Pedidos/PedidoModel
# Model usando ORM ActiveRecord
php console make:model Pedidos/PedidoModel --orm
```
Ambos os comandos criam o arquivo com toda a estrutura de namespace, imports e métodos prontos. Basta preencher os campos específicos.
**Ver todos os comandos disponíveis:**
```bash
php console
```
### Consultando Dados (Models)
A classe `Core\Database\Model` utiliza a conexão feita em `Core\Database\Database` e é ideal para quem prefere escrever **SQL puro**.
```php
namespace App\Auth\Models;
use Core\Database\Model;
use PDO;
class User extends Model
{
public function findByEmail(string $email)
{
$stmt = $this->db->prepare("SELECT * FROM users WHERE email = :email");
$stmt->execute(['email' => $email]);
return $stmt->fetch(PDO::FETCH_ASSOC);
}
}
```
### Consultando Dados via ORM (ActiveRecord)
O framework também fornece um ORM leve opcional. Com ele, você não precisa escrever queries de CRUD ou buscas simples.
```php
namespace App\Auth\Models;
use Core\Database\ORM\ActiveRecord;
class UserORM extends ActiveRecord
{
protected string $table = 'users';
}
```
**Usando o ORM no Controller:**
```php
// Inserir
$user = UserORM::create(['name' => 'João', 'email' => 'joao@teste.com']);
// Buscar por ID
$user = UserORM::find(1);
// Atualizar
$user->name = 'João Editado';
$user->save();
// Buscar com condições e ordenação
$users = UserORM::query()
->where('active', 1)
->orderBy('created_at', 'DESC')
->limit(10)
->get(); // Retorna array de objetos UserORM
// Deletar
$user->delete();
```
> **Integração com DTOs:** O ORM possui um método útil `$userModel->getAsDTO()` e `$user->toDTO()` para converter os resultados diretamente em DTOs tipados.
#### Paginação Nativa
Em vez de buscar todos os registros com `fetchAll`, você pode usar o helper de paginação passando a query crua. Ele lê automaticamente o parâmetro `?page=` da URL.
```php
$resultado = $this->paginate("SELECT * FROM users ORDER BY created_at DESC", [], 15);
// Retorna:
// ['data' => [...], 'current_page' => 1, 'last_page' => 10, 'per_page' => 15, 'total' => 150]
```
#### DTOs — Retorno Tipado do Banco
Em vez de arrays anônimos `array`, você pode usar **Data Transfer Objects (DTOs)** para receber os dados tipados e com autocomplete no editor.
**1. Crie o DTO da entidade** em `app/SeuModulo/DTOs/`:
```php
namespace App\Auth\DTOs;
final class UserDTO
{
public function __construct(
public readonly int $id,
public readonly string $name,
public readonly string $email,
public readonly string $created_at,
) {}
public static function fromArray(array $data): self
{
return new self(
id: (int) $data['id'],
name: (string) ($data['name'] ?? ''),
email: (string) ($data['email'] ?? ''),
created_at: (string) ($data['created_at'] ?? ''),
);
}
}
```
**2. Use `fetchAs()` no Model** passando a classe do DTO:
```php
// Retorna UserDTO[] em vez de array[]
$users = $this->fetchAs("SELECT id, name, email, created_at FROM users", UserDTO::class);
// Com parâmetros:
$users = $this->fetchAs("SELECT * FROM users WHERE active = ?", UserDTO::class, [1]);
// Paginado com DTOs:
$resultado = $this->paginateAs("SELECT * FROM users", UserDTO::class, [], 15);
// $resultado['data'] será um array de UserDTO
```
**Resultado no Controller:**
```php
foreach ($users as $user) {
echo $user->name; // ✅ Autocomplete no editor, erro imediato se errar o nome
}
```
> 💡 DTOs são **100% opcionais**. Você pode continuar usando `fetchAll()` e arrays normais se preferir.
---
## 🧪 Testes Unitários (PHPUnit 13)
O framework possui uma suíte de testes configurada com **PHPUnit 13**. Os testes ficam em `tests/Unit/` e podem ser rodados a qualquer momento.
### Instalação (primeira vez)
```bash
composer install
```
### Rodando os testes
```bash
# Todos os testes
./vendor/bin/phpunit
# Apenas uma classe específica
./vendor/bin/phpunit tests/Unit/Core/ValidatorTest.php
```
### Testes incluídos
| Arquivo | O que testa |
| ------------------------------------- | ---------------------------------------------------------------------- |
| `tests/Unit/Core/ValidatorTest.php` | Todas as regras do `Request::validate()`, mass-assignment, combinações |
| `tests/Unit/Core/RouterTest.php` | Registro de rotas, prefixos de grupo, herança de middlewares |
| `tests/Unit/App/DTOs/UserDTOTest.php` | Conversão `fromArray()`, casting de tipos, propriedades `readonly` |
### Criando novos testes
Extenda a classe `Tests\TestCase` que já inclui helpers prontos:
```php
namespace Tests\Unit\App;
use Tests\TestCase;
class MeuNovoTest extends TestCase
{
public function test_meu_cenario(): void
{
// Helper pronto: cria um Request fake sem precisar de HTTP real
$request = $this->makeRequest(post: ['campo' => 'valor']);
$this->assertSame('valor', $request->post('campo'));
}
}
```
---
## 🏃 Iniciando o Projeto localmente
Para iniciar um servidor rápido pelo PHP sem depender de Apache ou Nginx, rode no terminal:
```bash
php -S localhost:8000 -t public
```
Acesse `http://localhost:8000` no navegador.
---
## 🔭 Roadmap de Funcionalidades Futuras
Curioso sobre o que está por vir? Confira o documento de planejamento com as próximas funcionalidades previstas para o framework, incluindo um **ORM opcional**, mais regras de validação, envio de e-mail e sistema de cache.
👉 [Ver FUTURE_FEATURES.md](./FUTURE_FEATURES.md)