Skip to content

Commit 8f2e150

Browse files
authored
Merge pull request #64 from MortalFlesh/feature/add-functions-readme
Add function examples and documentation to readme
2 parents f2a613e + f7744f4 commit 8f2e150

File tree

1 file changed

+232
-3
lines changed

1 file changed

+232
-3
lines changed

README.md

Lines changed: 232 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,14 @@ Same if you want different settings per entity/table, it should be done by a spe
2828
- [IN + EQ](#in--eq-filter)
2929
- [GT + LT _(between)_](#gt--lt-filter-between)
3030
- [EQ `Tuple`](#eq-with-tuple)
31+
- [Functions in filters](#functions-in-filters)
32+
- [Example for fullName function](#example-for-fullname-function)
33+
- [Function parameters definition](#function-parameters-definition)
34+
- [By string](#defined-as-string)
35+
- [By array](#defined-as-array)
36+
- [By object](#defined-as-object)
37+
- [Combinations](#combinations)
38+
- [Register and Execute function](#register-and-execute-function)
3139
- [Exceptions and error handling](#exceptions-and-error-handling)
3240
- [Development](#development)
3341

@@ -72,7 +80,7 @@ $filters = $apiFilter->parseFilters($request->query->all());
7280
```php
7381
// in EntityRepository/Model
7482
$queryBuilder = $this->createQueryBuilder('alias');
75-
$queryBuilder = $apiFilter->applyAll($filters, $queryBuilder);
83+
$queryBuilder = $apiFilter->applyFilters($filters, $queryBuilder);
7684

7785
// or one by one
7886
foreach ($filters as $filter) {
@@ -94,7 +102,7 @@ $queryBuilder
94102
$queryBuilder = $this->createQueryBuilder('alias');
95103

96104
$apiFilter
97-
->applyAll($filters, $queryBuilder) // query builder with applied filters
105+
->applyFilters($filters, $queryBuilder) // query builder with applied filters
98106
->setParameters($apiFilter->getPreparedValues($filters, $queryBuilder)) // ['field_eq' => 'value']
99107
->getQuery();
100108
```
@@ -132,7 +140,7 @@ $stmt->execute($preparedValues);
132140
```php
133141
// in EntityRepository/Model
134142
$sql = 'SELECT * FROM table';
135-
$stmt = $connection->prepare($apiFilter->applyAll($filters, $sql)); // SELECT * FROM table WHERE 1 AND field = :field_eq
143+
$stmt = $connection->prepare($apiFilter->applyFilters($filters, $sql)); // SELECT * FROM table WHERE 1 AND field = :field_eq
136144
$stmt->execute($apiFilter->getPreparedValues($filters, $sql)); // ['field_eq' => 'value']
137145
```
138146

@@ -171,6 +179,12 @@ GET http://host/endpoint/?type[in][]=one&type[in][]=two
171179
```
172180
- `Tuples` are not allowed in `IN` filter
173181

182+
### Function
183+
```http request
184+
GET http://host/endpoint?fullName=(Jon,Snow)
185+
```
186+
- there is much more options and possibilities with `functions` which you can see [here](#functions-in-filters)
187+
174188
## `Tuples` in filters
175189
`Tuples`
176190
- are important in filters if you have some values, which **must** be sent together
@@ -385,6 +399,221 @@ Result:
385399
value: [ action, fantasy ]
386400
```
387401
402+
## Functions in filters
403+
With function you can handle all kinds of situations, which might be problematic with just a simple filters like `eq`, etc.
404+
405+
Let's see how to work with functions and what is required to do. We will show it right on the example.
406+
407+
### Example for `fullName` function
408+
409+
#### Expected api
410+
```http request
411+
GET http://host/endpoint?fullName=(Jon,Snow)
412+
```
413+
☝️ _this shows what we want to offer to our consumers. It's easy and explicit enough._
414+
415+
It may even hide some inner differences, for example with simple filters, database column must have same name as the field in the filter, but with function, we can change it.
416+
417+
Let's say that in database we have something like:
418+
```fs
419+
type Person = {
420+
first_name: string
421+
lastname: string
422+
}
423+
```
424+
425+
#### Initialization
426+
First of all, you have to define functions you want to use.
427+
```php
428+
// in DI container/factory
429+
$apiFilter = new ApiFilter();
430+
431+
$apiFilter->declareFunction(
432+
'fullName',
433+
[
434+
new ParameterDefinition('firstName', 'eq', 'first_name'), // parameter name and field name are different, so we need to define it
435+
'lastname`, // parameter name and field name are the same and we use the implicit `eq` filter, so it is defined simply
436+
]
437+
);
438+
```
439+
Method `declareFunction` will create a function with filters based on parameters.
440+
_There is also [registerFunction](#register-and-execute-function) method, which allows you to pass any function you want. This may be useful when you don't need filter functionality at all or have some custom logic/storage, etc._
441+
442+
#### Parsing and applying filters
443+
Now when request with `?fullName=(Jon,Snow)` comes, `ApiFilter` can parse it to:
444+
```php
445+
// in service/controller/...
446+
$sql = 'SELECT * FROM person';
447+
448+
$filters = $apiFilter->parseFilters($request->query->all());
449+
// [
450+
// 0 => Lmc\ApiFilter\Filter\FilterFunction {
451+
// private $title => 'function'
452+
// private $column => 'fullName'
453+
// private $value => Lmc\ApiFilter\Entity\Value {
454+
// private $value => Closure
455+
// }
456+
// },
457+
//
458+
// 1 => Lmc\ApiFilter\Filter\FunctionParameter {
459+
// private $title => 'function_parameter'
460+
// private $column => 'firstName'
461+
// private $value => Lmc\ApiFilter\Entity\Value {
462+
// private $value => 'Jon'
463+
// }
464+
// },
465+
//
466+
// 2 => Lmc\ApiFilter\Filter\FunctionParameter {
467+
// private $title => 'function_parameter'
468+
// private $column => 'lastname'
469+
// private $value => Lmc\ApiFilter\Entity\Value {
470+
// private $value => 'Snow'
471+
// }
472+
// }
473+
// ]
474+
475+
$appliedSql = $apiFilter->applyFilters($filters, $sql);
476+
// SELECT *
477+
// FROM person
478+
// WHERE
479+
// first_name = :firstName_function_parameter AND
480+
// lastname = :lastname_function_parameter
481+
482+
$preparedValues = $apiFilter->getPreparedValues($filters, $sql);
483+
// [
484+
// 'firstName_function_parameter' => 'Jon',
485+
// 'lastname_function_parameter' => 'Snow',
486+
// ]
487+
```
488+
489+
#### Supported function usage
490+
All examples below results the same. We have that many options, so we can allow as many different consumers as possible.
491+
492+
```http request
493+
### Explicit function call
494+
GET http://host/endpoint?fullName=(Jon,Snow)
495+
496+
### Explicit function call with values
497+
GET http://host/endpoint?function=fullName&firstName=Jon&lastname=Snow
498+
499+
### Implicit function call by values
500+
GET http://host/endpoint?firstName=Jon&lastname=Snow
501+
502+
### Explicit function call by tuple
503+
GET http://host/endpoint?(function,firstName,surname)=(fullName, Jon, Snow)
504+
505+
### Implicit function call by tuple
506+
GET http://host/endpoint?(firstName,surname)=(Jon, Snow)
507+
508+
### Explicit function call by filter parameter
509+
GET http://host/endpoint?filter[]=(fullName,Jon,Snow)
510+
```
511+
512+
### Function Parameters Definition
513+
To `declare` or `register` function, you have to define its parameters. There are many ways/needs to do it.
514+
515+
#### Defined as string
516+
This is the easiest way to do it. You just define a parameter(s) name.
517+
518+
```php
519+
$apiFilter->declareFunction('fullName', ['firstName', 'surname']);
520+
```
521+
522+
It means:
523+
- you want `eq` filter (_or `IN` for array_) and the column name and parameter name are the same
524+
- the value for this parameter is mandatory
525+
526+
#### Defined as array
527+
This allows you to pass more options for a paramater.
528+
529+
##### Only one item
530+
If you declare it just by giving the only item, it is the same as definition by string above.
531+
```php
532+
$apiFilter->declareFunction('fullName', [['firstName'], ['surname']]);
533+
```
534+
535+
##### More than one item
536+
```php
537+
$apiFilter->declareFunction('fullName', [
538+
['firstName', 'eq', 'first_name'],
539+
['surname', 'eq', 'lastname', 'Snow']
540+
]);
541+
```
542+
543+
It means
544+
- `firstName` parameter uses `eq` filter, has `first_name` column in a storage and is mandatory
545+
- `surname` parameter uses `eq` filter, has `lastname` column in a storage and its value is `Snow` (_which will always be used and no value can override it_)
546+
547+
#### Defined as object
548+
This allows you to pass same options as with the array, but explicitly defined in the object. (_It even has some special constructor methods to simplify special needs._)
549+
```php
550+
$apiFilter->declareFunction('fullName', [
551+
new ParameterDefinition('firstName', 'eq', 'first_name'),
552+
new ParameterDefinition('surname', 'eq', 'lastname, new Value('Snow'))
553+
]);
554+
```
555+
556+
#### Combinations
557+
All options can be combined to best suite the parameter.
558+
559+
##### Declaration
560+
```php
561+
$apiFilter->declareFunction('fullNameGrownMan', [
562+
['firstName', 'eq', 'first_name'],
563+
'surname',
564+
['age', 'gte', 'age', 18],
565+
ParameterDefinition::equalToDefaultValue('gender', new Value('male')),
566+
]);
567+
```
568+
569+
##### Usage
570+
```http request
571+
GET http://endpoint/host?fullNameGrownMan=(Jon,Snow)
572+
```
573+
574+
### Register and Execute function
575+
Example below is just for explicit demonstration, you should probably never allow execute SQL queries like this.
576+
577+
#### Usage in PHP
578+
```php
579+
// in DI container/factory
580+
$apiFilter = new ApiFilter();
581+
582+
$apiFilter->registerFunction(
583+
'sql',
584+
['query'],
585+
function (\PDO $client, FunctionParameter $query): \PDOStatement {
586+
return $client->query($query->getValue()->getValue());
587+
}
588+
)
589+
590+
// in service/controller/...
591+
$statement = $apiFilter->executeFunction('sql', $queryParameters, $client); // \PDOStatement
592+
593+
$statement->execute();
594+
// fetch result, etc...
595+
```
596+
597+
#### Usage of the API
598+
All examples below results the same. We have that many options, so we can allow as many different consumers as possible.
599+
600+
```http request
601+
### Explicit function call
602+
GET http://endpoint/host?sql=SELECT * FROM person
603+
604+
### Explicit function call with values
605+
GET http://host/endpoint?function=sql&query=SELECT * FROM person
606+
607+
### Implicit function call by values
608+
GET http://host/endpoint?query=SELECT * FROM person
609+
610+
### Explicit function call by tuple
611+
GET http://host/endpoint?(function,query)=(sql, SELECT * FROM person)
612+
613+
### Explicit function call by filter parameter
614+
GET http://host/endpoint?filter[]=(sql, SELECT * FROM person)
615+
```
616+
388617
## Exceptions and error handling
389618

390619
_Known_ exceptions occurring inside ApiFilter implements `Lmc\ApiFilter\Exception\ApiFilterExceptionInterface`. The exception tree is:

0 commit comments

Comments
 (0)