Skip to content

Commit

Permalink
[TASK] Use render() as primary ViewHelper method
Browse files Browse the repository at this point in the history
  • Loading branch information
s2b committed Aug 24, 2024
1 parent d6bb22b commit a2af289
Show file tree
Hide file tree
Showing 9 changed files with 54 additions and 72 deletions.
3 changes: 3 additions & 0 deletions Documentation/Changelog/2.x.rst
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ Changelog 2.x
2.15
----

* Deprecation: Trait :php:`TYPO3Fluid\Fluid\Core\ViewHelper\Traits\CompileWithRenderStatic`
has been marked as deprecated. It will log a deprecation level error message when called in
Fluid v4. It will be removed in Fluid v5.
* Deprecation: Trait :php:`TYPO3Fluid\Fluid\Core\ViewHelper\Traits\CompileWithContentArgumentAndRenderStatic`
has been marked as deprecated. It will log a deprecation level error message when called in
Fluid v4. It will be removed in Fluid v5.
Expand Down
5 changes: 5 additions & 0 deletions Documentation/Changelog/4.x.rst
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,13 @@ Changelog 4.x
:php:`TYPO3Fluid\Fluid\View\AbstractTemplateView::RENDERING_LAYOUT`
* Breaking: Careful addition of method and property type hints throughout the system.
This should be only mildly breaking and projects should be able to adapt easily.
* Deprecation: Trait :php:`TYPO3Fluid\Fluid\Core\ViewHelper\Traits\CompileWithRenderStatic`
now emits a E_USER_DEPRECATED level error.
* Deprecation: Trait :php:`TYPO3Fluid\Fluid\Core\ViewHelper\Traits\CompileWithContentArgumentAndRenderStatic`
now emits a E_USER_DEPRECATED level error.
* Deprecation: Static method :php:`renderStatic()` on ViewHelpers that don't use :php:`TYPO3Fluid\Fluid\Core\ViewHelper\Traits\CompileWithRenderStatic`
or :php:`TYPO3Fluid\Fluid\Core\ViewHelper\Traits\CompileWithContentArgumentAndRenderStatic` now emits a
E_USER_DEPRECATED level error.
* Deprecation: Method :php:`TYPO3Fluid\Fluid\Core\ViewHelper\AbstractViewHelper->overrideArgument()`
now emits a E_USER_DEPRECATED level error.
* Deprecation: Calling method :php:`TYPO3Fluid\Fluid\Core\ViewHelper\AbstractTagBasedViewHelper->registerUniversalTagAttributes()`
Expand Down
76 changes: 32 additions & 44 deletions src/Core/ViewHelper/AbstractViewHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -258,33 +258,7 @@ public function initializeArgumentsAndRender()
$this->validateArguments();
$this->initialize();

return $this->callRenderMethod();
}

/**
* Call the render() method and handle errors.
*
* @return string the rendered ViewHelper
* @throws Exception
*/
protected function callRenderMethod()
{
if (method_exists($this, 'render')) {
return $this->render();
}
if ((new \ReflectionMethod($this, 'renderStatic'))->getDeclaringClass()->getName() !== AbstractViewHelper::class) {
// Method is safe to call - will not recurse through ViewHelperInvoker via the default
// implementation of renderStatic() on this class.
return static::renderStatic($this->arguments, $this->buildRenderChildrenClosure(), $this->renderingContext);
}
throw new Exception(
sprintf(
'ViewHelper class "%s" does not declare a "render()" method and inherits the default "renderStatic". ' .
'Executing this ViewHelper would cause infinite recursion - please either implement "render()" or ' .
'"renderStatic()" on your ViewHelper class',
get_class($this),
),
);
return $this->render();
}

