First Commit
This commit is contained in:
+457
@@ -0,0 +1,457 @@
|
||||
# Manual: Como criar módulos para instalar no SysCMS
|
||||
|
||||
Este manual orienta a criação, estrutura e instalação de módulos compatíveis com o SysCMS, seguindo as diretrizes do projeto.
|
||||
|
||||
## Estrutura de Diretórios do Módulo
|
||||
|
||||
Crie o módulo dentro de `modules/<nome>/` em minúsculo.
|
||||
|
||||
```
|
||||
modules/
|
||||
<nome>/
|
||||
classes/
|
||||
Controllers/
|
||||
Models/
|
||||
Services/
|
||||
routes/
|
||||
web.php
|
||||
api.php
|
||||
views/
|
||||
index.twig
|
||||
admin/
|
||||
locales/
|
||||
pt_BR/
|
||||
en_US/
|
||||
module.json
|
||||
```
|
||||
|
||||
## Arquivo `module.json`
|
||||
|
||||
Define metadados, rotas, resources e providers. Exemplo mínimo (Company):
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "company",
|
||||
"display_name": "Company",
|
||||
"description": "Company management module",
|
||||
"version": "1.0.0",
|
||||
"author": "SysCMS Team",
|
||||
"dependencies": ["auth"],
|
||||
"permissions": [
|
||||
"company.view",
|
||||
"company.create",
|
||||
"company.edit",
|
||||
"company.delete"
|
||||
],
|
||||
"settings": {},
|
||||
"providers": [
|
||||
"Modules\\Company\\Providers\\CompanyServiceProvider"
|
||||
],
|
||||
"routes": {
|
||||
"web": "routes/web.php"
|
||||
},
|
||||
"menu": {
|
||||
"dashboard": {
|
||||
"icon": "bi bi-building",
|
||||
"label": "Companies",
|
||||
"route": "admin/companies",
|
||||
"order": 30
|
||||
}
|
||||
},
|
||||
"views": "views",
|
||||
"locales": "locales",
|
||||
"module_ative": true,
|
||||
"module_public": false,
|
||||
"module_blocked": false
|
||||
}
|
||||
```
|
||||
|
||||
## Registro de Rotas
|
||||
|
||||
Declare as rotas em `routes/web.php` e/ou `routes/api.php` retornando uma função que receba a aplicação e chame `addRoute`.
|
||||
|
||||
```php
|
||||
<?php
|
||||
use Kernel\Application;
|
||||
|
||||
return function (Application $app) {
|
||||
$app->addRoute('GET', '/meu-modulo', [\Modules\MeuModulo\Controllers\HomeController::class, 'index']);
|
||||
$app->addRoute('POST', '/meu-modulo/salvar', [\Modules\MeuModulo\Controllers\HomeController::class, 'store']);
|
||||
};
|
||||
```
|
||||
|
||||
- Carregamento automático de rotas de módulos: `kernel/ModuleLoader.php:142–170`
|
||||
|
||||
## Empacotamento para Instalação (.zip)
|
||||
|
||||
Para que o módulo seja instalável via painel administrativo (`/admin/modules/install`), ele deve ser compactado em um arquivo `.zip`.
|
||||
|
||||
> **⚠️ IMPORTANTE: Geração do ZIP**
|
||||
> Ao criar o arquivo `.zip`, certifique-se de que a estrutura interna não contenha caminhos absolutos ou barras invertidas (`\`) típicas do Windows.
|
||||
>
|
||||
> **Estrutura Correta do ZIP:**
|
||||
> Ao abrir o zip, os arquivos `module.json`, `routes/`, `classes/`, etc., devem estar **na raiz** ou dentro de uma pasta única com o nome do módulo (o instalador trata ambos os casos, mas prefira a raiz para simplicidade).
|
||||
>
|
||||
> **Comando Recomendado (Linux/Mac/WSL):**
|
||||
> ```bash
|
||||
> cd modules/meumodulo
|
||||
> zip -r ../meumodulo.zip .
|
||||
> ```
|
||||
>
|
||||
> **Comando Recomendado (Windows - PowerShell):**
|
||||
> Evite usar `Compress-Archive` diretamente se ele gerar barras invertidas. Prefira usar `tar` ou ferramentas como 7-Zip garantindo o formato ZIP.
|
||||
> ```powershell
|
||||
> cd modules/meumodulo
|
||||
> tar -a -c -f ../../meumodulo.zip *
|
||||
> ```
|
||||
|
||||
## Banco de Dados e Migrações
|
||||
|
||||
Todo módulo deve conter suas próprias migrações na pasta `database/migrations/`.
|
||||
|
||||
> **⚠️ REGRA CRÍTICA 1: Imutabilidade do Core**
|
||||
> Um novo módulo **NUNCA** deve modificar a estrutura (schema) de tabelas existentes do sistema (Core ou outros módulos). Ele deve criar apenas suas próprias tabelas. Se precisar de relacionamento, use chaves estrangeiras sem alterar a tabela referenciada.
|
||||
>
|
||||
> **⚠️ REGRA CRÍTICA 2: Independência na Instalação**
|
||||
> As migrações (especialmente a de inserção do módulo) **NUNCA** devem depender de Classes/Models de outros módulos (ex: `Modules\Company\Models\Company`), pois eles podem não estar carregados no momento da instalação.
|
||||
>
|
||||
> **Forma Correta:** Use `Capsule::table('nome_tabela')->insert(...)` para inserir dados em tabelas de outros módulos.
|
||||
>
|
||||
> **Exemplo Incorreto (NÃO FAÇA):**
|
||||
> ```php
|
||||
> $company = \Modules\Company\Models\Company::find(1); // Erro se o módulo Company não estiver carregado
|
||||
> ```
|
||||
>
|
||||
> **Exemplo Correto:**
|
||||
> ```php
|
||||
> Capsule::table('bus')->where('bus_id', 1)->update([...]);
|
||||
> ```
|
||||
|
||||
### 1. Criação de Tabelas
|
||||
Crie arquivos de migração para definir a estrutura do banco de dados do módulo. Exemplo: `001_create_meumodulo_tables.php`.
|
||||
|
||||
```php
|
||||
use Illuminate\Database\Capsule\Manager as Capsule;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
|
||||
class CreateMeuModuloTables
|
||||
{
|
||||
public function up()
|
||||
{
|
||||
if (!Capsule::schema()->hasTable('meumodulo_items')) {
|
||||
Capsule::schema()->create('meumodulo_items', function (Blueprint $table) {
|
||||
$table->increments('id');
|
||||
$table->string('title');
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public function down()
|
||||
{
|
||||
Capsule::schema()->dropIfExists('meumodulo_items');
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Registro do Módulo (Instalação)
|
||||
É obrigatório criar uma migração para registrar o módulo no sistema (tabelas `modules` e `bus_modules`). Isso garante que o módulo seja reconhecido e ativado após a instalação. Exemplo: `002_insert_meumodulo_record.php`.
|
||||
|
||||
```php
|
||||
use Illuminate\Database\Capsule\Manager as Capsule;
|
||||
use App\Models\Module;
|
||||
use Modules\Company\Models\Company;
|
||||
|
||||
class InsertMeuModuloRecord
|
||||
{
|
||||
public function up()
|
||||
{
|
||||
$moduleData = [
|
||||
'modules_name' => 'meumodulo',
|
||||
'modules_display_name' => 'Meu Módulo',
|
||||
'modules_version' => '1.0.0',
|
||||
// ... outros campos do module.json ...
|
||||
];
|
||||
|
||||
$existingModule = Module::where('modules_name', 'meumodulo')->first();
|
||||
|
||||
if (!$existingModule) {
|
||||
$module = Module::create($moduleData);
|
||||
|
||||
// Associar a todas as empresas existentes
|
||||
if (Capsule::schema()->hasTable('bus')) {
|
||||
$companies = Company::all();
|
||||
foreach ($companies as $company) {
|
||||
// Lógica de inserção na tabela bus_modules
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function down()
|
||||
{
|
||||
// Lógica para remover o módulo
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Controllers
|
||||
|
||||
Coloque controllers em `classes/Controllers/`. Exemplo:
|
||||
|
||||
```php
|
||||
<?php
|
||||
namespace Modules\MeuModulo\Controllers;
|
||||
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class HomeController
|
||||
{
|
||||
public function index(Request $request): Response
|
||||
{
|
||||
return new Response((new \Kernel\View())->render('meumodulo/views/index.twig', [
|
||||
'title' => 'Meu Módulo'
|
||||
]));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Views e Locales
|
||||
|
||||
- Views: declare pasta em `module.json` via chave `views` e renderize com o namespace `<modulo>/views/...`.
|
||||
- Locales: declare pasta em `module.json` via chave `locales`. O sistema registra e utiliza `Translator`.
|
||||
|
||||
- Registro automático: `kernel/ModuleLoader.php:175–192`
|
||||
|
||||
## Service Providers
|
||||
|
||||
Use providers para inicializar integrações, hooks, listeners e configurações dinâmicas do core (como rotas públicas). Registre-os em `module.json` na chave `providers`.
|
||||
|
||||
### Exemplo Completo de ServiceProvider
|
||||
|
||||
```php
|
||||
<?php
|
||||
namespace Modules\MeuModulo\Providers;
|
||||
|
||||
use Kernel\Application;
|
||||
use Kernel\View;
|
||||
|
||||
class MeuModuloServiceProvider
|
||||
{
|
||||
public function register(): void {}
|
||||
|
||||
public function boot(): void
|
||||
{
|
||||
$app = Application::getInstance();
|
||||
|
||||
// 1. Registrar rotas públicas (que não exigem login)
|
||||
$app->addPublicPath('/meu-modulo/landing-page');
|
||||
$app->addPublicPath('/meu-modulo/public-api/*');
|
||||
|
||||
// 2. Registrar Middlewares
|
||||
// $app->addGlobalMiddleware(new \Modules\MeuModulo\Middleware\GlobalGuard());
|
||||
|
||||
// 3. Registrar Hooks
|
||||
add_action('meumodulo.evento', function ($payload) {
|
||||
// lógica
|
||||
});
|
||||
|
||||
add_filter('meumodulo.sanitize', function ($value) {
|
||||
return trim(strip_tags((string)$value));
|
||||
});
|
||||
|
||||
// 4. Injetar variáveis globais no Twig
|
||||
try {
|
||||
$view = new View();
|
||||
$view->getEngine()->addGlobal('meu_modulo_config', ['active' => true]);
|
||||
} catch (\Throwable $e) {
|
||||
// Ignorar erros se view não estiver pronta
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- API de Hooks: `COMO_USAR_HOOK.md`
|
||||
- HookManager disponível em: `kernel/HookManager.php`
|
||||
- Exposição via aplicação: `kernel/Application.php:50–58` (getHookManager)
|
||||
- **IMPORTANTE**: Use o Service Provider para evitar alterar arquivos do core (`Application.php`).
|
||||
|
||||
## Migrations
|
||||
|
||||
Para compatibilidade com o comando de migração, coloque migrations em `app/database/migrations/`.
|
||||
|
||||
- Exemplo de criação de tabela:
|
||||
|
||||
```php
|
||||
<?php
|
||||
use Illuminate\Database\Capsule\Manager as Capsule;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
|
||||
class CreateMeuModuloTable
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
if (!Capsule::schema()->hasTable('meumodulo')) {
|
||||
Capsule::schema()->create('meumodulo', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('title');
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Capsule::schema()->dropIfExists('meumodulo');
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- Para registrar o módulo na tabela `modules`, crie uma migration que insere o registro (recomendado na pasta principal `app/database/migrations`).
|
||||
|
||||
### Exemplo: Registrar o módulo Company
|
||||
|
||||
Crie um arquivo como `0xx_register_company_module.php` em `app/database/migrations/` com o conteúdo abaixo:
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
use App\Models\Module;
|
||||
use Carbon\Carbon;
|
||||
|
||||
class RegisterCompanyModule
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
if (!Module::where('module_name', 'company')->exists()) {
|
||||
Module::create([
|
||||
'module_name' => 'company',
|
||||
'module_display_name' => 'Company',
|
||||
'module_description' => 'Company management module',
|
||||
'module_version' => '1.0.0',
|
||||
'module_author' => 'SysCMS Team',
|
||||
'module_ative' => true,
|
||||
'module_public' => false,
|
||||
'module_blocked' => false,
|
||||
'module_dependencies' => ['auth'],
|
||||
'module_permissions' => [
|
||||
'company.view',
|
||||
'company.create',
|
||||
'company.edit',
|
||||
'company.delete'
|
||||
],
|
||||
'module_settings' => [
|
||||
'menu' => [
|
||||
'dashboard' => [
|
||||
'icon' => 'bi bi-building',
|
||||
'label' => 'Companies',
|
||||
'route' => 'admin/companies',
|
||||
'order' => 30
|
||||
]
|
||||
]
|
||||
],
|
||||
'module_path' => 'modules/company',
|
||||
'module_namespace' => 'Modules\\Company',
|
||||
'module_icon' => 'bi bi-building',
|
||||
'module_order' => 30,
|
||||
'module_install_date' => Carbon::now(),
|
||||
'module_update_date' => Carbon::now()
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
$module = Module::where('module_name', 'company')->first();
|
||||
if ($module) {
|
||||
$module->delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Depois, execute:
|
||||
|
||||
```bash
|
||||
php syscli migrate
|
||||
```
|
||||
|
||||
## Permissões e Menu
|
||||
|
||||
- Defina permissões no `module.json` (`permissions`).
|
||||
- Defina itens de menu no `module.json` (`menu`), usando ícones somente do Bootstrap Icons.
|
||||
- Regra: `RULES.md:40–74`
|
||||
|
||||
## Instalação do Módulo
|
||||
|
||||
1. Copie o diretório do módulo para `modules/<nome>/`.
|
||||
2. Garanta que `module.json` está correto.
|
||||
3. Adicione migrations necessárias em `app/database/migrations/`.
|
||||
4. Execute migrações:
|
||||
- `php syscli migrate`
|
||||
5. Verifique rotas carregadas e acessíveis.
|
||||
6. Se o módulo requer habilitação por empresa, utilize o painel ou scripts próprios para vincular em `bus_modules` (conforme política da aplicação).
|
||||
|
||||
- Carregamento de módulos: `kernel/ModuleLoader.php:184–192` (loadAll)
|
||||
- Roteamento da aplicação: `kernel/Application.php:199–217`
|
||||
|
||||
## Empacotamento do Módulo (.zip)
|
||||
|
||||
Ao criar um novo módulo, é **obrigatório** criar também o arquivo `.zip` correspondente para permitir a instalação via painel administrativo.
|
||||
|
||||
**Regras para o ZIP:**
|
||||
1. O arquivo `module.json` deve estar na raiz do arquivo compactado (não dentro de uma pasta com o nome do módulo).
|
||||
2. O nome do arquivo zip deve corresponder ao nome do módulo (ex: `modelo.zip`).
|
||||
|
||||
**Comando sugerido (PowerShell):**
|
||||
```powershell
|
||||
Compress-Archive -Path modules/nome_do_modulo/* -DestinationPath nome_do_modulo.zip -Force
|
||||
```
|
||||
|
||||
## Instalador via Dashboard (Super Admin)
|
||||
|
||||
- Acesso: `GET /admin/modules/install` mapeado em `routes/web.php:41–45` e renderiza `templates/modules/install.twig` (`templates/modules/install.twig:1–5`).
|
||||
- Formulário: `POST /admin/modules/install` com `multipart/form-data`; campos `module_zip` (arquivo `.zip`) e opcional `enable_for_all` (`templates/modules/install.twig:44–59`).
|
||||
- Validações: tipo de arquivo ZIP e presença de `module.json` (`app/app/Controllers/AdminController.php:215–224`, `app/app/Controllers/AdminController.php:291–300`). Campos obrigatórios em `module.json`: `name`, `version`, `description` (`app/app/Controllers/AdminController.php:323–332`).
|
||||
- Instalação: extração para diretório temporário, cópia para `modules/<name>`, execução de migrations em `database/migrations` se existirem (`app/app/Controllers/AdminController.php:230–239`, `app/app/Controllers/AdminController.php:252–258`, `app/app/Controllers/AdminController.php:393–416`).
|
||||
- Registro: criação do registro em `modules` com metadados do `module.json` (`app/app/Controllers/AdminController.php:461–484`).
|
||||
- Habilitação: se marcado `enable_for_all`, habilita o módulo para todas as empresas (`app/app/Controllers/AdminController.php:492–498`).
|
||||
- Pós-instalação: limpeza de temporários e exibição de mensagens de sucesso/erro via flash (`app/app/Controllers/AdminController.php:267–283`).
|
||||
- Correção de menus: `GET /admin/modules/fix-menus` atualiza `module_settings.menu` consultando `module.json` (`routes/web.php:41–45`, `app/app/Controllers/AdminController.php:423–453`).
|
||||
|
||||
### Estrutura exigida no ZIP
|
||||
|
||||
- `module.json` com configurações do módulo.
|
||||
- `classes/` com controladores, modelos e serviços.
|
||||
- `views/` com templates Twig.
|
||||
- `database/migrations/` com migrações (opcional).
|
||||
|
||||
|
||||
## Hooks Recomendados
|
||||
|
||||
- `meumodulo.installed`: após instalação concluída (migration + registro).
|
||||
- `meumodulo.uninstalled`: antes de remover dados.
|
||||
- `meumodulo.updated`: após atualização de versão.
|
||||
|
||||
## Boas Práticas
|
||||
|
||||
- Seguir `RULES.md` e usar somente Bootstrap Icons.
|
||||
- Não expor segredos; use `.env`.
|
||||
- Respeitar estrutura e namespaces: `Modules\\<Nome>\\...` e pasta `modules/<nome>/` em minúsculo.
|
||||
- Validar entrada de dados, usar filters para sanitização.
|
||||
- Manter compatibilidade com os middlewares da aplicação;
|
||||
- Rotas públicas devem estar na lista pública se necessário (ver `kernel/Application.php:279–311`).
|
||||
|
||||
## Verificação
|
||||
|
||||
- Após instalar, acesse rotas do módulo e verifique renderização.
|
||||
- Se aplicável, execute testes (`composer test`).
|
||||
- Cheque logs em `logs/app.log` para erros em providers/hooks.
|
||||
|
||||
## Exemplo Real (Company)
|
||||
|
||||
- Rotas admin do módulo: `modules/company/routes/web.php:6–13`
|
||||
- Controller lista e gerencia empresas: `modules/company/classes/Controllers/CompanyController.php:14–22` (index) e `35–82` (store)
|
||||
- Views admin: `modules/company/views/admin/index.twig:13–23` (header) e tabela `25–71`
|
||||
- `module.json` configurado com rotas, views e menu: `modules/company/module.json:1–30`
|
||||
Reference in New Issue
Block a user