/Xpression

Xpression is a simple PHP implementation of Specification pattern

Primary LanguagePHPMIT LicenseMIT

Xpression

Codecov Travis branch Scrutinizer Latest Unstable Version Latest Stable Version Total Downloads License SensioLabsInsight

Xpression is a simple PHP implementation of Specification pattern.

Demo

You can try this library on Demo Xpression

Installation

The recommended way to install Xpression is through Composer.

# Install Composer
curl -sS https://getcomposer.org/installer | php
php composer require symftony/xpression

After installing, you need to require Composer's autoloader:

require 'vendor/autoload.php';

Documentation

This library provide:

  • lexer that tokenise your query
  • parser that use tokens and ExpressionBuilder to create the target Expression
  • a query string parser to correctly fix and parse the $_SERVER['QUERY_STRING'] into $_GET

Supported query operator

Operator Syntax Example ORM ODM ArrayCollection Closure
equal = param=value X X X X
not equal != param!=value param≠value X X X X
greater than > param>value X X X X
greater than equal >= param>=value param≥value X X X X
less than < param<value X X X X
less than equal <= param<=value param≤value X X X X
in [ ] param[value1,value2] X X X X
contains {{ }} param{{value}} X X X
not contains !{{ }} param!{{value}} X X X
and & param>1&param<10 X X X X
not and !& param>1!&param<10 X X
or | param>1|param<10 X X X X
not or !| param>1!|param<10 X
xor ^| param>1^|param<10 param>1⊕param<10 X

The composite operator precedence

The highest was compute before the lowest

  • and: 15
  • not and: 14
  • or: 10
  • xor: 9
  • not or: 8

if you want to force a certain order of computation use the group syntax with ( )

This expression search title 'Bar' with 'price' under '5' or title 'Foo'

title='Foo'|title='Bar'&price<5 is equal to title='Foo'|(title='Bar'&price<5)

The next one search the title 'Foo' or 'Bar' with 'price' less than '5'

(title='Foo'|title='Bar')&price<5

Use

Filter an array

use Symftony\Xpression\Expr\ClosureExpressionBuilder;
use Symftony\Xpression\Parser;
use Symftony\Xpression\QueryStringParser;

QueryStringParser::correctServerQueryString();

$products = array(
    array('id' => 1, 'constructor' => 'Volkswagen', 'model' => 'Golf', 'year' => 1990, 'price' => 11),
    array('id' => 2, 'constructor' => 'Volkswagen', 'model' => 'Rabbit', 'year' => 2009, 'price' => 7),
    array('id' => 3, 'constructor' => 'Volkswagen', 'model' => 'Rabbit', 'year' => 2006, 'price' => 12),
    array('id' => 4, 'constructor' => 'Cadillac', 'model' => 'Catera', 'year' => 1999, 'price' => 5),
    array('id' => 5, 'constructor' => 'Cadillac', 'model' => 'STS', 'year' => 2006, 'price' => 14),
    array('id' => 6, 'constructor' => 'Ford', 'model' => 'Mustang', 'year' => 1970, 'price' => 4),
    array('id' => 7, 'constructor' => 'Ford', 'model' => 'Laser', 'year' => 1989, 'price' => 2),
    array('id' => 8, 'constructor' => 'Ford', 'model' => 'Bronco II', 'year' => 1990, 'price' => 3),
    array('id' => 9, 'constructor' => 'Lexus', 'model' => 'LS', 'year' => 2007, 'price' => 18),
    array('id' => 10, 'constructor' => 'Lexus', 'model' => 'LS', 'year' => 2000, 'price' => 17),
    array('id' => 11, 'constructor' => 'Lexus', 'model' => 'LX', 'year' => 1999, 'price' => 4),
    array('id' => 12, 'constructor' => 'Hyundai', 'model' => 'Sonata', 'year' => 1996, 'price' => 13),
    array('id' => 13, 'constructor' => 'Hyundai', 'model' => 'XG350', 'year' => 2002, 'price' => 5),
    array('id' => 14, 'constructor' => 'Land Rover', 'model' => 'Discovery SeriesII', 'year' => 2000, 'price' => 17),
    array('id' => 15, 'constructor' => 'Land Rover', 'model' => 'Discovery', 'year' => 2002, 'price' => 20),
    array('id' => 16, 'constructor' => 'Oldsmobile Cutlass', 'model' => 'Supreme', 'year' => 1992, 'price' => 3),
    array('id' => 17, 'constructor' => 'Mitsubishi', 'model' => 'Eclipse', 'year' => 2001, 'price' => 8),
);

