diff --git a/phpstan.neon b/phpstan.neon index fbfff5e..1cd333b 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -1,23 +1,5 @@ parameters: - level: 6 + level: 8 paths: - src - tests - excludePaths: - - src/DependencyInjection/Configuration.php - ignoreErrors: - - '#type has no value type specified in iterable type#' - - '#has parameter .* with no value type specified in iterable type#' - - '#has no value type specified in iterable type array#' - - '#configureOptions\(\) has no return type specified.#' - - '#configure\(\) has no return type specified#' - - '#process\(\) has no return type specified#' - - '#should return Iterator but returns Traversable#' - - '#Negated boolean expression is always false#' - - - identifier: missingType.generics - - - identifier: doctrine.columnType - reportUnmatchedIgnoredErrors: false - inferPrivatePropertyTypeFromConstructor: true - treatPhpDocTypesAsCertain: false diff --git a/src/Admin/Filter/LogProcessFilter.php b/src/Admin/Filter/LogProcessFilter.php index c11c74e..15aac0e 100644 --- a/src/Admin/Filter/LogProcessFilter.php +++ b/src/Admin/Filter/LogProcessFilter.php @@ -26,6 +26,9 @@ class LogProcessFilter implements FilterInterface { use FilterTrait; + /** + * @param string[] $choices + */ public static function new( mixed $label, array $choices, diff --git a/src/Entity/ProcessExecution.php b/src/Entity/ProcessExecution.php index 2c51aa8..a924eda 100644 --- a/src/Entity/ProcessExecution.php +++ b/src/Entity/ProcessExecution.php @@ -40,9 +40,15 @@ class ProcessExecution implements \Stringable #[ORM\Column(type: Types::STRING, enumType: ProcessExecutionStatus::class)] public ProcessExecutionStatus $status = ProcessExecutionStatus::Started; + /** + * @var array + */ #[ORM\Column(type: Types::JSON)] private array $report = []; + /** + * @var array + */ #[ORM\Column(type: Types::JSON, nullable: true)] private ?array $context = []; @@ -51,9 +57,13 @@ public function getId(): ?int return $this->id; } + /** + * @param array $context + */ public function __construct( string $code, - #[ORM\Column(type: Types::STRING, length: 255)] public readonly string $logFilename, ?array $context = [], + #[ORM\Column(type: Types::STRING, length: 255)] public readonly string $logFilename, + ?array $context = [], ) { $this->code = (string) (new UnicodeString($code))->truncate(255); $this->startDate = \DateTimeImmutable::createFromMutable(new \DateTime()); @@ -104,11 +114,17 @@ public function getCode(): string return $this->code; } + /** + * @return array + */ public function getContext(): ?array { return $this->context; } + /** + * @param array $context + */ public function setContext(array $context): void { $this->context = $context; diff --git a/src/Entity/ProcessSchedule.php b/src/Entity/ProcessSchedule.php index 5eed3d6..8eea928 100644 --- a/src/Entity/ProcessSchedule.php +++ b/src/Entity/ProcessSchedule.php @@ -32,7 +32,7 @@ class ProcessSchedule #[ORM\Column(length: 255)] #[IsValidProcessCode] - private ?string $process = null; + private string $process; #[ORM\Column(length: 6)] private ProcessScheduleType $type; @@ -43,11 +43,14 @@ class ProcessSchedule #[Assert\When( expression: 'this.getType().value == "every"', constraints: [new EveryExpression()] )] - private ?string $expression = null; + private string $expression; #[ORM\Column(type: Types::TEXT, nullable: true)] private ?string $input = null; + /** + * @var string|array + */ #[ORM\Column(type: Types::JSON)] private string|array $context = []; @@ -68,11 +71,17 @@ public function setProcess(string $process): static return $this; } + /** + * @return array + */ public function getContext(): array { return \is_array($this->context) ? $this->context : json_decode($this->context); } + /** + * @param array $context + */ public function setContext(array $context): void { $this->context = $context; @@ -100,7 +109,7 @@ public function getExpression(): ?string return $this->expression; } - public function setExpression(?string $expression): self + public function setExpression(string $expression): self { $this->expression = $expression; diff --git a/src/Entity/User.php b/src/Entity/User.php index b59be10..11532f7 100644 --- a/src/Entity/User.php +++ b/src/Entity/User.php @@ -29,7 +29,7 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface private ?int $id = null; #[ORM\Column(type: Types::STRING, length: 255, unique: true)] - private ?string $email = null; + private string $email; #[ORM\Column(type: Types::STRING, length: 255, nullable: true)] private ?string $firstname = null; @@ -37,6 +37,9 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface #[ORM\Column(type: Types::STRING, length: 255, nullable: true)] private ?string $lastname = null; + /** + * @var string[] + */ #[ORM\Column(type: Types::JSON)] private array $roles = []; @@ -95,7 +98,7 @@ public function setLastname(?string $lastname): self public function getUserIdentifier(): string { - if (null === $this->email || '' === $this->email || '0' === $this->email) { + if ('' === $this->email) { throw new \LogicException('The User class must have an email.'); } diff --git a/src/Form/Type/LaunchType.php b/src/Form/Type/LaunchType.php index 5ca2489..3c876bc 100644 --- a/src/Form/Type/LaunchType.php +++ b/src/Form/Type/LaunchType.php @@ -25,6 +25,7 @@ use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\OptionsResolver\OptionsResolver; +/** @template-extends AbstractType> */ class LaunchType extends AbstractType { public function __construct( @@ -38,13 +39,15 @@ public function buildForm(FormBuilderInterface $builder, array $options): void $code = $options['process_code']; $configuration = $this->registry->getProcessConfiguration($code); $uiOptions = $this->configurationsManager->getUiOptions($code); - $builder->add( - 'input', - 'file' === ($uiOptions['entrypoint_type'] ?? null) ? FileType::class : TextType::class, - [ - 'required' => $configuration->getEntryPoint() instanceof TaskConfiguration, - ] - ); + if (isset($uiOptions['entrypoint_type'])) { + $builder->add( + 'input', + 'file' === $uiOptions['entrypoint_type'] ? FileType::class : TextType::class, + [ + 'required' => $configuration->getEntryPoint() instanceof TaskConfiguration, + ] + ); + } $builder->add( 'context', CollectionType::class, diff --git a/src/Form/Type/ProcessContextType.php b/src/Form/Type/ProcessContextType.php index e8e2c2d..d95fd19 100644 --- a/src/Form/Type/ProcessContextType.php +++ b/src/Form/Type/ProcessContextType.php @@ -18,6 +18,7 @@ use Symfony\Component\OptionsResolver\OptionsResolver; use Symfony\Component\Validator\Constraints\NotBlank; +/** @template-extends AbstractType */ class ProcessContextType extends AbstractType { public function buildForm(FormBuilderInterface $builder, array $options): void diff --git a/src/Form/Type/ProcessUploadFileType.php b/src/Form/Type/ProcessUploadFileType.php index 88b77c1..57619ff 100644 --- a/src/Form/Type/ProcessUploadFileType.php +++ b/src/Form/Type/ProcessUploadFileType.php @@ -17,6 +17,7 @@ use Symfony\Component\Form\Extension\Core\Type\FileType; use Symfony\Component\OptionsResolver\OptionsResolver; +/** @template-extends AbstractType */ class ProcessUploadFileType extends AbstractType { public function configureOptions(OptionsResolver $resolver): void diff --git a/src/Http/Model/HttpProcessExecution.php b/src/Http/Model/HttpProcessExecution.php index d6fbeea..fc4272f 100644 --- a/src/Http/Model/HttpProcessExecution.php +++ b/src/Http/Model/HttpProcessExecution.php @@ -19,6 +19,9 @@ final readonly class HttpProcessExecution { + /** + * @param array $context + */ public function __construct( #[Sequentially(constraints: [new NotNull(message: 'Process code is required.'), new IsValidProcessCode()])] public ?string $code = null, diff --git a/src/Http/ValueResolver/HttpProcessExecuteValueResolver.php b/src/Http/ValueResolver/HttpProcessExecuteValueResolver.php index 13d5584..5e2aafe 100644 --- a/src/Http/ValueResolver/HttpProcessExecuteValueResolver.php +++ b/src/Http/ValueResolver/HttpProcessExecuteValueResolver.php @@ -28,6 +28,9 @@ public function __construct(private string $storageDir) { } + /** + * @return iterable + */ public function resolve(Request $request, ArgumentMetadata $argument): iterable { $input = $request->get('input', $request->files->get('input')); diff --git a/src/Http/ValueResolver/ProcessConfigurationValueResolver.php b/src/Http/ValueResolver/ProcessConfigurationValueResolver.php index 3290d9b..bf88b84 100644 --- a/src/Http/ValueResolver/ProcessConfigurationValueResolver.php +++ b/src/Http/ValueResolver/ProcessConfigurationValueResolver.php @@ -13,6 +13,7 @@ namespace CleverAge\UiProcessBundle\Http\ValueResolver; +use CleverAge\ProcessBundle\Configuration\ProcessConfiguration; use CleverAge\ProcessBundle\Registry\ProcessConfigurationRegistry; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpKernel\Attribute\AsTargetedValueResolver; @@ -26,6 +27,9 @@ public function __construct(private ProcessConfigurationRegistry $registry) { } + /** + * @return iterable + */ public function resolve(Request $request, ArgumentMetadata $argument): iterable { return [$this->registry->getProcessConfiguration($request->get('process'))]; diff --git a/src/Manager/ProcessConfigurationsManager.php b/src/Manager/ProcessConfigurationsManager.php index 82dc3db..2db160e 100644 --- a/src/Manager/ProcessConfigurationsManager.php +++ b/src/Manager/ProcessConfigurationsManager.php @@ -18,7 +18,18 @@ use CleverAge\ProcessBundle\Validator\ConstraintLoader; use Symfony\Component\OptionsResolver\Options; use Symfony\Component\OptionsResolver\OptionsResolver; +use Symfony\Component\Validator\Constraint; +/** + * @phpstan-type UiOptions array{ + * 'source': ?string, + * 'target': ?string, + * 'entrypoint_type': string, + * 'constraints': Constraint[], + * 'run': 'null|bool', + * 'default': array{'input': mixed, 'context': array{array{'key': 'int|text', 'value':'int|text'}}} + * } + */ final readonly class ProcessConfigurationsManager { public function __construct(private ProcessConfigurationRegistry $registry) @@ -37,6 +48,9 @@ public function getPrivateProcesses(): array return array_filter($this->getConfigurations(), fn (ProcessConfiguration $cfg) => !$cfg->isPublic()); } + /** + * @return UiOptions|null + */ public function getUiOptions(string $processCode): ?array { if (false === $this->registry->hasProcessConfiguration($processCode)) { @@ -48,6 +62,11 @@ public function getUiOptions(string $processCode): ?array return $this->resolveUiOptions($configuration->getOptions())['ui']; } + /** + * @param array $options + * + * @return array{'ui': UiOptions} + */ private function resolveUiOptions(array $options): array { $resolver = new OptionsResolver(); @@ -77,8 +96,12 @@ private function resolveUiOptions(array $options): array $uiResolver->setAllowedValues('entrypoint_type', ['text', 'file']); $uiResolver->setNormalizer('constraints', fn (Options $options, array $values): array => (new ConstraintLoader())->buildConstraints($values)); }); + /** + * @var array{'ui': UiOptions} $options + */ + $options = $resolver->resolve($options); - return $resolver->resolve($options); + return $options; } /** @return ProcessConfiguration[] */ diff --git a/src/Message/ProcessExecuteMessage.php b/src/Message/ProcessExecuteMessage.php index 397f0ae..84fddb0 100644 --- a/src/Message/ProcessExecuteMessage.php +++ b/src/Message/ProcessExecuteMessage.php @@ -15,6 +15,9 @@ readonly class ProcessExecuteMessage { + /** + * @param mixed[] $context + */ public function __construct(public string $code, public mixed $input, public array $context = []) { }