--- 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.