From 34dcd7c85ccc62ef42c9efcf520d783fedb23ace Mon Sep 17 00:00:00 2001 From: MGatner Date: Sat, 19 Sep 2020 20:31:02 +0000 Subject: [PATCH 1/2] Implement model-like methods --- src/Handlers.php | 155 ++++++++++++++++++++++++++++++++----- tests/unit/LibraryTest.php | 19 +++-- tests/unit/SearchTest.php | 64 +++++++++++---- 3 files changed, 194 insertions(+), 44 deletions(-) diff --git a/src/Handlers.php b/src/Handlers.php index d55cf3b..05b5f04 100644 --- a/src/Handlers.php +++ b/src/Handlers.php @@ -29,11 +29,11 @@ class Handlers protected $cache; /** - * Array of attribute criteria. + * Array of filters. * - * @var array + * @var array of [key, operator, value, combine] */ - protected $criteria = []; + protected $filters = []; /** * Array of discovered HandlerInterface class names and their attributes. @@ -57,7 +57,7 @@ public function __construct(string $path = '', HandlersConfig $config = null, Ca } /** - * Returns the curent configuration. + * Returns the current configuration. * * @return HandlersConfig */ @@ -97,7 +97,7 @@ public function setPath(string $path): self //-------------------------------------------------------------------- /** - * Adds attribute criteria. + * Adds attribute filters. * * @param array $criteria * @@ -105,19 +105,33 @@ public function setPath(string $path): self */ public function where(array $criteria): self { - $this->criteria = array_merge($this->criteria, $criteria); + $this->parseCriteria($criteria, true); return $this; } /** - * Resets criteria between returns. + * Adds attribute filters that do not combine. + * + * @param array $criteria + * + * @return $this + */ + public function orWhere(array $criteria): self + { + $this->parseCriteria($criteria, false); + + return $this; + } + + /** + * Resets filters between returns. * * @return $this */ public function reset(): self { - $this->criteria = []; + $this->filters = []; return $this; } @@ -142,7 +156,7 @@ public function first(): ?string * * @return array */ - public function all(): array + public function findAll(): array { $classes = $this->filterHandlers(); $this->reset(); @@ -157,7 +171,7 @@ public function all(): array * * @return string|null The full class name, or null if none found */ - public function named(string $name): ?string + public function find(string $name): ?string { $this->discoverHandlers(); @@ -196,6 +210,58 @@ public function named(string $name): ?string //-------------------------------------------------------------------- + /** + * Returns an array of all matched classes. + * + * @return array + * @deprecated Use findAll() + */ + public function all(): array + { + return $this->findAll(); + } + + /** + * Returns a handler with a given name. Ignores filters. + * Searches: attribute "name" or "uid", namespaced class, and short class name. + * + * @param string $name The name of the handler + * + * @return string|null The full class name, or null if none found + * @deprecated Use find() + */ + public function named(string $name): ?string + { + return $this->find($name); + } + + //-------------------------------------------------------------------- + + /** + * Parses "where" $criteria and adds to $filters + * + * @param array $criteria Array of 'key [operator]' => 'value' + * @param bool $combine Whether the resulting filter should be combined with others + */ + protected function parseCriteria(array $criteria, bool $combine): void + { + foreach ($criteria as $key => $value) + { + // Check for an operator + $key = trim($key); + if (strpos($key, ' ')) + { + list($key, $operator) = explode(' ', $key); + } + else + { + $operator = '=='; + } + + $this->filters[] = [$key, $operator, $value, $combine]; + } + } + /** * Iterates through discovered handlers and attempts to register them. * @@ -231,18 +297,19 @@ public function register(): array //-------------------------------------------------------------------- /** - * Filters discovered classes by the criteria. + * Filters discovered classes by the defined criteria. * * @param int|null $limit Limit on how many classes to match * * @return array + * @throws \RuntimeException */ protected function filterHandlers(int $limit = null): array { $this->discoverHandlers(); // Make sure there is work to do - if (empty($this->criteria) || empty($this->discovered)) + if (empty($this->filters) || empty($this->discovered)) { $classes = array_keys($this->discovered); @@ -252,21 +319,67 @@ protected function filterHandlers(int $limit = null): array $classes = []; foreach ($this->discovered as $class => $attributes) { - // Check each attribute against the criteria - foreach ($this->criteria as $key => $value) + $result = true; + + // Check each attribute against the filters + foreach ($this->filters as $filter) { - if ($attributes[$key] !== $value) + // Split out the array to make it easier to read + list($key, $operator, $value, $combine) = $filter; + + if (! isset($attributes[$key])) + { + $result = false; + continue; + } + + switch ($operator) { - continue 2; + case '==': + case '=': + $test = $attributes[$key] == $value; + break; + + case '===': + $test = $attributes[$key] === $value; + break; + + case '>=': + $test = $attributes[$key] >= $value; + break; + + case '<=': + $test = $attributes[$key] <= $value; + break; + + // Assumes the attribute is a CSV + case 'has': + $test = in_array($value, explode(',', $attributes[$key])); + break; + + default: + throw new \RuntimeException($operator . ' is not a vald criteria operator'); } - } - // A match! - $classes[] = $class; + // If this filter was sufficient on its own then skip the rest of the filters + if ($test && ! $combine) + { + $result = true; + break; + } + + $result = $result && $test; + } - if ($limit && count($classes) >= $limit) + // Check for a match + if ($result) { - return $classes; + $classes[] = $class; + + if ($limit && count($classes) >= $limit) + { + return $classes; + } } } diff --git a/tests/unit/LibraryTest.php b/tests/unit/LibraryTest.php index f0e8b8d..bf1295e 100644 --- a/tests/unit/LibraryTest.php +++ b/tests/unit/LibraryTest.php @@ -29,25 +29,30 @@ public function testSetPathChangesPath() $this->assertEquals($path, $result); } - public function testWhereMergesCriteria() + public function testWhereCombinesFilters() { $this->handlers->where(['group' => 'East']); - $result = $this->getPrivateProperty($this->handlers, 'criteria'); - $this->assertEquals($result, ['group' => 'East']); + $result = $this->getPrivateProperty($this->handlers, 'filters'); + $this->assertEquals($result, [ + ['group', '==', 'East', true], + ]); $this->handlers->where(['uid' => 'pop']); - $result = $this->getPrivateProperty($this->handlers, 'criteria'); - $this->assertEquals($result, ['group' => 'East', 'uid' => 'pop']); + $result = $this->getPrivateProperty($this->handlers, 'filters'); + $this->assertEquals($result, [ + ['group', '==', 'East', true], + ['uid', '==', 'pop', true], + ]); } - public function testResetClearsCriteria() + public function testResetClearsFilters() { $this->handlers->where(['group' => 'East']); $this->handlers->reset(); - $result = $this->getPrivateProperty($this->handlers, 'criteria'); + $result = $this->getPrivateProperty($this->handlers, 'filters'); $this->assertEquals($result, []); } diff --git a/tests/unit/SearchTest.php b/tests/unit/SearchTest.php index 11c5e0e..c79ee36 100644 --- a/tests/unit/SearchTest.php +++ b/tests/unit/SearchTest.php @@ -7,34 +7,68 @@ class SearchTest extends HandlerTestCase { - public function testAllDiscoversAll() + public function testWhereFilters() + { + $expected = ['Tests\Support\Factories\WidgetFactory']; + + $result = $this->handlers + ->where(['uid' => 'widget']) + ->findAll(); + + $this->assertEquals($expected, $result); + } + + public function testWhereMissingAttribute() + { + $result = $this->handlers + ->where(['foo' => 'bar']) + ->findAll(); + + $this->assertEquals([], $result); + } + + public function testOrWhereIgnoresOtherFilters() + { + $expected = ['Tests\Support\Factories\WidgetFactory']; + + $result = $this->handlers + ->where(['foo' => 'bar']) + ->orWhere(['uid' => 'widget']) + ->findAll(); + + $this->assertEquals($expected, $result); + } + + //-------------------------------------------------------------------- + + public function testFindAllDiscoversAll() { $expected = [ 'Tests\Support\Factories\PopFactory', 'Tests\Support\Factories\WidgetFactory', ]; - $result = $this->handlers->all(); + $result = $this->handlers->findAll(); $this->assertEquals($expected, $result); } - public function testAllRespectsCriteria() + public function testFindAllRespectsFilters() { $expected = [ 'Tests\Support\Factories\WidgetFactory', ]; - $result = $this->handlers->where(['uid' => 'widget'])->all(); + $result = $this->handlers->where(['uid' => 'widget'])->findAll(); $this->assertEquals($expected, $result); } - public function testAllResetsCriteria() + public function testFindAllResetsFilters() { - $this->handlers->where(['uid' => 'widget'])->all(); + $this->handlers->where(['uid' => 'widget'])->findAll(); - $result = $this->getPrivateProperty($this->handlers, 'criteria'); + $result = $this->getPrivateProperty($this->handlers, 'filters'); $this->assertEquals([], $result); } @@ -48,22 +82,20 @@ public function testFirstReturnsSingleton() $this->assertEquals($expected, $result); } - public function testFirstRespectsCriteria() + public function testFirstRespectsFilters() { - $expected = [ - 'Tests\Support\Factories\WidgetFactory', - ]; + $expected = 'Tests\Support\Factories\WidgetFactory'; - $result = $this->handlers->where(['uid' => 'widget'])->all(); + $result = $this->handlers->where(['uid' => 'widget'])->first(); $this->assertEquals($expected, $result); } - public function testFirstResetsCriteria() + public function testFirstResetsFilters() { $this->handlers->where(['uid' => 'widget'])->first(); - $result = $this->getPrivateProperty($this->handlers, 'criteria'); + $result = $this->getPrivateProperty($this->handlers, 'filters'); $this->assertEquals([], $result); } @@ -73,9 +105,9 @@ public function testFirstResetsCriteria() /** * @dataProvider provideNames */ - public function testNamedFindsMatch($name, $success) + public function testFindFindsMatch($name, $success) { - $result = $this->handlers->named($name); + $result = $this->handlers->find($name); $this->assertEquals($success, (bool) $result); } From 99a841bef7a5bbe86fe4c52c720df0c1d2492ef2 Mon Sep 17 00:00:00 2001 From: MGatner Date: Sat, 19 Sep 2020 20:37:19 +0000 Subject: [PATCH 2/2] Add operator tests --- src/Handlers.php | 8 ++++++++ tests/_support/Factories/PopFactory.php | 2 ++ tests/_support/Factories/WidgetFactory.php | 2 ++ tests/unit/SearchTest.php | 22 ++++++++++++++++++++++ 4 files changed, 34 insertions(+) diff --git a/src/Handlers.php b/src/Handlers.php index 05b5f04..2afc302 100644 --- a/src/Handlers.php +++ b/src/Handlers.php @@ -344,10 +344,18 @@ protected function filterHandlers(int $limit = null): array $test = $attributes[$key] === $value; break; + case '>': + $test = $attributes[$key] > $value; + break; + case '>=': $test = $attributes[$key] >= $value; break; + case '<': + $test = $attributes[$key] < $value; + break; + case '<=': $test = $attributes[$key] <= $value; break; diff --git a/tests/_support/Factories/PopFactory.php b/tests/_support/Factories/PopFactory.php index f782af8..3abc310 100644 --- a/tests/_support/Factories/PopFactory.php +++ b/tests/_support/Factories/PopFactory.php @@ -12,6 +12,8 @@ class PopFactory implements HandlerInterface 'name' => 'Pop Factory', 'uid' => 'pop', 'summary' => 'Makes pop', + 'cost' => 1, + 'list' => 'five,six', ]; public function process() diff --git a/tests/_support/Factories/WidgetFactory.php b/tests/_support/Factories/WidgetFactory.php index 274f571..d246f89 100644 --- a/tests/_support/Factories/WidgetFactory.php +++ b/tests/_support/Factories/WidgetFactory.php @@ -10,6 +10,8 @@ class WidgetFactory extends BaseHandler 'name' => 'Widget Plant', 'uid' => 'widget', 'summary' => "The world's largest supplier of widgets!", + 'cost' => 10, + 'list' => 'one,two,three,four', ]; public function process() diff --git a/tests/unit/SearchTest.php b/tests/unit/SearchTest.php index c79ee36..4cb9dd9 100644 --- a/tests/unit/SearchTest.php +++ b/tests/unit/SearchTest.php @@ -18,6 +18,28 @@ public function testWhereFilters() $this->assertEquals($expected, $result); } + public function testWhereUsesOperators() + { + $expected = ['Tests\Support\Factories\WidgetFactory']; + + $result = $this->handlers + ->where(['cost >' => 5]) + ->findAll(); + + $this->assertEquals($expected, $result); + } + + public function testWhereSupportsCsv() + { + $expected = ['Tests\Support\Factories\WidgetFactory']; + + $result = $this->handlers + ->where(['list has' => 'three']) + ->findAll(); + + $this->assertEquals($expected, $result); + } + public function testWhereMissingAttribute() { $result = $this->handlers