KB-PLUGIN-032/033: segunda conexão DBmysql + comandos de console de plugin (GLPI 11)
Aprendizados validados no desenvolvimento do plugin sync: - KB-PLUGIN-032: subclasse DBmysql read-only para banco externo (gotchas de senha rawurldecode, query() guard, flag connected). - KB-PLUGIN-033: comandos de console de plugin exigem src/ + namespace de nome plugins:<key>:. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
135b621576
commit
f1ec0c6405
3 changed files with 226 additions and 1 deletions
39
index.json
39
index.json
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"last_updated": "2026-06-11",
|
"last_updated": "2026-06-16",
|
||||||
"records": [
|
"records": [
|
||||||
{
|
{
|
||||||
"id": "KB-INFRA-001",
|
"id": "KB-INFRA-001",
|
||||||
|
|
@ -571,6 +571,43 @@
|
||||||
"severity": "high",
|
"severity": "high",
|
||||||
"path": "records/plugin-dev/KB-PLUGIN-031-plugin-dev-workflow-runbook.md",
|
"path": "records/plugin-dev/KB-PLUGIN-031-plugin-dev-workflow-runbook.md",
|
||||||
"summary": "id: KB-PLUGIN-031"
|
"summary": "id: KB-PLUGIN-031"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "KB-PLUGIN-032",
|
||||||
|
"title": "\"GLPI 11 — Segunda conexão de banco read-only via subclasse de DBmysql\"",
|
||||||
|
"domain": "plugin-dev",
|
||||||
|
"tags": [
|
||||||
|
"glpi11",
|
||||||
|
"plugin",
|
||||||
|
"database",
|
||||||
|
"dbmysql",
|
||||||
|
"connection",
|
||||||
|
"read-only",
|
||||||
|
"external-db",
|
||||||
|
"sync"
|
||||||
|
],
|
||||||
|
"status": "active",
|
||||||
|
"severity": "high",
|
||||||
|
"path": "records/plugin-dev/KB-PLUGIN-032-glpi11-second-dbmysql-connection.md",
|
||||||
|
"summary": "id: KB-PLUGIN-032"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "KB-PLUGIN-033",
|
||||||
|
"title": "\"GLPI 11 — Comandos de console de plugin exigem src/ e namespace de nome plugins:<key>:\"",
|
||||||
|
"domain": "plugin-dev",
|
||||||
|
"tags": [
|
||||||
|
"glpi11",
|
||||||
|
"plugin",
|
||||||
|
"console",
|
||||||
|
"symfony",
|
||||||
|
"command",
|
||||||
|
"cli",
|
||||||
|
"gotcha"
|
||||||
|
],
|
||||||
|
"status": "active",
|
||||||
|
"severity": "high",
|
||||||
|
"path": "records/plugin-dev/KB-PLUGIN-033-glpi11-plugin-console-commands.md",
|
||||||
|
"summary": "id: KB-PLUGIN-033"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,98 @@
|
||||||
|
---
|
||||||
|
id: KB-PLUGIN-032
|
||||||
|
title: "GLPI 11 — Segunda conexão de banco read-only via subclasse de DBmysql"
|
||||||
|
domain: plugin-dev
|
||||||
|
tags:
|
||||||
|
- glpi11
|
||||||
|
- plugin
|
||||||
|
- database
|
||||||
|
- dbmysql
|
||||||
|
- connection
|
||||||
|
- read-only
|
||||||
|
- external-db
|
||||||
|
- sync
|
||||||
|
status: active
|
||||||
|
severity: high
|
||||||
|
created_at: 2026-06-16
|
||||||
|
updated_at: 2026-06-16
|
||||||
|
applies_to:
|
||||||
|
- plugins GLPI 11.x que precisam ler de um banco GLPI externo
|
||||||
|
- plugin Sync (ingestão PROD legado → QA)
|
||||||
|
related_records:
|
||||||
|
- KB-PLUGIN-030
|
||||||
|
- KB-PLUGIN-014
|
||||||
|
---
|
||||||
|
|
||||||
|
# GLPI 11 — Segunda conexão de banco via subclasse de DBmysql
|
||||||
|
|
||||||
|
## Contexto
|
||||||
|
|
||||||
|
Um plugin pode precisar ler de um **segundo banco** (ex.: o PROD legado de um
|
||||||
|
cliente) sem tocar na conexão padrão do GLPI (`global $DB`). O core já oferece
|
||||||
|
toda a infraestrutura: basta uma subclasse de `\DBmysql` apontando para o outro
|
||||||
|
host. Reaproveita-se `doQuery()`, `request()`, `fetchAssoc()`, `tableExists()` etc.
|
||||||
|
|
||||||
|
## Como fazer
|
||||||
|
|
||||||
|
`DBmysql::connect()` lê **propriedades de instância**: `$dbhost`, `$dbuser`,
|
||||||
|
`$dbpassword`, `$dbdefault`. O construtor nativo (`__construct($choice)`) chama
|
||||||
|
`connect()` imediatamente — portanto **não chame `parent::__construct()`**:
|
||||||
|
defina as propriedades primeiro e só então chame `$this->connect()`.
|
||||||
|
|
||||||
|
```php
|
||||||
|
namespace GlpiPlugin\Meuplugin;
|
||||||
|
|
||||||
|
use DBmysql;
|
||||||
|
|
||||||
|
class SourceDB extends DBmysql
|
||||||
|
{
|
||||||
|
public function __construct(string $host, string $dbname, string $user, string $password, int $port = 3306, bool $use_utf8mb4 = true)
|
||||||
|
{
|
||||||
|
$this->dbhost = $port > 0 ? $host . ':' . $port : $host;
|
||||||
|
$this->dbuser = $user;
|
||||||
|
$this->dbpassword = rawurlencode($password); // ver gotcha abaixo
|
||||||
|
$this->dbdefault = $dbname;
|
||||||
|
$this->use_utf8mb4 = $use_utf8mb4;
|
||||||
|
$this->connect();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isConnected(): bool
|
||||||
|
{
|
||||||
|
return (bool) $this->connected;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Gotchas confirmados
|
||||||
|
|
||||||
|
1. **Senha é rawurldecoded em `connect()`.** A linha real do core é
|
||||||
|
`real_connect($host, $this->dbuser, rawurldecode($this->dbpassword), ...)`.
|
||||||
|
Portanto guarde a senha **`rawurlencode()`-ada** na propriedade (idêntico ao
|
||||||
|
`config/config_db.php` nativo). Senha com `%`, `+`, etc. quebra se passada crua.
|
||||||
|
|
||||||
|
2. **Host com porta:** `connect()` faz `explode(':', $host)`. Passe `"host:porta"`
|
||||||
|
numa string só; não há parâmetro de porta separado.
|
||||||
|
|
||||||
|
3. **`query()` SEMPRE lança exceção** (`Executing direct queries is not allowed!`)
|
||||||
|
— é um guard, não o executor. Para **SELECT** use **`doQuery()`** (retorna
|
||||||
|
`mysqli_result`) + `fetchAssoc()`/`fetchArray()`, ou `request()` (iterator).
|
||||||
|
Para DDL, ver [KB-PLUGIN-030].
|
||||||
|
|
||||||
|
4. **Sucesso da conexão:** `connect()` seta `$this->connected = true` só em caso de
|
||||||
|
sucesso; em falha permanece `false` sem lançar exceção. Cheque `connected`
|
||||||
|
(a falha de credencial NÃO crasha — retorna conexão não-conectada).
|
||||||
|
|
||||||
|
5. **Defaults seguros** em DBmysql: `dbssl=false`, `use_utf8mb4=false`,
|
||||||
|
`connected=false`. Para ler bancos GLPI 10.0.6+ defina `use_utf8mb4=true`.
|
||||||
|
|
||||||
|
## Validação
|
||||||
|
|
||||||
|
Comprovado no plugin `sync` (2026-06-16): subclasse `SourceDB` conectou a um
|
||||||
|
GLPI 11.0.7 externo via `bin/console plugins:sync:test-connection`, leu
|
||||||
|
`VERSION()`, `glpi_configs` (versão) e `COUNT(*)` de `glpi_tickets`. Senha
|
||||||
|
inválida retornou `connected=false` com exit code 1, sem fatal.
|
||||||
|
|
||||||
|
## Classificação
|
||||||
|
|
||||||
|
- Tipo: padrão de implementação reutilizável.
|
||||||
|
- Reutilização: qualquer plugin com fonte de dados em segundo banco.
|
||||||
|
|
@ -0,0 +1,90 @@
|
||||||
|
---
|
||||||
|
id: KB-PLUGIN-033
|
||||||
|
title: "GLPI 11 — Comandos de console de plugin exigem src/ e namespace de nome plugins:<key>:"
|
||||||
|
domain: plugin-dev
|
||||||
|
tags:
|
||||||
|
- glpi11
|
||||||
|
- plugin
|
||||||
|
- console
|
||||||
|
- symfony
|
||||||
|
- command
|
||||||
|
- cli
|
||||||
|
- gotcha
|
||||||
|
status: active
|
||||||
|
severity: high
|
||||||
|
created_at: 2026-06-16
|
||||||
|
updated_at: 2026-06-16
|
||||||
|
applies_to:
|
||||||
|
- plugins GLPI 11.x que expõem comandos de bin/console
|
||||||
|
related_records:
|
||||||
|
- KB-PLUGIN-031
|
||||||
|
- KB-PLUGIN-032
|
||||||
|
---
|
||||||
|
|
||||||
|
# GLPI 11 — Comandos de console de plugin
|
||||||
|
|
||||||
|
## Como o core descobre os comandos
|
||||||
|
|
||||||
|
`Glpi\Console\CommandLoader::findPluginCommands()` varre, para cada plugin
|
||||||
|
**ativo**, os diretórios `inc/` e `src/`, instanciando classes que sejam
|
||||||
|
`Symfony\Component\Console\Command\Command`. Os prefixos de classe tentados são:
|
||||||
|
|
||||||
|
- `GlpiPlugin\<Ucfirst(key)>\` (namespaced — recomendado), ou
|
||||||
|
- `Plugin<Ucfirst(key)>` (legado não-namespaced), ou
|
||||||
|
- vazio (PSR-4 sem namespace).
|
||||||
|
|
||||||
|
O `src/` do plugin é registrado como PSR-4 `GlpiPlugin\<Ucfirst(key)>\ → src/`
|
||||||
|
(`Plugin.php`, ~linha 406). Então `src/Console/FooCommand.php` deve declarar
|
||||||
|
`namespace GlpiPlugin\<Ucfirst(key)>\Console;`.
|
||||||
|
|
||||||
|
## Regra rígida do nome (a armadilha)
|
||||||
|
|
||||||
|
O nome do comando **DEVE** casar com o padrão `plugins:<key>:<algo>` (regex no
|
||||||
|
core: `^plugins:<key>(:[^:]+)+$`). Se não casar, o core emite
|
||||||
|
`E_USER_WARNING` ("must be moved in the `plugins:<key>` namespace") e **não
|
||||||
|
registra** o comando — ele simplesmente não aparece em `bin/console`.
|
||||||
|
|
||||||
|
```php
|
||||||
|
namespace GlpiPlugin\Sync\Console;
|
||||||
|
|
||||||
|
use Symfony\Component\Console\Command\Command;
|
||||||
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
|
use Symfony\Component\Console\Input\InputOption;
|
||||||
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
|
|
||||||
|
class TestConnectionCommand extends Command
|
||||||
|
{
|
||||||
|
protected function configure(): void
|
||||||
|
{
|
||||||
|
$this->setName('plugins:sync:test-connection'); // <- obrigatório
|
||||||
|
$this->setDescription('...');
|
||||||
|
$this->addOption('host', null, InputOption::VALUE_REQUIRED, '...');
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||||
|
{
|
||||||
|
// ... lógica ...
|
||||||
|
return Command::SUCCESS; // SUCCESS / FAILURE / INVALID
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Notas
|
||||||
|
|
||||||
|
- Estender o `Command` do Symfony direto é suficiente (não exige
|
||||||
|
`Glpi\Console\AbstractCommand`). O Kernel já está bootado quando o comando roda,
|
||||||
|
então classes do core e dos plugins ativos estão disponíveis.
|
||||||
|
- O plugin precisa estar **ativado** para os comandos aparecerem.
|
||||||
|
- Arquivos de teste sob `src/`/`inc/` que casem o padrão também são varridos —
|
||||||
|
manter scripts de teste fora desses dirs (ver `.gitignore` do plugin).
|
||||||
|
|
||||||
|
## Validação
|
||||||
|
|
||||||
|
Comprovado no plugin `sync` (2026-06-16): `plugins:sync:test-connection`
|
||||||
|
registrou e executou; ver [KB-PLUGIN-032] para o conteúdo do comando (segunda
|
||||||
|
conexão DBmysql).
|
||||||
|
|
||||||
|
## Classificação
|
||||||
|
|
||||||
|
- Tipo: gotcha + padrão de implementação.
|
||||||
|
- Reutilização: qualquer plugin com comando de CLI.
|
||||||
Loading…
Reference in a new issue