$parser = new Parser(new ClosureExpressionBuilder());
$expression = $parser->parse($_GET['query']);
$filteredProducts = array_filter($products, $expression);

Filter an ArrayCollection

/!\ ArrayCollection doesn't support not and not or xor contains not contains

These not supported operator was not allowed to use and the parser will throw UnsupportedTokenTypeException

use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Criteria;
use Doctrine\Common\Collections\ExpressionBuilder;
use Symftony\Xpression\Bridge\Doctrine\Common\ExpressionBuilderAdapter;
use Symftony\Xpression\Parser;

QueryStringParser::correctServerQueryString();

$products = new ArrayCollection(
    array(
        array('id' => 1, 'title' => 'banana', 'price' => 2, 'quantity' => 5, 'category' => 'food'),
        array('id' => 2, 'title' => 'banana', 'price' => 5, 'quantity' => 15, 'category' => 'food'),
        array('id' => 3, 'title' => 'apple', 'price' => 1, 'quantity' => 1, 'category' => 'food'),
        array('id' => 4, 'title' => 'TV', 'price' => 399, 'quantity' => 1, 'category' => 'multimedia'),
    )
);

$parser = new Parser(new ExpressionBuilderAdapter(new ExpressionBuilder()));
$expression = $parser->parse($_GET['query']);

$filteredProducts = $products->matching(new Criteria($expression));

Create a Doctrine ORM Expression

/!\ ORM Expression doesn't support not and not or xor

These not supported operator was not allowed to use and the parser will throw UnsupportedTokenTypeException

use Doctrine\ORM\Query\Expr;
use Symftony\Xpression\Bridge\Doctrine\ORM\ExprAdapter;
use Symftony\Xpression\Parser;

QueryStringParser::correctServerQueryString();

$parser = new Parser(new ExprAdapter(new Expr()));
$expression = $parser->parse($_GET['query']);

//$yourQueryBuilder()->where($expression);

Create a Doctrine Mongodb Expression

/!\ ORM Expression doesn't support not or xor

These not supported operator was not allowed to use and the parser will throw UnsupportedTokenTypeException

use Symftony\Xpression\Bridge\Doctrine\MongoDb\ExprBuilder;
use Symftony\Xpression\Parser;

QueryStringParser::correctServerQueryString();

$parser = new Parser(new ExprBuilder());
$expression = $parser->parse($_GET['query']);

//$yourQueryBuilder()->where($expression);

You can disable token type

If you want disable an operator you can disable it manually The parser will throw ForbiddenTokenException

By default all token type was allowed Lexer::ALL

but when you call $parser->parse($query, $allowedToken)

Example to disable the greater than equal and the not equal

use Symftony\Xpression\Exception\Parser\InvalidExpressionException;
use Symftony\Xpression\Expr\HtmlExpressionBuilder;
use Symftony\Xpression\Lexer;
use Symftony\Xpression\Parser;
use Symftony\Xpression\QueryStringParser;

QueryStringParser::correctServerQueryString();

$allowedTokenType = Lexer::T_ALL - Lexer::T_GREATER_THAN_EQUALS - Lexer::T_NOT_EQUALS;
$parser = new Parser(new HtmlExpressionBuilder());
$expression = $parser->parse($_GET['query']), $allowedTokenType);