From da3b611767c8c2fed9775484af648b536d575855 Mon Sep 17 00:00:00 2001 From: Tomas Date: Wed, 2 Aug 2023 06:59:12 +0300 Subject: [PATCH] [TwigComponent] Improve exception message when component not found --- src/TwigComponent/src/ComponentFactory.php | 27 ++++++++++++++++++- .../Integration/ComponentFactoryTest.php | 16 +++++++---- 2 files changed, 37 insertions(+), 6 deletions(-) diff --git a/src/TwigComponent/src/ComponentFactory.php b/src/TwigComponent/src/ComponentFactory.php index 8e0ccc3f860..32b38501dd7 100644 --- a/src/TwigComponent/src/ComponentFactory.php +++ b/src/TwigComponent/src/ComponentFactory.php @@ -194,6 +194,31 @@ private function postMount(object $component, array $data): array */ private function throwUnknownComponentException(string $name): void { - throw new \InvalidArgumentException(sprintf('Unknown component "%s". The registered components are: %s', $name, implode(', ', array_keys($this->config)))); + $message = sprintf('Unknown component "%s".', $name); + + $alternatives = []; + + foreach (array_keys($this->config) as $type) { + $lowerName = strtolower($name); + $lowerType = strtolower($type); + + $lev = levenshtein($lowerName, $lowerType); + + if ($lev <= \strlen($lowerName) / 3 || str_contains($lowerType, $lowerName)) { + $alternatives[] = $type; + } + } + + if ($alternatives) { + if (1 === \count($alternatives)) { + $message .= ' Did you mean this: "'; + } else { + $message .= ' Did you mean one of these: "'; + } + + $message .= implode('", "', $alternatives).'"?'; + } + + throw new \InvalidArgumentException($message); } } diff --git a/src/TwigComponent/tests/Integration/ComponentFactoryTest.php b/src/TwigComponent/tests/Integration/ComponentFactoryTest.php index edbab16788a..0fc93e21f86 100644 --- a/src/TwigComponent/tests/Integration/ComponentFactoryTest.php +++ b/src/TwigComponent/tests/Integration/ComponentFactoryTest.php @@ -152,17 +152,23 @@ public function testCanGetMetadataForSameComponentWithDifferentName(): void public function testCannotGetConfigByNameForNonRegisteredComponent(): void { $this->expectException(\InvalidArgumentException::class); - $this->expectExceptionMessageMatches('/^Unknown component "invalid"\. The registered components are:.* component_a/'); + $this->expectExceptionMessage('Unknown component "tabl". Did you mean this: "table"?'); - $this->factory()->metadataFor('invalid'); + $this->factory()->metadataFor('tabl'); } - public function testCannotGetInvalidComponent(): void + /** + * @testWith ["tabl", "Unknown component \"tabl\". Did you mean this: \"table\"?"] + * ["Basic", "Unknown component \"Basic\". Did you mean this: \"BasicComponent\"?"] + * ["basic", "Unknown component \"basic\". Did you mean this: \"BasicComponent\"?"] + * ["with", "Unknown component \"with\". Did you mean one of these: \"with_attributes\", \"with_exposed_variables\", \"WithSlots\"?"] + */ + public function testCannotGetInvalidComponent(string $name, string $expectedExceptionMessage): void { $this->expectException(\InvalidArgumentException::class); - $this->expectExceptionMessageMatches('/^Unknown component "invalid"\. The registered components are:.* component_a/'); + $this->expectExceptionMessage($expectedExceptionMessage); - $this->factory()->get('invalid'); + $this->factory()->get($name); } public function testInputPropsStoredOnMountedComponent(): void