Master |
---|
Query Filter Bundle brings request filtering and pagination functionality to Symfony 4 applications that use Doctrine 2.
First, install the dependency:
$ composer require artprima/query-filter-bundle
- Controller
<?php
namespace App\Controller;
use Artprima\QueryFilterBundle\QueryFilter\Config\BaseConfig;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Component\HttpFoundation\Request as HttpRequest;
use Artprima\QueryFilterBundle\Request\Request;
use Artprima\QueryFilterBundle\QueryFilter\QueryFilter;
use Artprima\QueryFilterBundle\Response\Response;
use App\Repository\ItemRepository;
class DefaultController extends Controller
{
// ...
/**
* @Route("/")
*/
public function indexAction(HttpRequest $request, ItemRepository $repository)
{
// set up the config
$config = new BaseConfig();
$config->setSearchAllowedCols(['t.name']);
$config->setAllowedLimits([10, 25, 50, 100]);
$config->setDefaultLimit(10);
$config->setSortCols(['t.id'], ['t.id' => 'asc']);
$config->setRequest(new Request($request));
// here we provide a repository callback that will be used internally in the QueryFilter
// The signature of the method must be as follows: function functionName(QueryFilterArgs $args): QueryResult;
$config->setRepositoryCallback([$repository, 'findByOrderBy']);
// Response must implement Artprima\QueryFilterBundle\Response\ResponseInterface
$queryFilter = new QueryFilter(Response::class);
/** @var Response $data the type of the variable is defined by the class in the first argument of QueryFilter's constructor */
$response = $queryFilter->getData($config);
$data = $response->getData();
$meta = $response->getMeta();
// ... now do something with $data or $meta
}
// ...
}
- Repository
<?php
namespace App\Repository;
use App\Entity\Item;
use Artprima\QueryFilterBundle\Query\Mysql\PaginationWalker;
use Artprima\QueryFilterBundle\Query\ConditionManager;
use Artprima\QueryFilterBundle\Query\ProxyQueryBuilder;
use Artprima\QueryFilterBundle\QueryFilter\QueryFilterArgs;
use Artprima\QueryFilterBundle\QueryFilter\QueryResult;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Symfony\Bridge\Doctrine\RegistryInterface;
class ItemRepository extends ServiceEntityRepository
{
/**
* @var ConditionManager
*/
private $pqbManager;
public function __construct(RegistryInterface $registry, ConditionManager $manager)
{
parent::__construct($registry, Item::class);
$this->pqbManager = $manager;
}
public function findByOrderBy(QueryFilterArgs $args): QueryResult
{
// Build our request
$qb = $this->createQueryBuilder('t')
->setFirstResult($args->getOffset())
->setMaxResults($args->getLimit());
$proxyQb = new ProxyQueryBuilder($qb, $this->pqbManager);
$qb = $proxyQb->getSortedAndFilteredQueryBuilder($args->getSearchBy(), $args->getSortBy());
$query = $qb->getQuery();
$query->setHint(Query::HINT_CUSTOM_OUTPUT_WALKER, PaginationWalker::class);
$query->setHint("mysqlWalker.sqlCalcFoundRows", true);
$result = $query->getResult();
$totalRows = $this->_em->getConnection()->query('SELECT FOUND_ROWS()')->fetchColumn();
// return the wrapped result
return new QueryResult($result, $totalRows);
}
// ...
}
Now you can start your php server and filter the requests:
GET https://127.0.0.1:8000/?filter[t.name]=Doe&limit=100
This request will perform a LIKE request in DQL:
SELECT t FROM Item WHERE t.name LIKE "%Doe%" LIMIT 100
This filtering library is best used together with JMSSerializerBundle and FOSRestBundle. You will eventually write a lot less code that it was shown in the basic example.
To utilize the advanced usage, install all the packages.
composer require friendsofsymfony/rest-bundle
composer require jms/serializer-bundle
composer require artprima/query-filter-bundle
- Turn them on in
config/bundles.php
:
<?php
return [
// ...
Artprima\QueryFilterBundle\ArtprimaQueryFilterBundle::class => ['all' => true],
FOS\RestBundle\FOSRestBundle::class => ['all' => true],
JMS\SerializerBundle\JMSSerializerBundle::class => ['all' => true],
// ...
];
NOTE: you may need to add further bundles depending on your set up for FOSRestBundle and/or JMSSerializerBundle.
- Controller:
<?php
namespace App\Controller;
use App\QueryFilter\Response;
use App\Repository\ItemRepository;
use Artprima\QueryFilterBundle\QueryFilter\Config\ConfigInterface as QueryFilterConfigInterface;
use FOS\RestBundle\Controller\Annotations as Rest;
use FOS\RestBundle\Controller\FOSRestController;
use FOS\RestBundle\Routing\ClassResourceInterface;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
use Artprima\QueryFilterBundle\Controller\Annotations\QueryFilter;
class ItemController extends FOSRestController implements ClassResourceInterface
{
/**
* @Rest\View(serializerEnableMaxDepthChecks=true)
* @ParamConverter("config", class="App\QueryFilter\Config\ItemConfig",
* converter="query_filter_config_converter",
* options={"entity_class": "App\Entity\Item", "repository_method": "findByOrderBy"})
* @QueryFilter()
* @Rest\Get("/items")
*/
public function cgetAction(QueryFilterConfigInterface $config)
{
return $config;
}
}
- Repository:
<?php
namespace App\Repository;
use App\Entity\Item;
use Artprima\QueryFilterBundle\Query\Mysql\PaginationWalker;
use Artprima\QueryFilterBundle\Query\ConditionManager;
use Artprima\QueryFilterBundle\Query\ProxyQueryBuilder;
use Artprima\QueryFilterBundle\QueryFilter\QueryFilterArgs;
use Artprima\QueryFilterBundle\QueryFilter\QueryResult;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Symfony\Bridge\Doctrine\RegistryInterface;
/**
* @method Item|null find($id, $lockMode = null, $lockVersion = null)
* @method Item|null findOneBy(array $criteria, array $orderBy = null)
* @method Item[] findAll()
* @method Item[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
*/
class ItemRepository extends ServiceEntityRepository
{
/**
* @var ConditionManager
*/
private $pqbManager;
public function __construct(RegistryInterface $registry, ConditionManager $manager)
{
parent::__construct($registry, Item::class);
$this->pqbManager = $manager;
}
public function findByOrderBy(QueryFilterArgs $args): QueryResult
{
$qb = $this->createQueryBuilder('t')
->setFirstResult($args->getOffset())
->setMaxResults($args->getLimit());
$proxyQb = new ProxyQueryBuilder($qb, $this->pqbManager);
$qb = $proxyQb->getSortedAndFilteredQueryBuilder($args->getSearchBy(), $args->getSortBy());
$query = $qb->getQuery();
$query->setHint(Query::HINT_CUSTOM_OUTPUT_WALKER, PaginationWalker::class);
$query->setHint("mysqlWalker.sqlCalcFoundRows", true);
$result = $query->getResult();
$totalRows = $this->_em->getConnection()->query('SELECT FOUND_ROWS()')->fetchColumn();
return new QueryResult($result, $totalRows);
}
}
ItemConfig:
<?php
namespace App\QueryFilter\Config;
use Artprima\QueryFilterBundle\QueryFilter\Config\BaseConfig;
class ItemConfig extends BaseConfig
{
public function __construct()
{
$this->setSearchAllowedCols(array(
't.name',
));
$this->setSortCols(
array(
't.id',
),
array('t.id' => 'desc') // default
);
}
}
NOTE: assume that all the used fields are enabled in the configuration
- Performs
t.name LIKE
%doe% comparison - Performs
t.name = "Doe"
comparison - Performs
t.name <> "Doe"
comparison - Performs
t.name LIKE "Doe"
comparison - Performs
t.name NOT LIKE "Doe"
comparison - Performs
t.frequency BETWEEN 8 AND 10
comparison - Performs
t.frequency NOT BETWEEN 8 AND 10
comparison - Performs
t.frequency > 7
comparison - Performs
t.frequency >= 7
comparison - Performs
t.frequency IN (1, 2, 3, 4, 5)
comparison - Performs
t.frequency NOT IN (1, 2, 3, 4, 5)
comparison - Performs
t.description IS NULL
comparison - Performs
t.description IS NOT NULL
comparison - Performs
t.frequency < 7
comparison - Performs
t.frequency <= 7
comparison - Combined comparison
t.frequency < 7 AND t.monetary > 50
Simple mode should be enough for most of the cases, however sometimes we might need to build more complicated filters having one and the same field used.
- Performs
t.frequency = 10 OR t.frequency >= 85
(NOTE:filter[1][connector]=or
-connector
can beand
(default) oror
; connector used on the first filter has no effect)
- Second page (NOTE: if
page
is not given it defaults to 1) - Limit records to 100 (NOTE: if default limits were provided and
limit
is not within the allowed values, it will be reset to the default value)
- Performs
ORDER BY t.userId DESC
(ifsortdir
is not given it defaults toasc
)
NOTE: at the moment this bundle doesn't support more than one field for ORDER BY
.
This document is not finished yet, more examples will follow.
You are free to use the code in this repository under the terms of the MIT license. LICENSE contains a copy of this license.