Usuário Customizado
Como substituir o módulo Usuario padrão pelo seu próprio, com campos específicos da sua aplicação, mantendo o módulo Auth funcionando.
Como funciona
O módulo Auth não depende da entidade Usuario — ele depende de duas interfaces do kernel:
| Interface | Responsabilidade |
|---|---|
AuthenticatableInterface | Contrato que sua entidade de usuário deve implementar |
UserRepositoryInterface | Contrato que seu repositório deve implementar |
Basta implementar essas duas interfaces e registrar seu repositório no container — o Auth passa a usar sua tabela automaticamente. Login, refresh token, recuperação de senha e verificação de e-mail funcionam sem nenhuma alteração.
Você não precisa removê-lo. O binding de UserRepositoryInterface no container é sobrescrito pelo seu provider — o Auth usa o último binding registrado.
Estrutura do módulo
src/Modules/MeuApp/
├── Entities/
│ └── MeuUsuario.php ← sua entidade (implementa AuthenticatableInterface)
├── Repositories/
│ └── MeuUsuarioRepository.php ← seu repositório (implementa UserRepositoryInterface)
├── Database/migrations/
│ └── 001_criar_tabela_usuarios.php
├── Providers/
│ └── MeuAppServiceProvider.php ← registra no container
└── Routes/
└── web.php
1. Entidade
Implemente AuthenticatableInterface e adicione seus campos específicos:
namespace Src\Modules\MeuApp\Entities;
use Ramsey\Uuid\Uuid;
use Ramsey\Uuid\UuidInterface;
use Src\Kernel\Contracts\AuthenticatableInterface;
final class MeuUsuario implements AuthenticatableInterface
{
private function __construct(
private readonly UuidInterface $uuid,
private string $email,
private string $username,
private string $senhaHash,
private string $role,
private bool $ativo,
private bool $emailVerificado,
private ?int $senhaAlteradaEm,
private ?string $tokenRecuperacaoSenha,
private ?string $tokenVerificacaoEmail,
// ── Seus campos específicos ──────────────────
private string $nomeCompleto,
private string $cpf,
private string $plano,
) {}
// ── AuthenticatableInterface (obrigatório) ────────
public function getAuthId(): string { return $this->uuid->toString(); }
public function getAuthEmail(): string { return $this->email; }
public function getAuthUsername(): ?string { return $this->username; }
public function getAuthRole(): string { return $this->role; }
public function isAtivo(): bool { return $this->ativo; }
public function isEmailVerificado(): bool { return $this->emailVerificado; }
public function getSenhaAlteradaEm(): ?int { return $this->senhaAlteradaEm; }
public function verificarSenha(string $senhaPlana): bool
{
return password_verify($senhaPlana, $this->senhaHash);
}
// ── Seus getters específicos ──────────────────────
public function getCpf(): string { return $this->cpf; }
public function getPlano(): string { return $this->plano; }
}
2. Repositório
Implemente UserRepositoryInterface apontando para sua tabela:
namespace Src\Modules\MeuApp\Repositories;
use PDO;
use Src\Kernel\Contracts\UserRepositoryInterface;
use Src\Modules\MeuApp\Entities\MeuUsuario;
class MeuUsuarioRepository implements UserRepositoryInterface
{
private const TABELA = 'meu_app_usuarios';
public function __construct(private readonly PDO $pdo) {}
public function buscarPorEmail(string $email): ?MeuUsuario
{
$stmt = $this->pdo->prepare(
'SELECT * FROM ' . self::TABELA . ' WHERE email = ? LIMIT 1'
);
$stmt->execute([mb_strtolower(trim($email))]);
$row = $stmt->fetch(PDO::FETCH_ASSOC);
return $row ? $this->hidratar($row) : null;
}
public function buscarPorUsername(string $username): ?MeuUsuario
{
$stmt = $this->pdo->prepare(
'SELECT * FROM ' . self::TABELA . ' WHERE username = ? LIMIT 1'
);
$stmt->execute([mb_strtolower(trim($username))]);
$row = $stmt->fetch(PDO::FETCH_ASSOC);
return $row ? $this->hidratar($row) : null;
}
// ... demais métodos da interface (buscarPorUuid, salvarToken, etc.)
private function hidratar(array $row): MeuUsuario
{
return MeuUsuario::reconstituir(/* ... */);
}
}
3. Service Provider
Registre seu repositório no container. O Auth vai usá-lo automaticamente:
namespace Src\Modules\MeuApp\Providers;
use Src\Kernel\Contracts\ContainerInterface;
use Src\Kernel\Contracts\ModuleProviderInterface;
use Src\Kernel\Contracts\RouterInterface;
use Src\Kernel\Contracts\UserRepositoryInterface;
use Src\Modules\MeuApp\Repositories\MeuUsuarioRepository;
class MeuAppServiceProvider implements ModuleProviderInterface
{
public function getName(): string { return 'MeuApp'; }
public function boot(ContainerInterface $container): void
{
// Registra seu repositório — o Auth usa este binding automaticamente
$container->bind(
UserRepositoryInterface::class,
fn() => new MeuUsuarioRepository($container->make(\PDO::class)),
true // singleton
);
}
public function registerRoutes(RouterInterface $router): void
{
$routesFile = __DIR__ . '/../Routes/web.php';
if (is_file($routesFile)) {
require $routesFile;
}
}
public function describe(): array
{
return ['description' => 'Módulo principal com usuários customizados.', 'version' => '1.0.0', 'routes' => []];
}
}
4. Migration
Crie a tabela com os campos do kernel (obrigatórios para o Auth) mais os seus campos específicos:
// src/Modules/MeuApp/Database/migrations/001_criar_tabela_usuarios.php
return [
'up' => function (PDO $pdo): void {
$driver = $pdo->getAttribute(PDO::ATTR_DRIVER_NAME);
if ($driver === 'pgsql') {
$pdo->exec("
CREATE TABLE IF NOT EXISTS meu_app_usuarios (
uuid UUID PRIMARY KEY DEFAULT gen_random_uuid(),
email VARCHAR(254) NOT NULL UNIQUE,
username VARCHAR(64) NOT NULL UNIQUE,
senha_hash VARCHAR(255) NOT NULL,
role VARCHAR(50) NOT NULL DEFAULT 'usuario',
ativo BOOLEAN NOT NULL DEFAULT TRUE,
email_verificado BOOLEAN NOT NULL DEFAULT FALSE,
senha_alterada_em INTEGER,
token_recuperacao_senha VARCHAR(255),
token_verificacao_email VARCHAR(255),
-- seus campos específicos:
nome_completo VARCHAR(255) NOT NULL,
cpf VARCHAR(14),
plano VARCHAR(50) NOT NULL DEFAULT 'free',
criado_em TIMESTAMPTZ NOT NULL DEFAULT NOW()
)
");
} else {
$pdo->exec("
CREATE TABLE IF NOT EXISTS meu_app_usuarios (
uuid CHAR(36) PRIMARY KEY,
email VARCHAR(254) NOT NULL UNIQUE,
username VARCHAR(64) NOT NULL UNIQUE,
senha_hash VARCHAR(255) NOT NULL,
role VARCHAR(50) NOT NULL DEFAULT 'usuario',
ativo TINYINT(1) NOT NULL DEFAULT 1,
email_verificado TINYINT(1) NOT NULL DEFAULT 0,
senha_alterada_em INT UNSIGNED,
token_recuperacao_senha VARCHAR(255),
token_verificacao_email VARCHAR(255),
-- seus campos específicos:
nome_completo VARCHAR(255) NOT NULL,
cpf VARCHAR(14),
plano VARCHAR(50) NOT NULL DEFAULT 'free',
criado_em DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
");
}
},
'down' => function (PDO $pdo): void {
$pdo->exec("DROP TABLE IF EXISTS meu_app_usuarios");
},
];
5. Rodar a migration
php vupi migrate
Após registrar o provider, o endpoint POST /api/login já autentica contra sua tabela. Recuperação de senha, refresh token e verificação de e-mail funcionam automaticamente.
Campos obrigatórios na tabela
O módulo Auth precisa destes campos para funcionar. Os demais são livres:
| Coluna | Tipo | Usado pelo Auth para |
|---|---|---|
uuid | UUID / CHAR(36) | Identificar o usuário no JWT (sub) |
email | VARCHAR(254) | Login por e-mail |
username | VARCHAR(64) | Login por username |
senha_hash | VARCHAR(255) | Verificar senha (password_verify) |
role | VARCHAR(50) | Controle de acesso (RBAC) |
ativo | BOOLEAN | Bloquear contas desativadas |
email_verificado | BOOLEAN | Política de verificação de e-mail |
senha_alterada_em | INTEGER (unix) | Invalidar tokens antigos após troca de senha |
token_recuperacao_senha | VARCHAR(255) | Fluxo de recuperação de senha |
token_verificacao_email | VARCHAR(255) | Fluxo de verificação de e-mail |