Skip to content

Commit f4d061b

Browse files
committed
[FEATURE] Support Vendor.Package as namespace name
Using this format (or simply Package if no vendor name is used for the package) makes ViewHelperResolver look in that namespace + ViewHelpers, for example: ```xml <TYPO3Fluid.Fluid:render partial="Partial" /> ``` Will internally resolve to: ```php \TYPO3Fluid\Fluid\ViewHelpers\RenderViewHelper ``` Without the need to register the namespace. Note that the example uses the native Fluid namespace to demonstrate, although this namespace is always present (unless explicitly removed by a TemplatePaths override).
1 parent f583bad commit f4d061b

File tree

3 files changed

+62
-18
lines changed

3 files changed

+62
-18
lines changed

src/Core/ViewHelper/ViewHelperResolver.php

Lines changed: 40 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,10 @@ public function setNamespaces(array $namespaces)
173173
*/
174174
public function isNamespaceValid($namespaceIdentifier)
175175
{
176+
if (strpos($namespaceIdentifier, '.')) {
177+
return true;
178+
}
179+
176180
if (!array_key_exists($namespaceIdentifier, $this->namespaces)) {
177181
return false;
178182
}
@@ -245,18 +249,8 @@ public function isNamespaceIgnored($namespaceIdentifier)
245249
public function resolveViewHelperClassName($namespaceIdentifier, $methodIdentifier)
246250
{
247251
if (!isset($this->resolvedViewHelperClassNames[$namespaceIdentifier][$methodIdentifier])) {
248-
$resolvedViewHelperClassName = $this->resolveViewHelperName($namespaceIdentifier, $methodIdentifier);
249-
$actualViewHelperClassName = implode('\\', array_map('ucfirst', explode('.', $resolvedViewHelperClassName)));
250-
if (false === class_exists($actualViewHelperClassName) || $actualViewHelperClassName === false) {
251-
throw new ParserException(sprintf(
252-
'The ViewHelper "<%s:%s>" could not be resolved.' . chr(10) .
253-
'Based on your spelling, the system would load the class "%s", however this class does not exist.',
254-
$namespaceIdentifier,
255-
$methodIdentifier,
256-
$resolvedViewHelperClassName
257-
), 1407060572);
258-
}
259-
$this->resolvedViewHelperClassNames[$namespaceIdentifier][$methodIdentifier] = $actualViewHelperClassName;
252+
$this->resolvedViewHelperClassNames[$namespaceIdentifier][$methodIdentifier] =
253+
$this->resolveViewHelperName($namespaceIdentifier, $methodIdentifier);
260254
}
261255
return $this->resolvedViewHelperClassNames[$namespaceIdentifier][$methodIdentifier];
262256
}
@@ -320,13 +314,42 @@ protected function resolveViewHelperName($namespaceIdentifier, $methodIdentifier
320314
} else {
321315
$className = ucfirst($explodedViewHelperName[0]);
322316
}
323-
$className .= 'ViewHelper';
317+
$classNames = [
318+
$className . 'ViewHelper',
319+
$className
320+
];
324321

325-
$namespaces = (array) $this->namespaces[$namespaceIdentifier];
322+
if ($this->namespaces[$namespaceIdentifier] ?? false) {
323+
$namespaces = (array) $this->namespaces[$namespaceIdentifier];
324+
} else {
325+
$namespacePrefix = $this->namespaces[$namespaceIdentifier] = str_replace('.', '\\', $namespaceIdentifier);
326+
$namespaces = [
327+
$namespacePrefix . '\\ViewHelpers',
328+
$namespacePrefix
329+
];
330+
}
331+
332+
$checked = [];
333+
foreach (array_reverse($namespaces) as $namespace) {
334+
$namespace = rtrim($namespace, '\\');
335+
foreach ($classNames as $className) {
336+
$name = $namespace . '\\' . $className;
337+
if (class_exists($name) && is_a($name, ViewHelperInterface::class, true)) {
338+
return $name;
339+
}
340+
$checked[] = $name;
341+
}
342+
}
326343

327-
do {
328-
$name = rtrim(array_pop($namespaces), '\\') . '\\' . $className;
329-
} while (!class_exists($name) && count($namespaces));
344+
throw new ParserException(
345+
sprintf(
346+
'The ViewHelper "<%s:%s>" could not be resolved. Fluid checked for "%s" but none of those classes exist.',
347+
$namespaceIdentifier,
348+
$methodIdentifier,
349+
implode(', ', $checked)
350+
),
351+
1407060572
352+
);
330353

331354
return $name;
332355
}

