Skip to content
Merged
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: 2 additions & 6 deletions Classes/Command/Input/Decorator/CommandClassNameDecorator.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,22 +11,18 @@

namespace FriendsOfTYPO3\Kickstarter\Command\Input\Decorator;

use FriendsOfTYPO3\Kickstarter\Command\Input\Normalizer\CommandClassNameNormalizer;
use Symfony\Component\DependencyInjection\Attribute\AutoconfigureTag;

#[AutoconfigureTag('ext-kickstarter.inputHandler.command-class')]
readonly class CommandClassNameDecorator implements DecoratorInterface
{
public function __construct(
private CommandClassNameNormalizer $commandClassNameNormalizer
) {}

public function __invoke(?string $defaultValue = null): string
{
$className = $defaultValue ?? '';
if (str_contains($className, ':')) {
$className = substr($className, strpos($className, ':') + 1);
}
return $this->commandClassNameNormalizer->__invoke($className);

return ucfirst($className);
}
}
10 changes: 3 additions & 7 deletions Classes/Command/Input/Decorator/MiddlewareClassNameDecorator.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,22 +11,18 @@

namespace FriendsOfTYPO3\Kickstarter\Command\Input\Decorator;

use FriendsOfTYPO3\Kickstarter\Command\Input\Normalizer\MiddlewareClassNameNormalizer;
use Symfony\Component\DependencyInjection\Attribute\AutoconfigureTag;

