Refactored entity filtering
This commit is contained in:
parent
7b0d907acc
commit
2387dd4074
14 changed files with 236 additions and 195 deletions
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -1,21 +0,0 @@
|
|||
<?php
|
||||
namespace Szurubooru\Dao;
|
||||
|
||||
class SearchFilter
|
||||
{
|
||||
public $order;
|
||||
public $query;
|
||||
public $pageNumber;
|
||||
public $pageSize;
|
||||
|
||||
public function __construct($pageSize, \Szurubooru\FormData\SearchFormData $searchFormData = null)
|
||||
{
|
||||
$this->pageSize = intval($pageSize);
|
||||
if ($searchFormData)
|
||||
{
|
||||
$this->query = $searchFormData->query;
|
||||
$this->order = $searchFormData->order;
|
||||
$this->pageNumber = $searchFormData->pageNumber;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,16 +0,0 @@
|
|||
<?php
|
||||
namespace Szurubooru\Dao;
|
||||
|
||||
class SearchResult
|
||||
{
|
||||
public $filter;
|
||||
public $entities;
|
||||
public $totalRecords;
|
||||
|
||||
public function __construct(SearchFilter $filter, $entities, $totalRecords)
|
||||
{
|
||||
$this->filter = $filter;
|
||||
$this->entities = $entities;
|
||||
$this->totalRecords = $totalRecords;
|
||||
}
|
||||
}
|
|
@ -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]);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,4 +3,6 @@ namespace Szurubooru\SearchServices;
|
|||
|
||||
class UserSearchFilter extends AbstractSearchFilter
|
||||
{
|
||||
const ORDER_NAME = 'name';
|
||||
const ORDER_REGISTRATION_TIME = 'registrationTime';
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -1,103 +0,0 @@
|
|||
<?php
|
||||
namespace Szurubooru\Tests\Dao\Services;
|
||||
|
||||
class UserSearchServiceTest extends \Szurubooru\Tests\AbstractDatabaseTestCase
|
||||
{
|
||||
private $userDao;
|
||||
|
||||
public function setUp()
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$fileServiceMock = $this->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;
|
||||
}
|
||||
}
|
154
tests/Dao/UserDaoFilterTest.php
Normal file
154
tests/Dao/UserDaoFilterTest.php
Normal file
|
@ -0,0 +1,154 @@
|
|||
<?php
|
||||
namespace Szurubooru\Tests\Dao;
|
||||
|
||||
class UserDaoFilterTest extends \Szurubooru\Tests\AbstractDatabaseTestCase
|
||||
{
|
||||
private $fileServiceMock;
|
||||
private $thumbnailServiceMock;
|
||||
|
||||
public function setUp()
|
||||
{
|
||||
parent::setUp();
|
||||
$this->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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
Loading…
Reference in a new issue