/**
Expand Down Expand Up @@ -491,6 +465,36 @@ public function validateAdditionalArguments(array $arguments)
}
}

/**
* Main render method of the ViewHelper. Every modern ViewHelper implementation
* must implement this method.
*
* @todo Remove fallback implementation for renderStatic() and declare as abstract with Fluid v5
*
* @return mixed
*/
public function render()
{
if (!method_exists(static::class, 'renderStatic')) {
throw new Exception(
sprintf(
'ViewHelper class "%s" does not declare a "render()" method. Also, no implementation of "renderStatic"' .
'could be found to use as fallback. Please either implement "render()" on your ViewHelper class',
static::class,
),
);
}

// This covers the edge case where a ViewHelper implements neither CompileWithRenderStatic nor
// CompileWithContentArgumentAndRenderStatic, but still uses renderStatic().
trigger_error('renderStatic() has been deprecated and will be removed in Fluid v5.', E_USER_DEPRECATED);
return static::renderStatic(
$this->arguments,
$this->buildRenderChildrenClosure(),
$this->renderingContext,
);
}

/**
* You only should override this method *when you absolutely know what you
* are doing*, and really want to influence the generated PHP code during
Expand Down Expand Up @@ -528,7 +532,7 @@ public function validateAdditionalArguments(array $arguments)
public function compile($argumentsName, $closureName, &$initializationPhpCode, ViewHelperNode $node, TemplateCompiler $compiler)
{
$execution = sprintf(
'%s::renderStatic(%s, %s, $renderingContext)',
'$renderingContext->getViewHelperInvoker()->invoke(%s::class, %s, $renderingContext, %s)',
static::class,
$argumentsName,
$closureName,
Expand All @@ -551,22 +555,6 @@ public function compile($argumentsName, $closureName, &$initializationPhpCode, V
return $execution;
}

/**
* Default implementation of static rendering; useful API method if your ViewHelper
* when compiled is able to render itself statically to increase performance. This
* default implementation will simply delegate to the ViewHelperInvoker.
*
* @param array<string, mixed> $arguments
* @param \Closure $renderChildrenClosure
* @param RenderingContextInterface $renderingContext
* @return mixed
*/
public static function renderStatic(array $arguments, \Closure $renderChildrenClosure, RenderingContextInterface $renderingContext)
{
$viewHelperClassName = get_called_class();
return $renderingContext->getViewHelperInvoker()->invoke($viewHelperClassName, $arguments, $renderingContext, $renderChildrenClosure);
}