#[AutoconfigureTag('ext-kickstarter.inputHandler.middleware-class')]
readonly class MiddlewareClassNameDecorator implements DecoratorInterface
{
public function __construct(
private MiddlewareClassNameNormalizer $middlewareClassNameNormalizer
) {}

public function __invoke(?string $defaultValue = null): string
{
$className = $defaultValue ?? '';
if (str_contains($className, '/')) {
$className = substr($className, strpos($className, '/') + 1);
return substr($className, strpos($className, '/') + 1);
}
return $this->middlewareClassNameNormalizer->__invoke($className);

return $className;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
use FriendsOfTYPO3\Kickstarter\Traits\TryToCorrectClassNameTrait;
use Symfony\Component\DependencyInjection\Attribute\AutoconfigureTag;

#[AutoconfigureTag('ext-kickstarter.inputHandler.event-listener-class-name')]
#[AutoconfigureTag('ext-kickstarter.inputHandler.event-listener-class')]
class EventListenerClassNameNormalizer implements NormalizerInterface
{
use TryToCorrectClassNameTrait;
Expand Down
116 changes: 105 additions & 11 deletions Classes/Command/Input/Validator/ClassNameValidator.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,21 +11,115 @@

namespace FriendsOfTYPO3\Kickstarter\Command\Input\Validator;

use Symfony\Component\DependencyInjection\Attribute\AutoconfigureTag;

#[AutoconfigureTag('ext-kickstarter.inputHandler.command-class')]
#[AutoconfigureTag('ext-kickstarter.inputHandler.controller-class')]
#[AutoconfigureTag('ext-kickstarter.inputHandler.event-class')]
#[AutoconfigureTag('ext-kickstarter.inputHandler.event-listener-class')]
#[AutoconfigureTag('ext-kickstarter.inputHandler.middleware-class')]
#[AutoconfigureTag('ext-kickstarter.inputHandler.model-class')]
class ClassNameValidator implements ValidatorInterface
{
/**
* List of reserved keywords in PHP that cannot be used as class names (case-insensitive).
* This list is a common compilation of hard-reserved words.
* Some of these are context-sensitive keywords in newer PHP versions but are often
* treated as reserved in the context of class/interface/trait names for compatibility
* or future-proofing.
* Note: 'void', 'iterable', 'object', etc. are not reserved as class names
* in older PHP versions but might be in stricter contexts.
*
* @var array<string>
*/
private const RESERVED_KEYWORDS = [
'__halt_compiler',
'abstract',
'and',
'array',
'as',
'break',
'callable',
'case',
'catch',
'class',
'clone',
'const',
'continue',
'declare',
'default',
'die',
'do',
'echo',
'else',
'elseif',
'empty',
'enddeclare',
'endfor',
'endforeach',
'endif',
'endswitch',
'endwhile',
'enum', // Added for PHP 8.1+
'exit',
'extends',
'final',
'finally',
'fn', // Added for PHP 7.4+
'for',
'foreach',
'function',
'global',
'goto',
'if',
'implements',
'include',
'include_once',
'instanceof',
'insteadof',
'interface',
'isset',
'list',
'match', // Added for PHP 8.0+
'namespace',
'new',
'or',
'parent',
'print',
'private',
'protected',
'public',
'readonly', // Added for PHP 8.1+
'require',
'require_once',
'return',
'self',
'static',
'switch',
'throw',
'trait',
'try',
'unset',
'use',
'var',
'while',
'xor',
'yield',
];

public function __invoke(mixed $answer): string
{
if ($answer === null || $answer === '') {
throw new \RuntimeException('Class name must not be empty.', 7856569272);
}
if (preg_match('/^\d/', $answer)) {
throw new \RuntimeException('Class name must not start with a number.', 8716512611);
}
if (preg_match('/[^a-zA-Z0-9]/', $answer)) {
throw new \RuntimeException('Class name contains invalid chars. Please provide just letters and numbers.', 9569056953);
}
if (preg_match('/^[A-Z][a-zA-Z0-9]+$/', $answer) === 0) {
throw new \RuntimeException('Class name must be written in UpperCamelCase like "ProcessRequestEvent".', 8916750461);
$answer = (string)$answer;

// PHP Class Name Regex from official documentation: https://www.php.net/manual/en/language.oop5.basic.php
$isValidFormat = (bool)preg_match('/^[a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*$/', $answer);
$isReserved = in_array(strtolower($answer), self::RESERVED_KEYWORDS, true);

if (!$isValidFormat || $isReserved || $answer === '') {
throw new \RuntimeException(
'The provided class name is not a valid PHP class name or is a reserved keyword.',
1739132087,
);
}

return $answer;
Expand Down
33 changes: 0 additions & 33 deletions Classes/Command/Input/Validator/CommandClassValidator.php

This file was deleted.

31 changes: 0 additions & 31 deletions Classes/Command/Input/Validator/ControllerClassValidator.php

This file was deleted.

33 changes: 0 additions & 33 deletions Classes/Command/Input/Validator/EventClassValidator.php

This file was deleted.

33 changes: 0 additions & 33 deletions Classes/Command/Input/Validator/EventListenerClassValidator.php

This file was deleted.

27 changes: 0 additions & 27 deletions Classes/Command/Input/Validator/ModelClassValidator.php

This file was deleted.

6 changes: 5 additions & 1 deletion Configuration/Services.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,10 @@ services:
arguments:
$questions: !tagged_iterator { tag: 'ext-kickstarter.command.question.validator' }

###########################
## ASSIGN INPUT HANDLERS ##
###########################

FriendsOfTYPO3\Kickstarter\Command\Input\Question\Command\CommandAliasQuestion:
arguments:
$inputHandlers: !tagged_iterator { tag: 'ext-kickstarter.inputHandler.command-name' }
Expand Down Expand Up @@ -325,7 +329,7 @@ services:

FriendsOfTYPO3\Kickstarter\Command\Input\Question\EventListener\EventListenerClassNameQuestion:
arguments:
$inputHandlers: !tagged_iterator { tag: 'ext-kickstarter.inputHandler.event-listener-class-name' }
$inputHandlers: !tagged_iterator { tag: 'ext-kickstarter.inputHandler.event-listener-class' }

FriendsOfTYPO3\Kickstarter\Command\Input\Question\Locallang\LocallangFileNameQuestion:
arguments:
Expand Down