Skip to content

Commit

Permalink
Document existing Fragment implementations
Browse files Browse the repository at this point in the history
  • Loading branch information
sad-spirit committed Sep 3, 2023
1 parent 51d0e0a commit 9d60eb5
Show file tree
Hide file tree
Showing 3 changed files with 145 additions and 0 deletions.
1 change: 1 addition & 0 deletions README.md
Expand Up @@ -130,6 +130,7 @@ where gw_2."name" ~* $1::"text"
* [Working with table metadata](./docs/metadata.md)
* [`TableGateway` interface and its implementations](./docs/gateways.md)
* [Query fragments: base interfaces, parameters, aliases](./docs/fragments-base.md)
* [`Fragment` implementations](./docs/fragments-implementations.md)

## Requirements

Expand Down
143 changes: 143 additions & 0 deletions docs/fragments-implementations.md
@@ -0,0 +1,143 @@
# `Fragment` implementations

Everything that is passed to a query method of `GeenricTableGateway` will eventually be represented by
an implementation of `Fragment` and kept in a `FragmentList`.

## `FragmentList`

An instance of this class aggregates fragments used to build a query and parameter values used to execute it.
It implements `SelectFragment` and `Parametrized` package interfaces as well
as native `IteratorAggregate` and `Countable`.

Constructor of this class accepts a variable number of `Fragment` or `FragmentBuilder` instances
* `__construct(Fragment|FragmentBuilder ...$fragments)`

which are passed to `add()` method.

There is also a static `normalize()` method that accepts `$fragments` parameter that is passed to a query method
of `TableGateway` and returns an instance of `FragmentList`:
* `normalize($fragments): self` - `$fragments` can be either a `\Closure`, an implementation of `Fragment`
or `FragmentBuilder`, or, most commonly, iterable over `Fragment` or `FragmentBuilder` implementations.
Anything else will result in `InvalidArgumentException`.

The class defines several additional methods:
* `add(Fragment|FragmentBuilder $fragment): self` - adds a fragment to the list. If an instance of `FragmentList`
is given, it will be "flattened" with its items added rather than the list itself. If `FragmentBuilder`
is given, the return value of its `getFragment()` method is added to the list, not the builder.
* `mergeParameters(array<string, mixed> $parameters, ?KeyEquatable $owner = null): self` - adds values
for several named parameters. `$owner` is used only for a possible exception message in `RecursiveParameterHolder`.
* `getParameters(): array<string, mixed>` - shorthand for `$list->getParameterHolder()->getParameters()`.
Note that all parameter values are returned: those that were merged into the list itself and those that belong
to `Parametrized` fragments in the list.
* `getSortedFragments(): Fragment[]` - returns fragments sorted by priority (higher first) and key (alphabetically).
This is used by `applyTo()` to apply contained fragments in a defined order.
* `filter(\Closure $callback): self` - filters the `FragmentList` using the given callback (uses `array_filter()`
internally). `TableSelect::executeCount()` uses this to leave only relevant fragments in the list.

You only really need an explicit instance of `FragmentList` when you want to use `create*()` methods
of `GenericTableGateway`. Anywhere else the `$fragments` parameter will be normalized to `FragmentList` automatically.

## `ClosureFragment`

Wrapper for a closure passed to a `TableGateway` query method as `$fragments` parameter. Queries using this fragment
won't be cached.

## `InsertSelectFragment`

Wrapper for `SelectProxy` object passed as `$values` to `GenericTableGateway::insert()`.

## `SetClauseFragment`

Fragment populating either the `SET` clause of an `UPDATE` statement or columns and `VALUES` clause of an `INSERT`.

This is created from `$values` given as an array to `GenericTableGateway::insert()` and from `$set` parameter
to `GenericTableGateway::update()`.

You may need to use that explicitly if you want to create a preparable `INSERT` / `UPDATE` statement, e.g.
```PHP
$update = $gateway->createUpdateStatement(new FragmentList(
new SetClauseFragment(
$gateway->getColumns(),
$tableLocator,
['name' => null]
),
// For the sake of example only, using $gateway->primaryKey() is easier
new PrimaryKeyCondition($gateway->getPrimaryKey(), $tableLocator->getTypeConverterFactory())
));

$update->prepare($gateway->getConnection());
$update->executePrepared([
'id' => 1,
'name' => 'New name'
]);
$update->executePrepared([
'id' => 2,
'name' => 'Even newer name'
]);

```


## `WhereClauseFragment` and `HavingClauseFragment`

These fragments add an expression generated by a `Condition` instance to the `WHERE` or `HAVING` clause of
a `Statement` being built, respectively.

`Condition` instances can be used directly in the query methods of `TableGateway` as they implement
the `FragmentBuilder` interface. This will add their expressions to the `WHERE` clause due to their `getFragment()`
methods returning `WhereClauseFragment`:
```PHP
$gateway->select([
// ...
$gateway->isNotNull('field') // Returns a Condition
//
])
```

If a `Condition` should be applied to the `HAVING` clause, you should explicitly use `HavingClauseFragment`:
```PHP
$gateway->select([
// ...
new HavingClauseFragment(
$gateway->sqlCondition('count(self.field) > 1')
)
// ...
])
```

## `SelectListFragment` and `ReturningClauseFragment`

These fragments modify the output list of `SELECT` statement or the `RETURNING` clause of
`DELETE` / `INSERT` / `UPDATE`, respectively. Actual modification is performed by an instance of
`TargetListManipulator` used to configure the fragment.

It is rarely needed to use these directly as there are builders and builder methods available:
```PHP
$gateway->update([
// ...
$gateway->returningColumns()
->primaryKey()
]);

$gateway->select([
// ...
$gateway->outputExpression("coalesce(self.a, self.b) as ab")
]);
```

## `JoinFragment`

Joins an implementation of `SelectProxy` to the current statement using the given `JoinStrategy` implementation.
Can be additionally configured by a join `Condition`.

It is recommended to use `JoinBuilder` and related `GenericTableGateway::join()` method rather than instantiating
this class directly:
```PHP
$documentsGateway->select([
// ...
$documentsGateway->join('documents_tags') // Creates a SelectProxy for a given table
->onForeignKey() // configures join condition
->lateralLeft() // configures join strategy (LateralSubselectStrategy)
->useForCount(false) // join will not be used by executeCount()
]);
```
1 change: 1 addition & 0 deletions src/fragments/join_strategies/InlineStrategy.php
Expand Up @@ -37,6 +37,7 @@
* Select statement being joined can only contain
* - WITH clause
* - WHERE clause
* - ORDER BY clause
* in addition to obvious target list and FROM clause, everything else will trigger an Exception.
* All the clauses will be merged into the relevant clauses of the base statement.
*/
Expand Down

0 comments on commit 9d60eb5

Please sign in to comment.