Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/soyuka/esql

PHP Extended SQL
https://github.com/soyuka/esql

Last synced: about 2 months ago
JSON representation

PHP Extended SQL

Awesome Lists containing this project

README

        

# PHP Extended SQL

PHP Extended SQL is an alternative to the also-known DQL (Doctrine Query Language). It combines the flexibility of SQL with the powerful Doctrine metadata to give you more control over queries.

```php
getConnection();
$mapper = new ESQLMapper($autoMapper, $managerRegistry);
$esql = new ESQL($managerRegistry, $mapper);
$car = $esql(Car::class);
$model = $car(Model::class);

$query = <<columns()}, {$model->columns()} FROM {$car->table()}
INNER JOIN {$model->table()} ON {$car->join(Model::class)}
WHERE {$car->identifier()}
SQL;

$stmt = $connection->prepare($query);
$stmt->execute(['id' => 1]);

var_dump($esql->map($stmt->fetch()));
```

[Jump to the documentation](#documentation) or [read this blog article](https://soyuka.me/esql-alternative-to-doctrine-query-language-why/) to see it in action.

## API Platform bridge

This package comes with an API Platform bridge that supports filters and pagination. To use our bridge, use the `esql` attribute:

```php
automapper->map($data, 'array'); // map your object to an array somehow

$query = <<table()} SET {$car->predicates()}
WHERE {$car->identifier()}
SQL;

$connection->beginTransaction();
$stmt = $connection->prepare($query);
$stmt->execute($binding);
$connection->commit();
```

Same goes for inserting value:

```php
automapper->map($data, 'array'); // map your object to an array somehow
$car = $esql($data)
$query = <<table()} ({$car->columns()}) VALUES ({$car->parameters($binding)});
SQL;

$connection->beginTransaction();
$stmt = $connection->prepare($query);
$stmt->execute($binding);
$connection->commit();
```

Note that if you used a sequence you'd need to handle that yourself.

## Documentation

- [Doctrine](#doctrine)
- [Mapping](#mapping)
- [Bundle configuration](#bundle-configuration)
- [Paginator](#paginator)
- [Examples](#examples)

### Doctrine

An ESQL instance offers a few methods to help you write SQL with the help of Doctrine's metadata. To ease there use inside [HEREDOC](https://www.php.net/manual/en/language.types.string.php#language.types.string.syntax.heredoc) calling `__invoke($classOrObject)` on the `ESQL` class will return an array with the following closure:

```php
table();

// the sql alias
// outputs "car"
echo $car->alias();

// Get columns: columns(?array $fields = null, string $output = $car::AS_STRING): string
// columns() outputs "car.id, car.name, car.model_id"
// output can also take: $car::AS_ARRAY | $car::WITHOUT_ALIASES | $car::WITHOUT_JOIN_COLUMNS | $car::IDENTIFIERS
echo $car->columns();

// Get a single column: column(string $fieldName): string
// column('id') outputs "car.id"
echo $car->column('id');

// Get an identifier predicate: identifier(): string
// identifier() outputs "car.id = :id"
echo $car->identifier();

// Get a join predicate: join(string $relationClass): string
// join(Model::class) outputs "car.model_id = model.id"
echo $car->join(Model::class);

// All kinds of predicates: predicates(?array $fields = null, string $glue = ', '): string
// predicates() outputs "car.id = :id, car.name = :name"
echo $car->predicates();
```

More advanced utilities are available as:

```php
toSQLValue('sold', true);

// Given an array of bindings, will output keys prefixed by `:`: parameters(array $bindings): string
// parameters(['id' => 1, 'color' => 'blue']) will output ":id, :color"
$car->parameters();
```

This are useful to build filters, write systems or even a custom mapper.

ESQL works using aliases and mapping them to classes and their properties. When working on relation you'll have to use:

```php
alias(); // car
$model = $car(Model::class);
$model->alias(); // car_model
```

This way, ESQL knows to map the `Model` to the `Car->model` property. When working with DTOs the relation may not be found and you can alias the relation yourself:

```php
id = 1;
$model->name = 'Volkswagen';

$car = new Car();
$car->id = 1;
$car->name = 'Caddy';
$car->model = $model;

$car2 = new Car();
$car2->id = 2;
$car2->name = 'Passat';
$car2->model = $model;

// Aliases should be generated by ESQL to map properties and relation properly
$this->assertEquals([$car, $car2], $mapper->map([
['car_id' => '1', 'car_name' => 'Caddy', 'model_id' => '1', 'model_name' => 'Volkswagen'],
['car_id' => '2', 'car_name' => 'Passat', 'model_id' => '1', 'model_name' => 'Volkswagen'],
], Car::class));
```

There's also a Mapper built with the [`symfony/serializer`](https://symfony.com/doc/current/components/serializer.html).

### Bundle configuration

```yaml
esql:
mapper: Soyuka\ESQL\Bridge\Automapper\ESQLMapper
api-platform:
enabled: true
```

### Paginator

API Platform has great defaults for pagination. Using `Soyuka\ESQL\Bridge\ApiPlatform\DataProvider\DataPaginator`, fetching data would look like this:

```php
esql->__invoke(Car::class);
$parameters = [];

$query = <<columns()} FROM {$esql->table()}
SQL;

if ($paginator = $this->dataPaginator->getPaginator($resourceClass, $operationName)) {
return $paginator($esql, $query, $parameters, $context);
}
```

If you want to handle the pagination yourself, we provide a way to do so:

```php
esql->__invoke($resourceClass);
['itemsPerPage' => $itemsPerPage, 'firstResult' => $firstResult, 'nextResult' => $nextResult, 'page' => $page, 'partial' => $isPartialEnabled] = $this->dataPaginator->getPaginationOptions($resourceClass, $operationName);

$query = <<columns()} FROM {$esql->table()}
LIMIT $itemsPerPage OFFSET $firstResult
SQL;

// fetch data somehow and map
$data = $esql->map($data);

$countQuery = <<< SQL
SELECT COUNT(1) as count FROM {$esql->table()}
SQL;

// get count results somehow
$count = $countResult['count'];

return $isPartialEnabled ? new PartialPaginator($data, $page, $itemsPerPage) : new Paginator($data, $page, $itemsPerPage, $count);
```

### Examples

- [Aggregates](https://github.com/soyuka/esql/blob/main/tests/Fixtures/TestBundle/State/StatisticsProvider.php)
- [Product with CTE](https://github.com/soyuka/esql/blob/main/tests/Fixtures/TestBundle/State/ProductProvider.php)