chore: cleanup, add PHPStan type annotations
This commit is contained in:
@@ -200,6 +200,7 @@ class BpnXmlSyncCommand extends Command
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, int>
|
||||
* @throws FilesystemException
|
||||
*/
|
||||
private function syncFiles(SymfonyStyle $io): array
|
||||
|
||||
@@ -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)));
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,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) {
|
||||
|
||||
@@ -38,7 +38,7 @@ class UserDataProcessor
|
||||
return $record;
|
||||
}
|
||||
|
||||
/** @var User $user */
|
||||
/** @var User|null $user */
|
||||
$user = $this->security->getUser();
|
||||
|
||||
if (null === $user) {
|
||||
|
||||
@@ -28,6 +28,9 @@ class LogEntryRepository extends ServiceEntityRepository
|
||||
->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int, LogEntry>
|
||||
*/
|
||||
public function findWithErrorCodes(): array
|
||||
{
|
||||
return $this->createQueryBuilder('l')
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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']),
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user