tests/Functional/ExamplesTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -251,7 +251,7 @@ public function getExampleScriptTestValues()
251251
'Section rendering error: Section "DoesNotExist" does not exist. Section rendering is mandatory; "optional" is false.',
252252
'ViewHelper error: Undeclared arguments passed to ViewHelper TYPO3Fluid\Fluid\ViewHelpers\IfViewHelper: notregistered. Valid arguments are: then, else, condition - Offending code: <f:if notregistered="1" />',
253253
'Parser error: The ViewHelper "<f:invalid>" could not be resolved.',
254-
'Based on your spelling, the system would load the class "TYPO3Fluid\Fluid\ViewHelpers\InvalidViewHelper", however this class does not exist. Offending code: <f:invalid />',
254+
'Fluid checked for "TYPO3Fluid\Fluid\ViewHelpers\InvalidViewHelper, TYPO3Fluid\Fluid\ViewHelpers\Invalid" but none of those classes exist. Offending code: <f:invalid />',
255255
'Invalid expression: Invalid target conversion type "invalidtype" specified in casting expression "{foobar as invalidtype}".',
256256
]
257257
]

tests/Unit/Core/ViewHelper/ViewHelperResolverTest.php

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,16 @@ public function testResolveViewHelperClassNameSupportsMultipleNamespaces()
9292
$this->assertEquals('TYPO3Fluid\\Fluid\\ViewHelpers\\RenderViewHelper', $result);
9393
}
9494

95+
/**
96+
* @test
97+
*/
98+
public function testResolveViewHelperClassNameSupportsDirectDottedNamespace()
99+
{
100+
$resolver = $this->getAccessibleMock(ViewHelperResolver::class, ['dummy']);
101+
$result = $resolver->_call('resolveViewHelperName', 'TYPO3Fluid.Fluid', 'render');
102+
$this->assertEquals('TYPO3Fluid\\Fluid\\ViewHelpers\\RenderViewHelper', $result);
103+
}
104+
95105
/**
96106
* @test
97107
*/
@@ -103,6 +113,16 @@ public function testResolveViewHelperClassNameTrimsBackslashSuffixFromNamespace(
103113
$this->assertEquals('TYPO3Fluid\\Fluid\\ViewHelpers\\RenderViewHelper', $result);
104114
}
105115

116+
/**
117+
* @test
118+
*/
119+
public function testResolveViewHelperClassNameSupportsVendorAndPackageNameAsNamespace()
120+
{
121+
$resolver = $this->getAccessibleMock(ViewHelperResolver::class, ['dummy']);
122+
$result = $resolver->_call('resolveViewHelperName', 'TYPO3Fluid.Fluid', 'render');
123+
$this->assertEquals('TYPO3Fluid\\Fluid\\ViewHelpers\\RenderViewHelper', $result);
124+
}
125+
106126
/**
107127
* @test
108128
*/
@@ -255,6 +275,7 @@ public function getIsNamespaceValidTestValues()
255275
[['foo' => ['test']], 'foo', true],
256276
[['foo' => ['test']], 'foobar', false],
257277
[['foo*' => null], 'foo', false],
278+
[[], 'Vendor.Namespace', true],
258279
];
259280
}
260281

0 commit comments

Comments
 (0)