partkeepr

fork of partkeepr
git clone https://git.e1e0.net/partkeepr.git
Log | Files | Refs | Submodules | README | LICENSE

commit b9c0a89940ca08fff6ed13378e0fc86bcafe4742
parent 2ebd499185f46d1dd9fd0522be7663b2ff1ea4aa
Author: Felicitus <felicitus@felicitus.org>
Date:   Wed, 27 May 2015 18:33:52 +0200

Added doctrine rest controller which can be called by the ExtJS stores. Supports filtering and sorting

Diffstat:
Asrc/PartKeepr/DoctrineReflectionBundle/Controller/DoctrineRESTQueryController.php | 274+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/PartKeepr/DoctrineReflectionBundle/Filter/Filter.php | 142+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/PartKeepr/DoctrineReflectionBundle/Sorter/Sorter.php | 96+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/PartKeepr/FrontendBundle/Resources/public/js/Data/JsonReader.js | 0
Asrc/PartKeepr/FrontendBundle/Resources/public/js/Data/RestProxy.js | 16++++++++++++++++
Msrc/PartKeepr/FrontendBundle/Resources/public/js/ExtJS/Enhancements/Ext.data.reader.Json-exceptionHandling.js | 2+-
Msrc/PartKeepr/FrontendBundle/Resources/views/index.html.twig | 1+
Msrc/PartKeepr/SiPrefixBundle/Controller/DefaultController.php | 24+++++++++++-------------
8 files changed, 541 insertions(+), 14 deletions(-)

