Skip to content

Commit

Permalink
IBX-8534: Improved iteration over relation list (#444)
Browse files Browse the repository at this point in the history
For more details see https://issues.ibexa.co/browse/IBX-8534 and #444

Key changes:

* Implemented RelationListIteratorAdapter

* Implemented RelationListFacade
  • Loading branch information
ViniTou authored Nov 4, 2024
1 parent 2ba8857 commit 0c5f86c
Show file tree
Hide file tree
Showing 6 changed files with 211 additions and 1 deletion.
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

/**
* @copyright Copyright (C) Ibexa AS. All rights reserved.
* @license For full copyright and license information view LICENSE file distributed with this source code.
*/
declare(strict_types=1);

namespace Ibexa\Contracts\Core\Repository\ContentService;

use Ibexa\Contracts\Core\Repository\Values\Content\VersionInfo;

/**
* @internal
*/
interface RelationListFacadeInterface
{
/**
* @return iterable<\Ibexa\Contracts\Core\Repository\Values\Content\Relation>
*/
public function getRelations(VersionInfo $versionInfo): iterable;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<?php

/**
* @copyright Copyright (C) Ibexa AS. All rights reserved.
* @license For full copyright and license information view LICENSE file distributed with this source code.
*/
declare(strict_types=1);

namespace Ibexa\Contracts\Core\Repository\Iterator\BatchIteratorAdapter;

use Ibexa\Contracts\Core\Repository\ContentService;
use Ibexa\Contracts\Core\Repository\Iterator\BatchIteratorAdapter;
use Ibexa\Contracts\Core\Repository\Values\Content\RelationType;
use Ibexa\Contracts\Core\Repository\Values\Content\VersionInfo;
use Iterator;
use IteratorIterator;

final class RelationListIteratorAdapter implements BatchIteratorAdapter
{
public function __construct(
readonly private ContentService $contentService,
readonly private VersionInfo $versionInfo,
readonly private ?RelationType $relationType = null,
) {
}

public function fetch(int $offset, int $limit): Iterator
{
$iterator = $this->contentService->loadRelationList(
$this->versionInfo,
$offset,
$limit,
$this->relationType
)->getIterator();

if ($iterator instanceof Iterator) {
return $iterator;
}

return new IteratorIterator($iterator);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ interface RelationListItemInterface
public function getRelation(): ?Relation;

/**
* @return bool
* @phpstan-assert-if-true !null $this->getRelation()
*/
public function hasRelation(): bool;
}
39 changes: 39 additions & 0 deletions src/lib/Repository/ContentService/RelationListFacade.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<?php

/**
* @copyright Copyright (C) Ibexa AS. All rights reserved.
* @license For full copyright and license information view LICENSE file distributed with this source code.
*/
declare(strict_types=1);

namespace Ibexa\Core\Repository\ContentService;

use Ibexa\Contracts\Core\Repository\ContentService;
use Ibexa\Contracts\Core\Repository\Iterator\BatchIterator;
use Ibexa\Contracts\Core\Repository\Iterator\BatchIteratorAdapter\RelationListIteratorAdapter;
use Ibexa\Contracts\Core\Repository\Values\Content\VersionInfo;

final class RelationListFacade implements ContentService\RelationListFacadeInterface
{
public function __construct(
private readonly ContentService $contentService
) {
}

public function getRelations(VersionInfo $versionInfo): iterable
{
$relationListIterator = new BatchIterator(
new RelationListIteratorAdapter(
$this->contentService,
$versionInfo
)
);

/** @var \Ibexa\Contracts\Core\Repository\Values\Content\RelationList\RelationListItemInterface $relationListItem */
foreach ($relationListIterator as $relationListItem) {
if ($relationListItem->hasRelation()) {
yield $relationListItem->getRelation();
}
}
}
}
6 changes: 6 additions & 0 deletions src/lib/Resources/settings/repository/inner.yml
Original file line number Diff line number Diff line change
Expand Up @@ -272,3 +272,9 @@ services:

Ibexa\Core\Repository\Validator\TargetContentValidatorInterface:
alias: Ibexa\Core\Repository\Validator\TargetContentValidator

Ibexa\Contracts\Core\Repository\ContentService\RelationListFacadeInterface: '@Ibexa\Core\Repository\ContentService\RelationListFacade'

Ibexa\Core\Repository\ContentService\RelationListFacade:
arguments:
$contentService: '@ibexa.api.service.content'
101 changes: 101 additions & 0 deletions tests/lib/Repository/ContentService/RelationListFacadeTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
<?php

/**
* @copyright Copyright (C) Ibexa AS. All rights reserved.
* @license For full copyright and license information view LICENSE file distributed with this source code.
*/
declare(strict_types=1);

namespace Ibexa\Tests\Core\Repository\ContentService;

use Ibexa\Contracts\Core\Repository\ContentService;
use Ibexa\Contracts\Core\Repository\Values\Content\Relation;
use Ibexa\Contracts\Core\Repository\Values\Content\RelationList;
use Ibexa\Contracts\Core\Repository\Values\Content\RelationList\RelationListItemInterface;
use Ibexa\Contracts\Core\Repository\Values\Content\VersionInfo;
use Ibexa\Core\Repository\ContentService\RelationListFacade;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;

final class RelationListFacadeTest extends TestCase
{
private ContentService&MockObject $contentService;

private RelationListFacade $relationListFacade;

private VersionInfo&MockObject $versionInfo;

protected function setUp(): void
{
$this->contentService = $this->createMock(ContentService::class);
$this->versionInfo = $this->createMock(VersionInfo::class);
$this->relationListFacade = new RelationListFacade($this->contentService);
}

public function testGetRelationsReturnsEmptyIteratorWhenNoRelations(): void
{
$relationList = $this->createMock(RelationList::class);
$relationList->method('getIterator')
->willReturn(new \ArrayIterator([]));

$this->contentService
->expects(self::once())
->method('loadRelationList')
->with($this->versionInfo)
->willReturn($relationList);

$result = iterator_to_array($this->relationListFacade->getRelations($this->versionInfo));

self::assertEmpty($result);
}

public function testGetRelationsIgnoresItemsWithoutRelations(): void
{
$relationListItem = $this->createMock(RelationListItemInterface::class);
$relationListItem
->method('hasRelation')
->willReturn(false);

$relationList = $this->createMock(RelationList::class);
$relationList->method('getIterator')
->willReturn(new \ArrayIterator([$relationListItem]));

$this->contentService
->expects(self::once())
->method('loadRelationList')
->with(self::identicalTo($this->versionInfo))
->willReturn($relationList);

$result = iterator_to_array($this->relationListFacade->getRelations($this->versionInfo));

self::assertEmpty($result);
}

public function testGetRelationsYieldsRelationsWhenPresent(): void
{
$relation = $this->createMock(Relation::class);

$relationListItem = $this->createMock(RelationListItemInterface::class);
$relationListItem
->method('hasRelation')
->willReturn(true);
$relationListItem
->method('getRelation')
->willReturn($relation);

$relationList = $this->createMock(RelationList::class);
$relationList->method('getIterator')
->willReturn(new \ArrayIterator([$relationListItem]));

$this->contentService
->expects(self::once())
->method('loadRelationList')
->with($this->versionInfo)
->willReturn($relationList);

$result = iterator_to_array($this->relationListFacade->getRelations($this->versionInfo));

self::assertCount(1, $result);
self::assertSame($relation, $result[0]);
}
}

0 comments on commit 0c5f86c

Please sign in to comment.