Skip to content

Commit

Permalink
fix(Nodes): Fixed node offspring resolution cache with new `NodeOffsp…
Browse files Browse the repository at this point in the history
…ringResolverInterface` service
  • Loading branch information
ambroisemaupate committed Jun 17, 2024
1 parent 11a80e4 commit cf62690
Show file tree
Hide file tree
Showing 11 changed files with 160 additions and 32 deletions.
3 changes: 2 additions & 1 deletion config/packages/roadiz_rozier.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,8 @@ roadiz_rozier:
name: 'deleted.nodes'
route: nodesHomeDeletedPage
icon: uk-icon-rz-deleted-nodes
roles: ~
roles:
- EMPTY_TRASH
search_nodes:
name: 'search.nodes'
route: searchNodePage
Expand Down
2 changes: 1 addition & 1 deletion lib/RoadizCoreBundle/src/EntityHandler/NodeHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -529,7 +529,7 @@ public function cleanRootNodesPositions(bool $setPositions = true): float
* Return all node offspring id.
*
* @return array
* @deprecated Use NodeRepository::findAllOffspringIdByNode() instead.
* @deprecated Use NodeOffspringResolverInterface::getAllOffspringIds($chroot).
*/
public function getAllOffspringId(): array
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php

declare(strict_types=1);

namespace RZ\Roadiz\CoreBundle\EventSubscriber;

use RZ\Roadiz\CoreBundle\Event\FilterNodeEvent;
use RZ\Roadiz\CoreBundle\Event\Node\NodeCreatedEvent;
use RZ\Roadiz\CoreBundle\Event\Node\NodeDuplicatedEvent;
use RZ\Roadiz\CoreBundle\Event\Node\NodeUpdatedEvent;
use RZ\Roadiz\CoreBundle\Node\CachedNodeOffspringResolverInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;

