Adicione suporte a Full Text Search do Postgres em sua aplicação CakePHP.
- PHP 7.2+
- CakePHP 4.2.2+
- PostgreSQL 9.6+
Inclua o plugin como dependência
composer require autopage/pg-search
Para ter todos os recursos disponíveis, você deve configurar sua aplicação para usar o Driver
fornecido aqui na conexão com o banco de dados. Ele habilitará o uso do TableSchema
e PostgresSchemaDialect
incluidos no plugin que estendem as versões padrão do CakePHP para implementar o tipo de coluna tsvector
e aos índices do tipo gin
e gist
.
No seu app_local.php
:
// ...
'Datasources' => [
'default' => [
'className' => \Cake\Database\Connection::class,
'driver' => \Autopage\PgSearch\Database\Driver\Postgres::class,
// O restante da sua configuração vem normalmente
// ...
],
// ...
Outra configuração importante, mas opcional, é definir qual configuração de busca o PostgreSQL deve usar na hora de indexar ou buscar um campo do tipo tsvector
.
Você pode configurar ela com a chave PgSearch.config_name
. Supondo que você criou no seu banco de dados uma configuração de nome portuguese, o seu app_local.php
ficaria:
// ...
'PgSearch' => [
'config_name' => 'portuguese',
],
// ...
Associe as tabelas que deseja tornar pesquisável ao behavior, desta forma, sempre que um registro for criado/editado/excluído, as informações serão propagadas para a tabela de busca.
/**
* Método de inicialização da Table
*
* @param array $config The configuration for the Table.
* @return void
*/
public function initialize(array $config): void
{
parent::initialize($config);
$this->addBehavior('Searchable', [
'foreign_key' => 'origem_id',
'mapper' => function ($entidade) {
return [
'origem_id' => $entidade->id,
'conteudo' => $entidade->descricao,
];
},
]);
}
- target:
string
. Nome do repositório (Table
) que deve ser usada para persistir os registros de forma pesquisável. Por padrão usa umaTable
com nome igual a que está vinculada aoBehavior
, adicionado o sufixo 'Searches'. Exemplo:Posts
->PostsSearches
. - foreign_key:
string
. Nome da coluna em target que referencia o registro original. Por padrão usa o nome da tabela no singular adicionado o sufixo '_id'. Exemplo:Posts
->post_id
- mapper:
callable
. Método/função que converte uma entidade do repositório original para uma de target. Por padrão, ele copia todos os campos da entidade original na entidade nova e associa a chave estrangeira da nova a chave primária da original. - doIndex:
bool|callable
. Se deve ou não indexar o registro. Permite um controle individual. Por padrão o valor étrue
. - doDeindex:
bool|callable
. Se deve ou não desindexar o registro. Permite um controle individual e suporte a remoção lógica (soft-delete). Por padrão o valor éfalse
.
O behavior disponibiliza o finder de nome fts
. Como todo finder, ele recebe uma query e também retorna uma query. Dessa forma, você pode usar ele para preparar uma query antes ou depois de chamar ele, estendendo as condições e qualquer outra operação possível em uma query.
Os parâmetros especiais desse finder são:
- field:
string
(obrigatório). Nome do campo do tipo tsvector onde a busca será feita - value:
string
(obrigatório). Valor que deve ser comparado com o campo - highlight:
boolean
. Flag indicando se deve ou não incluir destaque nos termos encontrados. Por padrão é desativado. - highlight_field:
string
(obrigatório apenas se highlight fortrue
). Nome do campo textual onde o highlight será aplicado - exact:
boolean
. Se a comparação será do tipo exata ou aproximada. Por padrão é aproximada (false
). - ts_function:
string
. Caso seja necessário utilizar uma função diferente da aproximada ou exata para parseamento. - configuration:
string
. Nome da configuração de busca usada na comparação. Por padrão, usa a mesma definida emPgSearch.config_name
. - ranked:
boolean
. Flag indicando se a query deve ser ordenada por score.
Não, não precisa. Mas ao não usar, você terá de dizer em cada Table
que possui coluna tsvector
qual é essa coluna.
Nos seus testes unitários, também não será possível criar Fixture
com esse tipo de coluna ou um dos índices gin
e gist
.
Em resumo: não precisa, mas deveria.
É uma decisão pessoal, tirada após trabalhar com sistemas que migram de backend de busca/indexação ao longo do tempo. Separar as tabelas te fornece mais flexibilidade. E o tradeoff é o uso um pouco maior de disco.
Essa tabela indexada pode ser uma versão desnormalizada de outra, permitindo que você faça várias consultas que exigiriam joins
e subqueries
de outra maneira. Em um segundo momento, quando passar a ter bilhões de registros, você pode querer desacoplar essa tabela do banco principal e passar para uma outra instância, ou mesmo para um serviço especializado como Elasticsearch, Solr e Sphinx.
Até pode, mas você ganharia apenas o finder
como vantagem - os recursos de sincronização ficam desativados.