diff --git a/src/Controllers/ViewProxies/AbstractViewProxy.php b/src/Controllers/ViewProxies/AbstractViewProxy.php index 262f6465..4fa93f7c 100644 --- a/src/Controllers/ViewProxies/AbstractViewProxy.php +++ b/src/Controllers/ViewProxies/AbstractViewProxy.php @@ -7,11 +7,11 @@ abstract class AbstractViewProxy public function fromArray($entities) { - return array_map( + return array_values(array_map( function($entity) { return static::fromEntity($entity); }, - $entities); + $entities)); } } diff --git a/src/Dao/AbstractDao.php b/src/Dao/AbstractDao.php index 4bf96a09..4355d9f8 100644 --- a/src/Dao/AbstractDao.php +++ b/src/Dao/AbstractDao.php @@ -71,34 +71,20 @@ abstract class AbstractDao implements ICrudDao public function findFiltered(\Szurubooru\SearchServices\AbstractSearchFilter $searchFilter) { - $orderByString = $this->compileOrderBy($searchFilter->order); - - $query = $this->fpdo - ->from($this->tableName) - ->orderBy($orderByString); - - $this->decorateQueryFromFilter($query, $searchFilter); - + $query = $this->prepareBaseQuery($searchFilter); return $this->arrayToEntities(iterator_to_array($query)); } public function findFilteredAndPaged(\Szurubooru\SearchServices\AbstractSearchFilter $searchFilter, $pageNumber, $pageSize) { - $orderByString = $this->compileOrderBy($searchFilter->order); - - $query = $this->fpdo - ->from($this->tableName) - ->orderBy($orderByString) - ->limit($pageSize) - ->offset($pageSize * ($pageNumber - 1)); - - $this->decorateQueryFromFilter($query, $searchFilter); - + $query = $this->prepareBaseQuery($searchFilter); + $query->limit($pageSize); + $query->offset($pageSize * ($pageNumber - 1)); $entities = $this->arrayToEntities(iterator_to_array($query)); - $query = $this->fpdo - ->from($this->tableName) - ->select('COUNT(1) AS c'); - $totalRecords = intval(iterator_to_array($query)[0]['c']); + + $query = $this->prepareBaseQuery($searchFilter); + $query->orderBy(null); + $totalRecords = count($query); $pagedSearchResult = new \Szurubooru\SearchServices\PagedSearchResult(); $pagedSearchResult->setSearchFilter($searchFilter); @@ -204,7 +190,19 @@ abstract class AbstractDao implements ICrudDao return $entities; } - private function compileOrderBy($order) + private function prepareBaseQuery(\Szurubooru\SearchServices\AbstractSearchFilter $searchFilter) + { + $query = $this->fpdo->from($this->tableName); + + $orderByString = self::compileOrderBy($searchFilter->getOrder()); + if ($orderByString) + $query->orderBy($orderByString); + + $this->decorateQueryFromFilter($query, $searchFilter); + return $query; + } + + private static function compileOrderBy($order) { $orderByString = ''; foreach ($order as $orderColumn => $orderDir) diff --git a/src/Dao/SearchFilter.php b/src/Dao/SearchFilter.php deleted file mode 100644 index a5189b30..00000000 --- a/src/Dao/SearchFilter.php +++ /dev/null @@ -1,21 +0,0 @@ -pageSize = intval($pageSize); - if ($searchFormData) - { - $this->query = $searchFormData->query; - $this->order = $searchFormData->order; - $this->pageNumber = $searchFormData->pageNumber; - } - } -} diff --git a/src/Dao/SearchResult.php b/src/Dao/SearchResult.php deleted file mode 100644 index 490e2362..00000000 --- a/src/Dao/SearchResult.php +++ /dev/null @@ -1,16 +0,0 @@ -filter = $filter; - $this->entities = $entities; - $this->totalRecords = $totalRecords; - } -} diff --git a/src/SearchServices/AbstractSearchFilter.php b/src/SearchServices/AbstractSearchFilter.php index 239997cc..01c895fd 100644 --- a/src/SearchServices/AbstractSearchFilter.php +++ b/src/SearchServices/AbstractSearchFilter.php @@ -6,10 +6,20 @@ abstract class AbstractSearchFilter const ORDER_ASC = 1; const ORDER_DESC = -1; - public $order; + private $order; + + public function getOrder() + { + return $this->order; + } + + public function setOrder($order) + { + $this->order = $order; + } public function __construct() { - $this->order = []; + $this->setOrder(['id' => self::ORDER_DESC]); } } diff --git a/src/SearchServices/NamedSearchToken.php b/src/SearchServices/NamedSearchToken.php index 087bbede..d31e3323 100644 --- a/src/SearchServices/NamedSearchToken.php +++ b/src/SearchServices/NamedSearchToken.php @@ -3,5 +3,15 @@ namespace Szurubooru\SearchServices; class NamedSearchToken extends SearchToken { - public $key = false; + private $key = false; + + public function setKey($key) + { + $this->key = $key; + } + + public function getKey() + { + return $this->key; + } } diff --git a/src/SearchServices/Parsers/AbstractSearchParser.php b/src/SearchServices/Parsers/AbstractSearchParser.php index f8468ac5..51e72dd7 100644 --- a/src/SearchServices/Parsers/AbstractSearchParser.php +++ b/src/SearchServices/Parsers/AbstractSearchParser.php @@ -6,7 +6,7 @@ abstract class AbstractSearchParser public function createFilterFromFormData(\Szurubooru\FormData\SearchFormData $formData) { $filter = $this->createFilter(); - $filter->order = $this->getOrder($formData->order); + $filter->setOrder(array_merge($this->getOrder($formData->order), $filter->getOrder())); $tokens = $this->tokenize($formData->query); @@ -31,16 +31,6 @@ abstract class AbstractSearchParser protected abstract function getOrderColumn($token); - protected function getDefaultOrderColumn() - { - return 'id'; - } - - protected function getDefaultOrderDir() - { - return \Szurubooru\SearchServices\AbstractSearchFilter::ORDER_DESC; - } - private function getOrder($query) { $order = []; @@ -61,11 +51,6 @@ abstract class AbstractSearchParser $order[$orderColumn] = $orderDir; } - $defaultOrderColumn = $this->getDefaultOrderColumn(); - $defaultOrderDir = $this->getDefaultOrderDir(); - if ($defaultOrderColumn) - $order[$defaultOrderColumn] = $defaultOrderDir; - return $order; } @@ -85,16 +70,17 @@ abstract class AbstractSearchParser if (strpos($tokenText, ':') !== false) { $searchToken = new \Szurubooru\SearchServices\NamedSearchToken(); - list ($searchToken->key, $searchToken->value) = explode(':', $tokenText, 1); - + list ($tokenKey, $tokenValue) = explode(':', $tokenText, 1); + $searchToken->setKey($tokenKey); + $searchToken->setValue($tokenValue); } else { $searchToken = new \Szurubooru\SearchServices\SearchToken(); - $searchToken->value = $tokenText; + $searchToken->setValue($tokenText); } - $searchToken->negated = $negated; + $searchToken->setNegated($negated); $searchTokens[] = $searchToken; } diff --git a/src/SearchServices/Parsers/UserSearchParser.php b/src/SearchServices/Parsers/UserSearchParser.php index 7ca80086..b2cbe1b0 100644 --- a/src/SearchServices/Parsers/UserSearchParser.php +++ b/src/SearchServices/Parsers/UserSearchParser.php @@ -21,10 +21,10 @@ class UserSearchParser extends AbstractSearchParser protected function getOrderColumn($token) { if ($token === 'name') - return 'name'; + return \Szurubooru\SearchServices\UserSearchFilter::ORDER_NAME; if (in_array($token, ['registrationDate', 'registrationTime', 'registered', 'joinDate', 'joinTime', 'joined'])) - return 'registrationTime'; + return \Szurubooru\SearchServices\UserSearchFilter::ORDER_REGISTRATION_TIME; return null; } diff --git a/src/SearchServices/SearchToken.php b/src/SearchServices/SearchToken.php index 803a2442..8caabf88 100644 --- a/src/SearchServices/SearchToken.php +++ b/src/SearchServices/SearchToken.php @@ -3,6 +3,26 @@ namespace Szurubooru\SearchServices; class SearchToken { - public $negated = false; - public $value; + private $negated = false; + private $value; + + public function isNegated() + { + return $this->negated; + } + + public function setNegated($negated) + { + $this->negated = $negated; + } + + public function getValue() + { + return $this->value; + } + + public function setValue($value) + { + $this->value = $value; + } } diff --git a/src/SearchServices/UserSearchFilter.php b/src/SearchServices/UserSearchFilter.php index 8738c4f7..af50755b 100644 --- a/src/SearchServices/UserSearchFilter.php +++ b/src/SearchServices/UserSearchFilter.php @@ -3,4 +3,6 @@ namespace Szurubooru\SearchServices; class UserSearchFilter extends AbstractSearchFilter { + const ORDER_NAME = 'name'; + const ORDER_REGISTRATION_TIME = 'registrationTime'; } diff --git a/tests/AbstractDatabaseTestCase.php b/tests/AbstractDatabaseTestCase.php index 655c3818..2fead051 100644 --- a/tests/AbstractDatabaseTestCase.php +++ b/tests/AbstractDatabaseTestCase.php @@ -32,8 +32,8 @@ abstract class AbstractDatabaseTestCase extends \Szurubooru\Tests\AbstractTestCa $expected = [$expected]; $actual = [$actual]; } - $this->assertEquals(count($expected), count($actual)); - $this->assertEquals(array_keys($expected), array_keys($actual)); + $this->assertEquals(count($expected), count($actual), 'Unmatching array sizes'); + $this->assertEquals(array_keys($expected), array_keys($actual), 'Unmatching array keys'); foreach (array_keys($expected) as $key) { if ($expected[$key] === null) diff --git a/tests/Dao/Services/UserSearchServiceTest.php b/tests/Dao/Services/UserSearchServiceTest.php deleted file mode 100644 index f332a8b5..00000000 --- a/tests/Dao/Services/UserSearchServiceTest.php +++ /dev/null @@ -1,103 +0,0 @@ -mock(\Szurubooru\Services\FileService::class); - $thumbnailServiceMock = $this->mock(\Szurubooru\Services\ThumbnailService::class); - $this->userDao = new \Szurubooru\Dao\UserDao( - $this->databaseConnection, - $fileServiceMock, - $thumbnailServiceMock); - } - - public function testNothing() - { - $searchFilter = new \Szurubooru\Dao\SearchFilter(1); - $expected = new \Szurubooru\Dao\SearchResult($searchFilter, [], 0); - - $userSearchService = $this->getUserSearchService(); - $actual = $userSearchService->getFiltered($searchFilter); - $this->assertEquals($expected, $actual); - } - - public function testDefaultOrder() - { - list ($user1, $user2) = $this->prepareUsers(); - $this->doTestSorting(null, [$user2]); - } - - public function testOrderByNameAscending() - { - list ($user1, $user2) = $this->prepareUsers(); - $this->doTestSorting('name,asc', [$user1]); - } - - public function testOrderByNameDescending() - { - list ($user1, $user2) = $this->prepareUsers(); - $this->doTestSorting('name,desc', [$user2]); - } - - public function testOrderByRegistrationTimeAscending() - { - list ($user1, $user2) = $this->prepareUsers(); - $this->doTestSorting('registrationTime,asc', [$user2]); - } - - public function testOrderByRegistrationTimeDescending() - { - list ($user1, $user2) = $this->prepareUsers(); - $this->doTestSorting('registrationTime,desc', [$user1]); - } - - private function prepareUsers() - { - $user1 = $this->getTestUser('beartato'); - $user2 = $this->getTestUser('reginald'); - $user1->setRegistrationTime(date('c', mktime(3, 2, 1))); - $user2->setRegistrationTime(date('c', mktime(1, 2, 3))); - - $this->userDao->save($user1); - $this->userDao->save($user2); - return [$user1, $user2]; - } - - private function doTestSorting($order, $expectedUsers) - { - $userSearchService = $this->getUserSearchService(); - $searchFilter = new \Szurubooru\Dao\SearchFilter(1); - if ($order !== null) - $searchFilter->order = $order; - - $expected = new \Szurubooru\Dao\SearchResult($searchFilter, $expectedUsers, 2); - $actual = $userSearchService->getFiltered($searchFilter); - foreach ($actual->entities as $entity) - $entity->resetLazyLoaders(); - $this->assertEquals($expected->filter, $actual->filter); - $this->assertEquals($expected->totalRecords, $actual->totalRecords); - $this->assertEntitiesEqual($expected->entities, $actual->entities); - } - - private function getUserSearchService() - { - return new \Szurubooru\Dao\Services\UserSearchService($this->databaseConnection, $this->userDao); - } - - private function getTestUser($userName) - { - $user = new \Szurubooru\Entities\User(); - $user->setName($userName); - $user->setPasswordHash('whatever'); - $user->setLastLoginTime('whatever'); - $user->setRegistrationTime('whatever'); - $user->setAccessRank(\Szurubooru\Entities\User::ACCESS_RANK_REGULAR_USER); - return $user; - } -} diff --git a/tests/Dao/UserDaoFilterTest.php b/tests/Dao/UserDaoFilterTest.php new file mode 100644 index 00000000..f0846b27 --- /dev/null +++ b/tests/Dao/UserDaoFilterTest.php @@ -0,0 +1,154 @@ +fileServiceMock = $this->mock(\Szurubooru\Services\FileService::class); + $this->thumbnailServiceMock = $this->mock(\Szurubooru\Services\ThumbnailService::class); + } + + public function pagingProvider() + { + $allUsers = ['xena', 'gabrielle']; + list ($user1, $user2) = $allUsers; + return [ + [1, 1, [$user1], $allUsers], + [1, 2, [$user1, $user2], $allUsers], + [2, 1, [$user2], $allUsers], + [2, 2, [], $allUsers], + ]; + } + + public function testNothing() + { + $searchFilter = new \Szurubooru\SearchServices\UserSearchFilter(); + $userDao = $this->getUserDao(); + $result = $userDao->findFilteredAndPaged($searchFilter, 1, 2); + $this->assertEmpty($result->getEntities()); + $this->assertEquals(0, $result->getTotalRecords()); + $this->assertEquals(1, $result->getPageNumber()); + $this->assertEquals(2, $result->getPageSize()); + } + + /** + * @dataProvider pagingProvider + */ + public function testPaging($pageNumber, $pageSize, $expectedUserNames, $allUserNames) + { + $userDao = $this->getUserDao(); + $expectedUsers = []; + foreach ($allUserNames as $userName) + { + $user = self::getTestUser($userName); + $userDao->save($user); + if (in_array($userName, $expectedUserNames)) + $expectedUsers[] = $user; + } + + $searchFilter = new \Szurubooru\SearchServices\UserSearchFilter(); + $searchFilter->setOrder([ + \Szurubooru\SearchServices\UserSearchFilter::ORDER_NAME => + \Szurubooru\SearchServices\UserSearchFilter::ORDER_DESC]); + + $result = $userDao->findFilteredAndPaged($searchFilter, $pageNumber, $pageSize); + $this->assertEquals(count($allUserNames), $result->getTotalRecords()); + $this->assertEquals($pageNumber, $result->getPageNumber()); + $this->assertEquals($pageSize, $result->getPageSize()); + $this->assertEntitiesEqual($expectedUsers, array_values($result->getEntities())); + } + + public function testDefaultOrder() + { + list ($user1, $user2) = $this->prepareUsers(); + $this->doTestSorting(null, null, [$user2, $user1]); + } + + public function testOrderByNameAscending() + { + list ($user1, $user2) = $this->prepareUsers(); + $this->doTestSorting( + \Szurubooru\SearchServices\UserSearchFilter::ORDER_NAME, + \Szurubooru\SearchServices\UserSearchFilter::ORDER_ASC, + [$user1, $user2]); + } + + public function testOrderByNameDescending() + { + list ($user1, $user2) = $this->prepareUsers(); + $this->doTestSorting( + \Szurubooru\SearchServices\UserSearchFilter::ORDER_NAME, + \Szurubooru\SearchServices\UserSearchFilter::ORDER_DESC, + [$user2, $user1]); + } + + public function testOrderByRegistrationTimeAscending() + { + list ($user1, $user2) = $this->prepareUsers(); + $this->doTestSorting( + \Szurubooru\SearchServices\UserSearchFilter::ORDER_REGISTRATION_TIME, + \Szurubooru\SearchServices\UserSearchFilter::ORDER_ASC, + [$user2, $user1]); + } + + public function testOrderByRegistrationTimeDescending() + { + list ($user1, $user2) = $this->prepareUsers(); + $this->doTestSorting( + \Szurubooru\SearchServices\UserSearchFilter::ORDER_REGISTRATION_TIME, + \Szurubooru\SearchServices\UserSearchFilter::ORDER_DESC, + [$user1, $user2]); + } + + private function prepareUsers() + { + $user1 = self::getTestUser('beartato'); + $user2 = self::getTestUser('reginald'); + $user1->setRegistrationTime(date('c', mktime(3, 2, 1))); + $user2->setRegistrationTime(date('c', mktime(1, 2, 3))); + + $userDao = $this->getUserDao(); + $userDao->save($user1); + $userDao->save($user2); + return [$user1, $user2]; + } + + private function doTestSorting($order, $orderDirection, $expectedUsers) + { + $userDao = $this->getUserDao(); + $searchFilter = new \Szurubooru\SearchServices\UserSearchFilter(); + if ($order !== null) + $searchFilter->setOrder([$order => $orderDirection]); + + $result = $userDao->findFilteredAndPaged($searchFilter, 1, 10); + $this->assertInstanceOf(\Szurubooru\SearchServices\PagedSearchResult::class, $result); + $this->assertEquals($searchFilter, $result->getSearchFilter()); + $this->assertEntitiesEqual(array_values($expectedUsers), array_values($result->getEntities())); + $this->assertEquals(count($expectedUsers), $result->getTotalRecords()); + $this->assertEquals(1, $result->getPageNumber()); + } + + private function getUserDao() + { + return new \Szurubooru\Dao\UserDao( + $this->databaseConnection, + $this->fileServiceMock, + $this->thumbnailServiceMock); + } + + private static function getTestUser($userName) + { + $user = new \Szurubooru\Entities\User(); + $user->setName($userName); + $user->setPasswordHash('whatever'); + $user->setLastLoginTime('whatever'); + $user->setRegistrationTime('whatever'); + $user->setAccessRank(\Szurubooru\Entities\User::ACCESS_RANK_REGULAR_USER); + return $user; + } +} diff --git a/tests/Services/UserServiceTest.php b/tests/Services/UserServiceTest.php index 00f072ae..425dc144 100644 --- a/tests/Services/UserServiceTest.php +++ b/tests/Services/UserServiceTest.php @@ -72,7 +72,8 @@ final class UserServiceTest extends \Szurubooru\Tests\AbstractTestCase $mockUser = new \Szurubooru\Entities\User; $mockUser->setName('user'); $expected = [$mockUser]; - $this->userDaoMock->method('getFiltered')->willReturn($expected); + $this->userSearchParserMock->method('createFilterFromFormData')->willReturn(new \Szurubooru\SearchServices\UserSearchFilter()); + $this->userDaoMock->method('findFilteredAndPaged')->willReturn($expected); $this->configMock->set('users/usersPerPage', 1); $searchFormData = new \Szurubooru\FormData\SearchFormData;