Conditions
Subclasses of Condition serve as wrappers for Node instances implementing ScalarExpression interface
(defined in pg_builder package), that should (presumably) return boolean values when used in SQL.
Condition instances are used by fragments that modify WHERE and HAVING clauses and by the JoinFragment
for an actual JOIN condition.
Conditions behave like Specifications from
the similarly named pattern and can be combined
via AND / OR / NOT operators.
They do not implement isSatisfiedBy() method, though, for more or less obvious reasons.
Base Condition class
namespace sad_spirit\pg_gateway;
use sad_spirit\pg_builder\nodes\ScalarExpression;
abstract class Condition implements KeyEquatable, FragmentBuilder
{
// defined in FragmentBuilder
public function getFragment() : fragments\WhereClauseFragment;
final public function generateExpression() : ScalarExpression;
abstract protected function generateExpressionImpl() : ScalarExpression;
final public static function and(self ...$children) : conditions\AndCondition;
final public static function or(self ...$children) : conditions\OrCondition;
final public static function not(self $child) : conditions\NotCondition;
}
This abstract class implements KeyEquatable and FragmentBuilder interfaces. The latter allows passing instances of
Condition to query methods of TableGateway, appending them to the query’s WHERE clause.
getKey() method required by the former is not actually implemented, this should be done in child classes.
The public generateExpression() method returns a clone of whatever was returned by generateExpressionImpl().
This is done to prevent potential problems when reusing the same expression in multiple
queries / multiple parts of the query, as pg_builder’s Node classes keep a reference to their parent Node.
generateExpressionImpl() should be implemented by child classes. Its name starts with “generate” as a hint:
it should preferably generate the ScalarExpression on “as needed” basis rather than pre-generate and store that.
Real world Conditions will often use Parser and parsing may be slow.
Combining Conditions via AND / OR / NOT
Static methods of Condition allow combining conditions using logical operators:
and()Creates a
Conditionthat combines several otherConditions usingANDoperator.or()Creates a
Conditionthat combines several otherConditions usingORoperator.not()Creates a negated
Condition.
For example:
$combined = Condition::and(
Condition::not($firstCondition),
Condition::or($secondCondition, $thirdCondition)
);
Classes returned by the above methods can also be instantiated explicitly.
Constructors of AndCondition / OrCondition accept variable number of Condition arguments
__construct(Condition ...$children) and will throw an InvalidArgumentException if none are given.
Constructor of NotCondition naturally accepts only one Condition argument.
Using the above example with explicit classes:
$combined = new AndCondition(
new NotCondition($firstCondition),
new OrCondition($secondCondition, $thirdCondition)
);
All these classes implement Parametrized interface and will propagate parameters from their child Conditions.
Passing parameters with Conditions
While it is possible to also implement Parametrized in custom conditions, the suggested approach is to use
conditions\ParametrizedCondition decorator instead:
namespace sad_spirit\pg_gateway\conditions;
use sad_spirit\pg_gateway\{
Condition,
Parametrized
};
final class ParametrizedCondition extends Condition implements Parametrized
{
public function __construct(Condition $wrapped, array<string, mixed> $parameters)
}
Its constructor will throw an InvalidArgumentException if $wrapped already implements Parametrized.
Example:
$condition = new ParametrizedCondition(
new SqlStringCondition($parser, 'foo = :bar::baz'),
['bar' => new Baz()]
);
Other Condition subclasses
It is rarely needed to manually create these classes as they are all supported by custom builder classes and builder methods of FluentBuilder.
conditions\column\AnyCondition
Generates a self.foo = any(:foo::foo_type[]) condition for the foo table column.
This is similar to foo IN (...), but requires only one placeholder.
Constructor accepts a metadata\Column instance and
an implementation of TypeNameNodeHandler (from pg_builder):
$condition = new AnyCondition(
$gateway->getDefinition()->getColumns()->get('foo'),
$locator->getTypeConverterFactory()
);
conditions\column\BoolCondition
Uses the value of the bool-typed column as a Condition. Constructor accepts an instance of
metadata\Column, will throw LogicException if it is not of type bool.
$condition = new BoolCondition($gateway->getDefinition()->getColumns()->get('flag'));
conditions\ExistsCondition
Generates the EXISTS(SELECT ...) condition using the given SelectBuilder implementation.
The constructor may also accept a join Condition and an explicit alias for a table within EXISTS(...)
(will be autogenerated if not given):
$condition = new ExistsCondition(
$gateway->select(/* ... some configuration ... */),
new ForeignKeyCondition($foreignKey),
'custom'
);
conditions\ForeignKeyCondition
Generates a join condition using the given foreign key constraint. Constructor accepts a metadata\ForeignKey object
and a flag specifying whether we are joining from the side of the child table (one having the constraint defined)
or the parent one (referenced by constraint). self and joined aliases will be used according to that flag.
// This will use 'self' alias for referenced table and 'joined' alias for child one
$condition = new ForeignKeyCondition($foreignKey, false);
conditions\column\IsNullCondition
Generates a self.foo IS NULL Condition for the foo table column.
Constructor accepts an instance of metadata\Column.
$condition = new IsNullCondition($gateway->getDefinition()->getColumns()->get('foo'));
conditions\column\NotAllCondition
Generates a self.foo <> all(:foo::foo_type[]) condition for the foo table column.
This is similar to foo NOT IN (...), but requires only one placeholder.
Constructor accepts a metadata\Column instance and an implementation of TypeNameNodeHandler:
$condition = new NotAllCondition(
$gateway->getDefinition()->getColumns()->get('foo'),
$locator->getTypeConverterFactory()
);
conditions\column\OperatorCondition
Generates a self.foo OPERATOR :foo::foo_type condition for the foo table column. Constructor accepts an instance
of metadata\Column, an implementation of TypeNameNodeHandler and operator as string:
$condition = new OperatorCondition(
$gateway->getDefinition()->getColumns()->get('foo'),
$locator->getTypeConverterFactory(),
'>='
);
conditions\PrimaryKeyCondition
A condition for finding a table row by its primary key. Constructor accepts a metadata\PrimaryKey object and
an implementation of TypeNameNodeHandler:
$condition = new PrimaryKeyCondition(
$gateway->getDefinition()->getPrimaryKey(),
$locator->getTypeConverterFactory()
);
This class has an additional normalizeValue(mixed $value): array<string, mixed> method which accepts the value
probably passed to one of the methods of PrimaryKeyAccess interface and ensures that it can be used in parameter
values for the query. E.g.
$condition->normalizeValue(1);
will return a ['id' => 1] array if table’s primary key consists of a single id column and
$condition->normalizeValue(['foo_id' => 2]);
will throw an Exception if table’s primary key consists of foo_id and bar_id columns.
conditions\SqlStringCondition
Condition represented by an SQL string. Constructor accepts a Parser instance and a string.
$condition = new SqlStringCondition(
$locator->getParser(),
"current_date between coalesce(self.valid_from, 'yesterday') and coalesce(self.valid_to, 'tomorrow')"
);
The string will eventually be processed by Parser::parseExpression() method and added to query AST, so
self aliases in it will be replaced if needed and parameter placeholders will be processed.