Skip to content

Commit

Permalink
[FEATURE] Introduce FlatTemplateResolver
Browse files Browse the repository at this point in the history
  • Loading branch information
eliashaeussler committed Sep 18, 2024
1 parent 4310d40 commit 1c3ae1b
Show file tree
Hide file tree
Showing 16 changed files with 504 additions and 28 deletions.
3 changes: 2 additions & 1 deletion .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@
/docker-compose.yml export-ignore
/packaging_exclude.php export-ignore
/phpstan.neon export-ignore
/phpunit.xml export-ignore
/phpunit.functional.xml export-ignore
/phpunit.unit.xml export-ignore
/rector.php export-ignore
/renovate.json export-ignore
/typoscript-lint.yml export-ignore
22 changes: 20 additions & 2 deletions .github/workflows/tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ jobs:
typo3-version: ["12.4"]
php-version: ["8.1", "8.2", "8.3"]
dependencies: ["highest", "lowest"]
env:
typo3DatabaseName: typo3
typo3DatabaseHost: '127.0.0.1'
typo3DatabaseUsername: root
typo3DatabasePassword: root
steps:
- uses: actions/checkout@v4
with:
Expand All @@ -32,6 +37,10 @@ jobs:
tools: composer:v2
coverage: none

# Start MySQL service
- name: Start MySQL
run: sudo /etc/init.d/mysql start

# Install dependencies
- name: Install Composer dependencies
uses: ramsey/composer-install@v2
Expand All @@ -46,6 +55,11 @@ jobs:
coverage:
name: Test coverage
runs-on: ubuntu-latest
env:
typo3DatabaseName: typo3
typo3DatabaseHost: '127.0.0.1'
typo3DatabaseUsername: root
typo3DatabasePassword: root
steps:
- uses: actions/checkout@v4
with:
Expand All @@ -59,6 +73,10 @@ jobs:
tools: composer:v2
coverage: pcov

# Start MySQL service
- name: Start MySQL
run: sudo /etc/init.d/mysql start

# Install dependencies
- name: Install Composer dependencies
uses: ramsey/composer-install@v2
Expand All @@ -69,13 +87,13 @@ jobs:

# Upload artifact
- name: Fix coverage path
working-directory: .Build/log/coverage
working-directory: .Build/coverage
run: sed -i 's#/home/runner/work/handlebars/handlebars#${{ github.workspace }}#g' clover.xml
- name: Upload coverage artifact
uses: actions/upload-artifact@v4
with:
name: coverage
path: .Build/log/coverage/clover.xml
path: .Build/coverage/clover.xml
retention-days: 7

coverage-report:
Expand Down
146 changes: 146 additions & 0 deletions Classes/Renderer/Template/FlatTemplateResolver.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
<?php

declare(strict_types=1);

