--- name: perfex-database description: Operações de base de dados em módulos Perfex CRM — db_prefix(), options, queries e Active Record. Baseado apenas na documentação oficial. --- # /perfex-database - Base de Dados Perfex CRM Operações de base de dados em módulos. **Zero assumptions, zero hallucinations** - apenas documentação oficial. --- ## Documentação Base - [Module Basics](https://help.perfexcrm.com/module-basics/) - [CodeIgniter Query Builder](https://codeigniter.com/userguide3/database/query_builder.html) --- ## Regra Fundamental: db_prefix() **SEMPRE usar `db_prefix()` em todas as queries.** O Perfex CRM suporta prefixos de tabela customizados. O default é `tbl`, mas pode ser alterado. ```php // CORRECTO $CI->db->get(db_prefix() . 'meu_modulo'); // ERRADO - quebra se prefix não for "tbl" $CI->db->get('tblmeu_modulo'); ``` --- ## Aceder à Base de Dados ### Em Controllers/Models ```php // Acesso directo $this->db->get(db_prefix() . 'clients'); ``` ### Fora de Controllers/Models ```php $CI = &get_instance(); $CI->db->get(db_prefix() . 'clients'); ``` --- ## Criar Tabelas (Activation Hook) ```php function meu_modulo_activation_hook() { $CI = &get_instance(); // Verificar se tabela existe if (!$CI->db->table_exists(db_prefix() . 'meu_modulo')) { $CI->db->query(' CREATE TABLE `' . db_prefix() . 'meu_modulo` ( `id` INT(11) UNSIGNED NOT NULL AUTO_INCREMENT, `client_id` INT(11) UNSIGNED NOT NULL, `name` VARCHAR(255) NOT NULL, `description` TEXT, `amount` DECIMAL(15,2) NOT NULL DEFAULT 0.00, `status` VARCHAR(50) NOT NULL DEFAULT "pending", `created_at` DATETIME NOT NULL, `updated_at` DATETIME DEFAULT NULL, PRIMARY KEY (`id`), KEY `client_id` (`client_id`), KEY `status` (`status`) ) ENGINE=InnoDB DEFAULT CHARSET=' . $CI->db->char_set . '; '); } } ``` --- ## Query Builder (Active Record) ### SELECT ```php // Todos os registos $result = $this->db->get(db_prefix() . 'meu_modulo')->result(); // Com condições $this->db->where('status', 'active'); $this->db->where('client_id', $client_id); $result = $this->db->get(db_prefix() . 'meu_modulo')->result(); // Seleccionar campos específicos $this->db->select('id, name, amount'); $this->db->where('status', 'active'); $result = $this->db->get(db_prefix() . 'meu_modulo')->result(); // Um registo $this->db->where('id', $id); $row = $this->db->get(db_prefix() . 'meu_modulo')->row(); // Com LIKE $this->db->like('name', $search); $result = $this->db->get(db_prefix() . 'meu_modulo')->result(); // Com ORDER BY $this->db->order_by('created_at', 'DESC'); $result = $this->db->get(db_prefix() . 'meu_modulo')->result(); // Com LIMIT $this->db->limit(10, 0); // 10 registos, offset 0 $result = $this->db->get(db_prefix() . 'meu_modulo')->result(); // JOIN $this->db->select('m.*, c.company as client_name'); $this->db->from(db_prefix() . 'meu_modulo as m'); $this->db->join(db_prefix() . 'clients as c', 'c.userid = m.client_id', 'left'); $result = $this->db->get()->result(); ``` ### INSERT ```php $data = [ 'client_id' => $client_id, 'name' => $this->input->post('name'), 'description' => $this->input->post('description'), 'amount' => $this->input->post('amount'), 'status' => 'pending', 'created_at' => date('Y-m-d H:i:s'), ]; $this->db->insert(db_prefix() . 'meu_modulo', $data); $insert_id = $this->db->insert_id(); ``` ### UPDATE ```php $data = [ 'name' => $this->input->post('name'), 'description' => $this->input->post('description'), 'amount' => $this->input->post('amount'), 'updated_at' => date('Y-m-d H:i:s'), ]; $this->db->where('id', $id); $this->db->update(db_prefix() . 'meu_modulo', $data); // Verificar se actualizou $affected = $this->db->affected_rows(); ``` ### DELETE ```php $this->db->where('id', $id); $this->db->delete(db_prefix() . 'meu_modulo'); ``` ### COUNT ```php // Contar todos $count = $this->db->count_all(db_prefix() . 'meu_modulo'); // Contar com condições $this->db->where('status', 'active'); $count = $this->db->count_all_results(db_prefix() . 'meu_modulo'); ``` --- ## Sistema de Opções Para guardar configurações simples (key-value). ### add_option() Cria opção nova. **NÃO sobrescreve se existir.** ```php add_option('meu_modulo_enabled', '1'); add_option('meu_modulo_config', serialize(['key' => 'value'])); // Com autoload (0 = não, 1 = sim) add_option('meu_modulo_setting', 'valor', 0); ``` ### get_option() Obtém valor da opção. ```php $enabled = get_option('meu_modulo_enabled'); $config = unserialize(get_option('meu_modulo_config')); // Retorna string vazia se não existir if (get_option('meu_modulo_setting') === '') { // Opção não existe } ``` ### update_option() Actualiza ou cria opção (v2.3.3+). ```php update_option('meu_modulo_enabled', '0'); update_option('meu_modulo_last_sync', date('Y-m-d H:i:s')); ``` ### delete_option() Remove opção. ```php delete_option('meu_modulo_enabled'); ``` ### Boas Práticas Opções ```php // SEMPRE prefixar com nome do módulo add_option('meu_modulo_version', '1.0.0'); add_option('meu_modulo_api_key', ''); add_option('meu_modulo_enabled', '1'); // NUNCA usar nomes genéricos add_option('enabled', '1'); // ERRADO add_option('api_key', '...'); // ERRADO ``` --- ## Model Exemplo Completo ```php table = db_prefix() . 'meu_modulo'; } /** * Obter todos os registos */ public function get_all($where = []) { if (!empty($where)) { $this->db->where($where); } $this->db->order_by('created_at', 'DESC'); return $this->db->get($this->table)->result(); } /** * Obter por ID */ public function get($id) { $this->db->where('id', $id); return $this->db->get($this->table)->row(); } /** * Obter por cliente */ public function get_by_client($client_id) { $this->db->where('client_id', $client_id); $this->db->order_by('created_at', 'DESC'); return $this->db->get($this->table)->result(); } /** * Criar novo registo */ public function add($data) { $data['created_at'] = date('Y-m-d H:i:s'); // Trigger hook antes $data = hooks()->apply_filters('before_meu_modulo_added', $data); $this->db->insert($this->table, $data); $insert_id = $this->db->insert_id(); if ($insert_id) { // Trigger hook após hooks()->do_action('after_meu_modulo_added', $insert_id); log_activity('Meu Módulo criado [ID: ' . $insert_id . ']'); } return $insert_id; } /** * Actualizar registo */ public function update($id, $data) { $data['updated_at'] = date('Y-m-d H:i:s'); // Trigger hook antes $data = hooks()->apply_filters('before_meu_modulo_updated', $data, $id); $this->db->where('id', $id); $this->db->update($this->table, $data); if ($this->db->affected_rows() > 0) { // Trigger hook após hooks()->do_action('after_meu_modulo_updated', $id); log_activity('Meu Módulo actualizado [ID: ' . $id . ']'); return true; } return false; } /** * Apagar registo */ public function delete($id) { // Trigger hook antes hooks()->do_action('before_meu_modulo_deleted', $id); $this->db->where('id', $id); $this->db->delete($this->table); if ($this->db->affected_rows() > 0) { // Trigger hook após hooks()->do_action('after_meu_modulo_deleted', $id); log_activity('Meu Módulo apagado [ID: ' . $id . ']'); return true; } return false; } /** * Contar por status */ public function count_by_status($status) { $this->db->where('status', $status); return $this->db->count_all_results($this->table); } /** * Pesquisa */ public function search($term) { $this->db->group_start(); $this->db->like('name', $term); $this->db->or_like('description', $term); $this->db->group_end(); return $this->db->get($this->table)->result(); } } ``` --- ## Transações Para operações que devem ser atómicas: ```php $this->db->trans_begin(); try { $this->db->insert(db_prefix() . 'meu_modulo', $data1); $id = $this->db->insert_id(); $this->db->insert(db_prefix() . 'meu_modulo_items', [ 'parent_id' => $id, 'item' => $item, ]); if ($this->db->trans_status() === false) { $this->db->trans_rollback(); return false; } $this->db->trans_commit(); return $id; } catch (Exception $e) { $this->db->trans_rollback(); log_activity('Erro transação: ' . $e->getMessage()); return false; } ``` --- ## Funções Úteis ### log_activity() Registar actividade no sistema. ```php log_activity('Descrição da actividade'); log_activity('Item criado [ID: ' . $id . ', Cliente: ' . $client_id . ']'); ``` ### Datas ```php // Data actual formatada $now = date('Y-m-d H:i:s'); // Formatar para display $formatted = _d($row->created_at); // Data $formatted = _dt($row->created_at); // Data e hora ``` --- ## Anti-Patterns (NUNCA FAZER) | Anti-Pattern | Risco | Alternativa | |--------------|-------|-------------| | Query sem db_prefix() | Falha em instalações custom | `db_prefix()` sempre | | SQL directo com input user | SQL Injection | Query Builder | | Opções sem prefixo módulo | Conflitos | Prefixar sempre | | Não usar transações em multi-insert | Dados inconsistentes | `trans_begin/commit` | | Hardcode charset | Problemas encoding | `$CI->db->char_set` | --- ## Checklist Database ``` 1. [ ] db_prefix() em TODAS as queries 2. [ ] $CI->db->char_set na criação de tabelas 3. [ ] Opções com prefixo do módulo 4. [ ] Query Builder para inputs de utilizador 5. [ ] Transações para operações múltiplas 6. [ ] log_activity() para auditoria 7. [ ] Hooks em operações CRUD 8. [ ] Índices em colunas de pesquisa ``` --- **Versão:** 1.0.0 | **Autor:** Descomplicar® **Fonte:** help.perfexcrm.com/module-basics