refactor(php): Decouple view data from domain

Signed-off-by: Rubén D <nuxsmin@syspass.org>
This commit is contained in:
Rubén D
2024-05-09 09:31:44 +02:00
parent c6d2c4b857
commit acd3d06a33
10 changed files with 127 additions and 179 deletions

View File

@@ -1,4 +1,26 @@
<?php
/**
* sysPass
*
* @author nuxsmin
* @link https://syspass.org
* @copyright 2012-2024, Rubén Domínguez nuxsmin@$syspass.org
*
* This file is part of sysPass.
*
* sysPass is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* sysPass is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with sysPass. If not, see <http://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
/**
@@ -24,24 +46,21 @@ declare(strict_types=1);
* along with sysPass. If not, see <http://www.gnu.org/licenses/>.
*/
namespace SP\Domain\Account\Services\Builders;
namespace SP\Modules\Web\Controllers\Helpers\Account;
use SP\Core\Application;
use SP\Domain\Account\Adapters\AccountSearchItem;
use SP\Domain\Account\Dtos\AccountAclDto;
use SP\Domain\Account\Models\AccountSearchView;
use SP\Domain\Account\Ports\AccountAclService;
use SP\Domain\Account\Ports\AccountCacheService;
use SP\Domain\Account\Ports\AccountSearchDataBuilder;
use SP\Domain\Account\Ports\AccountToFavoriteService;
use SP\Domain\Account\Ports\AccountToTagRepository;
use SP\Domain\Common\Services\Service;
use SP\Domain\Config\Ports\ConfigDataInterface;
use SP\Domain\Core\Acl\AclActionsInterface;
use SP\Domain\Core\Bootstrap\UriContextInterface;
use SP\Domain\Core\Context\Context;
use SP\Domain\Core\Exceptions\ConstraintException;
use SP\Domain\Core\Exceptions\QueryException;
use SP\Domain\Core\Exceptions\SPException;
use SP\Domain\Storage\Ports\FileCacheService;
use SP\Infrastructure\Database\QueryResult;
use SP\Infrastructure\File\FileException;
@@ -50,9 +69,9 @@ use function SP\logger;
use function SP\processException;
/**
* Class AccountSearchDataBuilder
* Class AccountSearchData
*/
final class AccountSearchData extends Service implements AccountSearchDataBuilder
final class AccountSearchData
{
private const COLORS_CACHE_FILE = CACHE_PATH . DIRECTORY_SEPARATOR . 'colors.cache';
private const COLORS = [
@@ -79,7 +98,7 @@ final class AccountSearchData extends Service implements AccountSearchDataBuilde
private ?array $accountColor = null;
public function __construct(
Application $application,
private readonly Context $context,
private readonly AccountAclService $accountAclService,
private readonly AccountToTagRepository $accountToTagRepository,
private readonly AccountToFavoriteService $accountToFavoriteService,
@@ -88,8 +107,6 @@ final class AccountSearchData extends Service implements AccountSearchDataBuilde
private readonly ConfigDataInterface $configData,
private readonly UriContextInterface $uriContext,
) {
parent::__construct($application);
$this->loadColors();
}
@@ -108,14 +125,13 @@ final class AccountSearchData extends Service implements AccountSearchDataBuilde
}
/**
* @param QueryResult $queryResult
* @param QueryResult<AccountSearchView> $queryResult
*
* @return AccountSearchItem[]
* @return QueryResult<AccountSearchItem>
* @throws ConstraintException
* @throws QueryException
* @throws SPException
*/
public function buildFrom(QueryResult $queryResult): array
public function buildFrom(QueryResult $queryResult): QueryResult
{
$maxTextLength = $this->configData->isResultsAsCards() ? self::TEXT_LENGTH_CARDS : self::TEXT_LENGTH_NORMAL;
$userPreferencesData = $this->context->getUserData()->getPreferences();
@@ -124,16 +140,12 @@ final class AccountSearchData extends Service implements AccountSearchDataBuilde
|| $this->configData->isAccountLink();
$favorites = $this->accountToFavoriteService->getForUserId($this->context->getUserData()->getId());
return array_map(
/**
* @param AccountSearchView $accountSearchView
*
* @return AccountSearchItem
* @throws ConstraintException
* @throws QueryException
* @throws SPException
*/
function (AccountSearchView $accountSearchView) use ($maxTextLength, $accountLinkEnabled, $favorites) {
return $queryResult->mutateWithCallback(
function (AccountSearchView $accountSearchView) use (
$maxTextLength,
$accountLinkEnabled,
$favorites
): AccountSearchItem {
$cache = $this->accountCacheService->getCacheForAccount(
$accountSearchView->getId(),
strtotime($accountSearchView->getDateEdit())
@@ -169,8 +181,7 @@ final class AccountSearchData extends Service implements AccountSearchDataBuilde
$this->pickAccountColor($accountSearchView->getClientId()),
$accountLinkEnabled
);
},
$queryResult->getDataAsArray(AccountSearchView::class)
}
);
}

View File

@@ -53,6 +53,8 @@ use SP\Modules\Web\Controllers\Helpers\HelperBase;
use SP\Mvc\View\Components\SelectItemAdapter;
use SP\Mvc\View\TemplateInterface;
use function SP\getElapsedTime;
/**
* Class AccountSearch
*
@@ -63,26 +65,27 @@ final class AccountSearchHelper extends HelperBase
/**
* @var bool Indica si el filtrado de cuentas está activo
*/
private bool $filterOn = false;
private bool $isAjax = false;
private int $queryTimeStart;
private bool $filterOn = false;
private bool $isAjax = false;
private int $queryTimeStart;
private bool $isIndex;
private ?AccountSearchFilterDto $accountSearchFilter = null;
private ClientService $clientService;
private ClientService $clientService;
private AccountSearchService $accountSearchService;
private AccountActionsHelper $accountActionsHelper;
private CategoryService $categoryService;
private TagService $tagService;
private CategoryService $categoryService;
private TagService $tagService;
public function __construct(
Application $application,
TemplateInterface $template,
RequestService $request,
ClientService $clientService,
CategoryService $categoryService,
TagService $tagService,
AccountSearchService $accountSearchService,
AccountActionsHelper $accountActionsHelper
Application $application,
TemplateInterface $template,
RequestService $request,
ClientService $clientService,
CategoryService $categoryService,
TagService $tagService,
AccountSearchService $accountSearchService,
AccountActionsHelper $accountActionsHelper,
private readonly AccountSearchData $accountSearchData
) {
parent::__construct($application, $template, $request);
@@ -243,8 +246,11 @@ final class AccountSearchHelper extends HelperBase
);
}
$accountSearchData = $this->accountSearchData->buildFrom(
$this->accountSearchService->getByFilter($this->accountSearchFilter)
);
$dataGrid = $this->getGrid();
$dataGrid->getData()->setData($this->accountSearchService->getByFilter($this->accountSearchFilter));
$dataGrid->getData()->setData($accountSearchData);
$dataGrid->updatePager();
$dataGrid->setTime(round(getElapsedTime($this->queryTimeStart), 5));

View File

@@ -1,4 +1,5 @@
<?php
declare(strict_types=1);
/**
* sysPass
@@ -29,8 +30,6 @@ use Psr\Container\ContainerInterface;
use SP\Core\Application;
use SP\Core\Crypt\RequestBasedPassword;
use SP\Core\Crypt\UuidCookie;
use SP\Domain\Account\Ports\AccountSearchDataBuilder;
use SP\Domain\Account\Services\Builders\AccountSearchData;
use SP\Domain\Config\Ports\ConfigDataInterface;
use SP\Domain\Core\Crypt\CryptInterface;
use SP\Domain\Crypt\Ports\SecureSessionService;
@@ -84,7 +83,6 @@ final class DomainDefinitions
return [
...$sources,
AccountSearchDataBuilder::class => autowire(AccountSearchData::class),
SecureSessionService::class => factory(
static function (ContainerInterface $c) {
$fileCache = new FileCache(

View File

@@ -1,46 +0,0 @@
<?php
declare(strict_types=1);
/**
* sysPass
*
* @author nuxsmin
* @link https://syspass.org
* @copyright 2012-2022, Rubén Domínguez nuxsmin@$syspass.org
*
* This file is part of sysPass.
*
* sysPass is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* sysPass is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with sysPass. If not, see <http://www.gnu.org/licenses/>.
*/
namespace SP\Domain\Account\Ports;
use SP\Domain\Account\Adapters\AccountSearchItem;
use SP\Domain\Core\Exceptions\ConstraintException;
use SP\Domain\Core\Exceptions\QueryException;
use SP\Infrastructure\Database\QueryResult;
/**
* Class AccountSearchDataBuilder
*/
interface AccountSearchDataBuilder
{
/**
* @param QueryResult $queryResult
*
* @return AccountSearchItem[]
* @throws ConstraintException
* @throws QueryException
*/
public function buildFrom(QueryResult $queryResult): array;
}

View File

@@ -1,4 +1,5 @@
<?php
declare(strict_types=1);
/**
* sysPass
@@ -27,6 +28,7 @@ namespace SP\Domain\Account\Ports;
use Aura\SqlQuery\Common\SelectInterface;
use SP\Domain\Account\Dtos\AccountSearchFilterDto;
use SP\Domain\Account\Models\AccountSearchView as AccountSearchViewModel;
use SP\Domain\Common\Ports\Repository;
use SP\Infrastructure\Database\QueryResult;
@@ -38,9 +40,9 @@ interface AccountSearchRepository extends Repository
/**
* Obtener las cuentas de una búsqueda.
*
* @template T of AccountSearchViewModel
* @param AccountSearchFilterDto $accountSearchFilter
*
* @return QueryResult
* @return QueryResult<T>
*/
public function getByFilter(AccountSearchFilterDto $accountSearchFilter): QueryResult;

View File

@@ -1,4 +1,5 @@
<?php
declare(strict_types=1);
/**
* sysPass
@@ -28,16 +29,13 @@ namespace SP\Domain\Account\Services;
use Exception;
use SP\Core\Application;
use SP\Domain\Account\Dtos\AccountSearchFilterDto;
use SP\Domain\Account\Models\AccountSearchView as AccountSearchViewModel;
use SP\Domain\Account\Ports\AccountSearchConstants;
use SP\Domain\Account\Ports\AccountSearchDataBuilder;
use SP\Domain\Account\Ports\AccountSearchRepository;
use SP\Domain\Account\Ports\AccountSearchService;
use SP\Domain\Account\Services\Builders\AccountSearchTokenizer;
use SP\Domain\Common\Providers\Filter;
use SP\Domain\Common\Services\Service;
use SP\Domain\Core\Exceptions\ConstraintException;
use SP\Domain\Core\Exceptions\QueryException;
use SP\Domain\Core\Exceptions\SPException;
use SP\Domain\User\Ports\UserGroupService;
use SP\Domain\User\Ports\UserService;
use SP\Infrastructure\Database\QueryResult;
@@ -50,11 +48,10 @@ use function SP\processException;
final class AccountSearch extends Service implements AccountSearchService
{
public function __construct(
Application $application,
private readonly UserService $userService,
private readonly UserGroupService $userGroupService,
private readonly AccountSearchRepository $accountSearchRepository,
private readonly AccountSearchDataBuilder $accountSearchDataBuilder
Application $application,
private readonly UserService $userService,
private readonly UserGroupService $userGroupService,
private readonly AccountSearchRepository $accountSearchRepository
) {
parent::__construct($application);
}
@@ -62,9 +59,9 @@ final class AccountSearch extends Service implements AccountSearchService
/**
* Procesar los resultados de la búsqueda
*
* @throws ConstraintException
* @throws QueryException
* @throws SPException
* @template T of AccountSearchViewModel
* @param AccountSearchFilterDto $accountSearchFilter
* @return QueryResult<T>
*/
public function getByFilter(AccountSearchFilterDto $accountSearchFilter): QueryResult
{
@@ -80,12 +77,7 @@ final class AccountSearch extends Service implements AccountSearchService
}
}
$queryResult = $this->accountSearchRepository->getByFilter($accountSearchFilter);
return QueryResult::withTotalNumRows(
$this->accountSearchDataBuilder->buildFrom($queryResult),
$queryResult->getTotalNumRows()
);
return $this->accountSearchRepository->getByFilter($accountSearchFilter);
}
private function processFilterItems(array $filters): void

