Skip to content

Add in full text search filter #76

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 8 commits into
base: we-are-vertigo
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
98 changes: 98 additions & 0 deletions src/Filters/FullTextSearch.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
<?php

namespace Rhubarb\Stem\Filters;

use Rhubarb\Crown\Exceptions\ImplementationException;
use Rhubarb\Crown\String\StringTools;
use Rhubarb\Stem\Collections\Collection;

class FullTextSearch extends Filter
{
const MODE_NATURAL_LANGUAGE = "NATURAL LANGUAGE";
const MODE_BOOLEAN = "BOOLEAN";
const MODE_QUERY_EXPANSION = "QUERY EXPANSION";
const MODES = [
self::MODE_NATURAL_LANGUAGE,
self::MODE_BOOLEAN,
self::MODE_QUERY_EXPANSION
];

/** @var array $indexColumns */
protected $indexColumns;

/** @var string $searchPhrase */
protected $searchPhrase;

/** @var string $mode */
protected $mode;

/**
* Builds a filter that supports full text searching for MySql database tables.
*
* @param array $indexColumns The columns that comprise the index. Must be in the same order as the index itself
* @param string $searchPhrase The search phrase to match on
* @param string $mode The mode for the search filter. Can be one of "NATURAL LANGUAGE", "BOOLEAN" or "QUERY EXPANSION".
*
* @throws ImplementationException
*/
public function __construct(array $indexColumns, $searchPhrase, $mode = "NATURAL LANGUAGE")
{
if (strlen(StringTools::implodeIgnoringBlanks("", $indexColumns)) === 0) {
throw new ImplementationException("Index columns passed an empty array");
}

$this->indexColumns = $indexColumns;
$this->searchPhrase = $searchPhrase;
$this->mode = $mode;
}

/**
* @inheritDoc
*/
public function doGetUniqueIdentifiersToFilter(Collection $list)
{
$ids = [];

//We need to make sure our search value does not contain any diacritics
$searchValueCleaned = preg_replace('/&([a-z]{1,2})(acute|cedil|circ|grave|lig|orn|ring|slash|th|tilde|uml|caron);/i','$1', htmlentities($this->searchPhrase));
foreach ($list as $item) {
$columnsToSearch = $this->indexColumns;

foreach ($columnsToSearch as $column) {
if (preg_match("/$searchValueCleaned/i", $item[$column])) {
$ids[] = $item->UniqueIdentifier;
break;
}
}
}

return $ids;
}

/**
* @inheritDoc
*/
public function getSettingsArray()
{
$settings = parent::getSettingsArray();
$settings["indexColumns"] = $this->indexColumns;
$settings["searchPhrase"] = $this->searchPhrase;
$settings["mode"] = $this->mode;

return $settings;
}

/**
* Builds a new instance of the FullTextSearch from the settings array
*
* @param $settings
*
* @return FullTextSearch
*
* @throws ImplementationException
*/
public static function fromSettingsArray($settings)
{
return new self($settings["indexColumns"], $settings["searchPhrase"], $settings["mode"]);
}
}
75 changes: 75 additions & 0 deletions src/Repositories/MySql/Filters/MySqlFullTextSearch.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
<?php

namespace Rhubarb\Stem\Repositories\MySql\Filters;

use Rhubarb\Crown\Exceptions\ImplementationException;
use Rhubarb\Crown\String\StringTools;
use Rhubarb\Stem\Filters\Filter;
use Rhubarb\Stem\Filters\FullTextSearch;
use Rhubarb\Stem\Repositories\Repository;

class MySqlFullTextSearch extends FullTextSearch
{
use MySqlFilterTrait {
canFilter as canFilterTrait;
}

/**
* @inheritDoc
*/
protected static function doFilterWithRepository(Repository $repository, Filter $originalFilter, &$params, &$propertiesToAutoHydrate)
{
$settings = $originalFilter->getSettingsArray();
if (!isset($settings["indexColumns"]) || !isset($settings["searchPhrase"]) || !isset($settings["mode"])) {
throw new ImplementationException("Filter passed to doFilterWithRepository was \"" . get_class($originalFilter) . "\" expected " . \Unislim\WebApp\Shared\Filters\MySqlFullTextSearch::class);
}

$implodedColumns = StringTools::implodeIgnoringBlanks(",", $settings["indexColumns"]);
if (self::canFilter($repository, $implodedColumns, $propertiesToAutoHydrate)) {
$searchPhrase = $settings["searchPhrase"];
$paramName = uniqid() . str_replace(".", "", $implodedColumns);

$originalFilter->filteredByRepository = true;

$words = preg_split("/\W+/", $searchPhrase);
$newWords = [];
foreach($words as $word){
if ($word) {
$newWords[] = $word."*";
}
}

$searchPhrase = implode(" ", $newWords);

$params[$paramName] = $searchPhrase;

$parameter = ":" . $paramName;

return "MATCH ({$implodedColumns}) AGAINST ({$parameter} IN {$settings["mode"]} MODE)";
}

parent::doFilterWithRepository($repository, $originalFilter, $params, $propertiesToAutoHydrate);
}

/**
* @inheritDoc
*/
protected static function canFilter(Repository $repository, $columnName, &$propertiesToAutoHydrate)
{
$schema = $repository->getRepositorySchema();
$columns = $schema->getColumns();

//We need to explode the column name as we will have passed an imploded list of columns that
//comprise the index
$providedColumns = preg_split('@/@', $columnName, NULL, PREG_SPLIT_NO_EMPTY);
foreach ($providedColumns as $column) {
if (!isset($column, $columns)) {
if (!self::canFilterTrait($repository, $column, $propertiesToAutoHydrate)) {
return false;
}
}
}

return true;
}
}