Skip to content

Commit 057d276

Browse files
committed
[FEATURE] make:viewhelper
Create a ViewHelper inheriting from AbstractViewHelper or AbstractTagBasedViewHelper References: #198 Releases: main, 13.4
1 parent f8012ad commit 057d276

8 files changed

Lines changed: 527 additions & 0 deletions

File tree

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/*
6+
* This file is part of the package friendsoftypo3/kickstarter.
7+
*
8+
* For the full copyright and license information, please read the
9+
* LICENSE file that was distributed with this source code.
10+
*/
11+
12+
namespace FriendsOfTYPO3\Kickstarter\Command;
13+
14+
use FriendsOfTYPO3\Kickstarter\Command\Input\Question\ChooseExtensionKeyQuestion;
15+
use FriendsOfTYPO3\Kickstarter\Command\Input\QuestionCollection;
16+
use FriendsOfTYPO3\Kickstarter\Context\CommandContext;
17+
use FriendsOfTYPO3\Kickstarter\Information\ViewHelperInformation;
18+
use FriendsOfTYPO3\Kickstarter\Service\Creator\ViewHelperCreatorService;
19+
use FriendsOfTYPO3\Kickstarter\Traits\AskForExtensionKeyTrait;
20+
use FriendsOfTYPO3\Kickstarter\Traits\CreatorInformationTrait;
21+
use FriendsOfTYPO3\Kickstarter\Traits\ExtensionInformationTrait;
22+
use FriendsOfTYPO3\Kickstarter\Traits\TryToCorrectClassNameTrait;
23+
use Symfony\Component\Console\Attribute\AsCommand;
24+
use Symfony\Component\Console\Command\Command;
25+
use Symfony\Component\Console\Input\InputArgument;
26+
use Symfony\Component\Console\Input\InputInterface;
27+
use Symfony\Component\Console\Output\OutputInterface;
28+
use Symfony\Component\Console\Style\SymfonyStyle;
29+
use TYPO3\CMS\Core\Attribute\AsNonSchedulableCommand;
30+
31+
#[AsCommand('make:validator', 'Create a new ViewHelper. See also https://docs.typo3.org/permalink/t3coreapi:fluid-custom-viewhelper')]
32+
#[AsNonSchedulableCommand]
33+
class ViewHelperCommand extends Command
34+
{
35+
use AskForExtensionKeyTrait;
36+
use CreatorInformationTrait;
37+
use ExtensionInformationTrait;
38+
use TryToCorrectClassNameTrait;
39+
40+
public function __construct(
41+
private readonly ViewHelperCreatorService $viewHelperCreatorService,
42+
private readonly QuestionCollection $questionCollection,
43+
) {
44+
parent::__construct();
45+
}
46+
47+
protected function configure(): void
48+
{
49+
$this->addArgument(
50+
'extension_key',
51+
InputArgument::OPTIONAL,
52+
'Provide the extension key you want to extend',
53+
);
54+
}
55+
56+
protected function execute(InputInterface $input, OutputInterface $output): int
57+
{
58+
$commandContext = new CommandContext($input, $output);
59+
$io = $commandContext->getIo();
60+
$io->title('Welcome to the TYPO3 Extension Builder');
61+
62+
$io->text([
63+
'We are here to assist you in creating a new Fluid ViewHelper. ',
64+
'https://docs.typo3.org/permalink/t3coreapi:fluid-custom-viewhelper on how implement its functionality.',
65+
]);
66+
67+
$viewHelperInformation = $this->askForViewHelperInformation($commandContext);
68+
$this->viewHelperCreatorService->create($viewHelperInformation);
69+
$this->printCreatorInformation($viewHelperInformation->getCreatorInformation(), $commandContext);
70+
71+
return Command::SUCCESS;
72+
}
73+
74+
private function askForViewHelperInformation(CommandContext $commandContext): ViewHelperInformation
75+
{
76+
$io = $commandContext->getIo();
77+
$extensionInformation = $this->getExtensionInformation(
78+
(string)$this->questionCollection->askQuestion(
79+
ChooseExtensionKeyQuestion::ARGUMENT_NAME,
80+
$commandContext,
81+
),
82+
$commandContext
83+
);
84+
85+
$name = $this->askForViewHelperName($io);
86+
87+
return new ViewHelperInformation(
88+
$extensionInformation,
89+
$name,
90+
);
91+
}
92+
93+
private function askForViewHelperName(SymfonyStyle $io): string
94+
{
95+
$defaultName = null;
96+
do {
97+
$name = (string)$io->ask(
98+
'Please provide the name of your ViewHelper',
99+
$defaultName,
100+
);
101+
102+
if (preg_match('/^\d/', $name)) {
103+
$io->error('ViewHelper name should not start with a number.');
104+
$defaultName = $this->tryToCorrectClassName($name, '');
105+
$validValidatorName = false;
106+
} elseif (preg_match('/[^a-zA-Z0-9]/', $name)) {
107+
$io->error('ViewHelper name contains invalid chars. Please provide just letters and numbers.');
108+
$defaultName = $this->tryToCorrectClassName($name, '');
109+
$validValidatorName = false;
110+
} elseif (preg_match('/^[a-z0-9]+$/', $name)) {
111+
$io->error('ViewHelper must be written in UpperCamelCase like BlogExampleValidator.');
112+
$defaultName = $this->tryToCorrectClassName($name, '');
113+
$validValidatorName = false;
114+
} else {
115+
$validValidatorName = true;
116+
}
117+
} while (!$validValidatorName);
118+
119+
return $name;
120+
}
121+
}
Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/*
6+
* This file is part of the package friendsoftypo3/kickstarter.
7+
*
8+
* For the full copyright and license information, please read the
9+
* LICENSE file that was distributed with this source code.
10+
*/
11+
12+
namespace FriendsOfTYPO3\Kickstarter\Creator\ViewHelper;
13+
14+
use FriendsOfTYPO3\Kickstarter\Creator\FileManager;
15+
use FriendsOfTYPO3\Kickstarter\Information\ViewHelperInformation;
16+
use FriendsOfTYPO3\Kickstarter\PhpParser\NodeFactory;
17+
use FriendsOfTYPO3\Kickstarter\PhpParser\Structure\ClassStructure;
18+
use FriendsOfTYPO3\Kickstarter\PhpParser\Structure\DeclareStructure;
19+
use FriendsOfTYPO3\Kickstarter\PhpParser\Structure\FileStructure;
20+
use FriendsOfTYPO3\Kickstarter\PhpParser\Structure\MethodStructure;
21+
use FriendsOfTYPO3\Kickstarter\PhpParser\Structure\NamespaceStructure;
22+
use FriendsOfTYPO3\Kickstarter\PhpParser\Structure\UseStructure;
23+
use FriendsOfTYPO3\Kickstarter\Traits\FileStructureBuilderTrait;
24+
use PhpParser\BuilderFactory;
25+
use PhpParser\Node\Stmt\Return_;
26+
use TYPO3\CMS\Core\Utility\GeneralUtility;
27+
use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractTagBasedViewHelper;
28+
use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractViewHelper;
29+
30+
class ViewHelperCreator implements ViewHelperCreatorInterface
31+
{
32+
use FileStructureBuilderTrait;
33+
34+
private BuilderFactory $builderFactory;
35+
36+
public function __construct(
37+
private readonly NodeFactory $nodeFactory,
38+
private readonly FileManager $fileManager,
39+
) {
40+
$this->builderFactory = new BuilderFactory();
41+
}
42+
43+
public function create(ViewHelperInformation $viewHelperInformation): void
44+
{
45+
GeneralUtility::mkdir_deep($viewHelperInformation->getPath());
46+
47+
$filePath = $viewHelperInformation->getPath() . $viewHelperInformation->getFilename();
48+
49+
if (is_file($filePath)) {
50+
$viewHelperInformation->getCreatorInformation()->fileExists(
51+
$filePath,
52+
sprintf(
53+
'ViewHelpers can only be created, not modified. The file %s already exists and cannot be overridden. ',
54+
$filePath
55+
)
56+
);
57+
return;
58+
}
59+
$fileStructure = $this->buildFileStructure($filePath);
60+
$this->addClassNodes($fileStructure, $viewHelperInformation);
61+
$this->fileManager->createFile($filePath, $fileStructure->getFileContents(), $viewHelperInformation->getCreatorInformation());
62+
}
63+
64+
private function addClassNodes(FileStructure $fileStructure, ViewHelperInformation $viewHelperInformation): void
65+
{
66+
$fileStructure->addDeclareStructure(
67+
new DeclareStructure($this->nodeFactory->createDeclareStrictTypes())
68+
);
69+
if ($viewHelperInformation->isTagBased()) {
70+
$this->createTagBasedViewHelper($fileStructure, $viewHelperInformation);
71+
} else {
72+
$this->createPlainViewHelper($fileStructure, $viewHelperInformation);
73+
}
74+
}
75+
76+
private function createPlainViewHelper(FileStructure $fileStructure, ViewHelperInformation $viewHelperInformation): void
77+
{
78+
$fileStructure->addUseStructure(
79+
new UseStructure($this->nodeFactory->createUseImport(AbstractViewHelper::class))
80+
);
81+
$fileStructure->addNamespaceStructure(
82+
new NamespaceStructure($this->nodeFactory->createNamespace(
83+
$viewHelperInformation->getNamespace(),
84+
$viewHelperInformation->getExtensionInformation(),
85+
))
86+
);
87+
$fileStructure->addClassStructure(
88+
new ClassStructure(
89+
$this->builderFactory
90+
->class($viewHelperInformation->getClassname())
91+
->makeFinal()
92+
->extend('AbstractViewHelper')
93+
->getNode(),
94+
)
95+
);
96+
$fileStructure->addMethodStructure(
97+
new MethodStructure(
98+
$this->builderFactory
99+
->method('initializeArguments')
100+
->makePublic()
101+
->setReturnType('void')
102+
->getNode()
103+
)
104+
);
105+
$fileStructure->addMethodStructure(
106+
new MethodStructure(
107+
$this->builderFactory
108+
->method('render')
109+
->makePublic()
110+
->setReturnType('string')
111+
->addStmt(new Return_($this->builderFactory->val('ViewHelper ' . $viewHelperInformation->getClassname() . ' content. ')))
112+
->getNode()
113+
)
114+
);
115+
}
116+
117+
private function createTagBasedViewHelper(FileStructure $fileStructure, ViewHelperInformation $viewHelperInformation): void
118+
{
119+
120+
$fileStructure->addUseStructure(
121+
new UseStructure($this->nodeFactory->createUseImport(AbstractTagBasedViewHelper::class))
122+
);
123+
$fileStructure->addNamespaceStructure(
124+
new NamespaceStructure($this->nodeFactory->createNamespace(
125+
$viewHelperInformation->getNamespace(),
126+
$viewHelperInformation->getExtensionInformation(),
127+
))
128+
);
129+
$fileStructure->addClassStructure(
130+
new ClassStructure(
131+
$this->builderFactory
132+
->class($viewHelperInformation->getClassname())
133+
->makeFinal()
134+
->extend('AbstractTagBasedViewHelper')
135+
->getNode(),
136+
)
137+
);
138+
$fileStructure->addMethodStructure(
139+
new MethodStructure(
140+
$this->builderFactory
141+
->method('initializeArguments')
142+
->makePublic()
143+
->setReturnType('void')
144+
->getNode()
145+
)
146+
);
147+
$fileStructure->addMethodStructure(
148+
new MethodStructure(
149+
$this->builderFactory
150+
->method('render')
151+
->makePublic()
152+
->setReturnType('string')
153+
->addStmt(new Return_($this->builderFactory->val('ViewHelper ' . $viewHelperInformation->getClassname() . ' content. ')))
154+
->getNode()
155+
)
156+
);
157+
}
158+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/*
6+
* This file is part of the package friendsoftypo3/kickstarter.
7+
*
8+
* For the full copyright and license information, please read the
9+
* LICENSE file that was distributed with this source code.
10+
*/
11+
12+
namespace FriendsOfTYPO3\Kickstarter\Creator\ViewHelper;
13+
14+
use FriendsOfTYPO3\Kickstarter\Information\ViewHelperInformation;
15+
use Symfony\Component\DependencyInjection\Attribute\AutoconfigureTag;
16+
17+
#[AutoconfigureTag('ext-kickstarter.creator.view-helper')]
18+
interface ViewHelperCreatorInterface
19+
{
20+
public function create(ViewHelperInformation $viewHelperInformation): void;
21+
}
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/*
6+
* This file is part of the package friendsoftypo3/kickstarter.
7+
*
8+
* For the full copyright and license information, please read the
9+
* LICENSE file that was distributed with this source code.
10+
*/
11+
12+
namespace FriendsOfTYPO3\Kickstarter\Information;
13+
14+
readonly class ViewHelperInformation
15+
{
16+
private const CLASS_PATH = 'Classes/ViewHelper/';
17+
18+
private const NAMESPACE_PREFIX = 'ViewHelper';
19+
20+
public function __construct(
21+
private ExtensionInformation $extensionInformation,
22+
private string $name,
23+
private bool $tagBased = false,
24+
private string $tagName = '',
25+
private array $arguments = [],
26+
private bool $escapeOutput = true,
27+
private CreatorInformation $creatorInformation = new CreatorInformation()
28+
) {}
29+
30+
public function getExtensionInformation(): ExtensionInformation
31+
{
32+
return $this->extensionInformation;
33+
}
34+
35+
public function getFilename(): string
36+
{
37+
return $this->name . 'ViewHelper.php';
38+
}
39+
40+
public function getClassname(): string
41+
{
42+
return $this->name . 'ViewHelper';
43+
}
44+
45+
public function getPath(): string
46+
{
47+
return $this->extensionInformation->getExtensionPath() . self::CLASS_PATH;
48+
}
49+
50+
public function getName(): string
51+
{
52+
return $this->name;
53+
}
54+
55+
public function isTagBased(): bool
56+
{
57+
return $this->tagBased;
58+
}
59+
60+
public function getTagName(): string
61+
{
62+
return $this->tagName;
63+
}
64+
65+
public function getArguments(): array
66+
{
67+
return $this->arguments;
68+
}
69+
70+
public function isEscapeOutput(): bool
71+
{
72+
return $this->escapeOutput;
73+
}
74+
75+
public function getNamespace(): string
76+
{
77+
return $this->extensionInformation->getNamespacePrefix() . self::NAMESPACE_PREFIX;
78+
}
79+
80+
public function getCreatorInformation(): CreatorInformation
81+
{
82+
return $this->creatorInformation;
83+
}
84+
}

0 commit comments

Comments
 (0)