Skip to content
Alexey Borzov edited this page Nov 29, 2021 · 4 revisions

Node and NodeList interfaces

\sad_spirit\pg_builder\Node is an interface implemented by all AST nodes. Its descendant \sad_spirit\pg_builder\NodeList is a interface for nodes representing lists in SQL expressions (e.g. list of columns returned by a SELECT or list of tables in FROM).

Note that all relative class and interface names below assume \sad_spirit\pg_builder\ prefix, which is omitted for readability.

Node API

  • dispatch(TreeWalker $walker): mixed - A double-dispatch method supposed to call the method of TreeWalker relevant for the current Node.
  • getParser(): ?Parser - Returns the Parser, if one is available. Usually it will be added to a Statement instance that contains the current Node.
  • setParentNode(Node $parent = null): void - Adds the link to the Node containing current one.
  • getParentNode(): ?Node - Returns the node containing current one.

Nodes in the AST keep track of their parent nodes and can only be a child of one parent. This means that if you want to copy some node from one branch of the AST to the other, you actually need to clone it. Otherwise the result might be unexpected,

use sad_spirit\pg_builder\StatementFactory;

$factory = new StatementFactory();

/** @var \sad_spirit\pg_builder\SetOpSelect $select  */
$select = $factory->createFromString(
    'select foo_id, title, description, pub_date from foosourse union all select bar_id from barsource'
);
// let's copy the field list to the second argument of union
foreach ($select->left->list as $k => $v) {
    if ($k > 0) {
        $select->right->list[] = $v;
    }
}

echo $factory->createFromAST($select)->getSql();

will print

select foo_id
from foosourse
union all
select bar_id, title, description, pub_date
from barsource

If you change the assignment in the loop to read $select->right->list[] = clone $v; the result will be

select foo_id, title, description, pub_date
from foosourse
union all
select bar_id, title, description, pub_date
from barsource

Usually you work with node's children through exposed properties, but Node defines two special methods that allow working with any child:

  • replaceChild(Node $oldChild, Node $newChild): ?Node - Replaces the child Node with another one. Returns $newChild in case of successful replace, null otherwise.
  • removeChild(Node $child): ?Node - Removes the child Node (actually tries to store a null in a relevant property). Returns $child in case of successful removal, null otherwise.

These methods are useful for applications that transform AST: e.g. when ParameterWalker class needs to replace a node for a named parameter :foo with a node for a positional parameter $1 it just calls replaceChild() on the Parameter's parent node. It doesn't care about that node's type and doesn't know to what property of the parent the Parameter node is mapped.

nodes\ScalarExpression interface

This is implemented by Nodes that are used in scalar expressions. It is widely used for type hints and defines methods used when generating SQL to properly add parentheses:

  • getPrecedence(): int - Returns the integer value specifying relative precedence of this ScalarExpression, the value is a class constant of ScalarExpression.
  • getAssociativity(): string - Returns the associativity of this ScalarExpression. Either of ScalarExpression::ASSOCIATIVE_RIGHT, ScalarExpression::ASSOCIATIVE_LEFT, ScalarExpression::ASSOCIATIVE_NONE constants.

nodes\GenericNode class

This abstract class is a default implementation of Node, it contains implementations for all its methods except dispatch().

It also implements

  • Magic __get() / __set() / __isset() methods allowing access to its child nodes as properties.
  • Magic __clone() method that performs deep cloning of child nodes.
  • Serializable interface and magic __serialize() and __unserialize() methods to support caching itself. Implementing both of these is needed to support multiple PHP versions.

All Node implementations in the package actually extend GenericNode class.

Common NodeList API

NodeList extends IteratorAggregate, Countable, and ArrayAccess SPL interfaces, its instances behave like typed arrays / collections, allowing only objects of specific class or implementing specific interfaces as their elements.

There are two additional methods

  • merge(...$args) - Merges one or more lists with the current one.
  • replace($array) - Replaces the elements of the list with the given ones.

Note lack of typehints in method signatures, in most cases they will also allow strings as arguments, those will be processed by Parser if one is available.

Parseable and ElementParseable interfaces

Parseable interface defines one static method

  • static createFromString(Parser $parser, string $sql): self - Parses the SQL returning an AST for the relevant element.

Classes implementing this interface allow string arguments for merge() and replace() calls.

ElementParseable interface defines one method

  • createElementFromString(string $sql): Node - Parses the SQL for a list element, returning the AST for it.

Classes implementing this interface allow string arguments for offsetSet() and consequently for array offset assignment.

$select->list->merge('foo.id as foo_id, bar.title as bar_title');

$select->list[] = 'baz.*';

If you want to add several elements to the list at once, one merge() call with a string argument will be cheaper in terms of overhead than several assignments with string arguments.

nodes\lists\GenericNodeList class

This abstract class extending nodes\GenericNode is a default implementation of NodeList. It also updates methods of nodes\GenericNode to work with array offset as well as properties.

Most of the lists in the package, with the notable exception of nodes\lists\FunctionArgumentList inherit from its subclass nodes\lists\NonAssociativeList which disallows non-numeric array indexes.

Notable NodeList implementations

The following implementations of NodeList appear as properties of Statement objects:

NodeList subclass Allowed elements Implements Parseable? Implements ElementParseable?
nodes\lists\ExpressionList objects implementing nodes\ScalarExpression Yes Yes
nodes\lists\FromList instances of nodes\range\FromElement Yes Yes
nodes\group\GroupByClause objects implementing nodes\ScalarExpression or nodes\group\GroupByElement Yes Yes
nodes\lists\LockList instances of nodes\LockingElement Yes Yes
nodes\lists\OrderByList instances of nodes\OrderByElement Yes Yes
nodes\lists\RowList instances of nodes\expressions\RowExpression Yes Yes
nodes\lists\SetClauseList instances of nodes\SingleSetClause or nodes\MultipleSetClause Yes Yes
nodes\lists\SetTargetList instances of nodes\SetTargetElement Yes Yes
nodes\lists\TargetList instances of nodes\TargetElement or nodes\Star Yes Yes
nodes\lists\WindowList instances of nodes\WindowDefinition Yes Yes
nodes\WithClause instances of nodes\CommonTableExpression Yes Yes