View File

@@ -1,4 +1,5 @@
<?php
declare(strict_types=1);
/**
* sysPass
@@ -107,7 +108,8 @@ final class AccountSearch extends BaseRepository implements AccountSearchReposit
*
* @param AccountSearchFilterDto $accountSearchFilter
*
* @return QueryResult
* @template T of AccountSearchViewModel
* @return QueryResult<T>
* @throws ConstraintException
* @throws QueryException
*/

View File

@@ -1,4 +1,5 @@
<?php
declare(strict_types=1);
/**
* sysPass
@@ -46,23 +47,21 @@ class QueryResult
* QueryResult constructor.
*/
public function __construct(
?array $data = null,
private readonly int $affectedNumRows = 0,
private readonly int $lastId = 0
array|SplFixedArray|null $data = null,
private readonly int $affectedNumRows = 0,
private readonly int $lastId = 0
) {
if (null !== $data) {
$this->data = SplFixedArray::fromArray($data);
$this->numRows = $this->data->count();
if ($data instanceof SplFixedArray) {
$this->data = $data;
} else {
$this->data = new SplFixedArray();
$this->numRows = 0;
$this->data = SplFixedArray::fromArray($data ?? []);
}
$this->numRows = $this->data->count();
}
public static function withTotalNumRows(
array $data,
?int $totalNumRows = null
): QueryResult {
public static function withTotalNumRows(array $data, ?int $totalNumRows = null): QueryResult
{
$result = new self($data);
$result->totalNumRows = (int)$totalNumRows;
@@ -92,9 +91,7 @@ class QueryResult
&& (!is_object($this->data->offsetGet(0))
|| !is_a($this->data->offsetGet(0), $dataType))
) {
throw new TypeError(
sprintf(__u('Invalid data\'s type. Expected: %s'), $dataType)
);
throw new TypeError(sprintf(__u('Invalid data\'s type. Expected: %s'), $dataType));
}
}
@@ -109,7 +106,7 @@ class QueryResult
$this->checkDataType($dataType);
}
return $this->data->toArray();
return $this->data?->toArray();
}
public function getNumRows(): int
@@ -131,4 +128,15 @@ class QueryResult
{
return $this->lastId;
}
/**
* Mutate the current data into another {@link QueryResult} by applying the given callback function
*
* @param callable $callable
* @return QueryResult
*/
public function mutateWithCallback(callable $callable): QueryResult
{
return new self(SplFixedArray::fromArray(array_map($callable, $this->data->toArray())));
}
}

