Loading core/modules/menu_link_content/menu_link_content.services.yml +6 −0 Original line number Diff line number Diff line parameters: menu_link_content.skip_procedural_hook_scan: true services: _defaults: autoconfigure: true autowire: true Drupal\menu_link_content\EventSubscriber\DefaultContentSubscriber: ~ core/modules/menu_link_content/src/EventSubscriber/DefaultContentSubscriber.php 0 → 100644 +79 −0 Original line number Diff line number Diff line <?php declare(strict_types=1); namespace Drupal\menu_link_content\EventSubscriber; use Drupal\Core\DefaultContent\PreExportEvent; use Drupal\Core\Entity\EntityRepositoryInterface; use Drupal\menu_link_content\MenuLinkContentInterface; use Psr\Log\LoggerInterface; use Symfony\Component\DependencyInjection\Attribute\AutowireServiceClosure; use Symfony\Component\EventDispatcher\EventSubscriberInterface; /** * Subscribes to default content-related events. * * @internal * Event subscribers are internal. */ final class DefaultContentSubscriber implements EventSubscriberInterface { public function __construct( private readonly EntityRepositoryInterface $entityRepository, #[AutowireServiceClosure('logger.channel.default_content')] private readonly \Closure $logger, ) {} /** * {@inheritdoc} */ public static function getSubscribedEvents(): array { return [ PreExportEvent::class => 'preExport', ]; } /** * Reacts before an entity is exported. * * Adds an export callback to ensure parent menu links are marked as * dependencies, when exporting menu link content entities. * * @param \Drupal\Core\DefaultContent\PreExportEvent $event * The event object. */ public function preExport(PreExportEvent $event): void { if (!$event->entity instanceof MenuLinkContentInterface) { return; } $parentId = $event->entity->getParentId(); if (!str_starts_with($parentId, 'menu_link_content:')) { return; } [, $uuid] = explode(':', $parentId); $parent = $this->entityRepository->loadEntityByUuid('menu_link_content', $uuid); if ($parent instanceof MenuLinkContentInterface) { $event->metadata->addDependency($parent); return; } $this->getLogger()->error("The parent (%parent) of menu link %uuid could not be loaded.", [ '%parent' => $uuid, '%uuid' => $event->entity->uuid(), ]); } /** * Gets the logging service. * * @return \Psr\Log\LoggerInterface * The logging service. */ private function getLogger(): LoggerInterface { return ($this->logger)(); } } core/modules/menu_link_content/tests/src/Kernel/DefaultContentTest.php 0 → 100644 +95 −0 Original line number Diff line number Diff line <?php declare(strict_types=1); namespace Drupal\Tests\menu_link_content\Kernel; use ColinODell\PsrTestLogger\TestLogger; use Drupal\Component\Serialization\Yaml; use Drupal\Core\DefaultContent\Exporter; use Drupal\Core\Logger\RfcLogLevel; use Drupal\Core\Menu\MenuLinkManagerInterface; use Drupal\KernelTests\KernelTestBase; use Drupal\menu_link_content\Entity\MenuLinkContent; use Drupal\menu_link_content\EventSubscriber\DefaultContentSubscriber; use Drupal\Tests\node\Traits\ContentTypeCreationTrait; use Drupal\Tests\node\Traits\NodeCreationTrait; use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\Attributes\Group; use PHPUnit\Framework\Attributes\RunTestsInSeparateProcesses; /** * Tests exporting menu links in YAML format. */ #[Group('menu_link_content')] #[CoversClass(DefaultContentSubscriber::class)] #[RunTestsInSeparateProcesses] class DefaultContentTest extends KernelTestBase { use ContentTypeCreationTrait; use NodeCreationTrait; /** * {@inheritdoc} */ protected static $modules = [ 'field', 'filter', 'link', 'menu_link_content', 'node', 'system', 'text', 'user', ]; /** * Tests exporting of menu link content. */ public function testExportMenuLinkContent(): void { $this->installConfig(['filter', 'system']); $this->installEntitySchema('menu_link_content'); $this->installEntitySchema('node'); $this->installEntitySchema('user'); $this->createContentType(['type' => 'page']); $parent = $this->createNode(['type' => 'page']); $child = $this->createNode(['type' => 'page']); \Drupal::service(MenuLinkManagerInterface::class)->rebuild(); $parent_link = MenuLinkContent::create([ 'menu_name' => 'main', 'link' => 'internal:' . $parent->toUrl()->toString(), ]); $parent_link->save(); $child_link = MenuLinkContent::create([ 'menu_name' => 'main', 'link' => 'internal:' . $child->toUrl()->toString(), 'parent' => 'menu_link_content:' . $parent_link->uuid(), ]); $child_link->save(); $logger = new TestLogger(); \Drupal::service('logger.channel.default_content')->addLogger($logger); // If we export the child link, the parent should be one of its // dependencies. $data = (string) \Drupal::service(Exporter::class)->export($child_link); $data = Yaml::decode($data); $this->assertArrayHasKey($parent_link->uuid(), $data['_meta']['depends']); $this->assertEmpty($logger->records); // If we delete the parent link, exporting the child should log an error. $parent_link->delete(); \Drupal::service(Exporter::class)->export($child_link); $predicate = function (array $record) use ($child_link, $parent_link): bool { return ( $record['message'] === 'The parent (%parent) of menu link %uuid could not be loaded.' && $record['context']['%parent'] === $parent_link->uuid() && $record['context']['%uuid'] === $child_link->uuid() ); }; $this->assertTrue($logger->hasRecordThatPasses($predicate, RfcLogLevel::ERROR)); } } Loading
core/modules/menu_link_content/menu_link_content.services.yml +6 −0 Original line number Diff line number Diff line parameters: menu_link_content.skip_procedural_hook_scan: true services: _defaults: autoconfigure: true autowire: true Drupal\menu_link_content\EventSubscriber\DefaultContentSubscriber: ~
core/modules/menu_link_content/src/EventSubscriber/DefaultContentSubscriber.php 0 → 100644 +79 −0 Original line number Diff line number Diff line <?php declare(strict_types=1); namespace Drupal\menu_link_content\EventSubscriber; use Drupal\Core\DefaultContent\PreExportEvent; use Drupal\Core\Entity\EntityRepositoryInterface; use Drupal\menu_link_content\MenuLinkContentInterface; use Psr\Log\LoggerInterface; use Symfony\Component\DependencyInjection\Attribute\AutowireServiceClosure; use Symfony\Component\EventDispatcher\EventSubscriberInterface; /** * Subscribes to default content-related events. * * @internal * Event subscribers are internal. */ final class DefaultContentSubscriber implements EventSubscriberInterface { public function __construct( private readonly EntityRepositoryInterface $entityRepository, #[AutowireServiceClosure('logger.channel.default_content')] private readonly \Closure $logger, ) {} /** * {@inheritdoc} */ public static function getSubscribedEvents(): array { return [ PreExportEvent::class => 'preExport', ]; } /** * Reacts before an entity is exported. * * Adds an export callback to ensure parent menu links are marked as * dependencies, when exporting menu link content entities. * * @param \Drupal\Core\DefaultContent\PreExportEvent $event * The event object. */ public function preExport(PreExportEvent $event): void { if (!$event->entity instanceof MenuLinkContentInterface) { return; } $parentId = $event->entity->getParentId(); if (!str_starts_with($parentId, 'menu_link_content:')) { return; } [, $uuid] = explode(':', $parentId); $parent = $this->entityRepository->loadEntityByUuid('menu_link_content', $uuid); if ($parent instanceof MenuLinkContentInterface) { $event->metadata->addDependency($parent); return; } $this->getLogger()->error("The parent (%parent) of menu link %uuid could not be loaded.", [ '%parent' => $uuid, '%uuid' => $event->entity->uuid(), ]); } /** * Gets the logging service. * * @return \Psr\Log\LoggerInterface * The logging service. */ private function getLogger(): LoggerInterface { return ($this->logger)(); } }
core/modules/menu_link_content/tests/src/Kernel/DefaultContentTest.php 0 → 100644 +95 −0 Original line number Diff line number Diff line <?php declare(strict_types=1); namespace Drupal\Tests\menu_link_content\Kernel; use ColinODell\PsrTestLogger\TestLogger; use Drupal\Component\Serialization\Yaml; use Drupal\Core\DefaultContent\Exporter; use Drupal\Core\Logger\RfcLogLevel; use Drupal\Core\Menu\MenuLinkManagerInterface; use Drupal\KernelTests\KernelTestBase; use Drupal\menu_link_content\Entity\MenuLinkContent; use Drupal\menu_link_content\EventSubscriber\DefaultContentSubscriber; use Drupal\Tests\node\Traits\ContentTypeCreationTrait; use Drupal\Tests\node\Traits\NodeCreationTrait; use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\Attributes\Group; use PHPUnit\Framework\Attributes\RunTestsInSeparateProcesses; /** * Tests exporting menu links in YAML format. */ #[Group('menu_link_content')] #[CoversClass(DefaultContentSubscriber::class)] #[RunTestsInSeparateProcesses] class DefaultContentTest extends KernelTestBase { use ContentTypeCreationTrait; use NodeCreationTrait; /** * {@inheritdoc} */ protected static $modules = [ 'field', 'filter', 'link', 'menu_link_content', 'node', 'system', 'text', 'user', ]; /** * Tests exporting of menu link content. */ public function testExportMenuLinkContent(): void { $this->installConfig(['filter', 'system']); $this->installEntitySchema('menu_link_content'); $this->installEntitySchema('node'); $this->installEntitySchema('user'); $this->createContentType(['type' => 'page']); $parent = $this->createNode(['type' => 'page']); $child = $this->createNode(['type' => 'page']); \Drupal::service(MenuLinkManagerInterface::class)->rebuild(); $parent_link = MenuLinkContent::create([ 'menu_name' => 'main', 'link' => 'internal:' . $parent->toUrl()->toString(), ]); $parent_link->save(); $child_link = MenuLinkContent::create([ 'menu_name' => 'main', 'link' => 'internal:' . $child->toUrl()->toString(), 'parent' => 'menu_link_content:' . $parent_link->uuid(), ]); $child_link->save(); $logger = new TestLogger(); \Drupal::service('logger.channel.default_content')->addLogger($logger); // If we export the child link, the parent should be one of its // dependencies. $data = (string) \Drupal::service(Exporter::class)->export($child_link); $data = Yaml::decode($data); $this->assertArrayHasKey($parent_link->uuid(), $data['_meta']['depends']); $this->assertEmpty($logger->records); // If we delete the parent link, exporting the child should log an error. $parent_link->delete(); \Drupal::service(Exporter::class)->export($child_link); $predicate = function (array $record) use ($child_link, $parent_link): bool { return ( $record['message'] === 'The parent (%parent) of menu link %uuid could not be loaded.' && $record['context']['%parent'] === $parent_link->uuid() && $record['context']['%uuid'] === $child_link->uuid() ); }; $this->assertTrue($logger->hasRecordThatPasses($predicate, RfcLogLevel::ERROR)); } }