Skip to content

provide better abstraction for Condition shorthands

Jiří Pudil requested to merge even-more-abstract-condition into master

K diskuzi

Při vymýšlení příkladů do dokumentace jsem narazil na několik omezení stávajícího řešení ve chvíli, kdy chci vlastní chování, ale nechci se připravit o tu hezkou zkratku $expr->is():

  • SingleCondition předpokládá, že pořadí operandů bude vždy (1) expression, (2) argument, ale jsou podmínky, kde to může být naopak, jako třeba nalezení prvku v poli (argument = ANY(expression)).
  • BinaryOperation, který by se jinak dal použít pro 99 % custom operací, předpokládá, že argument je stejného typu jako expression, což ale taky nemusí být vždy pravda (třeba zrovna u hledání podle jsonpath je pattern prostý string, nikoliv jsonb).
  • Operation vlastně vůbec není (matematická) operace, jenom její část, úplnou se stává až spolu s expression, tomuhle celku ale už říkáme Condition, ne operation. Babylon.
  • Výsledkem je, že když potom chci napsat nějakou úplně vlastní podmínku, vlastně vůbec nevím, jestli chci implementovat Condition, nebo použít SingleCondition a implementovat Operation.

Vychází mi z toho, že ta vrstva abstrakce Operation je tam tak nějak navíc (nebo se přinejmenším špatně jmenuje). Zkusil jsem tu cibuli trochu oloupat tak, že jsem odstranil Operation a ty jednotlivé operace přepsal do implementací Condition. Díky tomu:

  • Každá Condition může vrátit argumenty v potřebném pořadí;
  • každá Condition může explicitně namapovat hodnoty do libovolného typu (ale třeba i nějak odvozeného z typu expression);
  • a především když teď budu chtít implementovat vlastní filtr, je mi hned jasné, že mám implementovat Condition.

Příklad:

<?php

declare(strict_types=1);

use Grifart\Tables\Expression;
use Grifart\Tables\Types\ArrayType;
use function Grifart\Tables\Types\mapToDatabase;

/**
 * @template ItemType
 */
final class Contains implements Condition
{
	/**
	 * @param Expression<ItemType[]> $expression
	 * @param ItemType $value
	 */
	public function __construct(
		private Expression $expression,
		private mixed $value,
	) {}

	public function format(): array
	{
		// grab the type of the array item
		$type = $this->expression->getType();
		\assert($type instanceof ArrayType);
		$itemType = $type->getItemType();

		return [
			'? = ANY(?)',
			mapToDatabase($this->value, $itemType), // map the value using $itemType
			$this->expression->toSql(), // $expression comes second
		];
	}
}

Ty shorthand funkce pro použití v is() teď místo Operation vracejí closure, která dostane expression a vyrobí Condition. Ještě se nabízí místo closure mít nějaký interface, ale říkal bych mu spíš nějak ve smyslu ConditionFactory než Operation :)

Merge request reports