View File

@@ -1,4 +1,5 @@
<?php
declare(strict_types=1);
/*
* sysPass
@@ -32,12 +33,8 @@ use PHPUnit\Framework\MockObject\MockObject;
use RuntimeException;
use SP\Domain\Account\Dtos\AccountSearchFilterDto;
use SP\Domain\Account\Ports\AccountSearchConstants;
use SP\Domain\Account\Ports\AccountSearchDataBuilder;
use SP\Domain\Account\Ports\AccountSearchRepository;
use SP\Domain\Account\Services\AccountSearch;
use SP\Domain\Core\Exceptions\ConstraintException;
use SP\Domain\Core\Exceptions\QueryException;
use SP\Domain\Core\Exceptions\SPException;
use SP\Domain\User\Models\User;
use SP\Domain\User\Models\UserGroup;
use SP\Domain\User\Ports\UserGroupService;
@@ -47,7 +44,7 @@ use SP\Tests\Domain\Account\Services\Builders\AccountSearchTokenizerDataTrait;
use SP\Tests\UnitaryTestCase;
/**
* Class AccountSearchServiceTest
* Class AccountSearchTest
*
*/
#[Group('unitary')]
@@ -55,14 +52,11 @@ class AccountSearchTest extends UnitaryTestCase
{
use AccountSearchTokenizerDataTrait;
private AccountSearchRepository|MockObject $accountSearchRepository;
private AccountSearch $accountSearch;
private AccountSearchDataBuilder|MockObject $accountSearchDataBuilder;
private AccountSearchRepository|MockObject $accountSearchRepository;
private AccountSearch $accountSearch;
/**
* @throws QueryException
* @throws ConstraintException
* @throws SPException
* @param string $search
*/
#[DataProvider('searchUsingStringDataProvider')]
public function testGetByFilter(string $search)
@@ -76,17 +70,14 @@ class AccountSearchTest extends UnitaryTestCase
->with($accountSearchFilter)
->willReturn($queryResult);
$this->accountSearchDataBuilder
->expects(self::once())
->method('buildFrom');
$out = $this->accountSearch->getByFilter($accountSearchFilter);
$this->accountSearch->getByFilter($accountSearchFilter);
$this->assertSame($queryResult, $out);
}
/**
* @throws QueryException
* @throws ConstraintException
* @throws SPException
* @param string $search
* @param array $expected
*/
#[DataProvider('searchByItemDataProvider')]
public function testGetByFilterUsingItems(string $search, array $expected)
@@ -100,10 +91,6 @@ class AccountSearchTest extends UnitaryTestCase
->with($accountSearchFilter)
->willReturn($queryResult);
$this->accountSearchDataBuilder
->expects(self::once())
->method('buildFrom');
$this->buildExpectationForFilter(array_keys($expected)[0]);
$this->accountSearch->getByFilter($accountSearchFilter);
@@ -154,9 +141,8 @@ class AccountSearchTest extends UnitaryTestCase
}
/**
* @throws QueryException
* @throws ConstraintException
* @throws SPException
* @param string $search
* @param array $expected
*/
#[DataProvider('searchByItemDataProvider')]
public function testGetByFilterUsingItemsDoesNotThrowException(string $search, array $expected)
@@ -170,10 +156,6 @@ class AccountSearchTest extends UnitaryTestCase
->with($accountSearchFilter)
->willReturn($queryResult);
$this->accountSearchDataBuilder
->expects(self::once())
->method('buildFrom');
$mock = $this->buildExpectationForFilter(array_keys($expected)[0]);
$mock->willThrowException(new RuntimeException('test'));
@@ -181,9 +163,8 @@ class AccountSearchTest extends UnitaryTestCase
}
/**
* @throws QueryException
* @throws ConstraintException
* @throws SPException
* @param string $search
* @param array $expected
*/
#[DataProvider('searchByConditionDataProvider')]
public function testGetByFilterUsingConditions(string $search, array $expected)
@@ -197,10 +178,6 @@ class AccountSearchTest extends UnitaryTestCase
->with($accountSearchFilter)
->willReturn($queryResult);
$this->accountSearchDataBuilder
->expects(self::once())
->method('buildFrom');
$this->buildExpectationForCondition($expected[0]);
$this->accountSearch->getByFilter($accountSearchFilter);
@@ -242,9 +219,9 @@ class AccountSearchTest extends UnitaryTestCase
->method('getByLogin')
->willReturn(
new User([
'id' => self::$faker->randomNumber(),
'userGroupId' => self::$faker->randomNumber(),
])
'id' => self::$faker->randomNumber(),
'userGroupId' => self::$faker->randomNumber(),
])
);
$userGroupService = $this->createMock(UserGroupService::class);
@@ -252,19 +229,17 @@ class AccountSearchTest extends UnitaryTestCase
->method('getByName')
->willReturn(
new UserGroup([
'id' => self::$faker->randomNumber(),
])
'id' => self::$faker->randomNumber(),
])
);
$this->accountSearchRepository = $this->createMock(AccountSearchRepository::class);
$this->accountSearchDataBuilder = $this->createMock(AccountSearchDataBuilder::class);
$this->accountSearch = new AccountSearch(
$this->application,
$userService,
$userGroupService,
$this->accountSearchRepository,
$this->accountSearchDataBuilder
$this->accountSearchRepository
);
}
}

