From 80fee6565b738f309a8f45329eed4473a1bb13e4 Mon Sep 17 00:00:00 2001 From: Steve Mareigner Date: Fri, 5 Jan 2018 17:23:05 +0100 Subject: [PATCH 1/2] fix(api): Do not instantiate RequestClass in RouteMap Improve perfs --- src/Container/Container.php | 4 +- src/Entity/EntityFactory.php | 22 ++- src/Entity/EntityFactoryConfig.php | 22 ++- src/Entity/EntityFactoryInterface.php | 8 ++ src/Service/QueryModifier/Util/EasyFilter.php | 84 ++++++++++++ src/Service/QueryModifier/Util/EasySort.php | 84 ++++++++++++ src/Service/QueryModifier/Util/EasyUtil.php | 129 ++++++++++++++++++ .../{EasySorter.php => EasyFilter.php} | 2 +- .../QueryModifier/RequestQueryModifier.php | 4 +- src/Service/Router/Route.php | 13 +- .../QueryModifier/Util/EasyFilterTest.php | 68 +++++++++ 11 files changed, 419 insertions(+), 21 deletions(-) create mode 100644 src/Service/QueryModifier/Util/EasyFilter.php create mode 100644 src/Service/QueryModifier/Util/EasySort.php create mode 100644 src/Service/QueryModifier/Util/EasyUtil.php rename src/Service/Request/QueryModifier/Modifier/{EasySorter.php => EasyFilter.php} (99%) create mode 100644 tests/Service/QueryModifier/Util/EasyFilterTest.php diff --git a/src/Container/Container.php b/src/Container/Container.php index 3755772..30b3f0f 100644 --- a/src/Container/Container.php +++ b/src/Container/Container.php @@ -74,8 +74,8 @@ public function __construct(array $values = []) # Default Request Query Modifier (Do nothing), # Use your own implementation of RequestQueryModifierInterface if (!isset($values[self::ENTITY_FACTORY])) { - $this[self::ENTITY_FACTORY] = function () { - return new EntityFactory(); + $this[self::ENTITY_FACTORY] = function (ContainerInterface $c) { + return new EntityFactory($c); }; } diff --git a/src/Entity/EntityFactory.php b/src/Entity/EntityFactory.php index ffe548d..f9ed9c0 100644 --- a/src/Entity/EntityFactory.php +++ b/src/Entity/EntityFactory.php @@ -9,6 +9,8 @@ namespace Eukles\Entity; use Eukles\Action\ActionInterface; +use Eukles\Container\ContainerInterface; +use Eukles\Container\ContainerTrait; use Eukles\Util\PksFinder; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; @@ -17,6 +19,18 @@ class EntityFactory implements EntityFactoryInterface { + use ContainerTrait; + + /** + * EntityFactoryInterface constructor. + * + * @param ContainerInterface $c + */ + public function __construct(ContainerInterface $c) + { + $this->container = $c; + } + /** * Create a new instance of activeRecord and add it to Request attributes * @@ -33,7 +47,7 @@ public function create( ResponseInterface $response, callable $next ): ResponseInterface { - $entityRequest = $config->getEntityRequest(); + $entityRequest = $config->createEntityRequest($this->container); # make a new empty record $obj = $entityRequest->instantiateActiveRecord(); @@ -77,13 +91,11 @@ public function fetch( ResponseInterface $response, callable $next ): ResponseInterface { - $entityRequest = $config->getEntityRequest(); + $entityRequest = $config->createEntityRequest($this->container); # First, we try to determine PK in request path (most common case) if (isset($request->getAttribute('routeInfo')[2][$config->getRequestParameterName()])) { - $config->getEntityRequest()->setPrimaryKey( - $request->getAttribute('routeInfo')[2][$config->getRequestParameterName()] - ); + $entityRequest->setPrimaryKey($request->getAttribute('routeInfo')[2][$config->getRequestParameterName()]); } # Next, we create the query (ModelCriteria), based on Action class (which can alter the query) diff --git a/src/Entity/EntityFactoryConfig.php b/src/Entity/EntityFactoryConfig.php index f3ea809..4c9b39b 100644 --- a/src/Entity/EntityFactoryConfig.php +++ b/src/Entity/EntityFactoryConfig.php @@ -8,6 +8,7 @@ namespace Eukles\Entity; +use Eukles\Container\ContainerInterface; class EntityFactoryConfig { @@ -44,13 +45,11 @@ class EntityFactoryConfig * @var string */ protected $requestParameterName = "id"; - /** * @var string */ protected $type; - /** * Constructor wrapper * @@ -62,19 +61,29 @@ public static function create() } /** - * @return EntityRequestInterface + * @return string */ - public function getEntityRequest(): EntityRequestInterface + public function getEntityRequest(): string { return $this->entityRequest; } /** - * @param EntityRequestInterface $entityRequestClass + * @param ContainerInterface $container + * + * @return EntityRequestInterface + */ + public function createEntityRequest(ContainerInterface $container): EntityRequestInterface + { + return new $this->entityRequest($container); + } + + /** + * @param string $entityRequestClass Name * * @return EntityFactoryConfig */ - public function setEntityRequest(EntityRequestInterface $entityRequestClass): EntityFactoryConfig + public function setEntityRequest(string $entityRequestClass): EntityFactoryConfig { $this->entityRequest = $entityRequestClass; @@ -121,7 +130,6 @@ public function setRequestParameterName(string $requestParameterName): EntityFac return $this; } - /** * @return string */ diff --git a/src/Entity/EntityFactoryInterface.php b/src/Entity/EntityFactoryInterface.php index ce87b57..e045bf2 100644 --- a/src/Entity/EntityFactoryInterface.php +++ b/src/Entity/EntityFactoryInterface.php @@ -2,6 +2,7 @@ namespace Eukles\Entity; +use Eukles\Container\ContainerInterface; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; @@ -13,6 +14,13 @@ interface EntityFactoryInterface { + /** + * EntityFactoryInterface constructor. + * + * @param ContainerInterface $c + */ + public function __construct(ContainerInterface $c); + /** * Create a new instance of activeRecord and add it to Request attributes * diff --git a/src/Service/QueryModifier/Util/EasyFilter.php b/src/Service/QueryModifier/Util/EasyFilter.php new file mode 100644 index 0000000..af9d1cc --- /dev/null +++ b/src/Service/QueryModifier/Util/EasyFilter.php @@ -0,0 +1,84 @@ + operators + elseif ($firstChar === '>') { + $value = substr($value, 1); + $operator = Criteria::GREATER_THAN; + if (mb_substr($value, 0, 1) === '=') { + $value = substr($value, 1); + $operator = Criteria::GREATER_EQUAL; + } + } # Handle < operators + elseif ($firstChar === '<') { + $value = substr($value, 1); + $operator = Criteria::LESS_THAN; + if (strpos($value, '=') === 0) { + $value = substr($value, 1); + $operator = Criteria::LESS_EQUAL; + } + } + + // TODO handle [a,b] and ]a,b[ + // TODO handle "a,b" as a string and not as an array of [a,b] + + return [$operator, $value]; + } + + /** + * @return bool + */ + protected function filter() + { + # Determine if method is callable in Query class + $method = 'filterBy' . ucfirst($this->property); + if (!method_exists($this->query, $method)) { + return false; + } + + list($value, $operator) = $this->build($this->value); + + call_user_func([$this->query, $method], $value, $operator); + + return true; + } +} diff --git a/src/Service/QueryModifier/Util/EasySort.php b/src/Service/QueryModifier/Util/EasySort.php new file mode 100644 index 0000000..12fb003 --- /dev/null +++ b/src/Service/QueryModifier/Util/EasySort.php @@ -0,0 +1,84 @@ + operators + elseif ($firstChar === '>') { + $value = substr($value, 1); + $operator = Criteria::GREATER_THAN; + if (mb_substr($value, 0, 1) === '=') { + $value = substr($value, 1); + $operator = Criteria::GREATER_EQUAL; + } + } # Handle < operators + elseif ($firstChar === '<') { + $value = substr($value, 1); + $operator = Criteria::LESS_THAN; + if (strpos($value, '=') === 0) { + $value = substr($value, 1); + $operator = Criteria::LESS_EQUAL; + } + } + + // TODO handle [a,b] and ]a,b[ + // TODO handle "a,b" as a string and not as an array of [a,b] + + return [$operator, $value]; + } + + /** + * @return bool + */ + protected function filter() + { + # Determine if method is callable in Query class + $method = 'filterBy' . ucfirst($this->property); + if (!method_exists($this->query, $method)) { + return false; + } + + list($value, $operator) = $this->build($this->value); + + call_user_func([$this->query, $method], $value, $operator); + + return true; + } +} diff --git a/src/Service/QueryModifier/Util/EasyUtil.php b/src/Service/QueryModifier/Util/EasyUtil.php new file mode 100644 index 0000000..4cab1e5 --- /dev/null +++ b/src/Service/QueryModifier/Util/EasyUtil.php @@ -0,0 +1,129 @@ +query = $query; + } + + abstract protected function filter(); + + /** + * @param $value + * + * @return array + */ + abstract public static function build($value); + + /** + * @param ModelCriteria $query + * + * @return EasyUtil + */ + public static function create(ModelCriteria $query): EasyUtil + { + return new static($query); + } + + /** + * @param $property + * @param $value + * + * @return bool + */ + public function apply($property, $value): bool + { + $this->value = $value; + $this->property = $property; + if ($this->isAutoUseRelationQuery()) { + return $this->useRelationQuery(); + } else { + return $this->filter(); + } + } + + /** + * @return bool + */ + public function isAutoUseRelationQuery(): bool + { + return $this->autoUseRelationQuery; + } + + /** + * @param bool $autoUseRelationQuery + * + * @return EasyUtil + */ + public function setAutoUseRelationQuery(bool $autoUseRelationQuery): EasyUtil + { + $this->autoUseRelationQuery = $autoUseRelationQuery; + + return $this; + } + + /** + * @return bool + */ + private function useRelationQuery() + { + $map = explode(self::RELATION_SEP, $this->property); + $this->property = array_pop($map); + + if (count($map) > 0) { + $this->relations = $map; + foreach ($this->relations as $relation) { + $method = sprintf('use%sQuery', ucfirst($relation)); + if (!method_exists($this->query, $method)) { + throw new \RuntimeException("Relation '{$relation}' not found"); + } + } + + $result = $this->filter(); + + foreach ($this->relations as $null) { + $this->query->endUse(); + } + } else { + return $this->filter(); + } + + return $result; + } +} diff --git a/src/Service/Request/QueryModifier/Modifier/EasySorter.php b/src/Service/Request/QueryModifier/Modifier/EasyFilter.php similarity index 99% rename from src/Service/Request/QueryModifier/Modifier/EasySorter.php rename to src/Service/Request/QueryModifier/Modifier/EasyFilter.php index cf9865d..0c4ecea 100644 --- a/src/Service/Request/QueryModifier/Modifier/EasySorter.php +++ b/src/Service/Request/QueryModifier/Modifier/EasyFilter.php @@ -12,7 +12,7 @@ use Propel\Runtime\ActiveQuery\ModelCriteria; use Psr\Http\Message\ServerRequestInterface; -class EasySorter +class EasyFilter { /** diff --git a/src/Service/Request/QueryModifier/RequestQueryModifier.php b/src/Service/Request/QueryModifier/RequestQueryModifier.php index d3559f9..60f979a 100644 --- a/src/Service/Request/QueryModifier/RequestQueryModifier.php +++ b/src/Service/Request/QueryModifier/RequestQueryModifier.php @@ -9,7 +9,7 @@ namespace Eukles\Service\Request\QueryModifier; use Eukles\Service\QueryModifier\QueryModifierInterface; -use Eukles\Service\Request\QueryModifier\Modifier\EasySorter; +use Eukles\Service\Request\QueryModifier\Modifier\EasyFilter; use Eukles\Service\Request\QueryModifier\Modifier\FilterModifier; use Eukles\Service\Request\QueryModifier\Modifier\SortModifier; use Propel\Runtime\ActiveQuery\ModelCriteria; @@ -66,7 +66,7 @@ public function apply(ModelCriteria $query) $sorters = new SortModifier($this->request); $sorters->apply($query); - $easySorters = new EasySorter($this->request, ["sort", "filter"]); + $easySorters = new EasyFilter($this->request, ["sort", "filter"]); $easySorters->apply($query); return $query; diff --git a/src/Service/Router/Route.php b/src/Service/Router/Route.php index e0d4a62..f303643 100644 --- a/src/Service/Router/Route.php +++ b/src/Service/Router/Route.php @@ -198,11 +198,13 @@ public function createEntity(EntityFactoryConfig $config): RouteInterface } # Auto add EntityRequest if not specified if (!$config->issetEntityRequest()) { - $config->setEntityRequest(new $this->requestClass($this->getContainer())); + $config->setEntityRequest($this->requestClass); } # Auto determine name of parameter to add if (!$config->issetParameterToInjectInto()) { - $config->setParameterToInjectInto($config->getEntityRequest()->getNameOfParameterToAdd(false)); + $config->setParameterToInjectInto( + $config->createEntityRequest($this->container)->getNameOfParameterToAdd(false) + ); } # Make sure config is clean @@ -228,6 +230,7 @@ public function deprecated(): RouteInterface * @param EntityFactoryConfig $config * * @return RouteInterface + * @throws \Eukles\Entity\EntityFactoryConfigException */ public function fetchEntity(EntityFactoryConfig $config): RouteInterface { @@ -240,11 +243,13 @@ public function fetchEntity(EntityFactoryConfig $config): RouteInterface } # Auto add EntityRequest if not specified if (!$config->issetEntityRequest()) { - $config->setEntityRequest(new $this->requestClass($this->getContainer())); + $config->setEntityRequest($this->requestClass); } # Auto determine name of parameter to add if (!$config->issetParameterToInjectInto()) { - $config->setParameterToInjectInto($config->getEntityRequest()->getNameOfParameterToAdd(false)); + $config->setParameterToInjectInto( + $config->createEntityRequest($this->container)->getNameOfParameterToAdd(false) + ); } # Make sure config is clean diff --git a/tests/Service/QueryModifier/Util/EasyFilterTest.php b/tests/Service/QueryModifier/Util/EasyFilterTest.php new file mode 100644 index 0000000..6eed6d3 --- /dev/null +++ b/tests/Service/QueryModifier/Util/EasyFilterTest.php @@ -0,0 +1,68 @@ +assertSame(null, $operator); + $this->assertSame("foo", $value); + + list($operator, $value) = EasyFilter::build("!foo"); + $this->assertSame(Criteria::NOT_EQUAL, $operator); + $this->assertSame("foo", $value); + + list($operator, $value) = EasyFilter::build("foo,bar"); + $this->assertSame(null, $operator); + $this->assertSame(["foo", "bar"], $value); + + list($operator, $value) = EasyFilter::build("!foo,bar"); + $this->assertSame(Criteria::NOT_IN, $operator); + $this->assertSame(["foo", "bar"], $value); + + list($operator, $value) = EasyFilter::build("%foo,bar"); + $this->assertSame(Criteria::LIKE, $operator); + $this->assertSame("%foo,bar", $value); + + list($operator, $value) = EasyFilter::build("foo%"); + $this->assertSame(Criteria::LIKE, $operator); + $this->assertSame("foo%", $value); + + list($operator, $value) = EasyFilter::build("!%foo,bar"); + $this->assertSame(Criteria::NOT_LIKE, $operator); + $this->assertSame("%foo,bar", $value); + } + + public function testCreate() + { + $this->assertInstanceOf(EasyFilter::class, EasyFilter::create(new ModelCriteria())); + } + + public function testIsAutoUseRelationQuery() + { + + } + + public function testSetAutoUseRelationQuery() + { + + } +} From 6176c2cc68a355c6826bfe5fe8e40c9bbb09b7df Mon Sep 17 00:00:00 2001 From: Steve Mareigner Date: Mon, 8 Jan 2018 14:10:12 +0100 Subject: [PATCH 2/2] feat(QueryModifier): Improve EasySort/EasyFilter --- src/Action/ActionAbstract.php | 4 +- src/Config/Config.php | 2 +- src/Service/QueryModifier/Util/EasyFilter.php | 57 +++++++-- src/Service/QueryModifier/Util/EasySort.php | 79 +++++------- src/Service/QueryModifier/Util/EasyUtil.php | 31 ++--- .../QueryModifier/Modifier/EasyFilter.php | 49 +------- .../QueryModifier/Modifier/SortModifier.php | 10 +- tests/Action/ActionTest.php | 5 + tests/Config/ConfigTest.php | 24 +++- .../QueryModifier/Util/EasyFilterTest.php | 115 ++++++++++++++++-- .../QueryModifier/Util/EasySortTest.php | 114 +++++++++++++++++ .../QueryModifier/Util/EasyUtilTest.php | 39 ++++++ tests/schema-small.xml | 25 ++++ 13 files changed, 406 insertions(+), 148 deletions(-) create mode 100644 tests/Service/QueryModifier/Util/EasySortTest.php create mode 100644 tests/Service/QueryModifier/Util/EasyUtilTest.php create mode 100644 tests/schema-small.xml diff --git a/src/Action/ActionAbstract.php b/src/Action/ActionAbstract.php index 43dc73c..91289b1 100644 --- a/src/Action/ActionAbstract.php +++ b/src/Action/ActionAbstract.php @@ -33,9 +33,7 @@ abstract class ActionAbstract implements ActionInterface public function __construct(ContainerInterface $c) { $this->container = $c; - if ($c->has('request')) { - $this->request = $c['request']; - } + if ($c->has('response')) { $this->response = $c['response']; } diff --git a/src/Config/Config.php b/src/Config/Config.php index 5869c28..6a48c65 100644 --- a/src/Config/Config.php +++ b/src/Config/Config.php @@ -33,7 +33,7 @@ public function export() */ public function isEnvironment($environment) { - return in_array($this->get('app.environment'), (array)$environment); + return strtolower($this->get('app.environment')) === strtolower($environment); } /** diff --git a/src/Service/QueryModifier/Util/EasyFilter.php b/src/Service/QueryModifier/Util/EasyFilter.php index af9d1cc..e6d3c17 100644 --- a/src/Service/QueryModifier/Util/EasyFilter.php +++ b/src/Service/QueryModifier/Util/EasyFilter.php @@ -13,6 +13,11 @@ class EasyFilter extends EasyUtil { + /** + * @var mixed + */ + protected $value; + /** * @param $value * @@ -35,11 +40,24 @@ public static function build($value) # Handle LIKE operator when % is present in value if ($firstChar === '%' || strpos($value, '%') === strlen($value) - 1) { $operator = $negate ? Criteria::NOT_LIKE : Criteria::LIKE; - }# Handle IN operator when comma is present - elseif (strpos($value, ',') !== false) { - # IN operator is handled by propel - $operator = $negate ? Criteria::NOT_IN : null; - $value = explode(',', $value); + }# Handle min/max operator when [ is present + elseif ($firstChar === '[') { + $operator = null; + $value = substr($value, 1); + $lastChar = substr($value, -1); + if ($lastChar === ']') { + $value = substr($value, 0, -1); + } + $value = explode(',', $value); + if (empty($value[0])) { + $value = null; + } else { + $valueTmp = ['min' => $value[0]]; + if (!empty($value[1])) { + $valueTmp['max'] = $value[1]; + } + $value = $valueTmp; + } } # Handle > operators elseif ($firstChar === '>') { $value = substr($value, 1); @@ -56,14 +74,35 @@ public static function build($value) $value = substr($value, 1); $operator = Criteria::LESS_EQUAL; } + } elseif ($firstChar === '"' || $firstChar === "'") { + $value = trim($value, "\"'"); + } # Handle IN operator when comma is present + elseif (strpos($value, ',') !== false) { + # IN operator is handled by propel + $operator = $negate ? Criteria::NOT_IN : null; + $value = explode(',', $value); } - // TODO handle [a,b] and ]a,b[ - // TODO handle "a,b" as a string and not as an array of [a,b] - return [$operator, $value]; } + /** + * @param $property + * @param $value + * + * @return bool + */ + public function apply($property, $value): bool + { + $this->value = $value; + $this->property = $property; + if ($this->isAutoUseRelationQuery()) { + return $this->useRelationQuery(); + } else { + return $this->filter(); + } + } + /** * @return bool */ @@ -77,7 +116,7 @@ protected function filter() list($value, $operator) = $this->build($this->value); - call_user_func([$this->query, $method], $value, $operator); + $this->query = call_user_func([$this->query, $method], $value, $operator); return true; } diff --git a/src/Service/QueryModifier/Util/EasySort.php b/src/Service/QueryModifier/Util/EasySort.php index 12fb003..3deb673 100644 --- a/src/Service/QueryModifier/Util/EasySort.php +++ b/src/Service/QueryModifier/Util/EasySort.php @@ -9,59 +9,43 @@ namespace Eukles\Service\QueryModifier\Util; use Propel\Runtime\ActiveQuery\Criteria; +use Propel\Runtime\ActiveQuery\Exception\UnknownColumnException; +use Propel\Runtime\ActiveQuery\Exception\UnknownModelException; class EasySort extends EasyUtil { /** - * @param $value + * @var string + */ + protected $direction; + + /** + * @param $property * * @return array */ - public static function build($value) + public static function build($property) { - # Use default operator - $operator = null; - - # Handle negate operator - $firstChar = mb_substr($value, 0, 1); - $negate = $firstChar === '!'; - if ($negate) { - $value = substr($value, 1); - $operator = Criteria::NOT_EQUAL; - $firstChar = mb_substr($value, 0, 1); - } - - # Handle LIKE operator when % is present in value - if ($firstChar === '%' || strpos($value, '%') === strlen($value) - 1) { - $operator = $negate ? Criteria::NOT_LIKE : Criteria::LIKE; - }# Handle IN operator when comma is present - elseif (strpos($value, ',') !== false) { - # IN operator is handled by propel - $operator = $negate ? Criteria::NOT_IN : null; - $value = explode(',', $value); - } # Handle > operators - elseif ($firstChar === '>') { - $value = substr($value, 1); - $operator = Criteria::GREATER_THAN; - if (mb_substr($value, 0, 1) === '=') { - $value = substr($value, 1); - $operator = Criteria::GREATER_EQUAL; - } - } # Handle < operators - elseif ($firstChar === '<') { - $value = substr($value, 1); - $operator = Criteria::LESS_THAN; - if (strpos($value, '=') === 0) { - $value = substr($value, 1); - $operator = Criteria::LESS_EQUAL; - } + $direction = Criteria::ASC; + if (strpos($property, '+') === 0) { + $property = substr($property, 1); + } elseif (strpos($property, '-') === 0) { + $property = substr($property, 1); + $direction = Criteria::DESC; } - // TODO handle [a,b] and ]a,b[ - // TODO handle "a,b" as a string and not as an array of [a,b] + return [$property, $direction]; + } - return [$operator, $value]; + public function apply($sort): bool + { + list($this->property, $this->direction) = self::build($sort); + if ($this->isAutoUseRelationQuery()) { + return $this->useRelationQuery(); + } else { + return $this->filter(); + } } /** @@ -69,16 +53,15 @@ public static function build($value) */ protected function filter() { - # Determine if method is callable in Query class - $method = 'filterBy' . ucfirst($this->property); - if (!method_exists($this->query, $method)) { + # Try to call method in Query class + try { + $this->query = $this->query->orderBy(ucfirst($this->property), $this->direction); + } catch (UnknownColumnException $e) { + return false; + } catch (UnknownModelException $e) { return false; } - list($value, $operator) = $this->build($this->value); - - call_user_func([$this->query, $method], $value, $operator); - return true; } } diff --git a/src/Service/QueryModifier/Util/EasyUtil.php b/src/Service/QueryModifier/Util/EasyUtil.php index 4cab1e5..c118e8b 100644 --- a/src/Service/QueryModifier/Util/EasyUtil.php +++ b/src/Service/QueryModifier/Util/EasyUtil.php @@ -30,7 +30,6 @@ abstract class EasyUtil * @var array */ protected $relations = []; - protected $value; /** * EasyFilter constructor. @@ -44,12 +43,6 @@ public function __construct(ModelCriteria $query) abstract protected function filter(); - /** - * @param $value - * - * @return array - */ - abstract public static function build($value); /** * @param ModelCriteria $query @@ -62,20 +55,11 @@ public static function create(ModelCriteria $query): EasyUtil } /** - * @param $property - * @param $value - * - * @return bool + * @return ModelCriteria */ - public function apply($property, $value): bool + public function getQuery(): ModelCriteria { - $this->value = $value; - $this->property = $property; - if ($this->isAutoUseRelationQuery()) { - return $this->useRelationQuery(); - } else { - return $this->filter(); - } + return $this->query; } /** @@ -89,7 +73,7 @@ public function isAutoUseRelationQuery(): bool /** * @param bool $autoUseRelationQuery * - * @return EasyUtil + * @return static */ public function setAutoUseRelationQuery(bool $autoUseRelationQuery): EasyUtil { @@ -101,7 +85,7 @@ public function setAutoUseRelationQuery(bool $autoUseRelationQuery): EasyUtil /** * @return bool */ - private function useRelationQuery() + protected function useRelationQuery() { $map = explode(self::RELATION_SEP, $this->property); $this->property = array_pop($map); @@ -111,14 +95,15 @@ private function useRelationQuery() foreach ($this->relations as $relation) { $method = sprintf('use%sQuery', ucfirst($relation)); if (!method_exists($this->query, $method)) { - throw new \RuntimeException("Relation '{$relation}' not found"); + return false; } + $this->query = call_user_func([$this->query, $method]); } $result = $this->filter(); foreach ($this->relations as $null) { - $this->query->endUse(); + $this->query = $this->query->endUse(); } } else { return $this->filter(); diff --git a/src/Service/Request/QueryModifier/Modifier/EasyFilter.php b/src/Service/Request/QueryModifier/Modifier/EasyFilter.php index 0c4ecea..1774660 100644 --- a/src/Service/Request/QueryModifier/Modifier/EasyFilter.php +++ b/src/Service/Request/QueryModifier/Modifier/EasyFilter.php @@ -8,7 +8,6 @@ namespace Eukles\Service\Request\QueryModifier\Modifier; -use Propel\Runtime\ActiveQuery\Criteria; use Propel\Runtime\ActiveQuery\ModelCriteria; use Psr\Http\Message\ServerRequestInterface; @@ -41,55 +40,15 @@ public function __construct(ServerRequestInterface $request, array $ignoredParam */ public function apply(ModelCriteria $query) { + $filter = new \Eukles\Service\QueryModifier\Util\EasyFilter($query); + $filter->setAutoUseRelationQuery(true); + foreach ($this->request->getQueryParams() as $column => $value) { # Ignored params if (in_array($column, $this->ignoredParams)) { continue; } - - # Determine if method is callable in Query class - $method = 'filterBy' . ucfirst($column); - if (!method_exists($query, $method)) { - continue; - } - - # Use default operator - $operator = Criteria::EQUAL; - - # Handle negate operator - $negate = strpos($value, '!') === 0; - if ($negate) { - $value = substr($value, 1); - $operator = Criteria::NOT_EQUAL; - } - - # Handle LIKE operator when % is present in value - if (strpos($value, '%') === 0 || strpos($value, '%') === strlen($value) - 1) { - $operator = $negate ? Criteria::NOT_LIKE : Criteria::LIKE; - }# Handle IN operator when comma is present - elseif (strpos($value, ',') !== false) { - # IN operator is handled by propel - $operator = null; - $value = explode(',', $value); - } # Handle > operators - elseif (strpos($value, '>') === 0) { - $value = substr($value, 1); - $operator = Criteria::GREATER_THAN; - if (strpos($value, '=') === 0) { - $value = substr($value, 1); - $operator = Criteria::GREATER_EQUAL; - } - } # Handle < operators - elseif (strpos($value, '<') === 0) { - $value = substr($value, 1); - $operator = Criteria::LESS_THAN; - if (strpos($value, '=') === 0) { - $value = substr($value, 1); - $operator = Criteria::LESS_EQUAL; - } - } - - call_user_func([$query, $method], $value, $operator); + $filter->apply($column, $value); } } } diff --git a/src/Service/Request/QueryModifier/Modifier/SortModifier.php b/src/Service/Request/QueryModifier/Modifier/SortModifier.php index 2b81d66..8a01e9f 100644 --- a/src/Service/Request/QueryModifier/Modifier/SortModifier.php +++ b/src/Service/Request/QueryModifier/Modifier/SortModifier.php @@ -11,6 +11,7 @@ namespace Eukles\Service\Request\QueryModifier\Modifier; +use Eukles\Service\QueryModifier\Util\EasySort; use Eukles\Service\Request\QueryModifier\Modifier\Base\ModifierBase; use Propel\Runtime\ActiveQuery\Criteria; use Propel\Runtime\ActiveQuery\Exception\UnknownColumnException; @@ -43,14 +44,7 @@ public function setModifierFromRequest(ServerRequestInterface $request) if (is_string($modifiers) && null === json_decode($modifiers)) { $sorters = explode(',', $modifiers); foreach ($sorters as $sorter) { - $direction = Criteria::ASC; - if (strpos($sorter, '+') === 0) { - $sorter = substr($sorter, 1); - } elseif (strpos($sorter, '-') === 0) { - $sorter = substr($sorter, 1); - $direction = Criteria::DESC; - } - + list($sorter, $direction) = EasySort::build($sorter); $this->modifiers[] = [ 'property' => $sorter, 'direction' => $direction, diff --git a/tests/Action/ActionTest.php b/tests/Action/ActionTest.php index f14341b..f0956d4 100644 --- a/tests/Action/ActionTest.php +++ b/tests/Action/ActionTest.php @@ -32,6 +32,11 @@ public function testConstructWithContainer() public function testResponse() { + $c = new \Slim\Container(["response" => function () { return new \Slim\Http\Response(); }]); + /** @var ActionInterface $a */ + $a = $this->getMockForAbstractClass(ActionAbstract::class, [$c]); + $this->assertSame($c["response"], $a->getResponse()); + /** @var ActionInterface $a */ $a = $this->getMockForAbstractClass(ActionAbstract::class, [], "", false); $r = new Response(); diff --git a/tests/Config/ConfigTest.php b/tests/Config/ConfigTest.php index a37f0b8..43bbe80 100644 --- a/tests/Config/ConfigTest.php +++ b/tests/Config/ConfigTest.php @@ -13,7 +13,7 @@ class ConfigTest extends TestCase { - + public function testExport() { $configArray = [ @@ -22,12 +22,28 @@ public function testExport() 'key2' => "value2", ], ]; - + $config = new Config($configArray); - + $configString = $config->export(); $newConfigArray = eval("return " . $configString . ";"); - + $this->assertSame($newConfigArray, $configArray); } + + public function testIsEnvironment() + { + $config = new Config(['app' => ['environment' => "DEV"]]); + + $this->assertTrue($config->isEnvironment('dev')); + $this->assertTrue($config->isEnvironment('DEV')); + $this->assertFalse($config->isEnvironment('foo')); + } + + public function testIsNotProduction() + { + $config = new Config(['app' => ['environment' => "DEV"]]); + + $this->assertTrue($config->isNotProduction()); + } } diff --git a/tests/Service/QueryModifier/Util/EasyFilterTest.php b/tests/Service/QueryModifier/Util/EasyFilterTest.php index 6eed6d3..7d00698 100644 --- a/tests/Service/QueryModifier/Util/EasyFilterTest.php +++ b/tests/Service/QueryModifier/Util/EasyFilterTest.php @@ -9,17 +9,13 @@ namespace Eukles\Service\QueryModifier\Util; use PHPUnit\Framework\TestCase; +use Propel\Generator\Util\QuickBuilder; use Propel\Runtime\ActiveQuery\Criteria; use Propel\Runtime\ActiveQuery\ModelCriteria; class EasyFilterTest extends TestCase { - public function testApply() - { - - } - public function testBuild() { list($operator, $value) = EasyFilter::build("foo"); @@ -46,9 +42,60 @@ public function testBuild() $this->assertSame(Criteria::LIKE, $operator); $this->assertSame("foo%", $value); + list($operator, $value) = EasyFilter::build(">1"); + $this->assertSame(Criteria::GREATER_THAN, $operator); + $this->assertSame("1", $value); + + list($operator, $value) = EasyFilter::build("<1"); + $this->assertSame(Criteria::LESS_THAN, $operator); + $this->assertSame("1", $value); + + list($operator, $value) = EasyFilter::build(">=1"); + $this->assertSame(Criteria::GREATER_EQUAL, $operator); + $this->assertSame("1", $value); + + list($operator, $value) = EasyFilter::build("<=1"); + $this->assertSame(Criteria::LESS_EQUAL, $operator); + $this->assertSame("1", $value); + list($operator, $value) = EasyFilter::build("!%foo,bar"); $this->assertSame(Criteria::NOT_LIKE, $operator); $this->assertSame("%foo,bar", $value); + + list($operator, $value) = EasyFilter::build("[1,5]"); + $this->assertSame(null, $operator); + $this->assertSame(["min" => "1", "max" => "5"], $value); + list($operator, $value) = EasyFilter::build("[1,5"); + $this->assertSame(null, $operator); + $this->assertSame(["min" => "1", "max" => "5"], $value); + + list($operator, $value) = EasyFilter::build("[1,5]"); + $this->assertSame(null, $operator); + $this->assertSame(["min" => "1", "max" => "5"], $value); + + list($operator, $value) = EasyFilter::build("[1,5"); + $this->assertSame(null, $operator); + $this->assertSame(["min" => "1", "max" => "5"], $value); + + list($operator, $value) = EasyFilter::build("[1"); + $this->assertSame(null, $operator); + $this->assertSame(["min" => "1"], $value); + + list($operator, $value) = EasyFilter::build("[1,"); + $this->assertSame(null, $operator); + $this->assertSame(["min" => "1"], $value); + + list($operator, $value) = EasyFilter::build("[]"); + $this->assertSame(null, $operator); + $this->assertSame(null, $value); + + list($operator, $value) = EasyFilter::build("["); + $this->assertSame(null, $operator); + $this->assertSame(null, $value); + + list($operator, $value) = EasyFilter::build("'test,quoted'"); + $this->assertSame(null, $operator); + $this->assertSame("test,quoted", $value); } public function testCreate() @@ -56,13 +103,67 @@ public function testCreate() $this->assertInstanceOf(EasyFilter::class, EasyFilter::create(new ModelCriteria())); } - public function testIsAutoUseRelationQuery() + public function testApplySimpleFailure() { + $f = new EasyFilter($this->mockQueryInstance()); + # FALSE when relation and no use query + $this->assertFalse($f->apply('baz', "foo")); } - public function testSetAutoUseRelationQuery() + public function testApplySimpleSuccess() { + $f = new EasyFilter($this->mockQueryInstance()); + + # TRUE when no relation + $this->assertTrue($f->apply('aName', "foo")); + } + + public function testApplyUseRelationQueryFailures() + { + $f = new EasyFilter($this->mockQueryInstance()); + + # FALSE when relation and no use query + $this->assertFalse($f->apply('b.bName', "foo")); + + # FALSE when relation and unknown property + $f->setAutoUseRelationQuery(true); + $this->assertFalse($f->apply('b.foo', "foo")); + } + + public function testApplyUseRelationQuerySuccess() + { + $f = new EasyFilter($this->mockQueryInstance()); + + # TRUE when no relation and unknown property + $this->assertTrue($f->apply('aName', "foo")); + + # TRUE when relation and no use query + $f->setAutoUseRelationQuery(true); + $this->assertTrue($f->apply('b.bName', "foo")); + $this->assertTrue($f->apply('b.c.cName', "foo")); + $this->assertTrue($f->apply('b.c.id', 0)); + } + + public static function setUpBeforeClass() + { + if (!class_exists("\\A")) { + $schema = file_get_contents(__DIR__ . '/../../../schema-small.xml'); + $builder = new QuickBuilder(); + $builder->setSchema($schema); + $builder->buildClasses(); + } + } + + /** + * @return ModelCriteria $query + */ + protected function mockQueryInstance() + { + + /** @var ModelCriteria $query */ + $query = new \AQuery; + return $query; } } diff --git a/tests/Service/QueryModifier/Util/EasySortTest.php b/tests/Service/QueryModifier/Util/EasySortTest.php new file mode 100644 index 0000000..5f2a637 --- /dev/null +++ b/tests/Service/QueryModifier/Util/EasySortTest.php @@ -0,0 +1,114 @@ +setSchema($schema); + $builder->buildClasses(); + } + } + + public function testApplySimpleFailure() + { + $f = new EasySort($this->mockQueryInstance()); + + # FALSE when relation and no use query + $this->assertFalse($f->apply('baz')); + } + + public function testApplySimpleSuccess() + { + $f = new EasySort($this->mockQueryInstance()); + + # TRUE when relation and no use query + $this->assertTrue($f->apply('-aName')); + } + + public function testApplyUseRelationQueryFailures() + { + $f = new EasySort($this->mockQueryInstance()); + + # FALSE when relation and no use query + $this->assertFalse($f->apply('-b.bName')); + + # FALSE when relation and unknown property + $f->setAutoUseRelationQuery(true); + $this->assertFalse($f->apply('b.foo')); + + # FALSE when relation and bad placement of direction + $f->setAutoUseRelationQuery(true); + $this->assertFalse($f->apply('b.-foo')); + + # FALSE when bad relation + $f->setAutoUseRelationQuery(true); + $this->assertFalse($f->apply('bad.foo')); + } + + public function testApplyUseRelationQuerySuccess() + { + $f = new EasySort($this->mockQueryInstance()); + $f->setAutoUseRelationQuery(true); + + $this->assertTrue($f->apply('-aName')); + $this->assertTrue($f->apply('-b.bName')); + $this->assertTrue($f->apply('-b.c.cName')); + $this->assertTrue($f->apply('-b.c.id')); + + $this->assertSame([ + 0 => 'a.a_name DESC', + 1 => 'b.b_name DESC', + 2 => 'c.c_name DESC', + 3 => 'c.id DESC', + ], $f->getQuery()->getOrderByColumns()); + } + + public function testBuild() + { + list($property, $direction) = EasySort::build("foo"); + $this->assertSame("foo", $property); + $this->assertSame($direction, Criteria::ASC); + + list($property, $direction) = EasySort::build("+foo"); + $this->assertSame("foo", $property); + $this->assertSame($direction, Criteria::ASC); + + list($property, $direction) = EasySort::build("-foo"); + $this->assertSame("foo", $property); + $this->assertSame($direction, Criteria::DESC); + } + + public function testCreate() + { + $this->assertInstanceOf(EasySort::class, EasySort::create(new ModelCriteria())); + } + + /** + * @return ModelCriteria $query + */ + protected function mockQueryInstance() + { + + /** @var ModelCriteria $query */ + $query = new \AQuery; + + return $query; + } +} diff --git a/tests/Service/QueryModifier/Util/EasyUtilTest.php b/tests/Service/QueryModifier/Util/EasyUtilTest.php new file mode 100644 index 0000000..473af7f --- /dev/null +++ b/tests/Service/QueryModifier/Util/EasyUtilTest.php @@ -0,0 +1,39 @@ +getMockForAbstractClass(EasyUtil::class, [$query]); + $this->assertSame($query, $t->getQuery()); + } + + public function testIsAutoUseRelationQuery() + { + /** @var EasyUtil $t */ + $t = $this->getMockForAbstractClass(EasyUtil::class, [new ModelCriteria]); + $this->assertFalse($t->isAutoUseRelationQuery()); + } + + public function testSetAutoUseRelationQuery() + { + /** @var EasyUtil $t */ + $t = $this->getMockForAbstractClass(EasyUtil::class, [new ModelCriteria]); + $t->setAutoUseRelationQuery(true); + $this->assertTrue($t->isAutoUseRelationQuery()); + } +} diff --git a/tests/schema-small.xml b/tests/schema-small.xml new file mode 100644 index 0000000..00eedfe --- /dev/null +++ b/tests/schema-small.xml @@ -0,0 +1,25 @@ + + + + +
+ + + + + + + + +
+ + + + + + + + + +
+