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
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
Latest
------

## Changes
* [#18](https://github.com/cleverage/ui-process-bundle/issues/18) PHPStan level 10 (was 8), on `src` and `tests`: `LogProcessFilter::new()` `$label` typed as `TranslatableInterface|string|false|null` (the type of `setLabel()`), `LogRecord::$context` documented as `array<mixed>` (the Monolog context), conditional return type of `ProcessExecution::getReport()`.

v3.1
------

Expand Down
2 changes: 1 addition & 1 deletion phpstan.neon
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
parameters:
level: 8
level: 10
paths:
- src
- tests
3 changes: 2 additions & 1 deletion src/Admin/Filter/LogProcessFilter.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
use EasyCorp\Bundle\EasyAdminBundle\Filter\FilterTrait;
use EasyCorp\Bundle\EasyAdminBundle\Form\Filter\Type\ChoiceFilterType;
use EasyCorp\Bundle\EasyAdminBundle\Form\Type\ComparisonType;
use Symfony\Contracts\Translation\TranslatableInterface;

class LogProcessFilter implements FilterInterface
{
Expand All @@ -30,7 +31,7 @@ class LogProcessFilter implements FilterInterface
* @param string[] $choices
*/
public static function new(
mixed $label,
TranslatableInterface|string|false|null $label,
array $choices,
string|int|null $executionId = null,
): self {
Expand Down
4 changes: 4 additions & 0 deletions src/Command/UserCreateCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,10 @@ protected function execute(InputInterface $input, OutputInterface $output): int
);
}

/**
* @var string $username
* @var string $password
*/
$user = new User();
$user->setEmail($username);
$user->setRoles(['ROLE_USER', 'ROLE_ADMIN']);
Expand Down
4 changes: 3 additions & 1 deletion src/Controller/Admin/LogRecordCrudController.php
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,9 @@ public function configureActions(Actions $actions): Actions
#[\Override]
public function configureFilters(Filters $filters): Filters
{
$id = $this->requestStack->getMainRequest()?->query->all('filters')['process']['value'] ?? null;
/** @var array{process?: array{value?: string|int|null}} $queryFilters */
$queryFilters = $this->requestStack->getMainRequest()?->query->all('filters') ?? [];
$id = $queryFilters['process']['value'] ?? null;
$processList = $this->processConfigurationsManager->getPublicProcesses();
$processList = array_map(static fn (ProcessConfiguration $cfg) => $cfg->getCode(), $processList);

Expand Down
10 changes: 5 additions & 5 deletions src/Controller/Admin/Process/LaunchAction.php
Original file line number Diff line number Diff line change
Expand Up @@ -98,11 +98,11 @@ public function __invoke(
(new Filesystem())->dumpFile($filename, $input->getContent());
$input = $filename;
}
$this->dispatch(
$form->getConfig()->getOption('process_code'),
$input,
$form->get('context')->getData()
);
/** @var string $processCode */
$processCode = $form->getConfig()->getOption('process_code');
/** @var mixed[] $context */
$context = $form->get('context')->getData();
$this->dispatch($processCode, $input, $context);
$this->addFlash(
'success',
'Process has been added to queue. It will start as soon as possible'
Expand Down
1 change: 1 addition & 0 deletions src/Controller/Admin/ProcessDashboardController.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ public function __construct(
#[\Override]
public function index(): Response
{
/** @var AdminUrlGenerator $adminUrlGenerator */
$adminUrlGenerator = $this->container->get(AdminUrlGenerator::class);

return $this->redirect($adminUrlGenerator->setController(ProcessExecutionCrudController::class)->generateUrl());
Expand Down
11 changes: 7 additions & 4 deletions src/Controller/Admin/UserCrudController.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
namespace CleverAge\UiProcessBundle\Controller\Admin;

use CleverAge\UiProcessBundle\Entity\User;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\Persistence\ManagerRegistry;
use EasyCorp\Bundle\EasyAdminBundle\Attribute\AdminRoute;
use EasyCorp\Bundle\EasyAdminBundle\Config\Action;
use EasyCorp\Bundle\EasyAdminBundle\Config\Actions;
Expand Down Expand Up @@ -114,10 +116,11 @@ public function generateToken(): Response
$user = $adminContext?->getEntity()->getInstance();
$token = md5(uniqid(date('YmdHis')));
$user->setToken((new Pbkdf2PasswordHasher())->hash($token));
$this->persistEntity(
$this->container->get('doctrine')->getManagerForClass($adminContext?->getEntity()->getFqcn()),
$user
);
/** @var ManagerRegistry $doctrine */
$doctrine = $this->container->get('doctrine');
/** @var EntityManagerInterface $entityManager */
$entityManager = $doctrine->getManagerForClass(User::class);
$this->persistEntity($entityManager, $user);
$this->addFlash('success', 'New token generated '.$token.' (keep it in secured area. This token will never be displayed anymore)');

return $this->redirect(
Expand Down
12 changes: 6 additions & 6 deletions src/Controller/ProcessExecuteController.php
Original file line number Diff line number Diff line change
Expand Up @@ -51,14 +51,16 @@ public function __invoke(
}
throw new UnprocessableEntityHttpException(implode('. ', $violationsMessages));
}
/** @var mixed[] $context */
$context = \is_string($httpProcessExecution->context)
? json_decode($httpProcessExecution->context, true)
: $httpProcessExecution->context;
if ($httpProcessExecution->queue) {
$this->bus->dispatch(
new ProcessExecuteMessage(
$httpProcessExecution->code ?? '',
$httpProcessExecution->input,
\is_string($httpProcessExecution->context)
? json_decode($httpProcessExecution->context, true)
: $httpProcessExecution->context
$context
)
);

Expand All @@ -69,9 +71,7 @@ public function __invoke(
$this->processManager->execute(
$httpProcessExecution->code ?? '',
$httpProcessExecution->input,
\is_string($httpProcessExecution->context)
? json_decode($httpProcessExecution->context, true)
: $httpProcessExecution->context
$context
);
} catch (\Throwable $e) {
// The id of the process execution allows to find its logs in the UI
Expand Down
7 changes: 7 additions & 0 deletions src/DependencyInjection/CleverAgeUiProcessExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,13 @@ public function load(array $configs, ContainerBuilder $container): void
/** @var string $env */
$env = $container->getParameter('kernel.environment');
$configuration = new Configuration($env);
/** @var array{
* security: array{roles: list<string>},
* logs: array{store_in_database: bool, database_level: string, file_level: string, report_increment_level: string},
* design: array{logo_path: string},
* notification: array{enabled: bool, ...},
* } $config
*/
$config = $this->processConfiguration($configuration, $configs);

$container->getDefinition(UserCrudController::class)
Expand Down
2 changes: 1 addition & 1 deletion src/Entity/LogRecord.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ class LogRecord
#[ORM\Column(type: Types::STRING, length: 512)]
public readonly string $message;

/** @var array<string, mixed> $context */
/** @var array<mixed> $context */
#[ORM\Column(type: Types::JSON)]
public readonly array $context;

Expand Down
3 changes: 3 additions & 0 deletions src/Entity/ProcessExecution.php
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,9 @@ public function addReport(string $key, mixed $value): void
$this->report[$key] = $value;
}

/**
* @return ($key is null ? array<string, mixed> : mixed)
*/
public function getReport(?string $key = null, mixed $default = null): mixed
{
if (null === $key) {
Expand Down
8 changes: 7 additions & 1 deletion src/Entity/ProcessSchedule.php
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,13 @@ public function setProcess(string $process): static
*/
public function getContext(): array
{
return \is_array($this->context) ? $this->context : json_decode($this->context, true);
if (\is_array($this->context)) {
return $this->context;
}
/** @var array<string|int, mixed> $context */
$context = json_decode($this->context, true);

return $context;
}

/**
Expand Down
3 changes: 2 additions & 1 deletion src/Form/Type/LaunchType.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ public function __construct(

public function buildForm(FormBuilderInterface $builder, array $options): void
{
/** @var string $code */
$code = $options['process_code'];
$configuration = $this->registry->getProcessConfiguration($code);
$uiOptions = $this->configurationsManager->getUiOptions($code);
Expand All @@ -62,7 +63,7 @@ public function buildForm(FormBuilderInterface $builder, array $options): void
static fn ($data) => $data ?? [],
// A row without key is left out (it is reported by the NotBlank constraint of ProcessContextType): a null
// key is deprecated as array offset, and an empty key breaks the property paths of the process constraints
static fn ($data) => array_column(
static fn (?array $data) => array_column(
array_filter($data ?? [], static fn ($row): bool => \is_array($row) && null !== ($row['key'] ?? null) && '' !== $row['key']),
'value',
'key'
Expand Down
1 change: 1 addition & 0 deletions src/Http/ValueResolver/HttpProcessExecuteValueResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ public function resolve(Request $request, ArgumentMetadata $argument): iterable
? $request->request->getBoolean('queue')
: $request->query->getBoolean('queue', true);

/** @var string|null $input */
$httpProcessExecution = new HttpProcessExecution(
(string) $code,
$input,
Expand Down
17 changes: 14 additions & 3 deletions src/Manager/ProcessConfigurationsManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,12 @@ private function resolveOptions(array $options): array
$notificationResolver->setDefaults(['enabled' => null, 'statuses' => null, 'channels' => null, 'recipients' => null]);
$notificationResolver->setAllowedTypes('enabled', ['null', 'bool']);
$notificationResolver->setAllowedTypes('statuses', ['null', 'string[]']);
$notificationResolver->setAllowedValues('statuses', static fn (?array $statuses): bool => null === $statuses || [] === array_diff($statuses, NotificationTrigger::values()));
$notificationResolver->setAllowedValues('statuses', static function (?array $statuses): bool {
/** @var string[]|null $statusValues */
$statusValues = $statuses;

return null === $statusValues || [] === array_diff($statusValues, NotificationTrigger::values());
});
$notificationResolver->setAllowedTypes('channels', ['null', 'string[]']);
$notificationResolver->setAllowedTypes('recipients', ['null', 'array[]']);
$notificationResolver->setNormalizer('recipients', static function (Options $options, ?array $recipients): ?array {
Expand All @@ -122,7 +127,10 @@ private function resolveOptions(array $options): array
return $phone;
});

return array_values(array_map($recipientResolver->resolve(...), $recipients));
/** @var array<array<string, mixed>> $recipientRows */
$recipientRows = $recipients;

return array_values(array_map($recipientResolver->resolve(...), $recipientRows));
});

return $notificationResolver->resolve($notification);
Expand Down Expand Up @@ -154,7 +162,10 @@ private function resolveOptions(array $options): array
$contextResolver = new OptionsResolver();
$contextResolver->setRequired(['key', 'value']);

return array_map($contextResolver->resolve(...), $context);
/** @var array<array<string, mixed>> $contextRows */
$contextRows = $context;

return array_map($contextResolver->resolve(...), $contextRows);
});

return $defaultResolver->resolve($default);
Expand Down
10 changes: 6 additions & 4 deletions src/Manager/ProcessExecutionManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -81,10 +81,12 @@ public function save(): self

public function increment(string $incrementKey, int $step = 1): void
{
$this->currentProcessExecution?->addReport(
$incrementKey,
$this->currentProcessExecution->getReport($incrementKey, 0) + $step
);
if (!$this->currentProcessExecution instanceof ProcessExecution) {
return;
}
/** @var int $count */
$count = $this->currentProcessExecution->getReport($incrementKey, 0);
$this->currentProcessExecution->addReport($incrementKey, $count + $step);
}

public function setReport(string $incrementKey, string $value): void
Expand Down
4 changes: 3 additions & 1 deletion src/Message/CronProcessMessageHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,9 @@ public function __construct(private MessageBusInterface $bus)
public function __invoke(CronProcessMessage $message): void
{
$schedule = $message->processSchedule;
$context = array_merge(...array_map(static fn ($ctx) => [$ctx['key'] => $ctx['value']], $schedule->getContext()));
/** @var list<array{key: string, value: mixed}> $contextRows */
$contextRows = $schedule->getContext();
$context = array_merge(...array_map(static fn (array $ctx) => [$ctx['key'] => $ctx['value']], $contextRows));
$this->bus->dispatch(
new ProcessExecuteMessage($schedule->getProcess() ?? '', $schedule->getInput(), $context)
);
Expand Down
6 changes: 4 additions & 2 deletions src/Repository/ProcessExecutionRepository.php
Original file line number Diff line number Diff line change
Expand Up @@ -75,13 +75,15 @@ public function getManaged(ProcessExecution $processExecution): ProcessExecution
public function getLastProcessExecution(string $code): ?ProcessExecution
{
$qb = $this->createQueryBuilder('pe');

return $qb->select('pe')
/** @var ProcessExecution|null $processExecution */
$processExecution = $qb->select('pe')
->where($qb->expr()->eq('pe.code', $qb->expr()->literal($code)))
->orderBy('pe.startDate', 'DESC')
->setMaxResults(1)
->getQuery()
->getOneOrNullResult();

return $processExecution;
}

public function hasLogs(ProcessExecution $processExecution): bool
Expand Down
6 changes: 5 additions & 1 deletion tests/DependencyInjection/ConfigurationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ public function testPartialConfigurationKeepsOtherDefaults(): void
public function testLastConfigurationWins(): void
{
$processor = new Processor();
/** @var array<string, array<string, mixed>> $config */
$config = $processor->processConfiguration(new Configuration('prod'), [
['design' => ['logo_path' => 'first.png'], 'logs' => ['store_in_database' => false]],
['design' => ['logo_path' => 'second.png']],
Expand Down Expand Up @@ -188,6 +189,9 @@ public function testInvalidConfiguration(array $config, string $expectedMessage)
*/
private function process(string $env, array $config): array
{
return (new Processor())->processConfiguration(new Configuration($env), [$config]);
/** @var array<string, mixed> $processedConfig */
$processedConfig = (new Processor())->processConfiguration(new Configuration($env), [$config]);

return $processedConfig;
}
}
2 changes: 2 additions & 0 deletions tests/Functional/LaunchActionTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ public function testLaunchWithForm(): void
self::assertSame('key1', $crawler->filter('input[name="launch[context][0][key]"]')->attr('value'));

$form = $crawler->selectButton('Launch')->form();
/** @var array<string, array<string, mixed>> $values */
$values = $form->getPhpValues();
$values['launch']['input'] = 'my input';
$values['launch']['context'] = [['key' => 'key1', 'value' => 'value1'], ['key' => 'key2', 'value' => 'value2']];
Expand Down Expand Up @@ -159,6 +160,7 @@ public function testContextRowWithoutKey(): void
$crawler = $this->client->request('GET', '/process?routeName=process_launch&process=test.form_constraints');
self::assertResponseIsSuccessful();
$form = $crawler->selectButton('Launch')->form();
/** @var array<string, array<string, mixed>> $values */
$values = $form->getPhpValues();
$values['launch']['context'] = [['key' => 'key1', 'value' => 'value1'], ['key' => '', 'value' => 'value2']];
$this->client->request($form->getMethod(), $form->getUri(), $values);
Expand Down
3 changes: 3 additions & 0 deletions tests/Functional/ProcessScheduleCrudTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ public function testCreate(): void
self::assertNotContains('test.private', $options);

$form = $crawler->selectButton('Create')->form();
/** @var array<string, array<string, mixed>> $values */
$values = $form->getPhpValues();
$values['ProcessSchedule']['process'] = 'test.process';
$values['ProcessSchedule']['type'] = 'cron';
Expand All @@ -123,6 +124,7 @@ public function testInvalidExpressionIsRejected(): void

$crawler = $this->client->request('GET', '/process/process-schedule/new');
$form = $crawler->selectButton('Create')->form();
/** @var array<string, array<string, mixed>> $values */
$values = $form->getPhpValues();
$values['ProcessSchedule']['process'] = 'test.process';
$values['ProcessSchedule']['type'] = 'cron';
Expand All @@ -141,6 +143,7 @@ public function testEdit(): void
$crawler = $this->client->request('GET', '/process/process-schedule/'.$schedule->getId().'/edit');
self::assertResponseIsSuccessful();
$form = $crawler->selectButton('Save changes')->form();
/** @var array<string, array<string, mixed>> $values */
$values = $form->getPhpValues();
$values['ProcessSchedule']['expression'] = '1 hour';
$this->client->request($form->getMethod(), $form->getUri(), $values);
Expand Down
2 changes: 2 additions & 0 deletions tests/Functional/UserCrudTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ public function testCreate(): void
$crawler = $this->client->request('GET', '/process/user/new');
self::assertResponseIsSuccessful();
$form = $crawler->selectButton('Create')->form();
/** @var array<string, array<string, mixed>> $values */
$values = $form->getPhpValues();
$values['User']['email'] = 'new@example.com';
$values['User']['password'] = ['first' => 'new password', 'second' => 'new password'];
Expand All @@ -110,6 +111,7 @@ public function testPasswordsMustMatch(): void

$crawler = $this->client->request('GET', '/process/user/new');
$form = $crawler->selectButton('Create')->form();
/** @var array<string, array<string, mixed>> $values */
$values = $form->getPhpValues();
$values['User']['email'] = 'new@example.com';
$values['User']['password'] = ['first' => 'password', 'second' => 'other'];
Expand Down
11 changes: 8 additions & 3 deletions tests/Manager/ProcessConfigurationsManagerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,14 @@ public function testPublicAndPrivateProcesses(): void
'public.two' => $this->rawProcess(),
]);

$codes = static fn (array $configurations): array => array_values(
array_map(static fn (ProcessConfiguration $configuration): string => $configuration->getCode(), $configurations)
);
$codes = static function (array $configurations): array {
/** @var array<ProcessConfiguration> $processConfigurations */
$processConfigurations = $configurations;

return array_values(
array_map(static fn (ProcessConfiguration $configuration): string => $configuration->getCode(), $processConfigurations)
);
};

self::assertSame(['public.one', 'public.two'], $codes($manager->getPublicProcesses()));
self::assertSame(['private.one'], $codes($manager->getPrivateProcesses()));
Expand Down
Loading
Loading