/**
* Save the associated ViewHelper node in a static public class variable.
* called directly after the ViewHelper was built.
Expand Down
4 changes: 3 additions & 1 deletion src/Core/ViewHelper/Traits/CompileWithRenderStatic.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@
* any ViewHelper that conforms to the `renderStatic`
* method pattern.
*
* @todo add missing types with Fluid v5
* @deprecated Will be removed in v5. The non-static render() method
* should be used instead
*/
trait CompileWithRenderStatic
{
Expand All @@ -27,6 +28,7 @@ trait CompileWithRenderStatic
*/
public function render()
{
trigger_error('CompileWithRenderStatic has been deprecated and will be removed in Fluid v5.', E_USER_DEPRECATED);
return static::renderStatic(
$this->arguments,
$this->buildRenderChildrenClosure(),
Expand Down
21 changes: 0 additions & 21 deletions src/Core/ViewHelper/ViewHelperInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -77,27 +77,6 @@ public function handleAdditionalArguments(array $arguments);
*/
public function validateAdditionalArguments(array $arguments);

/**
* Here follows a more detailed description of the arguments of this function:
*
* $arguments contains a plain array of all arguments this ViewHelper has received,
* including the default argument values if an argument has not been specified
* in the ViewHelper invocation.
*
* $renderChildrenClosure is a closure you can execute instead of $this->renderChildren().
* It returns the rendered child nodes, so you can simply do $renderChildrenClosure() to execute
* it. It does not take any parameters.
*
* $renderingContext contains references to the VariableProvider and the
* ViewHelperVariableContainer.
*
* @param array<string, mixed> $arguments
* @param \Closure $renderChildrenClosure
* @param RenderingContextInterface $renderingContext
* @return mixed the resulting value from the ViewHelper
*/
public static function renderStatic(array $arguments, \Closure $renderChildrenClosure, RenderingContextInterface $renderingContext);

/**
* Called when being inside a cached template.
*
Expand Down
6 changes: 3 additions & 3 deletions tests/Functional/ViewHelpers/GroupedForViewHelperTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
final class GroupedForViewHelperTest extends AbstractFunctionalTestCase
{
#[Test]
public function renderStaticThrowsExceptionWhenEachIsNotTraversable(): void
public function renderThrowsExceptionWhenEachIsNotTraversable(): void
{
$this->expectException(Exception::class);
$this->expectExceptionCode(1253108907);
Expand All @@ -33,7 +33,7 @@ public function renderStaticThrowsExceptionWhenEachIsNotTraversable(): void
}

#[Test]
public function renderStaticThrowsExceptionWhenEachIsOneDimensionalArray(): void
public function renderThrowsExceptionWhenEachIsOneDimensionalArray(): void
{
$this->expectException(Exception::class);
$this->expectExceptionCode(1253120365);
Expand All @@ -47,7 +47,7 @@ public function renderStaticThrowsExceptionWhenEachIsOneDimensionalArray(): void
}

#[Test]
public function renderStaticReturnsEmptyStringWhenEachIsNull(): void
public function renderReturnsEmptyStringWhenEachIsNull(): void
{
$source = '<f:groupedFor each="{items}" as="group" groupBy="by"></f:groupedFor>';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

final class CompilableViewHelper extends AbstractViewHelper
{
// We leave this here as a test case for the deprecated feature
use CompileWithRenderStatic;

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

namespace TYPO3Fluid\Fluid\Tests\Functional\ViewHelpers\StaticCacheable;

use PHPUnit\Framework\Attributes\IgnoreDeprecations;
use PHPUnit\Framework\Attributes\Test;
use TYPO3Fluid\Fluid\Core\ViewHelper\ViewHelperInterface;
use TYPO3Fluid\Fluid\Core\ViewHelper\ViewHelperResolver;
Expand All @@ -22,6 +23,7 @@
final class SharedStaticCompilableViewHelperTest extends AbstractFunctionalTestCase
{
#[Test]
#[IgnoreDeprecations]
public function renderWithSharedCompilableViewHelper(): void
{
// TYPO3 implements a custom ViewHelperResolver to provide DI-able ViewHelper instances. This allows
Expand Down
8 changes: 5 additions & 3 deletions tests/Unit/Core/ViewHelper/AbstractViewHelperTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
namespace TYPO3Fluid\Fluid\Tests\Unit\Core\ViewHelper;

use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\Attributes\IgnoreDeprecations;
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\TestCase;
use TYPO3Fluid\Fluid\Core\Compiler\TemplateCompiler;
Expand Down Expand Up @@ -144,14 +145,15 @@ public function testCompileReturnsAndAssignsExpectedPhpCode(): void
$subject = $this->getMockBuilder(AbstractViewHelper::class)->onlyMethods([])->getMock();
$result = $subject->compile('foobar', 'baz', $init, $node, new TemplateCompiler());
self::assertEmpty($init);
self::assertEquals(get_class($subject) . '::renderStatic(foobar, baz, $renderingContext)', $result);
self::assertEquals('$renderingContext->getViewHelperInvoker()->invoke(' . get_class($subject) . '::class, foobar, $renderingContext, baz)', $result);
}

#[Test]
#[IgnoreDeprecations]
public function testCallRenderMethodCanRenderViewHelperWithoutRenderMethodAndCallsRenderStatic(): void
{
$subject = new RenderMethodFreeViewHelper();
$method = new \ReflectionMethod($subject, 'callRenderMethod');
$method = new \ReflectionMethod($subject, 'render');
$subject->setRenderingContext(new RenderingContext());
$result = $method->invoke($subject);
self::assertSame('I was rendered', $result);
Expand All @@ -162,7 +164,7 @@ public function testCallRenderMethodOnViewHelperWithoutRenderMethodWithDefaultRe
{
$this->expectException(Exception::class);
$subject = new RenderMethodFreeDefaultRenderStaticViewHelper();
$method = new \ReflectionMethod($subject, 'callRenderMethod');
$method = new \ReflectionMethod($subject, 'render');
$subject->setRenderingContext(new RenderingContext());
$method->invoke($subject);
}
Expand Down

0 comments on commit a2af289

Please sign in to comment.