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>
98 lines
3.4 KiB
Markdown
98 lines
3.4 KiB
Markdown
---
|
|
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.
|