chore: cleanup, add PHPStan type annotations

This commit is contained in:
Björn Fromme
2026-04-03 13:45:41 +02:00
parent a264526127
commit fce6d1615a
14 changed files with 91 additions and 41 deletions
+1
View File
@@ -200,6 +200,7 @@ class BpnXmlSyncCommand extends Command
}
/**
* @return array<string, int>
* @throws FilesystemException
*/
private function syncFiles(SymfonyStyle $io): array
+4 -3
View File
@@ -5,6 +5,7 @@ declare(strict_types=1);
namespace App\Command;
use App\Repository\BookingEditDraftRepository;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
@@ -20,6 +21,7 @@ class DraftInspectCommand extends Command
{
public function __construct(
private readonly BookingEditDraftRepository $draftRepository,
private readonly EntityManagerInterface $entityManager,
) {
parent::__construct();
}
@@ -102,11 +104,10 @@ class DraftInspectCommand extends Command
return Command::SUCCESS;
}
$em = $this->draftRepository->getEntityManager();
foreach ($drafts as $draft) {
$em->remove($draft);
$this->entityManager->remove($draft);
}
$em->flush();
$this->entityManager->flush();
$io->success(sprintf('Deleted %d draft(s).', \count($drafts)));
}
+15
View File
@@ -14,6 +14,9 @@ use Symfony\Component\OptionsResolver\OptionsResolver;
class Mailer
{
/**
* @param array<string, mixed> $defaults
*/
public function __construct(
private readonly MailerInterface $mailer,
private readonly BodyRendererInterface $bodyRenderer,
@@ -22,6 +25,10 @@ class Mailer
) {
}
/**
* @param array<string, mixed> $context
* @param array<string, mixed> $options
*/
public function createAndSendEmail(array $context, array $options): void
{
$config = $this->resolveConfig($options);
@@ -35,6 +42,10 @@ class Mailer
}
}
/**
* @param array<string, mixed> $context
* @param array<string, mixed> $config
*/
public function create(array $context, array $config): TemplatedEmail
{
$email = (new TemplatedEmail())
@@ -73,6 +84,10 @@ class Mailer
}
}
/**
* @param array<string, mixed> $options
* @return array<string, mixed>
*/
private function resolveConfig(array $options): array
{
$resolver = new OptionsResolver();
+13 -1
View File
@@ -49,8 +49,11 @@ class BookingEditDraft
#[ORM\Column(type: 'integer', nullable: true)]
private ?int $hotelId = null;
/**
* @var array<string, mixed>
*/
#[ORM\Column(type: 'json')]
private array $formData = [];
private array $formData;
#[ORM\Column(type: 'datetime_immutable')]
private \DateTimeImmutable $createdAt;
@@ -58,6 +61,9 @@ class BookingEditDraft
#[ORM\Column(type: 'datetime_immutable')]
private \DateTimeImmutable $updatedAt;
/**
* @param array<string, mixed> $formData
*/
public function __construct(User $user, int $bookingId, \DateTimeImmutable $travelDate, array $formData)
{
$this->user = $user;
@@ -139,11 +145,17 @@ class BookingEditDraft
return null !== $this->dateId && null !== $this->hotelId;
}
/**
* @return array<string, mixed>
*/
public function getFormData(): array
{
return $this->formData;
}
/**
* @param array<string, mixed> $formData
*/
public function setFormData(array $formData): static
{
$this->formData = $formData;
+18
View File
@@ -22,9 +22,15 @@ class LogEntry
#[ORM\Column(type: 'string', length: 255)]
private ?string $message;
/**
* @var array<string, mixed>|null
*/
#[ORM\Column(type: 'json', nullable: true)]
private ?array $context = [];
/**
* @var array<string, mixed>|null
*/
#[ORM\Column(type: 'json', nullable: true)]
private ?array $extra = [];
@@ -70,11 +76,17 @@ class LogEntry
return $this;
}
/**
* @return array<string, mixed>|null
*/
public function getContext(): ?array
{
return $this->context;
}
/**
* @param array<string, mixed>|null $context
*/
public function setContext(?array $context): self
{
$this->context = $context;
@@ -82,11 +94,17 @@ class LogEntry
return $this;
}
/**
* @return array<string, mixed>|null
*/
public function getExtra(): ?array
{
return $this->extra;
}
/**
* @param array<string, mixed>|null $extra
*/
public function setExtra(?array $extra): self
{
$this->extra = $extra;
+18
View File
@@ -26,9 +26,15 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface
#[ORM\Column(type: 'integer', nullable: true)]
private ?int $addressId = null;
/**
* @var array<string>
*/
#[ORM\Column(type: 'json')]
private array $roles = [];
/**
* @var array<string>
*/
#[ORM\Column(type: 'json')]
private array $hotelCodes = [];
@@ -96,11 +102,17 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface
return $this;
}
/**
* @return array<string>
*/
public function getRoles(): array
{
return ['ROLE_USER', ...$this->roles];
}
/**
* @param array<string> $roles
*/
public function setRoles(array $roles): static
{
$this->roles = $roles;
@@ -108,11 +120,17 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface
return $this;
}
/**
* @return array<string>
*/
public function getHotelCodes(): array
{
return $this->hotelCodes;
}
/**
* @param array<string> $hotelCodes
*/
public function setHotelCodes(array $hotelCodes): static
{
$this->hotelCodes = $hotelCodes;
+7 -3
View File
@@ -7,6 +7,7 @@ namespace App\EventListener;
use Psr\Log\LoggerInterface;
use Symfony\Bundle\SecurityBundle\Security;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\Session\Session;
use Symfony\Component\HttpKernel\Event\ExceptionEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
@@ -54,12 +55,15 @@ class AccessDeniedListener implements EventSubscriberInterface
return;
}
/** @var Session $session */
$session = $request->getSession();
if (null === $this->security->getUser()) {
$request->getSession()->getFlashBag()->add('info', 'Bitte melde dich an.');
$session->getFlashBag()->add('info', 'Bitte melde dich an.');
} else {
$request->getSession()->getFlashBag()->add('error', 'Zugriff verweigert');
$session->getFlashBag()->add('error', 'Zugriff verweigert');
// Unset potentially set target path to avoid access denied errors
$request->getSession()->remove('_security.main.target_path');
$session->remove('_security.main.target_path');
}
$this->authLogger->warning('Access denied', [
@@ -43,7 +43,7 @@ class AuthorizationCodeListener
{
$request = $this->requestStack->getMainRequest();
/** @var User $user */
/** @var User|null $user */
$user = $this->security->getUser();
if (null === $user) {
+1 -1
View File
@@ -38,7 +38,7 @@ class UserDataProcessor
return $record;
}
/** @var User $user */
/** @var User|null $user */
$user = $this->security->getUser();
if (null === $user) {
+3
View File
@@ -28,6 +28,9 @@ class LogEntryRepository extends ServiceEntityRepository
->execute();
}
/**
* @return array<int, LogEntry>
*/
public function findWithErrorCodes(): array
{
return $this->createQueryBuilder('l')
+3 -1
View File
@@ -15,6 +15,8 @@ use Symfony\Component\Security\Core\Authorization\Voter\Voter;
* Enforces ownership check via personId matching. VIEW access requires ownership,
* EDIT access additionally requires the booking to be in an editable state
* (determined by booking.isEditable()).
*
* @extends Voter<string, mixed>
*/
class BookingVoter extends Voter
{
@@ -32,7 +34,7 @@ class BookingVoter extends Voter
protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token): bool
{
/** @var User $user */
/** @var User|null $user */
$user = $token->getUser();
if (null === $user) {
+7 -18
View File
@@ -76,17 +76,6 @@ class TravelSnapshotService
return null;
}
if (false === $travel instanceof Travel) {
$this->logger->warning('Snapshot payload deserialization returned unexpected type', [
'dateId' => $dateId,
'hotelId' => $hotelId,
'snapshotId' => $snapshot->getId(),
'type' => get_debug_type($travel),
]);
return null;
}
return $travel;
}
@@ -173,11 +162,15 @@ class TravelSnapshotService
/**
* Refreshes snapshot payloads with extended availability data.
*
* @param array<int>|null $xmlAvailableDateIds
* @return array{processed:int,updated:int,failed:int}
*/
public function refreshExtendedSnapshots(int $limit = 500, bool $force = false, int $refreshAfterMinutes = 360, ?array $xmlAvailableDateIds = null): array
{
public function refreshExtendedSnapshots(
int $limit = 500,
bool $force = false,
int $refreshAfterMinutes = 360,
?array $xmlAvailableDateIds = null
): array {
$dateToThreshold = new \DateTimeImmutable('today');
$refreshBefore = new \DateTimeImmutable(sprintf('-%d minutes', $refreshAfterMinutes));
@@ -313,10 +306,6 @@ class TravelSnapshotService
return null;
}
if (false === $travel instanceof Travel) {
return null;
}
return $travel;
}
-1
View File
@@ -21,7 +21,6 @@ class AppExtension extends AbstractExtension
{
return [
new TwigFilter('file_size', [AppRuntime::class, 'formatBytes']),
new TwigFilter('file_icon', [AppRuntime::class, 'fileIconFilter'], ['is_safe' => ['html']]),
new TwigFilter('format_money', [AppRuntime::class, 'formatMoney']),
new TwigFilter('format_service_price', [AppRuntime::class, 'formatServicePrice']),
new TwigFilter('map_gender', [AppRuntime::class, 'mapGender']),
-12
View File
@@ -38,18 +38,6 @@ class AppRuntime implements RuntimeExtensionInterface
) {
}
public function fileIconFilter(Environment $environment, string $mimeType, ?string $classes = 'w-4 h-4'): string
{
$icon = match ($mimeType) {
'application/pdf' => 'pdf',
'application/vnd.ms-excel', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' => 'excel',
'application/msword', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' => 'word',
default => 'download',
};
return $this->renderIcon($environment, $icon, $classes);
}
public function formatBytes(int $bytes, ?int $precision = 2): string
{
if (0 === $bytes) {