diff --git a/src/PartKeepr/DoctrineReflectionBundle/Controller/DoctrineRESTQueryController.php b/src/PartKeepr/DoctrineReflectionBundle/Controller/DoctrineRESTQueryController.php @@ -0,0 +1,273 @@ +<?php + + +namespace PartKeepr\DoctrineReflectionBundle\Controller; + + +use Doctrine\ORM\QueryBuilder; +use Doctrine\ORM\Tools\Pagination\Paginator; +use FOS\RestBundle\Controller\Annotations\QueryParam; +use FOS\RestBundle\Controller\FOSRestController; +use FOS\RestBundle\Request\ParamFetcher; +use PartKeepr\DoctrineReflectionBundle\Filter\Filter; +use PartKeepr\DoctrineReflectionBundle\Sorter\Sorter; +use PartKeepr\PartKeepr; + +class DoctrineRESTQueryController extends FOSRestController +{ + /** + * The target entity to operate on + * @var string + */ + protected $targetEntity; + + /** + * An array which contains all entity aliases used for queries + * @var array + */ + protected $queryAliases = array(); + + /** + * Returns the target entity to operate on + * + * @return string The target entity + */ + public function getTargetEntity() + { + return $this->targetEntity; + } + + /** + * Sets the target entity. Use a FQDN entity class + * + * @param string $targetEntity + */ + public function setTargetEntity($targetEntity) + { + $this->targetEntity = $targetEntity; + } + + /** + * @QueryParam(name="start",default=0,requirements="\d+",description="Sets the start record") + * @QueryParam(name="limit",default=-1,requirements="\d+",description="Sets the number of records to fetch. -1 means unlimited") + * @QueryParam(name="sort", default=null) + * @QueryParam(name="filter", default=null) + */ + public function getQueryResponseAction (ParamFetcher $paramFetcher) { + $qb = $this->getQueryBuilder(); + + $qb->select($this->getQueryAlias())->from($this->getTargetEntity(), $this->getQueryAlias()); + + $this->applyPagination($qb, $paramFetcher); + $this->applySorting($qb, $paramFetcher); + $this->applyFilters($qb, $paramFetcher); + + $query = $qb->getQuery(); + + $paginator = new Paginator($query); + + return array( + "totalCount" => count($paginator), + "data" => $paginator->getIterator()->getArrayCopy() + ); + } + + /** + * Applies filters to the specified query builder. Reads the "filter" parameter. + * + * @param QueryBuilder $qb + * @param ParamFetcher $paramFetcher + * + * @throws \Exception + */ + protected function applyFilters(QueryBuilder $qb, ParamFetcher $paramFetcher) + { + $filter = $paramFetcher->get("filter"); + + if ($filter === null) { + return; + } + + $filters = $this->parseFilters($filter); + + $expr = array(); + + foreach ($filters as $key => $filter) { + $filterField = $this->getQueryAlias($filter->getTargetEntity()).".".$filter->getFilterField(); + $parameterName = ":parameter" . $key; + + $qb->setParameter($parameterName, $filter->getFilterValue()); + switch ($filter->getFilterOperator()) { + case "<": + $expr[] = $qb->expr()->lt($filterField, $parameterName); + break; + case "<=": + $expr[] = $qb->expr()->lte($filterField, $parameterName); + break; + case "=": + $expr[] = $qb->expr()->eq($filterField, $parameterName); + break; + case ">": + $expr[] = $qb->expr()->gt($filterField, $parameterName); + break; + case ">=": + $expr[] = $qb->expr()->gte($filterField, $parameterName); + break; + case "!=": + $expr[] = $qb->expr()->neq($filterField, $parameterName); + break; + case "in": + $expr[] = $qb->expr()->in($filterField, $parameterName); + break; + case "like": + $expr[] = $qb->expr()->like($filterField, $parameterName); + break; + } + } + + $qb->where(call_user_func_array(array($qb->expr(), "andX"), $expr)); + } + + /** + * Applies sorting to the specified query builder. Reads the "sort" parameter. + * + * @param QueryBuilder $qb + * @param ParamFetcher $paramFetcher + * + * @throws \Exception + */ + protected function applySorting (QueryBuilder $qb, ParamFetcher $paramFetcher) { + $sort = $paramFetcher->get("sort"); + + if ($sort === NULL) { + return; + } + + $sorters = $this->parseSorters($sort); + + foreach ($sorters as $sorter) { + $sort = $this->getQueryAlias($sorter->getTargetEntity()) .".".$sorter->getSortField(); + $qb->addOrderBy($sort, $sorter->getSortDirection()); + } + } + + /** + * Parses the sorters and returns an array of sorters. + * + * @param $sort string A json string which includes the sorters + * @return Sorter[] + */ + protected function parseSorters ($sort) { + $decodedJson = json_decode($sort, true); + + if ($decodedJson === null) { + throw new \Exception("sort parameter has an invalid format"); + } + + /** + * @var $sorters Sorter[] + */ + $sorters = array(); + + foreach ($decodedJson as $sorter) { + if (!array_key_exists("property", $sorter)) { + throw new \Exception("property parameter has an invalid format"); + } + + if (!array_key_exists("direction", $sorter)) { + throw new \Exception("direction parameter has an invalid format"); + } + + $sorters[] = new Sorter($this->getTargetEntity(), $sorter["property"], $sorter["direction"]); + } + + return $sorters; + } + + /** + * Parses the sorters and returns an array of sorters. + * + * @param $sort string A json string which includes the sorters + * + * @return Filter[] + */ + protected function parseFilters($filter) + { + $decodedJson = json_decode($filter, true); + + if ($decodedJson === null) { + throw new \Exception("filter parameter has an invalid format"); + } + + /** + * @var $sorters Sorter[] + */ + $filters = array(); + + foreach ($decodedJson as $filter) { + if (!array_key_exists("property", $filter)) { + throw new \Exception("property parameter has an invalid format"); + } + + if (!array_key_exists("value", $filter)) { + throw new \Exception("value parameter has an invalid format"); + } + + if (!array_key_exists("operator", $filter)) { + throw new \Exception("operator parameter has an invalid format"); + } + + $filters[] = new Filter($this->getTargetEntity(), $filter["property"], $filter["value"], $filter["operator"]); + } + + return $filters; + } + /** + * Applies pagination to the query builder + * + * @param QueryBuilder $qb + * @param ParamFetcher $paramFetcher + */ + protected function applyPagination(QueryBuilder $qb, ParamFetcher $paramFetcher) + { + $start = intval($paramFetcher->get("start")); + $limit = intval($paramFetcher->get("limit")); + + + $qb->setFirstResult($start); + + if ($limit > 0) { + $qb->setMaxResults($limit); + } + } + + /** + * Returns the query builder + * + * @return QueryBuilder The query builder + */ + protected function getQueryBuilder() + { + return PartKeepr::getEM()->createQueryBuilder(); + } + + /** + * Returns a query alias for the query builder. + * + * @param $entity string The FQDN for the entity + * + * @return string A short alias for use in queries + */ + protected function getQueryAlias ($entity = NULL) { + if ($entity === NULL) { + $entity = $this->getTargetEntity(); + } + + if (!array_key_exists($entity, $this->queryAliases)) { + $reflectionClass = new \ReflectionClass($entity); + $this->queryAliases[$entity] = $reflectionClass->getShortName().count($this->queryAliases); + } + + return $this->queryAliases[$entity]; + } +}+ \ No newline at end of file diff --git a/src/PartKeepr/DoctrineReflectionBundle/Filter/Filter.php b/src/PartKeepr/DoctrineReflectionBundle/Filter/Filter.php @@ -0,0 +1,141 @@ +<?php +namespace PartKeepr\DoctrineReflectionBundle\Filter; + +/** + * Represents a filter + * + * This allows the developer to specify multiple filters. + */ +class Filter +{ + /** + * The FQDN target entity to filter + * + * @var string + */ + + private $targetEntity; + + /** + * The field to filter by + * + * @var string + */ + private $filterField = null; + + /** + * The value to filter for + * + * @var string + */ + private $filterValue = null; + + /** + * The operator to use + * @var string + */ + private $operator; + + /** + * Constructs a new filter. + * + * @param string $field The field to filter for + * @param string $value The value to filter for. Must be a scalar value, except if the operator is IN, where it may be an array + * @param string $operator The operator to use. Supported operators are <, <=, =, >=, >, !=, in, like + */ + public function __construct($targetEntity, $field, $value, $operator = "=") + { + $this->targetEntity = $targetEntity; + + $this->setFilterField($field); + $this->setFilterOperator($operator); + $this->setFilterValue($value); + } + + /** + * Sets the filter field for this filter + * + * @param string $field The field to filter by + */ + public function setFilterField($field) + { + $this->filterField = $field; + } + + /** + * Sets the filter operator + * + * @param string $operator The operator to use. Supported operators are <, <=, =, >=, >, !=, in, like + */ + public function setFilterOperator ($operator) + { + switch (strtolower($operator)) { + case "<": + $this->operator = "<"; + break; + case ">": + $this->operator = ">"; + break; + case "=": + $this->operator = "="; + break; + case "<=": + $this->operator = "<="; + break; + case ">=": + $this->operator = ">="; + break; + case "!=": + $this->operator = "!="; + break; + case "in": + $this->operator = "in"; + break; + case "like": + $this->operator = "like"; + break; + } + } + + /** + * Sets the filter value + * @param $value + */ + public function setFilterValue ($value) { + $this->filterValue = $value; + } + + public function getFilterValue () { + return $this->filterValue; + } + + /** + * Returns the target entity to sort + * + * @return string + */ + public function getTargetEntity() + { + return $this->targetEntity; + } + + /** + * Returns the filter field + * + * @return string The field name + */ + public function getFilterField() + { + return $this->filterField; + } + + /** + * Returns the operator for this filter + * + * @return string + */ + public function getFilterOperator() + { + return $this->operator; + } +}+ \ No newline at end of file diff --git a/src/PartKeepr/DoctrineReflectionBundle/Sorter/Sorter.php b/src/PartKeepr/DoctrineReflectionBundle/Sorter/Sorter.php @@ -0,0 +1,95 @@ +<?php +namespace PartKeepr\DoctrineReflectionBundle\Sorter; + +/** + * Represents a sorter + * + * This allows the developer to specify multiple sorters. One sorter contains a sort field and a sort direction. + */ +class Sorter { + /** + * The FQDN target entity to sort + * @var string + */ + + private $targetEntity; + /** + * The field to sort by + * @var string + */ + private $sortField = null; + + /** + * The direction to sort by + * @var string + */ + private $sortDirection = null; + + /** + * Constructs a new sorter. + * + * @param string $field The field to sort by + * @param string $direction The direction, either "asc" or "desc" + */ + public function __construct ($targetEntity, $field = null, $direction = null) { + $this->targetEntity = $targetEntity; + + if ($field !== null) { + $this->setSortField($field); + } + + if ($direction !== null) { + $this->setSortDirection($direction); + } + } + + /** + * Sets the sort field for this sorter + * @param string $field The field to sort by + */ + public function setSortField ($field) { + $this->sortField = $field; + } + + /** + * Sets the sort direction for this sorter + * @param string $direction Either "asc" or "desc" + */ + public function setSortDirection ($direction) { + switch (strtolower($direction)) { + case "desc": + case "d": + $this->sortDirection = "desc"; + break; + case "asc": + case "a": + default: + $this->sortDirection = "asc"; + break; + } + } + + /** + * Returns the target entity to sort + * @return string + */ + public function getTargetEntity () { + return $this->targetEntity; + } + + /** + * Returns the sort field for this sorter + * @return string The field name + */ + public function getSortField () { + return $this->sortField; + } + + /** + * Returns the sort order for this sorter + * @return string Either "asc" or "desc" + */ + public function getSortDirection () { + return $this->sortDirection; + } +}+ \ No newline at end of file diff --git a/src/PartKeepr/FrontendBundle/Resources/public/js/Data/JsonReader.js b/src/PartKeepr/FrontendBundle/Resources/public/js/Data/JsonReader.js diff --git a/src/PartKeepr/FrontendBundle/Resources/public/js/Data/RestProxy.js b/src/PartKeepr/FrontendBundle/Resources/public/js/Data/RestProxy.js @@ -0,0 +1,15 @@ +Ext.define("PartKeepr.data.RestProxy", { + extend: 'Ext.data.proxy.Rest', + alias: 'proxy.PartKeeprREST', + + reader: { + type: 'json', + rootProperty: 'data', + totalProperty: 'totalCount' + }, + + constructor: function (config) { + config.url = PartKeepr.getBasePath() + config.url; + this.callParent(arguments); + } +});+ \ No newline at end of file diff --git a/src/PartKeepr/FrontendBundle/Resources/public/js/ExtJS/Enhancements/Ext.data.reader.Json-exceptionHandling.js b/src/PartKeepr/FrontendBundle/Resources/public/js/ExtJS/Enhancements/Ext.data.reader.Json-exceptionHandling.js @@ -3,7 +3,7 @@ Ext.override(Ext.data.reader.Json, { var data; try { data = Ext.decode(response.responseText); - return this.readRecords(data); + return data; } catch (ex) { error = new Ext.data.ResultSet({ diff --git a/src/PartKeepr/FrontendBundle/Resources/views/index.html.twig b/src/PartKeepr/FrontendBundle/Resources/views/index.html.twig @@ -52,6 +52,7 @@ '@PartKeeprFrontendBundle/Resources/public/js/Dialogs/ExceptionWindow.js' '@PartKeeprFrontendBundle/Resources/public/js/Dialogs/FileUploadDialog.js' '@PartKeeprFrontendBundle/Resources/public/js/Dialogs/RememberChoiceMessageBox.js' + '@PartKeeprFrontendBundle/Resources/public/js/Data/RestProxy.js' '@PartKeeprFrontendBundle/Resources/public/js/Components/Statusbar.js' '@PartKeeprFrontendBundle/Resources/public/js/ExtJS/Enhancements/Ext.input.CharContextMenu.js' '@PartKeeprFrontendBundle/Resources/public/js/ExtJS/Enhancements/Ext.data.Connection-sessionInjection.js' diff --git a/src/PartKeepr/SiPrefixBundle/Controller/DefaultController.php b/src/PartKeepr/SiPrefixBundle/Controller/DefaultController.php @@ -2,18 +2,17 @@ namespace PartKeepr\SiPrefixBundle\Controller; -use FOS\RestBundle\Controller\FOSRestController; -use PartKeepr\Manager\ManagerFilter; -use PartKeepr\SiPrefix\SiPrefixManager; +use FOS\RestBundle\Request\ParamFetcher; +use PartKeepr\DoctrineReflectionBundle\Controller\DoctrineRESTQueryController; use Sensio\Bundle\FrameworkExtraBundle\Configuration as Routing; use JMS\Serializer\Annotation as JMS; use Nelmio\ApiDocBundle\Annotation\ApiDoc; use FOS\RestBundle\Controller\Annotations\View; -class DefaultController extends FOSRestController +class DefaultController extends DoctrineRESTQueryController { /** - * Retrieves all SI Prefixes in the database + * Retrieves SI Prefixes in the database * * @Routing\Route("/siprefix", defaults={"method" = "get","_format" = "json"}) * @Routing\Method({"GET"}) @@ -21,16 +20,15 @@ class DefaultController extends FOSRestController * * @View() * + * {@inheritdoc} */ - public function getSiPrefixesAction() + public function getQueryResponseAction(ParamFetcher $paramFetcher) { - $siPrefixes = SiPrefixManager::getInstance()->getList(new ManagerFilter()); + $this->setTargetEntity("PartKeepr\\SiPrefixBundle\\Entity\\SiPrefix"); - $data = array(); - - foreach ($siPrefixes["data"] as $siPrefix) { - $data[] = SiPrefixManager::getInstance()->getEntity($siPrefix["id"]); - } - return $data; + return parent::getQueryResponseAction($paramFetcher); } + + + } \ No newline at end of file