Skip to content

Commit b763b8b

Browse files
Validate shortcode controllername service configurations (Case 173693) (#40)
Resolves #35 --------- Co-authored-by: Malte Wunsch <[email protected]>
1 parent be426f0 commit b763b8b

File tree

6 files changed

+70
-17
lines changed

6 files changed

+70
-17
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
composer.lock
22
vendor/
33
phpunit.xml
4+
.phpunit.cache/
45
.phpunit.result.cache
56
.php-cs-fixer.cache
67
tests/Fixtures/cache/

src/Handler/EmbeddedShortcodeHandler.php

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace Webfactory\ShortcodeBundle\Handler;
44

5+
use InvalidArgumentException;
56
use Psr\Log\LoggerInterface;
67
use Psr\Log\NullLogger;
78
use Symfony\Component\HttpFoundation\RequestStack;
@@ -33,17 +34,15 @@ class EmbeddedShortcodeHandler
3334
/** @var RequestStack */
3435
private $requestStack;
3536

36-
/**
37-
* @param string $controllerName
38-
* @param string $renderer
39-
*/
4037
public function __construct(
4138
FragmentHandler $fragmentHandler,
42-
$controllerName,
43-
$renderer,
39+
string $controllerName,
40+
string $renderer,
4441
RequestStack $requestStack,
4542
?LoggerInterface $logger = null
4643
) {
44+
$this->validateControllerName($controllerName);
45+
4746
$this->fragmentHandler = $fragmentHandler;
4847
$this->controllerName = $controllerName;
4948
$this->renderer = $renderer;
@@ -90,4 +89,22 @@ public function getControllerName(): string
9089
{
9190
return $this->controllerName;
9291
}
92+
93+
private function validateControllerName(string $controllerName): void
94+
{
95+
if (class_exists($controllerName)) {
96+
// Check with method_exists instead of is_callable, because is_callable would need an object instance to
97+
// positively test an invokable classes
98+
if (method_exists($controllerName, '__invoke')) {
99+
return;
100+
}
101+
102+
throw new InvalidArgumentException('The configured controller "'.$controllerName.'" does not refer a method. Although a class "'.$controllerName.'" exists, but has no __invoke method.');
103+
}
104+
105+
$callableFragments = explode('::', $controllerName);
106+
if (!\is_array($callableFragments) || 2 !== \count($callableFragments) || !method_exists($callableFragments[0], $callableFragments[1])) {
107+
throw new InvalidArgumentException('The controller method: "'.$controllerName.'" does not exist.');
108+
}
109+
}
93110
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?php
2+
3+
namespace Webfactory\ShortcodeBundle\Tests\Fixtures\Controller;
4+
5+
use Symfony\Component\HttpFoundation\Response;
6+
7+
final class InvokableShortcodeTestController
8+
{
9+
public function __invoke(): Response
10+
{
11+
return new Response('invokable-controller-response');
12+
}
13+
}

tests/Fixtures/config/config.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,11 @@ webfactory_shortcode:
1414
test-config-esi:
1515
controller: 'Webfactory\ShortcodeBundle\Tests\Fixtures\Controller\ShortcodeTestController::test'
1616
method: esi
17+
test-config-invokable: 'Webfactory\ShortcodeBundle\Tests\Fixtures\Controller\InvokableShortcodeTestController'
1718
test-shortcode-guide:
1819
controller: 'Webfactory\ShortcodeBundle\Tests\Fixtures\Controller\ShortcodeTestController::test'
1920
description: "Description for the 'test-shortcode-guide' shortcode"
2021
example: "test-shortcode-guide test=true"
21-
test-config-invalid-controller: 'Foo\Bar::baz'
2222

2323
services:
2424
Webfactory\ShortcodeBundle\Tests\Fixtures\Controller\:

tests/Functional/EmbeddedShortcodeHandlerTest.php

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,14 @@
33
namespace Webfactory\ShortcodeBundle\Tests\Functional;
44

55
use Generator;
6+
use InvalidArgumentException;
67
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
78
use Symfony\Component\HttpFoundation\Request;
89
use Symfony\Component\HttpFoundation\RequestStack;
10+
use Symfony\Component\HttpKernel\Fragment\FragmentHandler;
11+
use Webfactory\ShortcodeBundle\Handler\EmbeddedShortcodeHandler;
912
use Webfactory\ShortcodeBundle\Test\EndToEndTestHelper;
13+
use Webfactory\ShortcodeBundle\Tests\Fixtures\Controller\ShortcodeTestController;
1014

1115
/**
1216
* Test shortcode processing using EmbeddedShortcodeHandler and a fixture ShortodeTestController,
@@ -64,6 +68,34 @@ public static function provideEsiShortcodes(): Generator
6468
yield 'ESI-based shortcode defined in service configuration' => ['test-service-esi'];
6569
}
6670

71+
/** @test */
72+
public function invokable_controller_can_be_used(): void
73+
{
74+
self::assertSame('invokable-controller-response', $this->processShortcodes('<p>[test-config-invokable]</p>'));
75+
}
76+
77+
/**
78+
* @test
79+
*
80+
* @dataProvider provideControllerNames
81+
*/
82+
public function throws_exception_on_invalid_controller_names(string $controllerName): void
83+
{
84+
$this->expectException(InvalidArgumentException::class);
85+
86+
new EmbeddedShortcodeHandler($this->createMock(FragmentHandler::class), $controllerName, 'inline', $this->createMock(RequestStack::class));
87+
}
88+
89+
public static function provideControllerNames(): Generator
90+
{
91+
yield 'Empty string' => [''];
92+
yield 'Not existing controller' => ['Foo\Bar::baz'];
93+
yield 'Missing method name' => [ShortcodeTestController::class];
94+
yield 'Not existing method' => [ShortcodeTestController::class.'_notExistingMethod'];
95+
yield 'Missing class' => ['ThisClassDoesNotExist'];
96+
yield 'Valid reference followed by a second scope resolution operator' => [ShortcodeTestController::class.'::test::'];
97+
}
98+
6799
private function processShortcodes(string $content, ?Request $request = null): string
68100
{
69101
self::bootKernel();

tests/Functional/ShortcodeDefinitionTestHelperTest.php

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
namespace Webfactory\ShortcodeBundle\Tests\Functional;
44

5-
use InvalidArgumentException;
65
use RuntimeException;
76
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
87
use Thunder\Shortcode\Handler\PlaceholderHandler;
@@ -31,15 +30,6 @@ public function throws_exception_for_handlers_that_do_not_use_controllers(): voi
3130
$this->helper->resolveShortcodeController('placeholder'); // uses the \Thunder\Shortcode\Handler\PlaceholderHandler handler class directly
3231
}
3332

34-
/**
35-
* @test
36-
*/
37-
public function throws_exception_for_shortcode_with_unresolvable_controller(): void
38-
{
39-
self::expectException(InvalidArgumentException::class);
40-
$this->helper->resolveShortcodeController('test-config-invalid-controller');
41-
}
42-
4333
/**
4434
* @test
4535
*/

0 commit comments

Comments
 (0)