search_api_meilisearch-1.0.x-dev/src/Parser/StringExpressionFilterParser.php
src/Parser/StringExpressionFilterParser.php
<?php
namespace Drupal\search_api_meilisearch\Parser;
use Drupal\search_api\IndexInterface;
use Drupal\search_api\Query\ConditionGroupInterface;
use Drupal\search_api\Query\ConditionInterface;
/**
* Provides parsing of string expression filters from condition group.
*/
final class StringExpressionFilterParser implements FilterParserInterface {
/**
* The list of applicable condition parsers.
*
* @var \Drupal\search_api_meilisearch\Parser\ConditionParserInterface[]
*/
private array $conditionParsers = [];
/**
* The list of sorted condition parsers.
*
* @var \Drupal\search_api_meilisearch\Parser\ConditionParserInterface[]
*/
private ?array $sortedConditionParsers = NULL;
/**
* The current recursion level.
*
* @var int
*/
private int $level = 0;
/**
* {@inheritdoc}
*/
public function parseExpression(ConditionGroupInterface $conditionGroup, IndexInterface $index): string {
$this->level = 0;
return $this->parseConditionGroup($conditionGroup, $index);
}
/**
* Appends a condition parser to the list.
*
* @param \Drupal\search_api_meilisearch\Parser\ConditionParserInterface $parser
* The condition parser to be appended to the list.
* @param int $priority
* The priority of the condition parser being added.
*
* @return $this
*/
public function addConditionParser(ConditionParserInterface $parser, int $priority = 0): self {
$this->conditionParsers[$priority][] = $parser;
$this->sortedConditionParsers = NULL;
return $this;
}
/**
* Recursively parses expression from a condition group.
*
* @param \Drupal\search_api\Query\ConditionGroupInterface $conditionGroup
* The condition group.
* @param \Drupal\search_api\IndexInterface $index
* The index the condition group belongs to.
*
* @return string
* The string expression filter.
*/
private function parseConditionGroup(ConditionGroupInterface $conditionGroup, IndexInterface $index): string {
$this->level++;
$conditions = $conditionGroup->getConditions();
if (!count($conditions)) {
return '';
}
$filter = '';
$conjunction = " {$conditionGroup->getConjunction()} ";
foreach ($conditions as $condition) {
if ($condition instanceof ConditionInterface) {
$filter .= $this->parseCondition($condition, $conjunction, $index);
continue;
}
$parsedGroup = $this->parseConditionGroup($condition, $index);
if ($parsedGroup === '') {
$this->level--;
continue;
}
if ($this->wrapConditionInParenthesis($condition, $conditionGroup)) {
$parsedGroup = '(' . $parsedGroup . ')';
}
$filter .= $parsedGroup . $conjunction;
$this->level--;
}
return rtrim($filter, $conjunction);
}
/**
* Parses expression from a condition.
*
* @param \Drupal\search_api\Query\ConditionInterface $condition
* The condition group.
* @param string $conjunction
* The conditions conjunction.
* @param \Drupal\search_api\IndexInterface $index
* The index the condition group belongs to.
*
* @return string
* The string expression of a condition.
*/
private function parseCondition(ConditionInterface $condition, string $conjunction, IndexInterface $index): string {
if ($this->sortedConditionParsers === NULL) {
$this->sortedConditionParsers = $this->sortConditionParsers();
}
foreach ($this->sortedConditionParsers as $conditionParser) {
if ($conditionParser->supports($condition, $index)) {
return $conditionParser->parse($condition, $index) . $conjunction;
}
}
return '';
}
/**
* Checks if a condition string should be wrapped in parenthesis.
*
* @param \Drupal\search_api\Query\ConditionGroupInterface $currentGroup
* The current group being processed.
* @param \Drupal\search_api\Query\ConditionGroupInterface $parentGroup
* The parent group of the current group.
*
* @return bool
* TRUE if condition should be wrapped in parenthesis, FALSE otherwise.
*/
private function wrapConditionInParenthesis(ConditionGroupInterface $currentGroup, ConditionGroupInterface $parentGroup): bool {
// First level conditions do not need to be wrapped.
if ($this->level < 2) {
return FALSE;
}
// A single condition in a parent group does not need to be wrapped.
if (count($parentGroup->getConditions()) < 2) {
return FALSE;
}
// A single condition in the current group does not need to be wrapped.
if (count($currentGroup->getConditions()) < 2) {
return FALSE;
}
if ($parentGroup->getConjunction() === 'OR' && $currentGroup->getConjunction() === 'AND') {
return FALSE;
}
return $currentGroup->getConjunction() != $parentGroup->getConjunction();
}
/**
* Sorts condition parsers by priority.
*
* @return \Drupal\search_api_meilisearch\Parser\ConditionParserInterface[]
* A sorted array of condition parser objects.
*/
private function sortConditionParsers(): array {
krsort($this->conditionParsers);
return array_merge([], ...$this->conditionParsers);
}
}
