Refactored entity filtering

This commit is contained in:
Marcin Kurczewski 2014-09-24 20:13:16 +02:00
parent 7b0d907acc
commit 2387dd4074
14 changed files with 236 additions and 195 deletions

View file

@ -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));
}
}

View file

@ -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)

View file

@ -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;
}
}
}

View file

@ -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;
}
}

View file

@ -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]);
}
}

View file

@ -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;
}
}

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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;
}
}

View file

@ -3,4 +3,6 @@ namespace Szurubooru\SearchServices;
class UserSearchFilter extends AbstractSearchFilter
{
const ORDER_NAME = 'name';
const ORDER_REGISTRATION_TIME = 'registrationTime';
}

View file

@ -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)

View file

@ -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;
}
}

View 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;
}
}

View file

@ -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;