Skip to content

Commit

Permalink
[FEATURE] Provide compatibility method for Extbase repositories
Browse files Browse the repository at this point in the history
  • Loading branch information
eliashaeussler committed Nov 12, 2021
1 parent 1073542 commit f710ff0
Show file tree
Hide file tree
Showing 6 changed files with 151 additions and 2 deletions.
38 changes: 38 additions & 0 deletions Classes/DataProcessing/AbstractDataProcessor.php
Expand Up @@ -29,6 +29,8 @@
use Fr\Typo3Handlebars\Traits\ErrorHandlingTrait;
use Psr\Log\LoggerAwareInterface;
use Psr\Log\LoggerAwareTrait;
use TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface;
use TYPO3\CMS\Extbase\Core\Bootstrap;
use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;

/**
Expand Down Expand Up @@ -67,6 +69,19 @@ abstract class AbstractDataProcessor implements DataProcessorInterface, LoggerAw
*/
protected $provider;

/**
* @var ConfigurationManagerInterface
*/
protected $configurationManager;

/**
* @todo Move to constructor with next BC break
*/
public function injectConfigurationManager(ConfigurationManagerInterface $configurationManager): void
{
$this->configurationManager = $configurationManager;
}

public function process(string $content, array $configuration): string
{
$this->content = $content;
Expand Down Expand Up @@ -108,6 +123,29 @@ public function setContentObjectRenderer(ContentObjectRenderer $cObj): self
return $this;
}

/**
* Make configuration manager stateless by resetting individual settings.
*
* Resets the extension name and plugin name applied to the configuration manager.
* This is required in order to fully respect "pages" and "recursive" configuration
* from the content object data in Extbase repositories. By default, this is handled
* by the controller context in an action controller. Since we're outside of Extbase
* context, we need to apply/reset those states by or own.
*
* @see Bootstrap::initializeConfiguration()
*/
protected function initializeConfigurationManager(): void
{
if (null !== $this->configurationManager && null !== $this->cObj) {
$fullConfiguration = $this->configurationManager->getConfiguration(
ConfigurationManagerInterface::CONFIGURATION_TYPE_FULL_TYPOSCRIPT
);
// setConfiguration() resets extensionName and pluginName
$this->configurationManager->setConfiguration($fullConfiguration);
$this->configurationManager->setContentObject($this->cObj);
}
}

/**
* Process and render data.
*
Expand Down
66 changes: 66 additions & 0 deletions Documentation/Compatibility/ExtbaseRepositories.rst
@@ -0,0 +1,66 @@
.. include:: ../Includes.txt

.. _extbase-repositories:

====================
Extbase repositories
====================

.. versionadded:: 0.7.3

`Feature: #23 - Provide compatibility method for Extbase repositories <https://github.com/CPS-IT/handlebars/pull/23>`__

.. warning::

**Use of the** `AbstractDataProcessor` **required**

This compatibility method is only applicable to `DataProcessors`
that extend the :php:`AbstractDataProcessor`, since it provides the
necessary method. It is not part of the :php:`DataProcessorInterface`.

When Extbase repositories are used to fetch data via the `DataProvider`,
it may be necessary to perform the necessary bootstrapping for Extbase
repositories. This is the case whenever the rendering process is executed
outside the Extbase context and fields such as `tt_content.pages` or
`tt_content.recursive` are to be accessed in the repository to determine
the storage PIDs.

To execute the necessary bootstrapping or to reset the underlying
:php:`ConfigurationManager` and to fill it with the current
:php:`ContentObjectRenderer`, the method
:php:`initializeConfigurationManager()` must be executed in the
`DataProcessor`.

.. _extbase-repositories-usage:

Usage
=====

.. code-block:: diff
# Classes/DataProcessing/HeaderProcessor.php
namespace Vendor\Extension\DataProcessing;
use Fr\Typo3Handlebars\DataProcessing\AbstractDataProcessor;
class HeaderProcessor extends AbstractDataProcessor
{
protected function render(): string
{
+ $this->initializeConfigurationManager();
$data = $this->provider->get($this->cObj->data);
return $this->presenter->present($data);
}
}
.. _extbase-repositories-sources:

Sources
=======

.. seealso::

View the sources on GitHub:

- `AbstractDataProcessor <https://github.com/CPS-IT/handlebars/blob/master/Classes/DataProcessing/AbstractDataProcessor.php>`__
1 change: 1 addition & 0 deletions Documentation/Compatibility/Index.rst
Expand Up @@ -13,3 +13,4 @@ for compatibility with other TYPO3 components.
:maxdepth: 1

ExtbaseControllers
ExtbaseRepositories
29 changes: 29 additions & 0 deletions Tests/Unit/DataProcessing/AbstractDataProcessorTest.php
Expand Up @@ -27,11 +27,13 @@
use Fr\Typo3Handlebars\Renderer\HandlebarsRenderer;
use Fr\Typo3Handlebars\Tests\Unit\Fixtures\Classes\Data\DummyProvider;
use Fr\Typo3Handlebars\Tests\Unit\Fixtures\Classes\DataProcessing\DummyProcessor;
use Fr\Typo3Handlebars\Tests\Unit\Fixtures\Classes\DummyConfigurationManager;
use Fr\Typo3Handlebars\Tests\Unit\Fixtures\Classes\Presenter\DummyPresenter;
use Fr\Typo3Handlebars\Tests\Unit\HandlebarsCacheTrait;
use Fr\Typo3Handlebars\Tests\Unit\HandlebarsTemplateResolverTrait;
use Psr\Log\Test\TestLogger;
use Symfony\Component\EventDispatcher\EventDispatcher;
use TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface;
use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;
use TYPO3\TestingFramework\Core\Unit\UnitTestCase;

Expand All @@ -51,6 +53,11 @@ class AbstractDataProcessorTest extends UnitTestCase
*/
protected $logger;