/*
* This file is part of the TYPO3 CMS extension "handlebars".
*
* Copyright (C) 2024 Elias Häußler <e.haeussler@familie-redlich.de>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/

namespace Fr\Typo3Handlebars\Renderer\Template;

use Fr\Typo3Handlebars\Exception;
use Symfony\Component\Finder;

/**
* FlatTemplateResolver
*
* @author Elias Häußler <e.haeussler@familie-redlich.de>
* @license GPL-2.0-or-later
* @see https://fractal.build/guide/core-concepts/naming.html
*/
class FlatTemplateResolver extends HandlebarsTemplateResolver
{
protected const VARIANT_SEPARATOR = '--';

/**
* @var array<string, Finder\SplFileInfo>
*/
protected array $flattenedTemplates = [];
protected int $depth = 30;

public function __construct(
TemplatePaths $templateRootPaths,
array $supportedFileExtensions = self::DEFAULT_FILE_EXTENSIONS,
) {
parent::__construct($templateRootPaths, $supportedFileExtensions);
$this->buildTemplateMap();
}

public function resolveTemplatePath(string $templatePath): string
{
// Use default path resolving if path is not prefixed by "@"
if (!str_starts_with($templatePath, '@')) {
return parent::resolveTemplatePath($templatePath);
}

// Strip "@" prefix from given template path
$templateName = ltrim($templatePath, '@');

// Return filename if template exists
if (isset($this->flattenedTemplates[$templateName])) {
return $this->flattenedTemplates[$templateName]->getPathname();
}

// Strip off template variant
if (str_contains($templateName, self::VARIANT_SEPARATOR)) {
[$templateName] = explode(self::VARIANT_SEPARATOR, $templateName, 2);

if (isset($this->flattenedTemplates[$templateName])) {
return $this->flattenedTemplates[$templateName]->getPathname();
}
}

throw new Exception\TemplateNotFoundException($templateName, 1628256108);
}

protected function buildTemplateMap(): void
{
// Reset flattened templates
$this->flattenedTemplates = [];

// Instantiate finder
$finder = new Finder\Finder();
$finder->files();
$finder->name([...$this->buildExtensionPatterns()]);
$finder->depth(sprintf('< %d', $this->depth));

// Explicitly sort files and directories by name in order to streamline ordering
// with logic used in Fractal to ensure that the first occurrence of a flattened
// file is always used instead of relying on random behavior,
// see https://fractal.build/guide/core-concepts/naming.html#uniqueness
$finder->sortByName();

// Build template map
foreach ($this->templateRootPaths as $templateRootPath) {
$path = $this->resolveFilename($templateRootPath);
$pathFinder = clone $finder;
$pathFinder->in($path);

foreach ($pathFinder as $file) {
if ($this->isFirstOccurrenceInTemplateRoot($file)) {
$this->registerTemplate($file);
}
}
}
}

protected function isFirstOccurrenceInTemplateRoot(Finder\SplFileInfo $file): bool
{
$filename = $this->resolveFlatFilename($file);

// Early return if template is not registered yet
if (!isset($this->flattenedTemplates[$filename])) {
return true;
}

// In order to streamline template file flattening with logic used in Fractal,
// we always use the first flat file occurrence as resolved template, but provide
// the option to override exactly this file within other template root paths.
return $this->flattenedTemplates[$filename]->getRelativePathname() === $file->getRelativePathname();
}

protected function registerTemplate(Finder\SplFileInfo $file): void
{
$this->flattenedTemplates[$this->resolveFlatFilename($file)] = $file;
}

protected function resolveFlatFilename(Finder\SplFileInfo $file): string
{
return pathinfo($file->getPathname(), PATHINFO_FILENAME);
}

/**
* @return \Generator<string>
*/
protected function buildExtensionPatterns(): \Generator
{
foreach ($this->supportedFileExtensions as $extension) {
yield sprintf('*.%s', $extension);
}
}
}
22 changes: 19 additions & 3 deletions Documentation/Contributing/Index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -104,13 +104,29 @@ Run tests

.. code-block:: bash
# Run tests
# All tests
composer test
# Run tests with code coverage
# Specific tests
composer test:functional
composer test:unit
# All tests with code coverage
composer test:coverage
The code coverage reports will be stored in :file:`.Build/log/coverage`.
# Specific tests with code coverage
composer test:coverage:functional
composer test:coverage:unit
# Merge code coverage of all test suites
composer test:coverage:merge
Code coverage reports are written to :file:`.Build/coverage`. You can
open the last merged HTML report like follows:

.. code-block:: bash
open .Build/coverage/html/_merged/index.html
.. _build-documentation:

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
this is the main block:

{{#block "main"}}
main block
{{/block}}

this is the second block:

{{#block "second"}}
second block
{{/block}}

this is the third block:

{{#block "third"}}
third block
{{/block}}

this is the fourth block:

{{#block "fourth"}}
fourth block
{{/block}}

this is the end. bye bye
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
this is the main block:

{{#block "main"}}
main block
{{/block}}

this is the second block:

{{#block "second"}}
second block
{{/block}}

this is the third block:

{{#block "third"}}
third block
{{/block}}

this is the fourth block:

{{#block "fourth"}}
fourth block
{{/block}}

this is the end. bye bye
20 changes: 20 additions & 0 deletions Tests/Functional/Fixtures/test_extension/composer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"name": "cpsit/typo3-handlebars-test-extension",
"description": "Test extension for EXT:handlebars",
"license": "GPL-2.0-or-later",
"type": "typo3-cms-extension",
"version": "1.0.0",
"require": {
"typo3/cms-core": "*"
},
"autoload": {
"psr-4": {
"Fr\\Typo3Handlebars\\TestExtension\\": "Classes/"
}
},
"extra": {
"typo3/cms": {
"extension-key": "test_extension"
}
}
}
Loading

0 comments on commit 1c3ae1b

Please sign in to comment.