https://github.com/bpolaszek/where
PHP7.1 Fluent, immutable SQL query builder.
https://github.com/bpolaszek/where
Last synced: 5 months ago
JSON representation
PHP7.1 Fluent, immutable SQL query builder.
- Host: GitHub
- URL: https://github.com/bpolaszek/where
- Owner: bpolaszek
- License: mit
- Created: 2017-09-21T09:01:20.000Z (over 8 years ago)
- Default Branch: master
- Last Pushed: 2019-07-22T10:55:55.000Z (over 6 years ago)
- Last Synced: 2025-05-16T00:42:58.231Z (8 months ago)
- Language: PHP
- Size: 46.9 KB
- Stars: 12
- Watchers: 2
- Forks: 1
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
[](https://packagist.org/packages/bentools/where)
[](https://packagist.org/packages/bentools/where)
[](https://travis-ci.org/bpolaszek/where)
[](https://coveralls.io/github/bpolaszek/where?branch=master)
[](https://scrutinizer-ci.com/g/bpolaszek/where)
[](https://packagist.org/packages/bentools/where)
# Where
The simplest fluent SQL query builder ever.
Built in PHP7.1 with immutability in mind.
## Features
* Framework agnostic, connection agnostic (you just render a string and an array of values)
* Natural language: where, and, or, ...
* Support named and numeric placeholders
* Build complex, nested WHERE conditions
* Helpers for building `SELECT`, `INSERT`, `UPDATE`, `DELETE`, `REPLACE` queries
* Helpers for SQL functions like `IN`, `BETWEEN`, `IS NULL`, `CASE ... WHEN`
## Why?
In most cases simple SQL queries are fine.
But if your application logic is designed in a way that several classes / methods can modify an SQL query (like the Visitor pattern), then you'll probably need query builder
(you can define LIMIT / OFFSET before WHERE for instance, and the query will be rendered in the correct order).
Conditions builder
--------------------------
**Where** allows you to build your conditions with **Expressions**. **Expressions** are objects that can be:
* Simple expressions: `date_added = CURRENT_DATE`
* Composite expressions: `date_added = CURRENT_DATE OR date_added = SUBDATE(CURRENT_DATE, INTERVAL 1 DAY)`
* Group expressions: `(country = 'UK' OR country = 'BE')`
* Negated expressions: `NOT date_added = CURRENT_DATE`
An **Expression** object can also contain an array of parameters to bind (to avoid SQL injections).
You don't need to instanciate them. Just rely on the powerful functions the library offers:
```php
require_once __DIR__ . '/vendor/autoload.php';
use function BenTools\Where\group;
use function BenTools\Where\not;
use function BenTools\Where\where;
$where = where('country IN (?, ?)', ['FRA', 'UK'])
->and(
not(
group(
where('continent = ?', 'Europe')
->or('population < ?', 100000)
)
)
);
print((string) $where);
print_r($where->getValues());
print_r($where->preview()); // For debugging purposes
```
Outputs:
```mysql
country IN (?, ?) AND NOT (continent = ? OR population < ?)
```
```php
Array
(
[0] => FRA
[1] => UK
[2] => Europe
[3] => 100000
)
```
```mysql
country IN ('FRA', 'UK') AND NOT (continent = 'Europe' OR population < 100000)
```
Every function `where()`, `group()`, `not()` accepts either an already instanciated **Expression** object, or a string and some optionnal parameters.
```php
$where = where('date > NOW()'); // valid
$where = where($where); // valid
$where = where(group($where)); // valid
$where = where(not($where)); // valid
$where = where('date = ?', date('Y-m-d')); // valid
$where = where('date BETWEEN ? AND ?', date('Y-m-d'), date('Y-m-d')); // valid
$where = where('date BETWEEN ? AND ?', [date('Y-m-d'), date('Y-m-d')]); // valid
$where = where('date BETWEEN :start AND :end', ['start' => date('Y-m-d'), 'end' => date('Y-m-d')]); // valid
$where = where('date BETWEEN :start AND :end', ['start' => date('Y-m-d')], ['end' => date('Y-m-d')]); // not valid
$where = where($where, date('Y-m-d'), date('Y-m-d')); // not valid (parameters already bound)
```
Thanks to the fluent interface, let your IDE guide you for the rest. Don't forget **Where** is always immutable: reassign `$where` everytime you do some changes.
Select Query Builder
-----------------------------
Now you've learnt how to build conditions, you'll see how building a whole select query is a piece of cake:
```php
require_once __DIR__ . '/vendor/autoload.php';
use function BenTools\Where\group;
use function BenTools\Where\not;
use function BenTools\Where\select;
use function BenTools\Where\where;
$select = select('b.id', 'b.name AS book_name', 'a.name AS author_name')
->from('books as b')
->innerJoin('authors as a', 'a.id = b.author_id')
->limit(10)
->orderBy('YEAR(b.published_at) DESC', 'MONTH(b.published_at) DESC', 'b.name')
->where(
group(
where('b.series = ?', 'Harry Potter')->or('b.series IN (?, ?)', ['A Song of Ice and Fire', 'Game of Thrones'])
)
->and('b.published_at >= ?', new \DateTime('2010-01-01'))
->and(
not('b.reviewed_at BETWEEN ? AND ?', new \DateTime('2016-01-01'), new \DateTime('2016-01-31 23:59:59'))
)
);
print_r((string) $select); // The SQL string
print_r($select->preview()); // For debugging purposes
```
```mysql
SELECT b.id, b.name AS book_name, a.name AS author_name
FROM books as b
INNER JOIN authors as a ON a.id = b.author_id
WHERE (b.series = ? OR b.series IN (?, ?))
AND b.published_at >= ?
AND NOT b.reviewed_at BETWEEN ? AND ?
ORDER BY YEAR(b.published_at) DESC, MONTH(b.published_at) DESC, b.name
LIMIT 10;
```
```mysql
SELECT b.id, b.name AS book_name, a.name AS author_name
FROM books as b
INNER JOIN authors as a ON a.id = b.author_id
WHERE (b.series = 'Harry Potter' OR b.series IN ('A Song of Ice and Fire', 'Game of Thrones'))
AND b.published_at >= '2010-01-01 00:00:00'
AND NOT b.reviewed_at BETWEEN '2016-01-01 00:00:00' AND '2016-01-31 23:59:59'
ORDER BY YEAR(b.published_at) DESC, MONTH(b.published_at) DESC, b.name
LIMIT 10;
```
Let your favorite IDE do the rest with autocompletion.
RulerZ
------
**Where** is mostly compatible with the awesome [RulerZ](https://github.com/K-Phoen/rulerz) DSL.
Be sure to `group` your statements to avoid downside effects.
Example:
```php
use function BenTools\Where\group;
use function BenTools\Where\not;
$fruits = [
'banana',
'apple',
];
$colors = [
'yellow',
'red',
];
$condition = group('fruit IN :fruits', ['fruits' => $fruits])->and(group('color IN :colors', ['colors' => $colors]));
$condition = not($condition);
var_dump($rulerz->satisfies(['fruit' => 'strawberry', 'color' => 'red'], (string) $condition, $condition->getValues())); // true
var_dump($rulerz->satisfies(['fruit' => 'apple', 'color' => 'yellow'], (string) $condition, $condition->getValues())); // false
```
Installation
----------------
> composer require bentools/where
Tests
-------
> ./vendor/bin/phpunit
See also
-----------
[bentools/simple-dbal](https://github.com/bpolaszek/simple-dbal) - A PHP 7.1+ wrapper for PDO & Mysqli. Can bind `DateTime` parameters.
[bentools/pager](https://github.com/bpolaszek/bentools-pager) - A PHP 7.1+ pager.
[bentools/flatten-iterator](https://github.com/bpolaszek/flatten-iterator) - Flattens multiple `array` or `Traversable` into one iterator.
[bentools/etl](https://github.com/bpolaszek/bentools-etl) - A PHP7.1 ETL pattern implementation.
[latitude/latitude](https://github.com/shadowhand/latitude) - Another SQL Query builder **Where** was inspired of.