diff --git a/packages/guides/src/ReferenceResolvers/AnchorHyperlinkResolver.php b/packages/guides/src/ReferenceResolvers/AnchorHyperlinkResolver.php index 261c8f627..0e5d24ab5 100644 --- a/packages/guides/src/ReferenceResolvers/AnchorHyperlinkResolver.php +++ b/packages/guides/src/ReferenceResolvers/AnchorHyperlinkResolver.php @@ -24,7 +24,7 @@ public function __construct( ) { } - public function resolve(LinkInlineNode $node, RenderContext $renderContext): bool + public function resolve(LinkInlineNode $node, RenderContext $renderContext, Messages $messages): bool { if (!$node instanceof HyperLinkNode) { return false; diff --git a/packages/guides/src/ReferenceResolvers/AnchorReferenceResolver.php b/packages/guides/src/ReferenceResolvers/AnchorReferenceResolver.php index 0fbbb3da0..0a7c56657 100644 --- a/packages/guides/src/ReferenceResolvers/AnchorReferenceResolver.php +++ b/packages/guides/src/ReferenceResolvers/AnchorReferenceResolver.php @@ -24,7 +24,7 @@ public function __construct( ) { } - public function resolve(LinkInlineNode $node, RenderContext $renderContext): bool + public function resolve(LinkInlineNode $node, RenderContext $renderContext, Messages $messages): bool { if (!$node instanceof ReferenceNode) { return false; diff --git a/packages/guides/src/ReferenceResolvers/DelegatingReferenceResolver.php b/packages/guides/src/ReferenceResolvers/DelegatingReferenceResolver.php index 7d39a787c..ed7ff930d 100644 --- a/packages/guides/src/ReferenceResolvers/DelegatingReferenceResolver.php +++ b/packages/guides/src/ReferenceResolvers/DelegatingReferenceResolver.php @@ -6,9 +6,6 @@ use phpDocumentor\Guides\Nodes\Inline\LinkInlineNode; use phpDocumentor\Guides\RenderContext; -use Psr\Log\LoggerInterface; - -use function sprintf; /** * Resolves the URL for all inline link nodes using reference resolvers. @@ -16,25 +13,18 @@ final class DelegatingReferenceResolver { /** @param iterable $resolvers */ - public function __construct(private readonly iterable $resolvers, private readonly LoggerInterface $logger) + public function __construct(private readonly iterable $resolvers) { } - public function resolve(LinkInlineNode $node, RenderContext $renderContext): void + public function resolve(LinkInlineNode $node, RenderContext $renderContext, Messages $messages): bool { foreach ($this->resolvers as $resolver) { - if ($resolver->resolve($node, $renderContext)) { - return; + if ($resolver->resolve($node, $renderContext, $messages)) { + return true; } } - $this->logger->warning( - sprintf( - 'Reference %s could not be resolved in %s', - $node->getTargetReference(), - $renderContext->getCurrentFileName(), - ), - $renderContext->getLoggerInformation(), - ); + return false; } } diff --git a/packages/guides/src/ReferenceResolvers/DocReferenceResolver.php b/packages/guides/src/ReferenceResolvers/DocReferenceResolver.php index 3398366d6..3e403af79 100644 --- a/packages/guides/src/ReferenceResolvers/DocReferenceResolver.php +++ b/packages/guides/src/ReferenceResolvers/DocReferenceResolver.php @@ -8,9 +8,7 @@ use phpDocumentor\Guides\Nodes\Inline\LinkInlineNode; use phpDocumentor\Guides\RenderContext; use phpDocumentor\Guides\Renderer\UrlGenerator\UrlGeneratorInterface; -use Psr\Log\LoggerInterface; -use function array_merge; use function sprintf; class DocReferenceResolver implements ReferenceResolver @@ -20,11 +18,10 @@ class DocReferenceResolver implements ReferenceResolver public function __construct( private readonly UrlGeneratorInterface $urlGenerator, private readonly DocumentNameResolverInterface $documentNameResolver, - private readonly LoggerInterface $logger, ) { } - public function resolve(LinkInlineNode $node, RenderContext $renderContext): bool + public function resolve(LinkInlineNode $node, RenderContext $renderContext, Messages $messages): bool { if (!$node instanceof DocReferenceNode) { return false; @@ -38,14 +35,11 @@ public function resolve(LinkInlineNode $node, RenderContext $renderContext): boo $document = $renderContext->getProjectNode()->findDocumentEntry($canonicalDocumentName); if ($document === null) { - $this->logger->warning( - sprintf( - 'Document with name "%s" not found, required in file "%s".', - $canonicalDocumentName, - $renderContext->getCurrentFileName(), - ), - array_merge($renderContext->getLoggerInformation(), $node->getDebugInformation()), - ); + $messages->addWarning(new Message(sprintf( + 'Document with name "%s" not found, required in file "%s".', + $canonicalDocumentName, + $renderContext->getCurrentFileName(), + ))); return false; } diff --git a/packages/guides/src/ReferenceResolvers/EmailReferenceResolver.php b/packages/guides/src/ReferenceResolvers/EmailReferenceResolver.php index 8e06a2f78..5d49a0968 100644 --- a/packages/guides/src/ReferenceResolvers/EmailReferenceResolver.php +++ b/packages/guides/src/ReferenceResolvers/EmailReferenceResolver.php @@ -18,7 +18,7 @@ class EmailReferenceResolver implements ReferenceResolver { public final const PRIORITY = -100; - public function resolve(LinkInlineNode $node, RenderContext $renderContext): bool + public function resolve(LinkInlineNode $node, RenderContext $renderContext, Messages $messages): bool { if (filter_var($node->getTargetReference(), FILTER_VALIDATE_EMAIL)) { $node->setUrl('mailto:' . $node->getTargetReference()); diff --git a/packages/guides/src/ReferenceResolvers/ExternalReferenceResolver.php b/packages/guides/src/ReferenceResolvers/ExternalReferenceResolver.php index 883388e9e..075b02f80 100644 --- a/packages/guides/src/ReferenceResolvers/ExternalReferenceResolver.php +++ b/packages/guides/src/ReferenceResolvers/ExternalReferenceResolver.php @@ -28,7 +28,7 @@ class ExternalReferenceResolver implements ReferenceResolver public final const PRIORITY = -100; final public const SUPPORTED_SCHEMAS = '(?:aaa|aaas|about|acap|acct|acd|acr|adiumxtra|adt|afp|afs|aim|amss|android|appdata|apt|ar|ark|at|attachment|aw|barion|bb|beshare|bitcoin|bitcoincash|blob|bolo|browserext|cabal|calculator|callto|cap|cast|casts|chrome|chrome-extension|cid|coap|coap+tcp|coap+ws|coaps|coaps+tcp|coaps+ws|com-eventbrite-attendee|content|content-type|crid|cstr|cvs|dab|dat|data|dav|dhttp|diaspora|dict|did|dis|dlna-playcontainer|dlna-playsingle|dns|dntp|doi|dpp|drm|drop|dtmi|dtn|dvb|dvx|dweb|ed2k|eid|elsi|embedded|ens|ethereum|example|facetime|fax|feed|feedready|fido|file|filesystem|finger|first-run-pen-experience|fish|fm|ftp|fuchsia-pkg|geo|gg|git|gitoid|gizmoproject|go|gopher|graph|grd|gtalk|h323|ham|hcap|hcp|http|https|hxxp|hxxps|hydrazone|hyper|iax|icap|icon|im|imap|info|iotdisco|ipfs|ipn|ipns|ipp|ipps|irc|irc6|ircs|iris|iris\.beep|iris\.lwz|iris\.xpc|iris\.xpcs|isostore|itms|jabber|jar|jms|keyparc|lastfm|lbry|ldap|ldaps|leaptofrogans|lorawan|lpa|lvlt|magnet|mailserver|mailto|maps|market|matrix|message|microsoft\.windows\.camera|microsoft\.windows\.camera\.multipicker|microsoft\.windows\.camera\.picker|mid|mms|modem|mongodb|moz|ms-access|ms-appinstaller|ms-browser-extension|ms-calculator|ms-drive-to|ms-enrollment|ms-excel|ms-eyecontrolspeech|ms-gamebarservices|ms-gamingoverlay|ms-getoffice|ms-help|ms-infopath|ms-inputapp|ms-launchremotedesktop|ms-lockscreencomponent-config|ms-media-stream-id|ms-meetnow|ms-mixedrealitycapture|ms-mobileplans|ms-newsandinterests|ms-officeapp|ms-people|ms-project|ms-powerpoint|ms-publisher|ms-remotedesktop|ms-remotedesktop-launch|ms-restoretabcompanion|ms-screenclip|ms-screensketch|ms-search|ms-search-repair|ms-secondary-screen-controller|ms-secondary-screen-setup|ms-settings|ms-settings-airplanemode|ms-settings-bluetooth|ms-settings-camera|ms-settings-cellular|ms-settings-cloudstorage|ms-settings-connectabledevices|ms-settings-displays-topology|ms-settings-emailandaccounts|ms-settings-language|ms-settings-location|ms-settings-lock|ms-settings-nfctransactions|ms-settings-notifications|ms-settings-power|ms-settings-privacy|ms-settings-proximity|ms-settings-screenrotation|ms-settings-wifi|ms-settings-workplace|ms-spd|ms-stickers|ms-sttoverlay|ms-transit-to|ms-useractivityset|ms-virtualtouchpad|ms-visio|ms-walk-to|ms-whiteboard|ms-whiteboard-cmd|ms-word|msnim|msrp|msrps|mss|mt|mtqp|mumble|mupdate|mvn|news|nfs|ni|nih|nntp|notes|num|ocf|oid|onenote|onenote-cmd|opaquelocktoken|openpgp4fpr|otpauth|p1|pack|palm|paparazzi|payment|payto|pkcs11|platform|pop|pres|prospero|proxy|pwid|psyc|pttp|qb|query|quic-transport|redis|rediss|reload|res|resource|rmi|rsync|rtmfp|rtmp|rtsp|rtsps|rtspu|sarif|secondlife|secret-token|service|session|sftp|sgn|shc|shttp (OBSOLETE)|sieve|simpleledger|simplex|sip|sips|skype|smb|smp|sms|smtp|snews|snmp|soap\.beep|soap\.beeps|soldat|spiffe|spotify|ssb|ssh|starknet|steam|stun|stuns|submit|svn|swh|swid|swidpath|tag|taler|teamspeak|tel|teliaeid|telnet|tftp|things|thismessage|tip|tn3270|tool|turn|turns|tv|udp|unreal|upt|urn|ut2004|uuid-in-package|v-event|vemmi|ventrilo|ves|videotex|vnc|view-source|vscode|vscode-insiders|vsls|w3|wais|web3|wcr|webcal|web+ap|wifi|wpid|ws|wss|wtai|wyciwyg|xcon|xcon-userid|xfire|xmlrpc\.beep|xmlrpc\.beeps|xmpp|xri|ymsgr|z39\.50|z39\.50r|z39\.50s)'; - public function resolve(LinkInlineNode $node, RenderContext $renderContext): bool + public function resolve(LinkInlineNode $node, RenderContext $renderContext, Messages $messages): bool { if (str_starts_with($node->getTargetReference(), '#')) { $node->setUrl($node->getTargetReference()); diff --git a/packages/guides/src/ReferenceResolvers/InterlinkReferenceResolver.php b/packages/guides/src/ReferenceResolvers/InterlinkReferenceResolver.php index e638ba658..d1cb2f659 100644 --- a/packages/guides/src/ReferenceResolvers/InterlinkReferenceResolver.php +++ b/packages/guides/src/ReferenceResolvers/InterlinkReferenceResolver.php @@ -9,9 +9,7 @@ use phpDocumentor\Guides\Nodes\Inline\DocReferenceNode; use phpDocumentor\Guides\Nodes\Inline\LinkInlineNode; use phpDocumentor\Guides\RenderContext; -use Psr\Log\LoggerInterface; -use function array_merge; use function sprintf; class InterlinkReferenceResolver implements ReferenceResolver @@ -20,11 +18,10 @@ class InterlinkReferenceResolver implements ReferenceResolver public function __construct( private readonly InventoryRepository $inventoryRepository, - private readonly LoggerInterface $logger, ) { } - public function resolve(LinkInlineNode $node, RenderContext $renderContext): bool + public function resolve(LinkInlineNode $node, RenderContext $renderContext, Messages $messages): bool { if (!$node instanceof CrossReferenceNode || $node->getInterlinkDomain() === '') { return false; @@ -33,13 +30,15 @@ public function resolve(LinkInlineNode $node, RenderContext $renderContext): boo $domain = $node->getInterlinkDomain(); $target = $node->getTargetReference(); if (!$this->inventoryRepository->hasInventory($domain)) { - $this->logger->warning( - sprintf( - 'Inventory with name "%s" could not be resolved in file "%s". ', - $domain, - $renderContext->getCurrentFileName(), + $messages->addWarning( + new Message( + sprintf( + 'Inventory with name "%s" could not be resolved in file "%s". ', + $domain, + $renderContext->getCurrentFileName(), + ), + $node->getDebugInformation(), ), - array_merge($renderContext->getLoggerInformation(), $node->getDebugInformation()), ); return false; @@ -48,22 +47,22 @@ public function resolve(LinkInlineNode $node, RenderContext $renderContext): boo $inventory = $this->inventoryRepository->getInventory($domain); $group = $node instanceof DocReferenceNode ? 'std:doc' : 'std:label'; if (!$inventory->hasInventoryGroup($group)) { - $this->logger->warning( + $messages->addWarning(new Message( sprintf( 'Inventory with name "%s" does not contain group %s, required in file "%s". ', $domain, $group, $renderContext->getCurrentFileName(), ), - array_merge($renderContext->getLoggerInformation(), $node->getDebugInformation()), - ); + $node->getDebugInformation(), + )); return false; } $inventoryGroup = $inventory->getInventory($group); if (!$inventoryGroup->hasLink($target)) { - $this->logger->warning( + $messages->addWarning(new Message( sprintf( 'Link with name "%s:%s" not found in group "%s", required in file "%s".', $domain, @@ -71,8 +70,8 @@ public function resolve(LinkInlineNode $node, RenderContext $renderContext): boo $group, $renderContext->getCurrentFileName(), ), - array_merge($renderContext->getLoggerInformation(), $node->getDebugInformation()), - ); + $node->getDebugInformation(), + )); return false; } diff --git a/packages/guides/src/ReferenceResolvers/InternalReferenceResolver.php b/packages/guides/src/ReferenceResolvers/InternalReferenceResolver.php index 873e96342..f180b2bb1 100644 --- a/packages/guides/src/ReferenceResolvers/InternalReferenceResolver.php +++ b/packages/guides/src/ReferenceResolvers/InternalReferenceResolver.php @@ -11,7 +11,7 @@ class InternalReferenceResolver implements ReferenceResolver { public final const PRIORITY = 100; - public function resolve(LinkInlineNode $node, RenderContext $renderContext): bool + public function resolve(LinkInlineNode $node, RenderContext $renderContext, Messages $messages): bool { $link = $renderContext->getLink($node->getTargetReference()); if ($link) { diff --git a/packages/guides/src/ReferenceResolvers/Message.php b/packages/guides/src/ReferenceResolvers/Message.php new file mode 100644 index 000000000..68bea258b --- /dev/null +++ b/packages/guides/src/ReferenceResolvers/Message.php @@ -0,0 +1,26 @@ + $debugInfo */ + public function __construct( + private readonly string $message, + private readonly array $debugInfo = [], + ) { + } + + public function getMessage(): string + { + return $this->message; + } + + /** @return mixed[] */ + public function getDebugInfo(): array + { + return $this->debugInfo; + } +} diff --git a/packages/guides/src/ReferenceResolvers/Messages.php b/packages/guides/src/ReferenceResolvers/Messages.php new file mode 100644 index 000000000..1cc3727fc --- /dev/null +++ b/packages/guides/src/ReferenceResolvers/Messages.php @@ -0,0 +1,33 @@ + */ + private array $warnings = []; + + public function addWarning(Message $warning): void + { + $this->warnings[] = $warning; + } + + /** @return Message[] */ + public function getWarnings(): array + { + return $this->warnings; + } + + public function getLastWarning(): Message|null + { + if (!empty($this->warnings)) { + return end($this->warnings); + } + + return null; + } +} diff --git a/packages/guides/src/ReferenceResolvers/PageHyperlinkResolver.php b/packages/guides/src/ReferenceResolvers/PageHyperlinkResolver.php index 5a253d924..a153640ed 100644 --- a/packages/guides/src/ReferenceResolvers/PageHyperlinkResolver.php +++ b/packages/guides/src/ReferenceResolvers/PageHyperlinkResolver.php @@ -29,7 +29,7 @@ public function __construct( ) { } - public function resolve(LinkInlineNode $node, RenderContext $renderContext): bool + public function resolve(LinkInlineNode $node, RenderContext $renderContext, Messages $messages): bool { if (!$node instanceof HyperLinkNode) { return false; diff --git a/packages/guides/src/ReferenceResolvers/ReferenceResolver.php b/packages/guides/src/ReferenceResolvers/ReferenceResolver.php index 1227a9e1d..ef5d17121 100644 --- a/packages/guides/src/ReferenceResolvers/ReferenceResolver.php +++ b/packages/guides/src/ReferenceResolvers/ReferenceResolver.php @@ -10,7 +10,7 @@ interface ReferenceResolver { /** @return bool true if the reference is resolved */ - public function resolve(LinkInlineNode $node, RenderContext $renderContext): bool; + public function resolve(LinkInlineNode $node, RenderContext $renderContext, Messages $messages): bool; public static function getPriority(): int; } diff --git a/packages/guides/src/ReferenceResolvers/ReferenceResolverPreRender.php b/packages/guides/src/ReferenceResolvers/ReferenceResolverPreRender.php index c00994deb..9a0813e5d 100644 --- a/packages/guides/src/ReferenceResolvers/ReferenceResolverPreRender.php +++ b/packages/guides/src/ReferenceResolvers/ReferenceResolverPreRender.php @@ -8,12 +8,18 @@ use phpDocumentor\Guides\Nodes\Inline\LinkInlineNode; use phpDocumentor\Guides\Nodes\Node; use phpDocumentor\Guides\RenderContext; +use Psr\Log\LoggerInterface; use Webmozart\Assert\Assert; +use function array_merge; +use function sprintf; + final class ReferenceResolverPreRender implements PreNodeRenderer { - public function __construct(private readonly DelegatingReferenceResolver $referenceResolver) - { + public function __construct( + private readonly DelegatingReferenceResolver $referenceResolver, + private readonly LoggerInterface $logger, + ) { } public function supports(Node $node): bool @@ -24,7 +30,18 @@ public function supports(Node $node): bool public function execute(Node $node, RenderContext $renderContext): Node { Assert::isInstanceOf($node, LinkInlineNode::class); - $this->referenceResolver->resolve($node, $renderContext); + $messages = new Messages(); + $resolved = $this->referenceResolver->resolve($node, $renderContext, $messages); + if (!$resolved) { + $this->logger->warning( + $messages->getLastWarning()?->getMessage() ?? sprintf( + 'Reference %s could not be resolved in %s', + $node->getTargetReference(), + $renderContext->getCurrentFileName(), + ), + array_merge($renderContext->getLoggerInformation(), $messages->getLastWarning()?->getDebugInfo() ?? []), + ); + } return $node; } diff --git a/packages/guides/tests/unit/ReferenceResolvers/AnchorReferenceResolverTest.php b/packages/guides/tests/unit/ReferenceResolvers/AnchorReferenceResolverTest.php index 8f02454a2..46dca05fe 100644 --- a/packages/guides/tests/unit/ReferenceResolvers/AnchorReferenceResolverTest.php +++ b/packages/guides/tests/unit/ReferenceResolvers/AnchorReferenceResolverTest.php @@ -40,14 +40,18 @@ public function testAnchorReducerGetsCalledOndResolvingReference(): void { $this->anchorReducer->expects(self::once())->method('reduceAnchor')->willReturn('reduced-anchor'); $input = new ReferenceNode('lorem-ipsum'); - self::assertTrue($this->subject->resolve($input, $this->renderContext)); + $messages = new Messages(); + self::assertTrue($this->subject->resolve($input, $this->renderContext, $messages)); + self::assertEmpty($messages->getWarnings()); } public function testResolvedReferenceReturnsCanonicalUrl(): void { $this->urlGenerator->method('generateCanonicalOutputUrl')->willReturn('canonical-url'); $input = new ReferenceNode('lorem-ipsum'); - self::assertTrue($this->subject->resolve($input, $this->renderContext)); + $messages = new Messages(); + self::assertTrue($this->subject->resolve($input, $this->renderContext, $messages)); + self::assertEmpty($messages->getWarnings()); self::assertEquals('canonical-url', $input->getUrl()); } }