feat: upgrade to symfony 7.4

This commit is contained in:
2026-09-01 11:24:44 +02:00
parent 8ed20db90c
commit 2459af122f
41 changed files with 1527 additions and 1219 deletions
+2 -1
View File
@@ -26,7 +26,8 @@ APP_SECRET=b3cb0285aea14bbffb57df0cc3aa84f4
# DATABASE_URL="sqlite:///%kernel.project_dir%/var/data.db"
# DATABASE_URL="mysql://app:[email protected]:3306/app?serverVersion=8.0.32&charset=utf8mb4"
# DATABASE_URL="mysql://app:[email protected]:3306/app?serverVersion=10.11.2-MariaDB&charset=utf8mb4"
DATABASE_URL="postgresql://app:[email protected]:5432/app?serverVersion=15&charset=utf8"
# DATABASE_URL="postgresql://app:[email protected]:5432/app?serverVersion=15&charset=utf8"
DATABASE_URL="mysql://db:db@db:3306/db?serverVersion=8.4.5&charset=utf8mb4"
###< doctrine/doctrine-bundle ###
###> symfony/messenger ###
+1
View File
@@ -4,6 +4,7 @@
/.env.local.php
/.env.*.local
/config/secrets/prod/prod.decrypt.private.php
/config/reference.php
/public/bundles/
/var/
/vendor/
+37 -36
View File
@@ -10,7 +10,7 @@
}
],
"require": {
"php": ">=8.1",
"php": ">=8.2",
"ext-ctype": "*",
"ext-iconv": "*",
"ext-simplexml": "*",
@@ -29,7 +29,7 @@
"liip/imagine-bundle": "^2.11",
"maennchen/zipstream-php": "^3.1",
"nelmio/security-bundle": "^3.0",
"nesbot/carbon": "^2.70",
"nesbot/carbon": "^3.8",
"oneup/uploader-bundle": "^5.1",
"phpdocumentor/reflection-docblock": "^5.3",
"phpoffice/phpspreadsheet": "^5.9",
@@ -41,38 +41,38 @@
"spatie/blink": "^1.4",
"spatie/icalendar-generator": "^2.5",
"symfony/apache-pack": "^1.0",
"symfony/asset": "6.4.*",
"symfony/console": "6.4.*",
"symfony/doctrine-messenger": "6.4.*",
"symfony/dotenv": "6.4.*",
"symfony/expression-language": "6.4.*",
"symfony/asset": "7.4.*",
"symfony/console": "7.4.*",
"symfony/doctrine-messenger": "7.4.*",
"symfony/dotenv": "7.4.*",
"symfony/expression-language": "7.4.*",
"symfony/flex": "^2",
"symfony/form": "6.4.*",
"symfony/framework-bundle": "6.4.*",
"symfony/http-client": "6.4.*",
"symfony/intl": "6.4.*",
"symfony/mailer": "6.4.*",
"symfony/mailjet-mailer": "6.4.*",
"symfony/mime": "6.4.*",
"symfony/form": "7.4.*",
"symfony/framework-bundle": "7.4.*",
"symfony/http-client": "7.4.*",
"symfony/intl": "7.4.*",
"symfony/mailer": "7.4.*",
"symfony/mailjet-mailer": "7.4.*",
"symfony/mime": "7.4.*",
"symfony/monolog-bundle": "^3.0",
"symfony/notifier": "6.4.*",
"symfony/process": "6.4.*",
"symfony/property-access": "6.4.*",
"symfony/property-info": "6.4.*",
"symfony/runtime": "6.4.*",
"symfony/security-bundle": "6.4.*",
"symfony/serializer": "6.4.*",
"symfony/notifier": "7.4.*",
"symfony/process": "7.4.*",
"symfony/property-access": "7.4.*",
"symfony/property-info": "7.4.*",
"symfony/runtime": "7.4.*",
"symfony/security-bundle": "7.4.*",
"symfony/serializer": "7.4.*",
"symfony/stimulus-bundle": "^2.11",
"symfony/string": "6.4.*",
"symfony/translation": "6.4.*",
"symfony/twig-bundle": "6.4.*",
"symfony/uid": "6.4.*",
"symfony/string": "7.4.*",
"symfony/translation": "7.4.*",
"symfony/twig-bundle": "7.4.*",
"symfony/uid": "7.4.*",
"symfony/ux-twig-component": "^2.16",
"symfony/validator": "6.4.*",
"symfony/web-link": "6.4.*",
"symfony/validator": "7.4.*",
"symfony/web-link": "7.4.*",
"symfony/webpack-encore-bundle": "^2.0",
"symfony/workflow": "6.4.*",
"symfony/yaml": "6.4.*",
"symfony/workflow": "7.4.*",
"symfony/yaml": "7.4.*",
"twig/extra-bundle": "^2.12|^3.0",
"twig/html-extra": "^3.7",
"twig/intl-extra": "^3.7",
@@ -126,7 +126,7 @@
"extra": {
"symfony": {
"allow-contrib": true,
"require": "6.4.*"
"require": "7.4.*"
}
},
"require-dev": {
@@ -135,13 +135,14 @@
"friendsofphp/php-cs-fixer": "^3.84",
"marcocesarato/php-conventional-changelog": "^1.17",
"phpunit/phpunit": "^9.5",
"symfony/browser-kit": "6.4.*",
"symfony/css-selector": "6.4.*",
"symfony/debug-bundle": "6.4.*",
"rector/rector": "^2.6",
"symfony/browser-kit": "7.4.*",
"symfony/css-selector": "7.4.*",
"symfony/debug-bundle": "7.4.*",
"symfony/maker-bundle": "^1.0",
"symfony/phpunit-bridge": "^6.3",
"symfony/stopwatch": "6.4.*",
"symfony/web-profiler-bundle": "6.4.*"
"symfony/phpunit-bridge": "^7.4",
"symfony/stopwatch": "7.4.*",
"symfony/web-profiler-bundle": "7.4.*"
},
"version": "0.2.5"
}
Generated
+1347 -1029
View File
File diff suppressed because it is too large Load Diff
+7
View File
@@ -8,6 +8,13 @@ doctrine:
#server_version: '15'
orm:
auto_generate_proxy_classes: true
# Keep implicit controller-argument entity resolution on (many actions type-hint
# an entity against a matching {id}/{uuid} route param without #[MapEntity]).
# Setting this explicitly silences the "default will flip to false" deprecation;
# the separate "automapping is deprecated" notice stays until those actions are
# migrated to #[MapEntity], which is its own task.
controller_resolver:
auto_mapping: true
enable_lazy_ghost_objects: true
report_fields_where_declared: true
validate_xml_mapping: true
+5
View File
@@ -0,0 +1,5 @@
framework:
property_info:
# Keep the current (7.x) default; enabling the constructor extractor is a
# behaviour change to evaluate on its own, not part of the 6.4 -> 7.4 bump.
with_constructor_extractor: false
+1 -1
View File
@@ -1,4 +1,4 @@
when@dev:
_errors:
resource: '@FrameworkBundle/Resources/config/routing/errors.xml'
resource: '@FrameworkBundle/Resources/config/routing/errors.php'
prefix: /_error
+2 -2
View File
@@ -1,8 +1,8 @@
when@dev:
web_profiler_wdt:
resource: '@WebProfilerBundle/Resources/config/routing/wdt.xml'
resource: '@WebProfilerBundle/Resources/config/routing/wdt.php'
prefix: /_wdt
web_profiler_profiler:
resource: '@WebProfilerBundle/Resources/config/routing/profiler.xml'
resource: '@WebProfilerBundle/Resources/config/routing/profiler.php'
prefix: /_profiler
+26
View File
@@ -0,0 +1,26 @@
<?php
declare(strict_types=1);
use Rector\Config\RectorConfig;
use Rector\Symfony\Set\SymfonySetList;
return RectorConfig::configure()
->withPaths([
__DIR__ . '/src',
__DIR__ . '/tests',
])
->withSymfonyContainerXml(__DIR__ . '/var/cache/dev/App_KernelDevDebugContainer.xml')
// Upgrade rules bound to the Symfony / Twig / Doctrine versions currently
// installed (resolved from composer.lock). Bump the constraints and run
// `composer update` before executing Rector so it targets 7.x.
->withComposerBased(twig: true, doctrine: true, symfony: true)
->withSets([
SymfonySetList::CONFIGS,
])
->withSkip([
// Generated migrations are frozen once shipped.
__DIR__ . '/migrations',
__DIR__ . '/src/**/DoctrineMigrations',
])
->withParallel();
+1 -1
View File
@@ -45,7 +45,7 @@ class DownloadController extends AbstractController
}
}
$inline = (bool) $request->get('inline');
$inline = $request->query->getBoolean('inline');
$disposition = $inline ? ResponseHeaderBag::DISPOSITION_INLINE : ResponseHeaderBag::DISPOSITION_ATTACHMENT;
// mark file downloaded if applicable
@@ -27,13 +27,8 @@ class PasswordResetController extends AbstractController
->add('email', EmailType::class, [
'label' => 'E-Mail',
'constraints' => [
new NotBlank([
'message' => 'Bitte angeben',
]),
new Email([
'mode' => 'strict',
'message' => 'Ungültige E-Mail-Adresse',
]),
new NotBlank(message: 'Bitte angeben'),
new Email(mode: 'strict', message: 'Ungültige E-Mail-Adresse'),
],
])
->getForm()
+1 -1
View File
@@ -19,7 +19,7 @@ class DeleteController extends AbstractController
#[IsGranted('ROLE_USER')]
public function index(Request $request): JsonResponse
{
$uuid = $request->get('uuid');
$uuid = $request->query->get('uuid');
$this->uploadHandler->removeUploadFromSession($uuid);
+1 -1
View File
@@ -24,7 +24,7 @@ class Contact
#[ORM\Column(length: 255)]
#[Assert\NotBlank(message: 'Bitte gib die E-Mail-Adresse an')]
#[Assert\Email(message: 'Bitte gib eine gültige E-Mail-Adresse an', mode: 'strict')]
#[Assert\Email(message: 'Bitte gib eine gültige E-Mail-Adresse an', mode: Assert\Email::VALIDATION_MODE_STRICT)]
private ?string $email = null;
#[ORM\Column(length: 255)]
+1 -1
View File
@@ -18,7 +18,7 @@ class Communication
protected ?string $mobile = null;
#[ORM\Column(type: 'string', nullable: true)]
#[Assert\Email(mode: 'strict', message: 'Bitte gib eine gültige E-Mail-Adresse an', groups: ['profile'])]
#[Assert\Email(mode: Assert\Email::VALIDATION_MODE_STRICT, message: 'Bitte gib eine gültige E-Mail-Adresse an', groups: ['profile'])]
#[Assert\NotBlank(message: 'Bitte gib deine E-Mail-Adresse an', groups: ['profile'])]
protected ?string $email = null;
+1
View File
@@ -322,6 +322,7 @@ class User implements UserInterface, TimestampableEntityInterface, SoftDeletable
return 'app_teamer_index';
}
#[\Deprecated]
public function eraseCredentials(): void
{
}
+2 -2
View File
@@ -20,10 +20,10 @@ class UploadSessionListener
$uploadedFile = $event->getFile();
// Get upload uuid from Dropzone request
$uuid = $request->get('uuid');
$uuid = $request->request->get('uuid');
// Get original filename from Dropzone request
$originalFileName = $request->get('originalFilename');
$originalFileName = $request->request->get('originalFilename');
$upload = new UploadDto(
$uuid,
@@ -17,7 +17,7 @@ class UploadSessionValidationListener
$request = $event->getRequest();
$session = $request->getSession();
$uploadSessionId = $request->get(UploadHandler::SESSION_KEY);
$uploadSessionId = $request->request->get(UploadHandler::SESSION_KEY);
if (null === $uploadSessionId || false === $session->has(UploadHandler::SESSION_KEY)) {
throw new ValidationException('Invalid upload session');
+5
View File
@@ -24,6 +24,11 @@ class AbbreviatedDateType extends AbstractType
{
$resolver->setDefaults([
'input' => 'datetime_immutable',
// Explicit 'choice' preserves the pre-Symfony-7.4 default (7.4 flipped it to
// 'single_text'). This type needs the multi-field widget: the 'years' option at
// the call sites and FirstDayOfMonthDateTransformer both assume array submissions,
// and 'single_text' + the custom 'format' below throws under html5.
'widget' => 'choice',
'format' => 'y-MMMM-d',
]);
}
+1 -3
View File
@@ -17,9 +17,7 @@ class DeleteAccountType extends AbstractType
'label' => 'Ja, ich möchte meinen Account endgültig löschen',
'mapped' => false,
'constraints' => [
new IsTrue([
'message' => 'Bitte bestätige die Löschung',
]),
new IsTrue(message: 'Bitte bestätige die Löschung'),
],
]);
}
+1 -3
View File
@@ -21,9 +21,7 @@ class DisableUserType extends AbstractType
'data-action' => 'textarea-autosize#resize',
],
'constraints' => [
new NotBlank([
'message' => 'Bitte gib die Begründung ein',
]),
new NotBlank(message: 'Bitte gib die Begründung ein'),
],
])
->add('disabledReasonInternal', TextareaType::class, [
+1 -3
View File
@@ -30,9 +30,7 @@ class DispositionCallOffType extends AbstractType
'data-action' => 'textarea-autosize#resize',
],
'constraints' => [
new NotBlank([
'message' => 'Bitte gib die Begründung ein',
]),
new NotBlank(message: 'Bitte gib die Begründung ein'),
],
])
->add('sendNotification', CheckboxType::class, [
+1 -3
View File
@@ -58,9 +58,7 @@ class TeamerApplicationType extends AbstractType
'label' => 'Ich bestätige den abweichenden Buszustieg',
'mapped' => false,
'constraints' => [
new IsTrue([
'message' => 'Deine Bestätigung ist erforderlich',
]),
new IsTrue(message: 'Deine Bestätigung ist erforderlich'),
],
]);
}
+1 -3
View File
@@ -42,9 +42,7 @@ class TeamerDispositionType extends AbstractType
'label' => 'Ich bestätige, dass meine persönlichen Daten, Bankverbindung und Kontoinhaber korrekt sind',
'mapped' => false,
'constraints' => [
new IsTrue([
'message' => 'Deine Bestätigung ist erforderlich',
]),
new IsTrue(message: 'Deine Bestätigung ist erforderlich'),
],
]);
}
+3 -3
View File
@@ -98,8 +98,8 @@ abstract class AbstractMenuBuilder
$request = $this->requestStack->getMainRequest();
return [
$parameter => $request->get($parameter, $default),
'r' => $request->get('r'),
$parameter => $request->attributes->get($parameter, $default),
'r' => $request->query->get('r'),
];
}
@@ -123,7 +123,7 @@ abstract class AbstractMenuBuilder
$returnUrl = $options['return_url'] ?? null;
if (false === is_string($returnUrl) || '' === $returnUrl) {
$requestReturnUrl = $this->requestStack->getMainRequest()?->get('r');
$requestReturnUrl = $this->requestStack->getMainRequest()?->query->get('r');
$returnUrl = is_string($requestReturnUrl) && '' !== $requestReturnUrl
? rawurldecode($requestReturnUrl)
+2 -1
View File
@@ -3,6 +3,7 @@
namespace App\Security;
use App\Entity\User;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Exception\CustomUserMessageAccountStatusException;
use Symfony\Component\Security\Core\User\UserCheckerInterface;
use Symfony\Component\Security\Core\User\UserInterface;
@@ -34,7 +35,7 @@ class UserChecker implements UserCheckerInterface
}
}
public function checkPostAuth(UserInterface $user): void
public function checkPostAuth(UserInterface $user, ?TokenInterface $token = null): void
{
}
}
+2 -1
View File
@@ -6,6 +6,7 @@ use App\Entity\Application;
use App\Entity\User;
use Symfony\Bundle\SecurityBundle\Security;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authorization\Voter\Vote;
use Symfony\Component\Security\Core\Authorization\Voter\Voter;
class ApplicationVoter extends Voter
@@ -29,7 +30,7 @@ class ApplicationVoter extends Voter
return in_array($attribute, [static::VIEW, static::WITHDRAW, static::DELETE, static::DISPOSE, static::STATUS]);
}
protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token): bool
protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token, ?Vote $vote = null): bool
{
/** @var User $user */
$user = $token->getUser();
+2 -1
View File
@@ -5,6 +5,7 @@ namespace App\Security\Voter;
use App\Entity\Assignment;
use Symfony\Bundle\SecurityBundle\Security;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authorization\Voter\Vote;
use Symfony\Component\Security\Core\Authorization\Voter\Voter;
class AssignmentVoter extends Voter
@@ -28,7 +29,7 @@ class AssignmentVoter extends Voter
return in_array($attribute, [self::VIEW, self::EDIT, self::APPLY, self::PUBLISH, self::CALL_OFF], true);
}
protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token): bool
protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token, ?Vote $vote = null): bool
{
if (null === $token->getUser()) {
return false;
+2 -1
View File
@@ -6,6 +6,7 @@ use App\Entity\Availability;
use App\Entity\User;
use Symfony\Bundle\SecurityBundle\Security;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authorization\Voter\Vote;
use Symfony\Component\Security\Core\Authorization\Voter\Voter;
class AvailabilityVoter extends Voter
@@ -26,7 +27,7 @@ class AvailabilityVoter extends Voter
return in_array($attribute, [static::ASSIGN, static::DELETE]);
}
protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token): bool
protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token, ?Vote $vote = null): bool
{
/** @var Availability $availability */
$availability = $subject;
+2 -1
View File
@@ -7,6 +7,7 @@ use App\Entity\Upload;
use App\Entity\User;
use Symfony\Bundle\SecurityBundle\Security;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authorization\Voter\Vote;
use Symfony\Component\Security\Core\Authorization\Voter\Voter;
class DispositionVoter extends Voter
@@ -44,7 +45,7 @@ class DispositionVoter extends Voter
]);
}
protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token): bool
protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token, ?Vote $vote = null): bool
{
/** @var Disposition $disposition */
$disposition = $subject;
+2 -1
View File
@@ -5,6 +5,7 @@ namespace App\Security\Voter;
use App\Entity\FeedbackSet;
use App\Repository\JobProfileRepository;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authorization\Voter\Vote;
use Symfony\Component\Security\Core\Authorization\Voter\Voter;
class FeedbackSetVoter extends Voter
@@ -20,7 +21,7 @@ class FeedbackSetVoter extends Voter
return $subject instanceof FeedbackSet && static::DELETE === $attribute;
}
protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token): bool
protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token, ?Vote $vote = null): bool
{
/** @var FeedbackSet $feedbackSet */
$feedbackSet = $subject;
+2 -1
View File
@@ -5,6 +5,7 @@ namespace App\Security\Voter;
use App\Entity\Feedback;
use App\Entity\User;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authorization\Voter\Vote;
use Symfony\Component\Security\Core\Authorization\Voter\Voter;
class FeedbackVoter extends Voter
@@ -18,7 +19,7 @@ class FeedbackVoter extends Voter
return $subject instanceof Feedback && in_array($attribute, [static::VIEW, static::PUBLISH, static::DELETE]);
}
protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token): bool
protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token, ?Vote $vote = null): bool
{
/** @var Feedback $feedback */
$feedback = $subject;
+2 -1
View File
@@ -5,6 +5,7 @@ namespace App\Security\Voter;
use App\Entity\User;
use Symfony\Bundle\SecurityBundle\Security;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authorization\Voter\Vote;
use Symfony\Component\Security\Core\Authorization\Voter\Voter;
use Symfony\Component\Security\Core\User\UserInterface;
@@ -19,7 +20,7 @@ class ImpersonationVoter extends Voter
return 'CAN_IMPERSONATE' === $attribute && $subject instanceof UserInterface;
}
protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token): bool
protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token, ?Vote $vote = null): bool
{
$currentUser = $token->getUser();
$targetUser = $subject;
+2 -1
View File
@@ -6,6 +6,7 @@ use App\Entity\License;
use App\Entity\User;
use Symfony\Bundle\SecurityBundle\Security;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authorization\Voter\Vote;
use Symfony\Component\Security\Core\Authorization\Voter\Voter;
class LicenseVoter extends Voter
@@ -26,7 +27,7 @@ class LicenseVoter extends Voter
return in_array($attribute, [static::VIEW, static::DELETE]);
}
protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token): bool
protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token, ?Vote $vote = null): bool
{
if (true === $this->security->isGranted('ROLE_ADMINISTRATIVE')) {
return true;
+2 -1
View File
@@ -6,6 +6,7 @@ use App\Entity\Upload;
use App\Entity\User;
use Symfony\Bundle\SecurityBundle\Security;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authorization\Voter\Vote;
use Symfony\Component\Security\Core\Authorization\Voter\Voter;
class UploadVoter extends Voter
@@ -29,7 +30,7 @@ class UploadVoter extends Voter
return in_array($attribute, [static::VIEW, static::DELETE, static::DOWNLOAD, static::CHECK, static::COMMENT]);
}
protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token): bool
protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token, ?Vote $vote = null): bool
{
/** @var Upload $upload */
$upload = $subject;
+2 -1
View File
@@ -5,6 +5,7 @@ namespace App\Security\Voter;
use App\Entity\User;
use Symfony\Bundle\SecurityBundle\Security;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authorization\Voter\Vote;
use Symfony\Component\Security\Core\Authorization\Voter\Voter;
class UserVoter extends Voter
@@ -20,7 +21,7 @@ class UserVoter extends Voter
return self::EDIT === $attribute && $subject instanceof User;
}
protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token): bool
protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token, ?Vote $vote = null): bool
{
$currentUser = $token->getUser();
$targetUser = $subject;
+1 -1
View File
@@ -25,7 +25,7 @@ class KnownPlaceholders extends Constraint
*/
public function __construct(array $allowed = [], ?string $message = null, ?array $groups = null, mixed $payload = null)
{
parent::__construct([], $groups, $payload);
parent::__construct(null, $groups, $payload);
$this->allowed = $allowed;
$this->message = $message ?? $this->message;
+36
View File
@@ -116,6 +116,18 @@
"config/routes/oneup_uploader.yaml"
]
},
"phpstan/phpstan": {
"version": "2.2",
"recipe": {
"repo": "github.com/symfony/recipes-contrib",
"branch": "main",
"version": "1.0",
"ref": "5e490cc197fb6bb1ae22e5abbc531ddc633b6767"
},
"files": [
"phpstan.dist.neon"
]
},
"phpunit/phpunit": {
"version": "9.6",
"recipe": {
@@ -178,6 +190,18 @@
".env"
]
},
"symfony/form": {
"version": "7.4",
"recipe": {
"repo": "github.com/symfony/recipes",
"branch": "main",
"version": "7.2",
"ref": "7d86a6723f4a623f59e2bf966b6aad2fc461d36b"
},
"files": [
"config/packages/csrf.yaml"
]
},
"symfony/framework-bundle": {
"version": "6.3",
"recipe": {
@@ -278,6 +302,18 @@
"tests/bootstrap.php"
]
},
"symfony/property-info": {
"version": "7.4",
"recipe": {
"repo": "github.com/symfony/recipes",
"branch": "main",
"version": "7.3",
"ref": "dae70df71978ae9226ae915ffd5fad817f5ca1f7"
},
"files": [
"config/packages/property_info.yaml"
]
},
"symfony/routing": {
"version": "6.3",
"recipe": {
+8 -10
View File
@@ -71,14 +71,12 @@
{% endif %}
{% endblock %}
{% block label %}
{% apply spaceless %}
{% if item.extras.icon is defined %}
{{ icon(item.extras.icon, 'h-5 w-5 shrink-0 text-gray-400') }}
<span class="flex-1">{{ item.label|raw }}</span>
{% else %}
{{ item.label|raw }}
{% endif %}
{% endapply %}
{% endblock %}
{% block label -%}
{% if item.extras.icon is defined -%}
{{ icon(item.extras.icon, 'h-5 w-5 shrink-0 text-gray-400') -}}
<span class="flex-1">{{ item.label|raw }}</span>
{%- else -%}
{{ item.label|raw }}
{%- endif %}
{%- endblock %}
+8 -10
View File
@@ -1,10 +1,8 @@
{% apply spaceless %}
{% for message in messages %}
{% if message is iterable %}
{% set message = message.id | trans(message.parameters | default({}), message.domain | default(null), message.locale | default(null)) | raw %}
{% else %}
{% set message = message | trans | raw %}
{% endif %}
<div {{ stimulus_controller('toast', { 'text': message, 'class': 'toastify--' ~ level}) }}{{ qa_attribute('toast', level)}}></div>
{% endfor %}
{% endapply %}
{% for message in messages -%}
{% if message is iterable %}
{%- set message = message.id | trans(message.parameters | default({}), message.domain | default(null), message.locale | default(null)) | raw %}
{%- else %}
{%- set message = message | trans | raw %}
{%- endif -%}
<div {{ stimulus_controller('toast', { 'text': message, 'class': 'toastify--' ~ level}) }}{{ qa_attribute('toast', level)}}></div>
{%- endfor %}
-84
View File
@@ -1,84 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Tests\Migrations;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Yaml\Yaml;
/**
* Version20260820000000 rewrites the wording of mails that go out to every teamer, and it
* runs exactly once with nobody watching, so the patterns it does that with are pinned
* here. The migration is not autoloaded (only src/ and tests/ are), hence the require.
*/
class EmphasisRewriteTest extends TestCase
{
private string $single;
private string $double;
protected function setUp(): void
{
require_once __DIR__.'/../../migrations/Version20260820000000.php';
$reflection = new \ReflectionClass(\DoctrineMigrations\Version20260820000000::class);
$this->single = $reflection->getConstant('SINGLE_EMPHASIS');
$this->double = $reflection->getConstant('DOUBLE_EMPHASIS');
}
/**
* @dataProvider upCases
*/
public function testTheUpwardRewrite(string $before, string $after): void
{
$this->assertSame($after, preg_replace($this->single, '**$1**', $before));
}
/**
* @return iterable<string, array{string, string}>
*/
public static function upCases(): iterable
{
yield 'one word' => ['Das ist *wichtig*', 'Das ist **wichtig**'];
yield 'several words' => ['*Dein Einsatz wurde angenommen!*', '**Dein Einsatz wurde angenommen!**'];
yield 'around a placeholder' => ['*{destination}*', '**{destination}**'];
yield 'already bold' => ['Das ist **wichtig**', 'Das ist **wichtig**'];
yield 'escaped asterisk' => ['Ein Sternchen: \*', 'Ein Sternchen: \*'];
// Whitespace next to a marker means it is not emphasis - CommonMark's own rule, and
// the reason arithmetic and stray asterisks come through untouched.
yield 'multiplication' => ['5 * 3 = 15', '5 * 3 = 15'];
yield 'spaced asterisks' => ['a * b * c', 'a * b * c'];
yield 'bullet at line start' => ["* Ausweis\n* Bankverbindung", "* Ausweis\n* Bankverbindung"];
yield 'not across lines' => ["*offen\ngeblieben*", "*offen\ngeblieben*"];
}
public function testTheRewriteIsReversible(): void
{
$before = 'Das ist *wichtig* und *{destination}* auch';
$up = preg_replace($this->single, '**$1**', $before);
$this->assertSame($before, preg_replace($this->double, '*$1*', $up));
}
/**
* The delivered defaults were rewritten by hand in the same commit, so nothing should
* be left in them for the migration to find. Read through the parser rather than off
* the raw file: the header comment documents *kursiv* on purpose.
*/
public function testTheDeliveredDefaultsAreAlreadyMigrated(): void
{
$texts = Yaml::parseFile(__DIR__.'/../../config/email_texts.yaml')['texts'];
foreach ($texts as $key => $text) {
$body = (string) ($text['body'] ?? '');
$this->assertSame(
$body,
preg_replace($this->single, '**$1**', $body),
sprintf('Default wording of "%s" still carries single-asterisk emphasis', $key)
);
}
}
}
@@ -21,8 +21,7 @@ class TeamerMailingDraftHandlerTest extends TestCase
$request = new Request();
$request->setSession(new Session(new MockArraySessionStorage()));
$requestStack = new RequestStack();
$requestStack->push($request);
$requestStack = new RequestStack([$request]);
$this->handler = new TeamerMailingDraftHandler($requestStack);
}