diff --git a/composer.json b/composer.json index b50c4e180..f7d7588ae 100644 --- a/composer.json +++ b/composer.json @@ -26,6 +26,7 @@ "pimple/pimple": "^3.0", "slim/slim": "^2.6.3", "slim/views": "^0.1.0", + "symfony/options-resolver": "^3.3", "twig/twig": "~1.17" }, "require-dev": { diff --git a/composer.lock b/composer.lock index ff3f38bc0..8e6517a68 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "85d3c2536a059671db43cee714e5ff3c", + "content-hash": "551d596307af7e264baf5b407fe9f1e8", "packages": [ { "name": "alcaeus/mongo-php-adapter", @@ -331,6 +331,60 @@ ], "time": "2014-12-09T23:48:51+00:00" }, + { + "name": "symfony/options-resolver", + "version": "v3.3.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/options-resolver.git", + "reference": "ff48982d295bcac1fd861f934f041ebc73ae40f0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/options-resolver/zipball/ff48982d295bcac1fd861f934f041ebc73ae40f0", + "reference": "ff48982d295bcac1fd861f934f041ebc73ae40f0", + "shasum": "" + }, + "require": { + "php": ">=5.5.9" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.3-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\OptionsResolver\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony OptionsResolver Component", + "homepage": "https://symfony.com", + "keywords": [ + "config", + "configuration", + "options" + ], + "time": "2017-04-12T14:14:56+00:00" + }, { "name": "symfony/polyfill-ctype", "version": "v1.18.1", diff --git a/src/Xhgui/Controller/RunController.php b/src/Xhgui/Controller/RunController.php index a8688e62d..288ee7989 100644 --- a/src/Xhgui/Controller/RunController.php +++ b/src/Xhgui/Controller/RunController.php @@ -7,6 +7,7 @@ use Slim\Http\Response; use Slim\Slim as App; use XHGui\AbstractController; +use XHGui\Options\SearchOptions; use XHGui\Searcher\SearcherInterface; class RunController extends AbstractController @@ -44,14 +45,14 @@ public function index(Request $request, Response $response) } $sort = $request->get('sort'); - $result = $this->searcher->getAll([ + $result = $this->searcher->getAll(new SearchOptions([ 'sort' => $sort, 'page' => (int)$request->get('page', SearcherInterface::DEFAULT_PAGE), 'direction' => $request->get('direction'), 'perPage' => (int)$this->app->config('page.limit'), 'conditions' => $search, 'projection' => true, - ]); + ])); $title = 'Recent runs'; $titleMap = [ diff --git a/src/Xhgui/Controller/WaterfallController.php b/src/Xhgui/Controller/WaterfallController.php index b421b7550..1499bd384 100644 --- a/src/Xhgui/Controller/WaterfallController.php +++ b/src/Xhgui/Controller/WaterfallController.php @@ -6,6 +6,7 @@ use Slim\Http\Response; use Slim\Slim as App; use XHGui\AbstractController; +use XHGui\Options\SearchOptions; use XHGui\Profile; use XHGui\Searcher\SearcherInterface; @@ -32,12 +33,12 @@ public function index() $search[$key] = trim($request->get($key)); } } - $result = $this->searcher->getAll([ + $result = $this->searcher->getAll(new SearchOptions([ 'sort' => 'time', 'direction' => 'asc', 'conditions' => $search, 'projection' => true, - ]); + ])); $paging = [ 'total_pages' => $result['totalPages'], @@ -62,12 +63,12 @@ public function query(Request $request, Response $response) foreach ($keys as $key) { $search[$key] = $request->get($key); } - $result = $this->searcher->getAll([ + $result = $this->searcher->getAll(new SearchOptions([ 'sort' => 'time', 'direction' => 'asc', 'conditions' => $search, 'projection' => true, - ]); + ])); $datas = []; /** @var Profile $r */ foreach ($result['results'] as $r) { diff --git a/src/Xhgui/Db/Mapper.php b/src/Xhgui/Db/Mapper.php index 1e8908645..c33f9d594 100644 --- a/src/Xhgui/Db/Mapper.php +++ b/src/Xhgui/Db/Mapper.php @@ -18,18 +18,15 @@ public function convert(array $options): array 'conditions' => [], 'sort' => null, 'direction' => null, - 'perPage' => 25, + 'perPage' => $options['perPage'] ?? SearcherInterface::DEFAULT_PER_PAGE, ]; + if (isset($options['conditions'])) { $result['conditions'] = $this->buildConditions($options['conditions']); } $result['direction'] = $this->buildDirection($options); $result['sort'] = $this->buildSort($options); - if (isset($options['perPage'])) { - $result['perPage'] = $options['perPage']; - } - return $result; } diff --git a/src/Xhgui/Options/OptionsConfigurator.php b/src/Xhgui/Options/OptionsConfigurator.php new file mode 100644 index 000000000..93e2f1002 --- /dev/null +++ b/src/Xhgui/Options/OptionsConfigurator.php @@ -0,0 +1,61 @@ +configureOptions($resolver); + + $this->options = $resolver->resolve($options); + } + + abstract protected function configureOptions(OptionsResolver $resolver); + + public function offsetExists($offset): bool + { + return array_key_exists($offset, $this->options); + } + + public function offsetGet($offset) + { + return $this->options[$offset]; + } + + public function offsetSet($offset, $value) + { + if (null === $offset) { + $this->options[] = $value; + } else { + $this->options[$offset] = $value; + } + } + + public function offsetUnset($offset) + { + unset($this->options[$offset]); + } + + public function getIterator(): ArrayIterator + { + return new ArrayIterator($this->options); + } + + public function toArray(): array + { + return $this->options; + } +} diff --git a/src/Xhgui/Options/SearchOptions.php b/src/Xhgui/Options/SearchOptions.php new file mode 100644 index 000000000..47ff829d1 --- /dev/null +++ b/src/Xhgui/Options/SearchOptions.php @@ -0,0 +1,60 @@ + -1 ????) + * - direction: an string, either 'desc' or 'asc' + * - page: an integer, the page to display (e.g. 3) + * - perPage: an integer, how many profiles to display per page (e.g. 25) + * - conditions: an array of criteria to match + * - projection: an array or bool + */ + protected function configureOptions(OptionsResolver $resolver) + { + // NOTE: the null values is trickery to set default values via null value + $defaults = [ + 'sort' => null, + 'direction' => SearcherInterface::DEFAULT_DIRECTION, + 'page' => SearcherInterface::DEFAULT_PAGE, + 'perPage' => SearcherInterface::DEFAULT_PER_PAGE, + 'conditions' => [], + 'projection' => false, + ]; + $resolver->setDefaults($defaults); + $resolver->setRequired(['sort', 'direction', 'page', 'perPage']); + + $resolver->setAllowedTypes('sort', ['null', 'string']); + $resolver->setAllowedTypes('direction', ['null', 'string']); + $resolver->setAllowedTypes('page', 'int'); + $resolver->setAllowedTypes('perPage', ['null', 'int']); + $resolver->setAllowedTypes('conditions', 'array'); + $resolver->setAllowedTypes('projection', ['bool', 'array']); + + $resolver->setAllowedValues('direction', [null, 'asc', 'desc']); + $resolver->setAllowedValues('sort', [null, 'time', 'wt', 'cpu', 'mu', 'pmu']); + + $resolver->setNormalizer('direction', function (Options $options, $value) use ($defaults) { + if (!$value) { + return $defaults['direction']; + } + + return $value; + }); + $resolver->setNormalizer('page', function (Options $options, $value) use ($defaults) { + if (!$value) { + return $defaults['page']; + } + + return $value; + }); + } +} diff --git a/src/Xhgui/Searcher/MongoSearcher.php b/src/Xhgui/Searcher/MongoSearcher.php index 6ed73df0a..22beb7eac 100644 --- a/src/Xhgui/Searcher/MongoSearcher.php +++ b/src/Xhgui/Searcher/MongoSearcher.php @@ -8,6 +8,7 @@ use MongoDb; use MongoId; use XHGui\Db\Mapper; +use XHGui\Options\SearchOptions; use XHGui\Profile; /** @@ -206,9 +207,9 @@ public function getAvgsForUrl($url, $search = []) /** * {@inheritdoc} */ - public function getAll($options = []) + public function getAll(SearchOptions $options): array { - return $this->paginate($options); + return $this->paginate($options->toArray()); } /** @@ -230,7 +231,7 @@ public function truncate() /** * {@inheritdoc} */ - public function saveWatch(array $data) + public function saveWatch(array $data): bool { if (empty($data['name'])) { return false; @@ -267,7 +268,7 @@ public function saveWatch(array $data) /** * {@inheritdoc} */ - public function getAllWatches() + public function getAllWatches(): array { $cursor = $this->_watches->find(); @@ -285,7 +286,7 @@ public function truncateWatches() /** * {@inheritdoc} */ - private function paginate($options) + private function paginate(array $options): array { $opts = $this->_mapper->convert($options); @@ -357,7 +358,7 @@ private function _wrap($data) /** * {@inheritdoc} */ - public function stats() + public function stats(): array { return [ 'profiles' => 0, diff --git a/src/Xhgui/Searcher/PdoSearcher.php b/src/Xhgui/Searcher/PdoSearcher.php index 43a70ec77..acbbcdf72 100644 --- a/src/Xhgui/Searcher/PdoSearcher.php +++ b/src/Xhgui/Searcher/PdoSearcher.php @@ -3,6 +3,7 @@ namespace XHGui\Searcher; use XHGui\Db\PdoRepository; +use XHGui\Options\SearchOptions; use XHGui\Profile; class PdoSearcher implements SearcherInterface @@ -96,7 +97,7 @@ public function getAvgsForUrl($url, $search = []) /** * {@inheritdoc} */ - public function getAll($options = []) + public function getAll(SearchOptions $options): array { $page = (int)$options['page']; $direction = $options['direction'] ?? SearcherInterface::DEFAULT_DIRECTION; diff --git a/src/Xhgui/Searcher/SearcherInterface.php b/src/Xhgui/Searcher/SearcherInterface.php index c1db386da..001238df7 100644 --- a/src/Xhgui/Searcher/SearcherInterface.php +++ b/src/Xhgui/Searcher/SearcherInterface.php @@ -4,6 +4,7 @@ use Exception; use MongoCursor; +use XHGui\Options\SearchOptions; use XHGui\Profile; /** @@ -12,6 +13,7 @@ interface SearcherInterface { const DEFAULT_DIRECTION = 'desc'; + const DEFAULT_PER_PAGE = 25; const DEFAULT_PAGE = 1; /** @@ -80,7 +82,7 @@ public function getAvgsForUrl($url, $search = []); /** * Get a paginated set of results. * - * @param array $options the find options to use + * @param SearchOptions $options the find options to use * @return array An array of result data with the following keys: * - results: an array of Profile objects * - sort: an array of search criteria (TODO meta.SERVER.REQUEST_TIME => -1 ????) @@ -89,7 +91,7 @@ public function getAvgsForUrl($url, $search = []); * - perPage: an integer, how many profiles to display per page (e.g. 25) * - totalPages: an integer, total number of pages (e.g. 10) */ - public function getAll($options = []); + public function getAll(SearchOptions $options): array; /** * Delete a profile run. diff --git a/tests/Controller/RunTest.php b/tests/Controller/RunTest.php index a80235437..51168d0bf 100644 --- a/tests/Controller/RunTest.php +++ b/tests/Controller/RunTest.php @@ -6,6 +6,7 @@ use Slim\Slim as App; use XHGui\Controller\ImportController; use XHGui\Controller\RunController; +use XHGui\Options\SearchOptions; use XHGui\Saver\MongoSaver; use XHGui\Searcher\MongoSearcher; use XHGui\ServiceContainer; @@ -210,12 +211,12 @@ public function testDeleteSubmit() $this->app->expects($this->once()) ->method('redirect'); - $result = $this->profiles->getAll(); + $result = $this->profiles->getAll(new SearchOptions()); $this->assertCount(5, $result['results']); $this->runs->deleteSubmit($this->app->request()); - $result = $this->profiles->getAll(); + $result = $this->profiles->getAll(new SearchOptions()); $this->assertCount(4, $result['results']); } @@ -236,12 +237,12 @@ public function testDeleteAllSubmit() $this->app->expects($this->once()) ->method('redirect'); - $result = $this->profiles->getAll(); + $result = $this->profiles->getAll(new SearchOptions()); $this->assertCount(5, $result['results']); $this->runs->deleteAllSubmit(); - $result = $this->profiles->getAll(); + $result = $this->profiles->getAll(new SearchOptions()); $this->assertCount(0, $result['results']); } diff --git a/tests/Searcher/MongoTest.php b/tests/Searcher/MongoTest.php index 379fb9ccb..fc60104e6 100644 --- a/tests/Searcher/MongoTest.php +++ b/tests/Searcher/MongoTest.php @@ -2,6 +2,7 @@ namespace XHGui\Test\Searcher; +use XHGui\Options\SearchOptions; use XHGui\Profile; use XHGui\Searcher\MongoSearcher; use XHGui\ServiceContainer; @@ -125,13 +126,13 @@ public function testGetPercentileForUrlWithLimit() public function testGetAllConditions() { - $result = $this->mongo->getAll([ + $result = $this->mongo->getAll(new SearchOptions([ 'conditions' => [ 'date_start' => '2013-01-20', 'date_end' => '2013-01-21', 'url' => 'tasks', ], - ]); + ])); $this->assertEquals(1, $result['page']); $this->assertEquals(25, $result['perPage']); $this->assertEquals(1, $result['totalPages']);