Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions documentation/server/tools/typo3_fluid_namespace_list.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@
List the Fluid ViewHelper namespaces that are globally available in the TYPO3
installation you are working in, so a template knows which prefixes it may use
without declaring them. Every other namespace has to be declared per template
with an xmlns attribute. Where the console cannot be reached, the
Configuration/Fluid/Namespaces.php the installed packages declare is read
instead. Answers from: installation, packages.
with an xmlns attribute. On TYPO3 v14 and later, the fluid:namespaces console
command answers; where it cannot be reached, the
Configuration/Fluid/Namespaces.php files introduced in that version are read
instead. Earlier versions are booted and answered from SYS/fluid/namespaces in
TYPO3_CONF_VARS. Answers from: installation, packages.

``readOnlyHint: true`` · ``destructiveHint: false`` · ``idempotentHint: true`` · ``openWorldHint: false``

Expand Down
7 changes: 4 additions & 3 deletions src/Installation/FluidNamespaces.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,15 @@
* The globally registered Fluid namespaces, read from the packages that declare
* them.
*
* `fluid:namespaces` is the better answer and stays the first one asked, because
* it reports what the container actually assembled. But it needs a console that
* On TYPO3 v14 and later, `fluid:namespaces` is the better answer because it
* reports what the container actually assembled. But it needs a console that
* boots, and the declaration itself is a file: every package may ship a
* Configuration/Fluid/Namespaces.php returning prefix => list of PHP namespaces,
* and the core merges them across all active packages.
*
* The files are parsed, never included — the same rule as for the icon registry.
* They are ordinary PHP that would run in this process.
* They are ordinary PHP that would run in this process. Earlier TYPO3 versions
* register these namespaces in TYPO3_CONF_VARS and have no such file.
*/
final class FluidNamespaces
{
Expand Down
48 changes: 35 additions & 13 deletions src/Tool/FluidNamespaceList.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
namespace TYPO3\DevCompanion\Tool;

use TYPO3\DevCompanion\Installation\FluidNamespaces;
use TYPO3\DevCompanion\Installation\Instance;
use TYPO3\DevCompanion\Installation\Typo3Cli;
use TYPO3\DevCompanion\Installation\Typo3Runtime;
use TYPO3\DevCompanion\Result\Schema;
use TYPO3\DevCompanion\Result\ToolResult;
use TYPO3\DevCompanion\Result\Unsupported;
Expand All @@ -28,7 +30,7 @@ public static function answersFrom(): array

public static function description(): string
{
return 'List the Fluid ViewHelper namespaces that are globally available in the TYPO3 installation you are working in, so a template knows which prefixes it may use without declaring them. Every other namespace has to be declared per template with an xmlns attribute. Where the console cannot be reached, the Configuration/Fluid/Namespaces.php the installed packages declare is read instead.';
return 'List the Fluid ViewHelper namespaces that are globally available in the TYPO3 installation you are working in, so a template knows which prefixes it may use without declaring them. Every other namespace has to be declared per template with an xmlns attribute. On TYPO3 v14 and later, the fluid:namespaces console command answers; where it cannot be reached, the Configuration/Fluid/Namespaces.php files introduced in that version are read instead. Earlier versions are booted and answered from SYS/fluid/namespaces in TYPO3_CONF_VARS.';
}

public static function inputSchema(): array
Expand All @@ -50,18 +52,38 @@ public static function outputSchema(): array

public static function answer(array $args): ToolResult
{
$answer = Typo3Cli::json(['fluid:namespaces', '--json']);
$answeredBy = 'installation';
$declared = is_array($answer['data']) ? $answer['data'] : [];
if (!$answer['ok'] || !is_array($answer['data'])) {
// The declarations are files in the same packages, so a console
// that cannot boot does not have to end the question. What the
// files cannot say is what the container did with them.
$declared = FluidNamespaces::all();
if ($declared === []) {
return Unsupported::because($answer['error']);
$limitation = '';

if ((Instance::typo3Major() ?? 0) >= 14) {
$answer = Typo3Cli::json(['fluid:namespaces', '--json']);
$declared = is_array($answer['data']) ? $answer['data'] : [];
if (!$answer['ok'] || !is_array($answer['data'])) {
// The declarations are files in the same packages, so a
// console that cannot boot does not have to end the question.
// What the files cannot say is what the container did with
// them.
$declared = FluidNamespaces::all();
if ($declared === []) {
return Unsupported::because($answer['error']);
}
$answeredBy = 'packages';
$limitation = $answer['error'];
}
} else {
// Before v14 there is no fluid:namespaces command and no
// Configuration/Fluid/Namespaces.php. The assembled registry is
// the TYPO3_CONF_VARS value after every extension has run.
$read = Typo3Runtime::configuration('SYS/fluid/namespaces');
if ($read === null || isset($read['unavailable'])) {
return Unsupported::because(
is_array($read) ? (string) $read['unavailable'] : Typo3Runtime::reason(),
);
}
if ($read['found'] === true && !is_array($read['value'])) {
return Unsupported::because('SYS/fluid/namespaces is not an array in this installation');
}
$answeredBy = 'packages';
$declared = $read['found'] === true ? $read['value'] : [];
}

$namespaces = [];
Expand All @@ -81,12 +103,12 @@ public static function answer(array $args): ToolResult
$lines[] = 'These prefixes work in any template without being declared. Every other namespace is declared '
. 'in the template itself — xmlns:be="http://typo3.org/ns/TYPO3/CMS/Backend/ViewHelpers" on the root '
. 'element, together with data-namespace-typo3-fluid="true" so the declaration is stripped from the output.';
if ($answeredBy === 'packages') {
if ($limitation !== '') {
$lines[] = '';
$lines[] = sprintf(
'Read from the Configuration/Fluid/Namespaces.php of the installed packages: the console could not '
. 'be asked (%s). That is what the packages declare, not what the container assembled from them.',
$answer['error'],
$limitation,
);
}

Expand Down
123 changes: 123 additions & 0 deletions tests/Unit/FluidNamespaceListTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
<?php

declare(strict_types=1);

namespace TYPO3\DevCompanion\Tests\Unit;

use PHPUnit\Framework\Attributes\After;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\TestCase;
use TYPO3\DevCompanion\Installation\Instance;
use TYPO3\DevCompanion\Installation\Typo3Cli;
use TYPO3\DevCompanion\Installation\Typo3Runtime;
use TYPO3\DevCompanion\Process\CommandRunner;
use TYPO3\DevCompanion\Tests\Support\TemporaryInstallation;
use TYPO3\DevCompanion\Tool\Registry;

/** Which Fluid namespace source each TYPO3 version exposes. */
final class FluidNamespaceListTest extends TestCase
{
use TemporaryInstallation;

#[After]
public function forgetTheInstallation(): void
{
putenv(Typo3Cli::CONSOLE_VARIABLE);
Instance::discoverFrom(null);
Typo3Cli::useRunner(null);
Typo3Cli::forget();
Typo3Runtime::forget();
}

#[DataProvider('versionsBeforeFourteen')]
#[Test]
public function aVersionBeforeFourteenReadsTheRuntimeConfiguration(string $version): void
{
$root = $this->coreCheckout($version);
Instance::discoverFrom($root);
putenv(Typo3Cli::CONSOLE_VARIABLE . '=' . PHP_BINARY . ' ' . $root . '/bin/typo3');
Typo3Cli::forget();

$commands = [];
$runner = self::createStub(CommandRunner::class);
$runner->method('run')->willReturnCallback(static function (array $command) use (&$commands): array {
$commands[] = $command;

return [
'ok' => true,
'exitCode' => 0,
'output' => json_encode([
'state' => Typo3Runtime::STATE_FULL,
'reason' => '',
'topics' => ['configuration' => [
'found' => true,
'value' => ['acme' => ['Acme\\ViewHelpers']],
]],
], JSON_THROW_ON_ERROR),
'error' => '',
];
});
Typo3Cli::useRunner($runner);

$result = Registry::call('typo3_fluid_namespace_list', []);

self::assertSame('installation', $result->data['answeredBy']);
self::assertSame([[
'prefix' => 'acme',
'phpNamespaces' => ['Acme\\ViewHelpers'],
]], $result->data['namespaces']);
self::assertCount(1, $commands);
self::assertSame('-r', $commands[0][1]);
if (preg_match('/base64_decode\("([A-Za-z0-9+\/=]+)"\)/', $commands[0][2], $payload) !== 1) {
self::fail('The runtime probe was not passed as a base64-encoded PHP payload.');
}
self::assertStringContainsString(
"'configurationPath' => 'SYS/fluid/namespaces'",
(string) base64_decode($payload[1], true),
);
}

/** @return iterable<string, array{string}> */
public static function versionsBeforeFourteen(): iterable
{
yield 'TYPO3 v12' => ['12.4.42'];
yield 'TYPO3 v13' => ['13.4.22'];
}

#[Test]
public function fourteenUsesTheFluidNamespaceCommand(): void
{
$root = $this->coreCheckout('14.0.0');
Instance::discoverFrom($root);
putenv(Typo3Cli::CONSOLE_VARIABLE . '=' . PHP_BINARY . ' ' . $root . '/bin/typo3');
Typo3Cli::forget();

$commands = [];
$runner = self::createStub(CommandRunner::class);
$runner->method('run')->willReturnCallback(static function (array $command) use (&$commands): array {
$commands[] = $command;

return [
'ok' => true,
'exitCode' => 0,
'output' => '{"core":["TYPO3\\\\CMS\\\\Core\\\\ViewHelpers"]}',
'error' => '',
];
});
Typo3Cli::useRunner($runner);

$result = Registry::call('typo3_fluid_namespace_list', []);

self::assertSame('installation', $result->data['answeredBy']);
self::assertSame([
PHP_BINARY,
$root . '/bin/typo3',
'fluid:namespaces',
'--json',
'--no-interaction',
'--no-ansi',
], $commands[0]);
self::assertSame('core', $result->data['namespaces'][0]['prefix']);
}
}
2 changes: 1 addition & 1 deletion tests/Unit/PackageSourcesTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ public function fluidNamespacesAreReadFromThePackagesThatDeclareThem(): void
#[Test]
public function withoutAConsoleTheDeclarationsAreTheAnswerAndSaySoAsOne(): void
{
$root = $this->coreCheckout();
$root = $this->coreCheckout('14.3.0');
$this->namespaceFile($root . '/typo3/sysext/core', ['core' => ['TYPO3\\CMS\\Core\\ViewHelpers']]);
Instance::discoverFrom($root);
Typo3Cli::forget();
Expand Down