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:
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