/**
* @var DummyConfigurationManager
*/
protected $configurationManager;

/**
* @var DummyPresenter
*/
Expand All @@ -71,12 +78,14 @@ protected function setUp(): void
parent::setUp();

$this->logger = new TestLogger();
$this->configurationManager = new DummyConfigurationManager();
$this->presenter = new DummyPresenter(new HandlebarsRenderer($this->getCache(), new EventDispatcher(), $this->getTemplateResolver()));
$this->provider = new DummyProvider();
$this->subject = new DummyProcessor();
$this->subject->setPresenter($this->presenter);
$this->subject->setProvider($this->provider);
$this->subject->setLogger($this->logger);
$this->subject->injectConfigurationManager($this->configurationManager);
}

/**
Expand Down Expand Up @@ -111,6 +120,26 @@ public function processReturnsRenderedContent(): void
self::assertSame($expected, $this->subject->process('', ['another' => 'foo']));
}

/**
* @test
*/
public function processInitializesConfigurationManager(): void
{
$contentObjectRenderer = new ContentObjectRenderer();

$this->configurationManager->setConfiguration(['foo' => 'baz']);

self::assertSame(['foo' => 'baz'], $this->configurationManager->getConfiguration(ConfigurationManagerInterface::CONFIGURATION_TYPE_FULL_TYPOSCRIPT));
self::assertNull($this->configurationManager->getContentObject());

$this->subject->shouldInitializeConfigurationManager = true;
$this->subject->setContentObjectRenderer($contentObjectRenderer);
$this->subject->process('', []);

self::assertSame(['foo' => 'baz'], $this->configurationManager->getConfiguration(ConfigurationManagerInterface::CONFIGURATION_TYPE_FULL_TYPOSCRIPT));
self::assertSame($contentObjectRenderer, $this->configurationManager->getContentObject());
}

/**
* @test
*/
Expand Down
10 changes: 10 additions & 0 deletions Tests/Unit/Fixtures/Classes/DataProcessing/DummyProcessor.php
Expand Up @@ -40,15 +40,25 @@ final class DummyProcessor extends AbstractDataProcessor
*/
public $shouldThrowException = false;

/**
* @var bool
*/
public $shouldInitializeConfigurationManager = false;

protected function render(): string
{
if ($this->shouldThrowException) {
throw new UnableToPresentException();
}
if ($this->shouldInitializeConfigurationManager) {
$this->initializeConfigurationManager();
}

$content = $this->content . $this->presenter->present($this->provider->get([]));
if ($this->configuration !== []) {
$content .= ' ' . json_encode($this->configuration);
}

return $content;
}

Expand Down
9 changes: 7 additions & 2 deletions Tests/Unit/Fixtures/Classes/DummyConfigurationManager.php
Expand Up @@ -40,14 +40,19 @@ final class DummyConfigurationManager implements ConfigurationManagerInterface
*/
public $configuration = [];

/**
* @var ContentObjectRenderer
*/
private $cObj;

public function setContentObject(ContentObjectRenderer $contentObject): void
{
// Intentionally left blank.
$this->cObj = $contentObject;
}

public function getContentObject(): ?ContentObjectRenderer
{
return null;
return $this->cObj;
}

/**
Expand Down

0 comments on commit f710ff0

Please sign in to comment.