View File

@@ -1,6 +1,5 @@
<?php
declare(strict_types=1);
/*
/**
* sysPass
*
* @author nuxsmin
@@ -23,7 +22,9 @@ declare(strict_types=1);
* along with sysPass. If not, see <http://www.gnu.org/licenses/>.
*/
namespace SP\Tests\Domain\Account\Services\Builders;
declare(strict_types=1);
namespace SP\Tests\Modules\Web\Controllers\Helpers\Account;
use PHPUnit\Framework\Attributes\Group;
use PHPUnit\Framework\MockObject\Exception;
@@ -35,7 +36,6 @@ use SP\Domain\Account\Ports\AccountAclService;
use SP\Domain\Account\Ports\AccountCacheService;
use SP\Domain\Account\Ports\AccountToFavoriteService;
use SP\Domain\Account\Ports\AccountToTagRepository;
use SP\Domain\Account\Services\Builders\AccountSearchData;
use SP\Domain\Common\Models\Item;
use SP\Domain\Core\Acl\AclActionsInterface;
use SP\Domain\Core\Bootstrap\UriContextInterface;
@@ -45,6 +45,7 @@ use SP\Domain\Core\Exceptions\SPException;
use SP\Domain\Storage\Ports\FileCacheService;
use SP\Infrastructure\Database\QueryResult;
use SP\Infrastructure\File\FileException;
use SP\Modules\Web\Controllers\Helpers\Account\AccountSearchData;
use SP\Tests\Generators\AccountDataGenerator;
use SP\Tests\UnitaryTestCase;
@@ -52,7 +53,7 @@ use function PHPUnit\Framework\exactly;
use function PHPUnit\Framework\once;
/**
* Class AccountSearchDataBuilderTest
* Class AccountSearchDataTest
*
*/
#[Group('unitary')]
@@ -168,7 +169,7 @@ class AccountSearchDataTest extends UnitaryTestCase
->willThrowException(new FileException('test'));
new AccountSearchData(
$this->application,
$this->context,
$this->accountAclService,
$this->accountToTagRepository,
$this->accountToFavoriteService,
@@ -196,7 +197,7 @@ class AccountSearchDataTest extends UnitaryTestCase
$this->accountSearchDataBuilder =
new AccountSearchData(
$this->application,
$this->context,
$this->accountAclService,
$this->accountToTagRepository,
$this->accountToFavoriteService,
@@ -206,5 +207,4 @@ class AccountSearchDataTest extends UnitaryTestCase
$uriContext
);
}
}