class CachedNodeOffspringEventSubscriber implements EventSubscriberInterface
{
public function __construct(private readonly CachedNodeOffspringResolverInterface $cachedNodeOffspringResolver)
{
}

public static function getSubscribedEvents(): array
{
return [
NodeCreatedEvent::class => 'invalidateNodeOffspringCache',
NodeDuplicatedEvent::class => 'invalidateNodeOffspringCache',
NodeUpdatedEvent::class => 'invalidateNodeOffspringCache',
];
}

public function invalidateNodeOffspringCache(FilterNodeEvent $event): void
{
$this->cachedNodeOffspringResolver->purgeOffspringCache($event->getNode());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,21 @@
namespace RZ\Roadiz\CoreBundle\Message\Handler;

use Doctrine\Persistence\ManagerRegistry;
use RZ\Roadiz\Core\Handlers\HandlerFactoryInterface;
use RZ\Roadiz\CoreBundle\Entity\Node;
use RZ\Roadiz\CoreBundle\Entity\Realm;
use RZ\Roadiz\CoreBundle\Entity\RealmNode;
use RZ\Roadiz\CoreBundle\EntityHandler\NodeHandler;
use RZ\Roadiz\CoreBundle\Message\ApplyRealmNodeInheritanceMessage;
use RZ\Roadiz\CoreBundle\Model\RealmInterface;
use RZ\Roadiz\CoreBundle\Node\NodeOffspringResolverInterface;
use Symfony\Component\Messenger\Exception\UnrecoverableMessageHandlingException;
use Symfony\Component\Messenger\Handler\MessageHandlerInterface;

final class ApplyRealmNodeInheritanceMessageHandler implements MessageHandlerInterface
{
public function __construct(private readonly ManagerRegistry $managerRegistry)
{
public function __construct(
private readonly ManagerRegistry $managerRegistry,
private readonly NodeOffspringResolverInterface $nodeOffspringResolver
) {
}

public function __invoke(ApplyRealmNodeInheritanceMessage $message): void
Expand Down Expand Up @@ -49,7 +50,7 @@ public function __invoke(ApplyRealmNodeInheritanceMessage $message): void
}

$nodeRepository = $this->managerRegistry->getRepository(Node::class);
$childrenIds = $nodeRepository->findAllOffspringIdByNode($node);
$childrenIds = $this->nodeOffspringResolver->getAllOffspringIds($node);

foreach ($childrenIds as $childId) {
/** @var Node|null $child */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,20 @@
namespace RZ\Roadiz\CoreBundle\Message\Handler;

use Doctrine\Persistence\ManagerRegistry;
use RZ\Roadiz\Core\Handlers\HandlerFactoryInterface;
use RZ\Roadiz\CoreBundle\Entity\Node;
use RZ\Roadiz\CoreBundle\Entity\Realm;
use RZ\Roadiz\CoreBundle\Entity\RealmNode;
use RZ\Roadiz\CoreBundle\EntityHandler\NodeHandler;
use RZ\Roadiz\CoreBundle\Message\CleanRealmNodeInheritanceMessage;
use RZ\Roadiz\CoreBundle\Node\NodeOffspringResolverInterface;
use Symfony\Component\Messenger\Exception\UnrecoverableMessageHandlingException;
use Symfony\Component\Messenger\Handler\MessageHandlerInterface;

final class CleanRealmNodeInheritanceMessageHandler implements MessageHandlerInterface
{
public function __construct(private readonly ManagerRegistry $managerRegistry)
{
public function __construct(
private readonly ManagerRegistry $managerRegistry,
private readonly NodeOffspringResolverInterface $nodeOffspringResolver
) {
}

public function __invoke(CleanRealmNodeInheritanceMessage $message): void
Expand All @@ -35,8 +36,7 @@ public function __invoke(CleanRealmNodeInheritanceMessage $message): void
throw new UnrecoverableMessageHandlingException('Realm does not exist');
}

$nodeRepository = $this->managerRegistry->getRepository(Node::class);
$childrenIds = $nodeRepository->findAllOffspringIdByNode($node);
$childrenIds = $this->nodeOffspringResolver->getAllOffspringIds($node);

$realmNodes = $this->managerRegistry
->getRepository(RealmNode::class)
Expand Down
68 changes: 68 additions & 0 deletions lib/RoadizCoreBundle/src/Node/CachedNodeOffspringResolver.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
<?php

declare(strict_types=1);

namespace RZ\Roadiz\CoreBundle\Node;

use Doctrine\Persistence\ManagerRegistry;
use Psr\Cache\CacheException;
use Psr\Cache\CacheItemPoolInterface;
use Psr\Cache\InvalidArgumentException;
use RZ\Roadiz\CoreBundle\Entity\Node;
use Symfony\Contracts\Cache\ItemInterface;
use Symfony\Contracts\Cache\TagAwareCacheInterface;

final class CachedNodeOffspringResolver implements CachedNodeOffspringResolverInterface
{
public function __construct(
private readonly CacheItemPoolInterface $cache,
private readonly ManagerRegistry $managerRegistry
) {
}

/**
* @inheritDoc
* @throws InvalidArgumentException
* @throws CacheException
*/
public function getAllOffspringIds(Node $ancestor): array
{
$cacheItem = $this->cache->getItem(self::CACHE_PREFIX . $ancestor->getId());
if (!$cacheItem->isHit()) {
$nodeRepository = $this->managerRegistry->getRepository(Node::class);
$offspringIds = $nodeRepository->findAllOffspringIdByNode($ancestor);
$cacheItem->set($offspringIds);
$cacheItem->expiresAfter(300);
if ($cacheItem instanceof ItemInterface && $this->cache instanceof TagAwareCacheInterface) {
$cacheItem->tag(array_map(function (int $nodeId) {
return self::CACHE_TAG_PREFIX . $nodeId;
}, $offspringIds));
}
$this->cache->save($cacheItem);
} else {
$offspringIds = $cacheItem->get();
}
return $offspringIds;
}

/**
* @throws InvalidArgumentException
*/
public function purgeOffspringCache(Node $node): void
{
$this->cache->deleteItem(self::CACHE_PREFIX . $node->getId());
if ($this->cache instanceof TagAwareCacheInterface) {
/*
* If cache pool supports tags, we can invalidate all nodes at once.
*/
$this->cache->invalidateTags([self::CACHE_TAG_PREFIX . $node->getId()]);
} else {
$ancestorsId = $this->managerRegistry
->getRepository(Node::class)
->findAllParentsIdByNode($node);
foreach ($ancestorsId as $ancestorId) {
$this->cache->deleteItem(self::CACHE_PREFIX . $ancestorId);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php

declare(strict_types=1);

namespace RZ\Roadiz\CoreBundle\Node;

use RZ\Roadiz\CoreBundle\Entity\Node;

interface CachedNodeOffspringResolverInterface extends NodeOffspringResolverInterface
{
public const CACHE_PREFIX = 'node_offspring_ids_';
public const CACHE_TAG_PREFIX = 'node_';
public function purgeOffspringCache(Node $node): void;
}
16 changes: 16 additions & 0 deletions lib/RoadizCoreBundle/src/Node/NodeOffspringResolverInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

declare(strict_types=1);

namespace RZ\Roadiz\CoreBundle\Node;

use RZ\Roadiz\CoreBundle\Entity\Node;

interface NodeOffspringResolverInterface
{
/**
* @param Node $ancestor
* @return array<int>
*/
public function getAllOffspringIds(Node $ancestor): array;
}
7 changes: 6 additions & 1 deletion lib/RoadizCoreBundle/src/Repository/NodeRepository.php
Original file line number Diff line number Diff line change
Expand Up @@ -910,6 +910,7 @@ public function findByReverseNodeAndFieldAndTranslation(
/**
* @param Node $node
* @return array<int>
* @internal Use NodeOffspringResolverInterface service instead
*/
public function findAllOffspringIdByNode(Node $node): array
{
Expand Down Expand Up @@ -969,6 +970,10 @@ public function findAllNodeParentsBy(
);
}

/**
* @param Node $node
* @return array<int|string>
*/
public function findAllParentsIdByNode(Node $node): array
{
$theParents = [];
Expand All @@ -979,7 +984,7 @@ public function findAllParentsIdByNode(Node $node): array
$parent = $parent->getParent();
}

return $theParents;
return array_filter($theParents);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,9 @@

namespace RZ\Roadiz\CoreBundle\Security\Authorization\Voter;

use Doctrine\Persistence\ManagerRegistry;
use Psr\Cache\CacheItemPoolInterface;
use RZ\Roadiz\CoreBundle\Entity\Node;
use RZ\Roadiz\CoreBundle\Entity\NodesSources;
use RZ\Roadiz\CoreBundle\Node\NodeOffspringResolverInterface;
use RZ\Roadiz\CoreBundle\Security\Authorization\Chroot\NodeChrootResolver;
use Symfony\Bundle\SecurityBundle\Security;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
Expand Down Expand Up @@ -38,8 +37,7 @@ final class NodeVoter extends Voter
public function __construct(
private readonly NodeChrootResolver $chrootResolver,
private readonly Security $security,
private readonly ManagerRegistry $managerRegistry,
private readonly CacheItemPoolInterface $cache
private readonly NodeOffspringResolverInterface $nodeOffspringResolver,
) {
}

Expand Down Expand Up @@ -124,17 +122,7 @@ private function isNodeInsideUserChroot(Node $node, Node $chroot, bool $includeC
* Test if node is inside user chroot using all Chroot node offspring ids
* to be able to cache all results.
*/
$cacheItem = $this->cache->getItem('node_offspring_ids_' . $chroot->getId());
if (!$cacheItem->isHit()) {
$nodeRepository = $this->managerRegistry->getRepository(Node::class);
$offspringIds = $nodeRepository->findAllOffspringIdByNode($chroot);
$cacheItem->set($offspringIds);
$this->cache->save($cacheItem);
} else {
$offspringIds = $cacheItem->get();
}

return \in_array($node->getId(), $offspringIds, true);
return \in_array($node->getId(), $this->nodeOffspringResolver->getAllOffspringIds($chroot), true);
}

private function isGrantedWithUserChroot(Node $node, UserInterface $user, array|string $roles, bool $includeChroot): bool
Expand Down
8 changes: 5 additions & 3 deletions lib/Rozier/src/Controllers/Nodes/NodesController.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
use RZ\Roadiz\CoreBundle\Node\Exception\SameNodeUrlException;
use RZ\Roadiz\CoreBundle\Node\NodeFactory;
use RZ\Roadiz\CoreBundle\Node\NodeMover;
use RZ\Roadiz\CoreBundle\Node\NodeOffspringResolverInterface;
use RZ\Roadiz\CoreBundle\Node\UniqueNodeGenerator;
use RZ\Roadiz\CoreBundle\Security\Authorization\Chroot\NodeChrootResolver;
use RZ\Roadiz\CoreBundle\Security\Authorization\Voter\NodeVoter;
Expand All @@ -38,7 +39,7 @@
use Themes\Rozier\Traits\NodesTrait;
use Twig\Error\RuntimeError;

class NodesController extends RozierApp
final class NodesController extends RozierApp
{
use NodesTrait;

Expand All @@ -49,6 +50,7 @@ class NodesController extends RozierApp
* @param HandlerFactoryInterface $handlerFactory
* @param UniqueNodeGenerator $uniqueNodeGenerator
* @param NodeFactory $nodeFactory
* @param NodeOffspringResolverInterface $nodeOffspringResolver
* @param class-string<AbstractType> $nodeFormTypeClass
* @param class-string<AbstractType> $addNodeFormTypeClass
*/
Expand All @@ -59,6 +61,7 @@ public function __construct(
private readonly HandlerFactoryInterface $handlerFactory,
private readonly UniqueNodeGenerator $uniqueNodeGenerator,
private readonly NodeFactory $nodeFactory,
private readonly NodeOffspringResolverInterface $nodeOffspringResolver,
private readonly string $nodeFormTypeClass,
private readonly string $addNodeFormTypeClass
) {
Expand Down Expand Up @@ -569,8 +572,7 @@ public function emptyTrashAction(Request $request): Response
/** @var Node|null $chroot */
$chroot = $this->nodeChrootResolver->getChroot($this->getUser());
if ($chroot !== null) {
$nodeRepository = $this->em()->getRepository(Node::class);
$criteria["parent"] = $nodeRepository->findAllOffspringIdByNode($chroot);
$criteria["parent"] = $this->nodeOffspringResolver->getAllOffspringIds($chroot);
}

$nodes = $this->em()
Expand Down

0 comments on commit cf62690

Please sign in to comment.