technik: Statische Analyse wieder aussagekräftig machen

phpstan lief mit 390 Meldungen außerhalb der Baseline, der größte Teil davon
Falschmeldungen. Ursache: Larastan löste keinen einzigen Cast auf, weil alle
Models ihre Casts über die casts()-METHODE deklarieren (Laravel-11-Stil).
Laravel führt die erst zur Laufzeit mit $casts zusammen, Larastan instanziiert
Models aber ohne Konstruktor — $pressRelease->status galt deshalb als string
und jeder Enum-Vergleich als "immer falsch". Dafür gibt es den Schalter
parseModelCastsMethod, dessen Larastan-Default false ist.

Zweite Sammelursache waren fehlende Generics an den Relations-Methoden: Ohne
sie landete phpstan auf Illuminate\Database\Eloquent\Model und meldete jede
Modell-Eigenschaft und jeden eigenen Scope als unbekannt. Alle 102
Relationen tragen jetzt ihr @return <Related, $this>; das verbessert
nebenbei die Vervollständigung in der IDE.

Zusammen fällt die Zahl von 390 auf 169. Die neu erzeugte Baseline umfasst 166
Einträge, überwiegend Closure-Parameter, die als blankes Builder typisiert
sind, sowie zwei nachgeprüfte Falschmeldungs-Gruppen: die Concerns unter
app/Livewire werden ausschließlich aus Volt-Komponenten eingebunden, die
phpstan nicht analysiert, und Cashier typisiert findBillable() gegen den TRAIT
Billable, was phpstan nicht mit User vereinbaren kann — die Laufzeitprüfung
dort ist richtig und bleibt.

Was die Analyse an echten toten Zweigen zutage gefördert hat, ist mit
entfernt: eine doppelte Null-Prüfung im Nachweis-PDF, Prüfungen gegen
NOT-NULL-Spalten in der Rechnungsstellung und im Vorschau-Controller, eine
immer wahre Bedingung im Logo-Pfad und in RetireAgencyCompanyType, ein
überflüssiges ?? [] auf einer Symfony-Methode, die immer ein Array liefert,
sowie der unerreichbare null-Arm in publicPortalLinks(). MigrateLegacyMedia
deklariert seine Medientypen jetzt als Literal-Union, womit das match ohne
default nachweislich vollständig ist statt nur faktisch.

Damit das nicht wieder abdriftet — die Baseline stand seit Mai unverändert —
läuft die Analyse ab jetzt als eigener Schritt in der Test-Pipeline.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Kevin Adametz 2026-09-01 11:07:51 +00:00
parent eb1eba4533
commit e746deda4f
48 changed files with 780 additions and 228 deletions

View file

@ -50,5 +50,12 @@ jobs:
- name: Build Assets
run: npm run build
# Vor den Tests: Die statische Analyse ist ohne Gate binnen weniger Wochen
# abgedriftet (Baseline zuletzt 12.05.2026, danach 390 neue Meldungen).
# Neue Meldungen gehören in den Code oder — begründet — in die Baseline,
# nicht ins Rauschen.
- name: Run Static Analysis
run: ./vendor/bin/phpstan analyse --memory-limit=2G --no-progress
- name: Run Tests
run: ./vendor/bin/pest

View file

@ -342,6 +342,9 @@ class MigrateLegacyMedia extends Command
/**
* @return list<string>
*/
/**
* @return list<'company-logos'|'press-release-images'>
*/
private function selectedTypes(): array
{
$type = (string) $this->option('type');

View file

@ -84,7 +84,7 @@ class RetireAgencyCompanyType extends Command
continue;
}
if (! $isNewsroom && $isProtected) {
if (! $isNewsroom) {
$report['protected'][] = $entry + [
'third_party_members' => (bool) $agency->has_third_party_members,
'payment_option_links' => (bool) $agency->has_payment_option_links,

View file

@ -23,7 +23,7 @@ class PressReleasePreviewController extends Controller
return $this->renderError(__('Der Vorschau-Link ist ungültig.'), 404);
}
if ($magicLink->expires_at && $magicLink->expires_at->isPast()) {
if ($magicLink->expires_at->isPast()) {
return $this->renderError(__('Der Vorschau-Link ist abgelaufen.'), 410);
}

View file

@ -27,7 +27,7 @@ class RecordMailActivity
{
$recipients = array_values(array_map(
fn ($address): string => $address->getAddress(),
$event->message->getTo() ?? [],
$event->message->getTo(),
));
if ($recipients === []) {

View file

@ -35,11 +35,17 @@ class ActivityEvent extends Model
];
}
/**
* @return BelongsTo<User, $this>
*/
public function user(): BelongsTo
{
return $this->belongsTo(User::class);
}
/**
* @return MorphTo<Model, $this>
*/
public function subject(): MorphTo
{
return $this->morphTo();

View file

@ -30,11 +30,17 @@ class ApiUsageLog extends Model
];
}
/**
* @return BelongsTo<User, $this>
*/
public function user(): BelongsTo
{
return $this->belongsTo(User::class);
}
/**
* @return BelongsTo<PersonalAccessToken, $this>
*/
public function personalAccessToken(): BelongsTo
{
return $this->belongsTo(PersonalAccessToken::class);

View file

@ -26,6 +26,9 @@ class BillingAddress extends Model
'vat_id',
];
/**
* @return BelongsTo<User, $this>
*/
public function user(): BelongsTo
{
return $this->belongsTo(User::class);

View file

@ -38,6 +38,9 @@ class BookingConfirmation extends Model
];
}
/**
* @return BelongsTo<User, $this>
*/
public function user(): BelongsTo
{
return $this->belongsTo(User::class);
@ -46,6 +49,8 @@ class BookingConfirmation extends Model
/**
* Die akzeptierte AGB-Fassung (E3: "agb_version_id" am
* Buchungsdatensatz); `agb_version` hält den Klartext redundant fest.
*
* @return BelongsTo<LegalDocument, $this>
*/
public function legalDocument(): BelongsTo
{

View file

@ -40,11 +40,17 @@ class Boost extends Model
];
}
/**
* @return BelongsTo<PressRelease, $this>
*/
public function pressRelease(): BelongsTo
{
return $this->belongsTo(PressRelease::class);
}
/**
* @return BelongsTo<User, $this>
*/
public function user(): BelongsTo
{
return $this->belongsTo(User::class);

View file

@ -31,16 +31,25 @@ class Category extends Model
];
}
/**
* @return BelongsTo<self, $this>
*/
public function parent(): BelongsTo
{
return $this->belongsTo(self::class, 'parent_id');
}
/**
* @return HasMany<self, $this>
*/
public function children(): HasMany
{
return $this->hasMany(self::class, 'parent_id');
}
/**
* @return HasMany<CategoryTranslation, $this>
*/
public function translations(): HasMany
{
return $this->hasMany(CategoryTranslation::class);
@ -56,11 +65,17 @@ class Category extends Model
return $this->translations->first()?->name ?? '#'.$this->id;
}
/**
* @return HasMany<PressRelease, $this>
*/
public function pressReleases(): HasMany
{
return $this->hasMany(PressRelease::class);
}
/**
* @return BelongsToMany<FooterCode, $this>
*/
public function footerCodes(): BelongsToMany
{
return $this->belongsToMany(FooterCode::class, 'category_footer_code');

View file

@ -16,6 +16,9 @@ class CategoryTranslation extends Model
'description',
];
/**
* @return BelongsTo<Category, $this>
*/
public function category(): BelongsTo
{
return $this->belongsTo(Category::class);

View file

@ -131,11 +131,17 @@ class Company extends Model
return $this->gone_at !== null;
}
/**
* @return BelongsTo<User, $this>
*/
public function owner(): BelongsTo
{
return $this->belongsTo(User::class, 'owner_user_id');
}
/**
* @return BelongsToMany<User, $this>
*/
public function users(): BelongsToMany
{
return $this->belongsToMany(User::class)
@ -143,16 +149,25 @@ class Company extends Model
->withTimestamps();
}
/**
* @return HasMany<Contact, $this>
*/
public function contacts(): HasMany
{
return $this->hasMany(Contact::class);
}
/**
* @return HasMany<PressRelease, $this>
*/
public function pressReleases(): HasMany
{
return $this->hasMany(PressRelease::class);
}
/**
* @return BelongsToMany<UserPaymentOption, $this>
*/
public function userPaymentOptions(): BelongsToMany
{
return $this->belongsToMany(UserPaymentOption::class, 'user_payment_option_company')
@ -177,7 +192,7 @@ class Company extends Model
$candidates[] = ltrim($logoPath, '/');
}
if ($legacyPortal && filled($logoFilename)) {
if (filled($logoFilename)) {
$candidates[] = "company-logos/{$legacyPortal}/{$this->id}/{$logoFilename}";
$candidates[] = "{$legacyPortal}/company/{$logoFilename}";
}

View file

@ -58,6 +58,9 @@ class Contact extends Model
];
}
/**
* @return BelongsTo<Company, $this>
*/
public function company(): BelongsTo
{
return $this->belongsTo(Company::class);
@ -74,11 +77,17 @@ class Contact extends Model
return $name !== '' ? $name : __('Kontakt ohne Name');
}
/**
* @return BelongsToMany<PressRelease, $this>
*/
public function pressReleases(): BelongsToMany
{
return $this->belongsToMany(PressRelease::class, 'press_release_contact');
}
/**
* @return BelongsToMany<User, $this>
*/
public function users(): BelongsToMany
{
return $this->belongsToMany(User::class)

View file

@ -55,11 +55,17 @@ class ContactMessage extends Model
return 'KT-'.$this->id;
}
/**
* @return BelongsTo<User, $this>
*/
public function user(): BelongsTo
{
return $this->belongsTo(User::class);
}
/**
* @return BelongsTo<User, $this>
*/
public function resolver(): BelongsTo
{
return $this->belongsTo(User::class, 'resolved_by_user_id');

View file

@ -37,6 +37,9 @@ class CreditTopup extends Model
];
}
/**
* @return BelongsTo<User, $this>
*/
public function user(): BelongsTo
{
return $this->belongsTo(User::class);

View file

@ -38,16 +38,25 @@ class CreditTransaction extends Model
];
}
/**
* @return BelongsTo<CreditWallet, $this>
*/
public function wallet(): BelongsTo
{
return $this->belongsTo(CreditWallet::class, 'credit_wallet_id');
}
/**
* @return BelongsTo<User, $this>
*/
public function user(): BelongsTo
{
return $this->belongsTo(User::class);
}
/**
* @return MorphTo<Model, $this>
*/
public function reference(): MorphTo
{
return $this->morphTo();

View file

@ -32,11 +32,17 @@ class CreditWallet extends Model
];
}
/**
* @return BelongsTo<User, $this>
*/
public function user(): BelongsTo
{
return $this->belongsTo(User::class);
}
/**
* @return HasMany<CreditTransaction, $this>
*/
public function transactions(): HasMany
{
return $this->hasMany(CreditTransaction::class)->orderByDesc('created_at');

View file

@ -33,6 +33,9 @@ class EmailNotificationOptOut extends Model
];
}
/**
* @return BelongsTo<User, $this>
*/
public function user(): BelongsTo
{
return $this->belongsTo(User::class);

View file

@ -36,6 +36,9 @@ class FooterCode extends Model
];
}
/**
* @return BelongsToMany<Category, $this>
*/
public function categories(): BelongsToMany
{
return $this->belongsToMany(Category::class, 'category_footer_code');

View file

@ -46,16 +46,25 @@ class Invoice extends Model
];
}
/**
* @return BelongsTo<User, $this>
*/
public function user(): BelongsTo
{
return $this->belongsTo(User::class);
}
/**
* @return BelongsTo<UserPayment, $this>
*/
public function userPayment(): BelongsTo
{
return $this->belongsTo(UserPayment::class, 'user_payment_id');
}
/**
* @return BelongsTo<InvoiceBillingAddress, $this>
*/
public function invoiceBillingAddress(): BelongsTo
{
return $this->belongsTo(InvoiceBillingAddress::class);

View file

@ -23,6 +23,9 @@ class InvoiceBillingAddress extends Model
'vat_id',
];
/**
* @return HasMany<Invoice, $this>
*/
public function invoices(): HasMany
{
return $this->hasMany(Invoice::class);

View file

@ -44,6 +44,9 @@ class KiAudit extends Model
];
}
/**
* @return BelongsTo<PressRelease, $this>
*/
public function pressRelease(): BelongsTo
{
return $this->belongsTo(PressRelease::class);

View file

@ -49,6 +49,9 @@ class LegacyInvoice extends Model
];
}
/**
* @return BelongsTo<User, $this>
*/
public function user(): BelongsTo
{
return $this->belongsTo(User::class);

View file

@ -79,11 +79,17 @@ class LegalRequest extends Model
return $this->payload['reported_url'] ?? null;
}
/**
* @return BelongsTo<PressRelease, $this>
*/
public function pressRelease(): BelongsTo
{
return $this->belongsTo(PressRelease::class);
}
/**
* @return BelongsTo<User, $this>
*/
public function resolver(): BelongsTo
{
return $this->belongsTo(User::class, 'resolved_by_user_id');
@ -91,6 +97,8 @@ class LegalRequest extends Model
/**
* Bezahlvorgang der kostenpflichtigen Spuren (Depublizierung/Änderung).
*
* @return BelongsTo<SinglePurchase, $this>
*/
public function singlePurchase(): BelongsTo
{
@ -100,6 +108,8 @@ class LegalRequest extends Model
/**
* Verschlüsselte Vorher-Snapshots ausgeführter Mutationen (Korrektur/
* Anonymisierung) — Grundlage der Rückgängig-Option bis zur Löschfrist.
*
* @return HasMany<PressReleaseRedactionSnapshot, $this>
*/
public function redactionSnapshots(): HasMany
{

View file

@ -27,6 +27,9 @@ class MagicLink extends Model
];
}
/**
* @return BelongsTo<User, $this>
*/
public function user(): BelongsTo
{
return $this->belongsTo(User::class);

View file

@ -45,11 +45,17 @@ class NewsletterCampaign extends Model
];
}
/**
* @return BelongsTo<User, $this>
*/
public function createdBy(): BelongsTo
{
return $this->belongsTo(User::class, 'created_by_user_id');
}
/**
* @return HasMany<NewsletterCampaignRecipient, $this>
*/
public function recipients(): HasMany
{
return $this->hasMany(NewsletterCampaignRecipient::class);

View file

@ -30,11 +30,17 @@ class NewsletterCampaignRecipient extends Model
];
}
/**
* @return BelongsTo<NewsletterCampaign, $this>
*/
public function campaign(): BelongsTo
{
return $this->belongsTo(NewsletterCampaign::class, 'newsletter_campaign_id');
}
/**
* @return BelongsTo<User, $this>
*/
public function user(): BelongsTo
{
return $this->belongsTo(User::class);

View file

@ -40,6 +40,9 @@ class NewsletterSubscription extends Model
];
}
/**
* @return BelongsTo<User, $this>
*/
public function user(): BelongsTo
{
return $this->belongsTo(User::class);

View file

@ -33,11 +33,17 @@ class PaymentOption extends Model
];
}
/**
* @return HasMany<PaymentOptionTranslation, $this>
*/
public function translations(): HasMany
{
return $this->hasMany(PaymentOptionTranslation::class);
}
/**
* @return HasMany<UserPaymentOption, $this>
*/
public function userPaymentOptions(): HasMany
{
return $this->hasMany(UserPaymentOption::class);

View file

@ -17,6 +17,9 @@ class PaymentOptionTranslation extends Model
'description',
];
/**
* @return BelongsTo<PaymentOption, $this>
*/
public function paymentOption(): BelongsTo
{
return $this->belongsTo(PaymentOption::class);

View file

@ -57,7 +57,7 @@ class PressRelease extends Model
static::creating(function (self $pressRelease): void {
if (blank($pressRelease->placeholder_variant)) {
$seed = $pressRelease->uuid ?? $pressRelease->title ?? (string) now()->timestamp;
$pressRelease->placeholder_variant = PressReleasePlaceholder::fromSeed($seed)->value;
$pressRelease->placeholder_variant = PressReleasePlaceholder::fromSeed($seed);
}
});
}
@ -309,36 +309,57 @@ class PressRelease extends Model
return $publishedAt->isoFormat('D. MMM');
}
/**
* @return BelongsTo<User, $this>
*/
public function user(): BelongsTo
{
return $this->belongsTo(User::class);
}
/**
* @return BelongsTo<Company, $this>
*/
public function company(): BelongsTo
{
return $this->belongsTo(Company::class);
}
/**
* @return BelongsTo<Category, $this>
*/
public function category(): BelongsTo
{
return $this->belongsTo(Category::class);
}
/**
* @return HasMany<PressReleaseImage, $this>
*/
public function images(): HasMany
{
return $this->hasMany(PressReleaseImage::class);
}
/**
* @return HasMany<PressReleaseAttachment, $this>
*/
public function attachments(): HasMany
{
return $this->hasMany(PressReleaseAttachment::class);
}
/**
* @return BelongsToMany<Contact, $this>
*/
public function contacts(): BelongsToMany
{
return $this->belongsToMany(Contact::class, 'press_release_contact');
}
/**
* @return HasMany<PressReleaseStatusLog, $this>
*/
public function statusLogs(): HasMany
{
return $this->hasMany(PressReleaseStatusLog::class)->orderByDesc('created_at');
@ -346,6 +367,8 @@ class PressRelease extends Model
/**
* Ereignis-Log (Aufrufe/Shares) — Aufruf-Gesamtzähler bleibt `hits`.
*
* @return HasMany<PressReleaseEvent, $this>
*/
public function events(): HasMany
{
@ -383,6 +406,8 @@ class PressRelease extends Model
/**
* Versionierter Audit-Trail der KI-Deklarationen (Art. 50 KI-VO) —
* jeder Submit erzeugt einen neuen, unveränderlichen Eintrag.
*
* @return HasMany<PressReleaseAiDeclaration, $this>
*/
public function aiDeclarations(): HasMany
{
@ -436,11 +461,17 @@ class PressRelease extends Model
], $portals);
}
/**
* @return HasMany<KiAudit, $this>
*/
public function kiAudits(): HasMany
{
return $this->hasMany(KiAudit::class)->orderByDesc('created_at');
}
/**
* @return HasMany<Boost, $this>
*/
public function boosts(): HasMany
{
return $this->hasMany(Boost::class);

View file

@ -38,16 +38,25 @@ class PressReleaseAiDeclaration extends Model
];
}
/**
* @return BelongsTo<PressRelease, $this>
*/
public function pressRelease(): BelongsTo
{
return $this->belongsTo(PressRelease::class);
}
/**
* @return BelongsTo<User, $this>
*/
public function user(): BelongsTo
{
return $this->belongsTo(User::class);
}
/**
* @return BelongsTo<Company, $this>
*/
public function company(): BelongsTo
{
return $this->belongsTo(Company::class);

View file

@ -36,6 +36,9 @@ class PressReleaseAttachment extends Model
];
}
/**
* @return BelongsTo<PressRelease, $this>
*/
public function pressRelease(): BelongsTo
{
return $this->belongsTo(PressRelease::class);

View file

@ -59,6 +59,9 @@ class PressReleaseImage extends Model
];
}
/**
* @return BelongsTo<PressRelease, $this>
*/
public function pressRelease(): BelongsTo
{
return $this->belongsTo(PressRelease::class);

View file

@ -52,11 +52,17 @@ class PressReleaseRedactionSnapshot extends Model
]);
}
/**
* @return BelongsTo<LegalRequest, $this>
*/
public function legalRequest(): BelongsTo
{
return $this->belongsTo(LegalRequest::class);
}
/**
* @return BelongsTo<PressRelease, $this>
*/
public function pressRelease(): BelongsTo
{
return $this->belongsTo(PressRelease::class);

View file

@ -29,11 +29,17 @@ class PressReleaseStatusLog extends Model
];
}
/**
* @return BelongsTo<PressRelease, $this>
*/
public function pressRelease(): BelongsTo
{
return $this->belongsTo(PressRelease::class);
}
/**
* @return BelongsTo<User, $this>
*/
public function changedBy(): BelongsTo
{
return $this->belongsTo(User::class, 'changed_by_user_id');

View file

@ -45,6 +45,9 @@ class Profile extends Model
];
}
/**
* @return BelongsTo<User, $this>
*/
public function user(): BelongsTo
{
return $this->belongsTo(User::class);

View file

@ -34,11 +34,17 @@ class ReviewCheck extends Model
];
}
/**
* @return BelongsTo<User, $this>
*/
public function user(): BelongsTo
{
return $this->belongsTo(User::class);
}
/**
* @return BelongsTo<PressRelease, $this>
*/
public function pressRelease(): BelongsTo
{
return $this->belongsTo(PressRelease::class);

View file

@ -43,11 +43,17 @@ class SinglePurchase extends Model
];
}
/**
* @return BelongsTo<User, $this>
*/
public function user(): BelongsTo
{
return $this->belongsTo(User::class);
}
/**
* @return BelongsTo<PressRelease, $this>
*/
public function pressRelease(): BelongsTo
{
return $this->belongsTo(PressRelease::class);

View file

@ -343,21 +343,33 @@ class User extends Authenticatable implements MustVerifyEmail
->implode('');
}
/**
* @return HasOne<Profile, $this>
*/
public function profile(): HasOne
{
return $this->hasOne(Profile::class);
}
/**
* @return HasMany<MagicLink, $this>
*/
public function magicLinks(): HasMany
{
return $this->hasMany(MagicLink::class);
}
/**
* @return HasMany<Company, $this>
*/
public function ownedCompanies(): HasMany
{
return $this->hasMany(Company::class, 'owner_user_id');
}
/**
* @return BelongsToMany<Company, $this>
*/
public function companies(): BelongsToMany
{
return $this->belongsToMany(Company::class)
@ -365,22 +377,34 @@ class User extends Authenticatable implements MustVerifyEmail
->withTimestamps();
}
/**
* @return BelongsToMany<Contact, $this>
*/
public function contacts(): BelongsToMany
{
return $this->belongsToMany(Contact::class)
->withTimestamps();
}
/**
* @return HasMany<PressRelease, $this>
*/
public function pressReleases(): HasMany
{
return $this->hasMany(PressRelease::class);
}
/**
* @return HasMany<NewsletterSubscription, $this>
*/
public function newsletterSubscriptions(): HasMany
{
return $this->hasMany(NewsletterSubscription::class);
}
/**
* @return HasMany<EmailNotificationOptOut, $this>
*/
public function emailNotificationOptOuts(): HasMany
{
return $this->hasMany(EmailNotificationOptOut::class);
@ -408,36 +432,57 @@ class User extends Authenticatable implements MustVerifyEmail
$this->emailNotificationOptOuts()->where('type', $type->value)->delete();
}
/**
* @return HasOne<BillingAddress, $this>
*/
public function billingAddress(): HasOne
{
return $this->hasOne(BillingAddress::class);
}
/**
* @return HasMany<UserPaymentOption, $this>
*/
public function userPaymentOptions(): HasMany
{
return $this->hasMany(UserPaymentOption::class);
}
/**
* @return HasMany<SinglePurchase, $this>
*/
public function singlePurchases(): HasMany
{
return $this->hasMany(SinglePurchase::class);
}
/**
* @return HasOne<CreditWallet, $this>
*/
public function creditWallet(): HasOne
{
return $this->hasOne(CreditWallet::class);
}
/**
* @return HasMany<CreditTransaction, $this>
*/
public function creditTransactions(): HasMany
{
return $this->hasMany(CreditTransaction::class);
}
/**
* @return HasMany<ReviewCheck, $this>
*/
public function reviewChecks(): HasMany
{
return $this->hasMany(ReviewCheck::class);
}
/**
* @return HasMany<CreditTopup, $this>
*/
public function creditTopups(): HasMany
{
return $this->hasMany(CreditTopup::class);
@ -456,12 +501,17 @@ class User extends Authenticatable implements MustVerifyEmail
* Lokale Rechnungen (STR- und MAN-Kreis). Überschreibt bewusst die
* gleichnamige Cashier-Methode — Stripe-Rechnungen werden beim
* Webhook-Sync (9E) in diese Tabelle gespiegelt.
*
* @return HasMany<Invoice, $this>
*/
public function invoices(): HasMany
{
return $this->hasMany(Invoice::class);
}
/**
* @return HasMany<LegacyInvoice, $this>
*/
public function legacyInvoices(): HasMany
{
return $this->hasMany(LegacyInvoice::class);
@ -476,6 +526,9 @@ class User extends Authenticatable implements MustVerifyEmail
return $this->legacyInvoices()->latestFirst()->first();
}
/**
* @return HasMany<UserFilterPreset, $this>
*/
public function filterPresets(): HasMany
{
return $this->hasMany(UserFilterPreset::class);

View file

@ -25,6 +25,9 @@ class UserFilterPreset extends Model
];
}
/**
* @return BelongsTo<User, $this>
*/
public function user(): BelongsTo
{
return $this->belongsTo(User::class);

View file

@ -29,11 +29,17 @@ class UserPayment extends Model
];
}
/**
* @return BelongsTo<UserPaymentOption, $this>
*/
public function userPaymentOption(): BelongsTo
{
return $this->belongsTo(UserPaymentOption::class);
}
/**
* @return HasMany<Invoice, $this>
*/
public function invoices(): HasMany
{
return $this->hasMany(Invoice::class, 'user_payment_id');

View file

@ -38,16 +38,25 @@ class UserPaymentOption extends Model
];
}
/**
* @return BelongsTo<User, $this>
*/
public function user(): BelongsTo
{
return $this->belongsTo(User::class);
}
/**
* @return BelongsTo<PaymentOption, $this>
*/
public function paymentOption(): BelongsTo
{
return $this->belongsTo(PaymentOption::class);
}
/**
* @return BelongsToMany<Company, $this>
*/
public function companies(): BelongsToMany
{
return $this->belongsToMany(Company::class, 'user_payment_option_company')
@ -55,6 +64,9 @@ class UserPaymentOption extends Model
->withTimestamps();
}
/**
* @return HasMany<UserPayment, $this>
*/
public function payments(): HasMany
{
return $this->hasMany(UserPayment::class);

View file

@ -75,7 +75,6 @@ class ManualInvoiceService
UserPaymentOptionStatus::Grandfathered,
], true)
|| $lockedOption->stripe_subscription_id !== null
|| $lockedOption->current_period_end === null
|| $lockedOption->current_period_end->isAfter($asOf)
) {
return null;

View file

@ -208,7 +208,7 @@ class ProofPdfRenderer
$image = $this->coverImage($pressRelease);
$embed = $image ? $this->loadCoverImage($image) : null;
if ($embed === null || $image === null) {
if ($embed === null) {
$this->sectionHeading('Angaben zur Veröffentlichung');
$lastIndex = count($rows) - 1;

View file

@ -1,11 +1,29 @@
parameters:
ignoreErrors:
-
message: '#^Instanceof between App\\Models\\User and Illuminate\\Contracts\\Auth\\MustVerifyEmail will always evaluate to true\.$#'
identifier: instanceof.alwaysTrue
count: 1
path: app/Actions/Fortify/UpdateUserProfileInformation.php
-
message: '#^Expression on left side of \?\? is not nullable\.$#'
identifier: nullCoalesce.expr
count: 1
path: app/Console/Commands/FixLegacyTimestamps.php
-
message: '#^Using nullsafe property access on non\-nullable type App\\Enums\\Portal\. Use \-\> instead\.$#'
identifier: nullsafe.neverNull
count: 1
path: app/Console/Commands/MigrateLegacyAgencyNames.php
-
message: '#^Offset ''priority'' on array\{path\: string, priority\: 0\|10\|20\|30\|40\|50\} on left side of \?\? always exists and is not nullable\.$#'
identifier: nullCoalesce.offset
count: 1
path: app/Console/Commands/MigrateLegacyMedia.php
-
message: '#^Call to static method chunk\(\) on an unknown class App\\Models\\Legacy\\Presseecho\\Article\.$#'
identifier: class.notFound
@ -43,88 +61,94 @@ parameters:
path: app/Console/Commands/MigratePresseData.php
-
message: '#^Call to function is_array\(\) with string\|null will always evaluate to false\.$#'
identifier: function.impossibleType
message: '#^Access to an undefined property App\\Models\\Company\:\:\$has_contacts\.$#'
identifier: property.notFound
count: 1
path: app/Console/Commands/SyncPressReleaseImages.php
path: app/Console/Commands/RetireAgencyCompanyType.php
-
message: '#^Result of \|\| is always true\.$#'
identifier: booleanOr.alwaysTrue
message: '#^Access to an undefined property App\\Models\\Company\:\:\$has_payment_option_links\.$#'
identifier: property.notFound
count: 2
path: app/Console/Commands/RetireAgencyCompanyType.php
-
message: '#^Access to an undefined property App\\Models\\Company\:\:\$has_press_releases\.$#'
identifier: property.notFound
count: 1
path: app/Console/Commands/SyncPressReleaseImages.php
path: app/Console/Commands/RetireAgencyCompanyType.php
-
message: '#^Strict comparison using \=\=\= between \*NEVER\* and array\{\} will always evaluate to false\.$#'
identifier: identical.alwaysFalse
message: '#^Access to an undefined property App\\Models\\Company\:\:\$has_third_party_members\.$#'
identifier: property.notFound
count: 2
path: app/Console/Commands/RetireAgencyCompanyType.php
-
message: '#^Using nullsafe property access on non\-nullable type App\\Enums\\Portal\. Use \-\> instead\.$#'
identifier: nullsafe.neverNull
count: 1
path: app/Console/Commands/SyncPressReleaseImages.php
path: app/Console/Commands/RetireAgencyCompanyType.php
-
message: '#^Cannot access property \$value on string\.$#'
identifier: property.nonObject
message: '#^Using nullsafe property access "\?\-\>legacy_id" on left side of \?\? is unnecessary\. Use \-\> instead\.$#'
identifier: nullsafe.neverNull
count: 1
path: app/Http/Controllers/Api/V1/NewsletterSubscriptionController.php
path: app/Console/Commands/SeedLegacyApiTestdata.php
-
message: '#^Cannot access property \$value on string\.$#'
identifier: property.nonObject
count: 5
path: app/Http/Controllers/Api/V1/PressReleaseController.php
message: '#^Using nullsafe property access on non\-nullable type App\\Enums\\Portal\. Use \-\> instead\.$#'
identifier: nullsafe.neverNull
count: 1
path: app/Http/Controllers/Api/Legacy/LegacyContactController.php
-
message: '#^Instanceof between string and App\\Enums\\PressReleaseStatus will always evaluate to false\.$#'
identifier: instanceof.alwaysFalse
message: '#^Using nullsafe property access on non\-nullable type App\\Enums\\Portal\. Use \-\> instead\.$#'
identifier: nullsafe.neverNull
count: 1
path: app/Http/Controllers/Api/Legacy/LegacyPressReleaseController.php
-
message: '#^Instanceof between App\\Enums\\PressReleaseStatus and App\\Enums\\PressReleaseStatus will always evaluate to true\.$#'
identifier: instanceof.alwaysTrue
count: 1
path: app/Http/Controllers/Api/V1/PressReleaseImageController.php
-
message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$is_active\.$#'
message: '#^Method App\\Http\\Controllers\\Auth\\GoogleController\:\:redirect\(\) should return Illuminate\\Http\\RedirectResponse but returns Symfony\\Component\\HttpFoundation\\RedirectResponse\.$#'
identifier: return.type
count: 1
path: app/Http/Controllers/Auth/GoogleController.php
-
message: '#^Access to an undefined property Laravel\\Cashier\\Payment\:\:\$id\.$#'
identifier: property.notFound
count: 1
path: app/Http/Controllers/Auth/MagicLinkConsumeController.php
path: app/Http/Controllers/CheckoutController.php
-
message: '#^Call to an undefined method Illuminate\\Database\\Eloquent\\Model\:\:canAccessAdmin\(\)\.$#'
message: '#^Using nullsafe property access on non\-nullable type App\\Enums\\Portal\. Use \-\> instead\.$#'
identifier: nullsafe.neverNull
count: 1
path: app/Http/Controllers/LegalRequestController.php
-
message: '#^Using nullsafe property access on non\-nullable type App\\Enums\\Portal\. Use \-\> instead\.$#'
identifier: nullsafe.neverNull
count: 1
path: app/Http/Controllers/Web/PressReleaseReportController.php
-
message: '#^Using nullsafe property access "\?\-\>value" on left side of \?\? is unnecessary\. Use \-\> instead\.$#'
identifier: nullsafe.neverNull
count: 1
path: app/Http/Controllers/Web/ReportFormController.php
-
message: '#^Call to an undefined method Illuminate\\Database\\Eloquent\\Builder\<Illuminate\\Database\\Eloquent\\Model\>\:\:visibleInArchive\(\)\.$#'
identifier: method.notFound
count: 1
path: app/Http/Controllers/Auth/MagicLinkConsumeController.php
-
message: '#^Cannot call method isPast\(\) on string\.$#'
identifier: method.nonObject
count: 1
path: app/Http/Controllers/Auth/MagicLinkConsumeController.php
-
message: '#^Cannot call method isPast\(\) on string\.$#'
identifier: method.nonObject
count: 1
path: app/Http/Controllers/PressReleasePreviewController.php
-
message: '#^Negated boolean expression is always true\.$#'
identifier: booleanNot.alwaysTrue
count: 1
path: app/Http/Controllers/PressReleasePreviewController.php
-
message: '#^Offset ''press_release_id'' on string\|null on left side of \?\? does not exist\.$#'
identifier: nullCoalesce.offset
count: 1
path: app/Http/Controllers/PressReleasePreviewController.php
-
message: '#^Ternary operator condition is always false\.$#'
identifier: ternary.alwaysFalse
count: 1
path: app/Http/Controllers/PressReleasePreviewController.php
-
message: '#^Unreachable statement \- code above always terminates\.$#'
identifier: deadCode.unreachable
count: 1
path: app/Http/Controllers/PressReleasePreviewController.php
path: app/Http/Controllers/Web/SitemapController.php
-
message: '#^Call to function method_exists\(\) with Laravel\\Sanctum\\PersonalAccessToken and ''getKey'' will always evaluate to true\.$#'
@ -348,6 +372,12 @@ parameters:
count: 2
path: app/Http/Resources/PressReleaseImageResource.php
-
message: '#^Call to an undefined method App\\Http\\Resources\\PressReleaseImageResource\:\:variantUrl\(\)\.$#'
identifier: method.notFound
count: 1
path: app/Http/Resources/PressReleaseImageResource.php
-
message: '#^Access to an undefined property App\\Http\\Resources\\PressReleaseResource\:\:\$backlink_url\.$#'
identifier: property.notFound
@ -469,10 +499,142 @@ parameters:
path: app/Http/Responses/RoleAwareLoginResponse.php
-
message: '#^Cannot access property \$value on string\.$#'
identifier: property.nonObject
message: '#^Instanceof between Laravel\\Cashier\\Billable\|null and App\\Models\\User will always evaluate to false\.$#'
identifier: instanceof.alwaysFalse
count: 3
path: app/Listeners/ProcessStripeWebhook.php
-
message: '#^Method App\\Listeners\\ProcessStripeWebhook\:\:localSubscription\(\) is unused\.$#'
identifier: method.unused
count: 1
path: app/Models/Company.php
path: app/Listeners/ProcessStripeWebhook.php
-
message: '#^Method App\\Listeners\\ProcessStripeWebhook\:\:snapshotAddress\(\) is unused\.$#'
identifier: method.unused
count: 1
path: app/Listeners/ProcessStripeWebhook.php
-
message: '#^Property App\\Listeners\\ProcessStripeWebhook\:\:\$activityLog is never read, only written\.$#'
identifier: property.onlyWritten
count: 1
path: app/Listeners/ProcessStripeWebhook.php
-
message: '#^Property App\\Listeners\\ProcessStripeWebhook\:\:\$numbers is never read, only written\.$#'
identifier: property.onlyWritten
count: 1
path: app/Listeners/ProcessStripeWebhook.php
-
message: '#^Unreachable statement \- code above always terminates\.$#'
identifier: deadCode.unreachable
count: 3
path: app/Listeners/ProcessStripeWebhook.php
-
message: '#^Using nullsafe property access "\?\-\>address1" on left side of \?\? is unnecessary\. Use \-\> instead\.$#'
identifier: nullsafe.neverNull
count: 1
path: app/Listeners/ProcessStripeWebhook.php
-
message: '#^Using nullsafe property access "\?\-\>city" on left side of \?\? is unnecessary\. Use \-\> instead\.$#'
identifier: nullsafe.neverNull
count: 1
path: app/Listeners/ProcessStripeWebhook.php
-
message: '#^Using nullsafe property access "\?\-\>country_code" on left side of \?\? is unnecessary\. Use \-\> instead\.$#'
identifier: nullsafe.neverNull
count: 1
path: app/Listeners/ProcessStripeWebhook.php
-
message: '#^Using nullsafe property access "\?\-\>name" on left side of \?\? is unnecessary\. Use \-\> instead\.$#'
identifier: nullsafe.neverNull
count: 1
path: app/Listeners/ProcessStripeWebhook.php
-
message: '#^Using nullsafe property access "\?\-\>postal_code" on left side of \?\? is unnecessary\. Use \-\> instead\.$#'
identifier: nullsafe.neverNull
count: 1
path: app/Listeners/ProcessStripeWebhook.php
-
message: '#^Using nullsafe property access "\?\-\>name" on left side of \?\? is unnecessary\. Use \-\> instead\.$#'
identifier: nullsafe.neverNull
count: 1
path: app/Listeners/RecordMailActivity.php
-
message: '#^Trait App\\Livewire\\Concerns\\HandlesAdminPressReleaseActions is used zero times and is not analysed\.$#'
identifier: trait.unused
count: 1
path: app/Livewire/Concerns/HandlesAdminPressReleaseActions.php
-
message: '#^Trait App\\Livewire\\Concerns\\HasFullTextSearch is used zero times and is not analysed\.$#'
identifier: trait.unused
count: 1
path: app/Livewire/Concerns/HasFullTextSearch.php
-
message: '#^Trait App\\Livewire\\Concerns\\HasSortableTable is used zero times and is not analysed\.$#'
identifier: trait.unused
count: 1
path: app/Livewire/Concerns/HasSortableTable.php
-
message: '#^Trait App\\Livewire\\Concerns\\InteractsWithAiDeclaration is used zero times and is not analysed\.$#'
identifier: trait.unused
count: 1
path: app/Livewire/Concerns/InteractsWithAiDeclaration.php
-
message: '#^Trait App\\Livewire\\Concerns\\InteractsWithPressReleaseContacts is used zero times and is not analysed\.$#'
identifier: trait.unused
count: 1
path: app/Livewire/Concerns/InteractsWithPressReleaseContacts.php
-
message: '#^Trait App\\Livewire\\Concerns\\InteractsWithPressReleaseScheduling is used zero times and is not analysed\.$#'
identifier: trait.unused
count: 1
path: app/Livewire/Concerns/InteractsWithPressReleaseScheduling.php
-
message: '#^Trait App\\Livewire\\Concerns\\InteractsWithPressReleaseTags is used zero times and is not analysed\.$#'
identifier: trait.unused
count: 1
path: app/Livewire/Concerns/InteractsWithPressReleaseTags.php
-
message: '#^Trait App\\Livewire\\Concerns\\ProvidesPressReleaseFormRules is used zero times and is not analysed\.$#'
identifier: trait.unused
count: 1
path: app/Livewire/Concerns/ProvidesPressReleaseFormRules.php
-
message: '#^Trait App\\Livewire\\Concerns\\ProvidesPressReleasePresubmitChecks is used zero times and is not analysed\.$#'
identifier: trait.unused
count: 1
path: app/Livewire/Concerns/ProvidesPressReleasePresubmitChecks.php
-
message: '#^Trait App\\Livewire\\Concerns\\ProvidesUserFormRules is used zero times and is not analysed\.$#'
identifier: trait.unused
count: 1
path: app/Livewire/Concerns/ProvidesUserFormRules.php
-
message: '#^Using nullsafe property access "\?\-\>name" on left side of \?\? is unnecessary\. Use \-\> instead\.$#'
identifier: nullsafe.neverNull
count: 1
path: app/Models/Category.php
-
message: '#^PHPDoc tag @var with type Illuminate\\Database\\Eloquent\\Model is not subtype of native type \$this\(App\\Models\\Company\)\.$#'
@ -481,11 +643,47 @@ parameters:
path: app/Models/Company.php
-
message: '#^Using nullsafe property access on non\-nullable type string\. Use \-\> instead\.$#'
message: '#^Using nullsafe property access on non\-nullable type App\\Enums\\Portal\. Use \-\> instead\.$#'
identifier: nullsafe.neverNull
count: 1
path: app/Models/Company.php
-
message: '#^Using nullsafe property access "\?\-\>version" on left side of \?\? is unnecessary\. Use \-\> instead\.$#'
identifier: nullsafe.neverNull
count: 1
path: app/Models/LegalDocument.php
-
message: '#^Call to an undefined method Illuminate\\Database\\Eloquent\\Builder\:\:forPlacement\(\)\.$#'
identifier: method.notFound
count: 1
path: app/Models/PressRelease.php
-
message: '#^Call to an undefined method Illuminate\\Database\\Eloquent\\Builder\:\:published\(\)\.$#'
identifier: method.notFound
count: 1
path: app/Models/PressRelease.php
-
message: '#^Call to an undefined method Illuminate\\Database\\Eloquent\\Builder\<Illuminate\\Database\\Eloquent\\Model\>\:\:active\(\)\.$#'
identifier: method.notFound
count: 1
path: app/Models/PressRelease.php
-
message: '#^Method App\\Models\\PressRelease\:\:embargoAtLocal\(\) should return Illuminate\\Support\\Carbon\|null but returns Carbon\\Carbon\|null\.$#'
identifier: return.type
count: 1
path: app/Models/PressRelease.php
-
message: '#^Method App\\Models\\PressRelease\:\:scheduledAtLocal\(\) should return Illuminate\\Support\\Carbon\|null but returns Carbon\\Carbon\|null\.$#'
identifier: return.type
count: 1
path: app/Models/PressRelease.php
-
message: '#^PHPDoc tag @var with type Illuminate\\Database\\Eloquent\\Model is not subtype of native type \$this\(App\\Models\\PressRelease\)\.$#'
identifier: varTag.nativeType
@ -493,64 +691,28 @@ parameters:
path: app/Models/PressRelease.php
-
message: '#^Call to function is_string\(\) with null will always evaluate to false\.$#'
identifier: function.impossibleType
count: 1
path: app/Models/PressReleaseImage.php
message: '#^Using nullsafe method call on non\-nullable type Illuminate\\Database\\Eloquent\\Collection\<int, App\\Models\\PressReleaseImage\>\. Use \-\> instead\.$#'
identifier: nullsafe.neverNull
count: 2
path: app/Models/PressRelease.php
-
message: '#^Call to function method_exists\(\) with Illuminate\\Filesystem\\FilesystemAdapter and ''url'' will always evaluate to true\.$#'
identifier: function.alreadyNarrowedType
message: '#^Using nullsafe property access "\?\-\>balance_credits" on left side of \?\? is unnecessary\. Use \-\> instead\.$#'
identifier: nullsafe.neverNull
count: 1
path: app/Models/PressReleaseImage.php
path: app/Models/User.php
-
message: '#^Offset string on string\|null on left side of \?\? does not exist\.$#'
identifier: nullCoalesce.offset
count: 1
path: app/Models/PressReleaseImage.php
message: '#^Using nullsafe property access "\?\-\>press_release_quota" on left side of \?\? is unnecessary\. Use \-\> instead\.$#'
identifier: nullsafe.neverNull
count: 2
path: app/Models/User.php
-
message: '#^Result of \|\| is always true\.$#'
identifier: booleanOr.alwaysTrue
message: '#^Call to an undefined method Illuminate\\Database\\Eloquent\\Builder\:\:grandfatheredNow\(\)\.$#'
identifier: method.notFound
count: 1
path: app/Models/PressReleaseImage.php
-
message: '#^Unreachable statement \- code above always terminates\.$#'
identifier: deadCode.unreachable
count: 1
path: app/Models/PressReleaseImage.php
-
message: '#^Call to function in_array\(\) with arguments string, array\{App\\Enums\\PressReleaseStatus\:\:Draft, App\\Enums\\PressReleaseStatus\:\:Rejected, App\\Enums\\PressReleaseStatus\:\:Review\} and true will always evaluate to false\.$#'
identifier: function.impossibleType
count: 1
path: app/Policies/PressReleasePolicy.php
-
message: '#^Call to function in_array\(\) with arguments string, array\{App\\Enums\\PressReleaseStatus\:\:Draft, App\\Enums\\PressReleaseStatus\:\:Rejected\} and true will always evaluate to false\.$#'
identifier: function.impossibleType
count: 1
path: app/Policies/PressReleasePolicy.php
-
message: '#^Result of && is always false\.$#'
identifier: booleanAnd.alwaysFalse
count: 1
path: app/Policies/PressReleasePolicy.php
-
message: '#^Strict comparison using \!\=\= between string and App\\Enums\\PressReleaseStatus\:\:Published will always evaluate to true\.$#'
identifier: notIdentical.alwaysTrue
count: 1
path: app/Policies/PressReleasePolicy.php
-
message: '#^Called ''env'' outside of the config directory which returns null when the config is cached, use ''config''\.$#'
identifier: larastan.noEnvCallsOutsideOfConfig
count: 1
path: app/Providers/ThemeServiceProvider.php
path: app/Models/UserPaymentOption.php
-
message: '#^Method App\\Services\\Admin\\AdminSlowRequestReporter\:\:entries\(\) should return Illuminate\\Support\\Collection\<int, array\<string, mixed\>\> but returns Illuminate\\Support\\Collection\<int, non\-empty\-array\<string, mixed\>\>\.$#'
@ -558,12 +720,6 @@ parameters:
count: 1
path: app/Services/Admin/AdminSlowRequestReporter.php
-
message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$status\.$#'
identifier: property.notFound
count: 1
path: app/Services/Api/ApiAccessEligibilityService.php
-
message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$requests\.$#'
identifier: property.notFound
@ -577,13 +733,7 @@ parameters:
path: app/Services/Api/ApiUsageReporter.php
-
message: '#^Cannot call method toIso8601String\(\) on string\.$#'
identifier: method.nonObject
count: 1
path: app/Services/Api/ApiUsageReporter.php
-
message: '#^Using nullsafe method call on non\-nullable type string\. Use \-\> instead\.$#'
message: '#^Using nullsafe method call on non\-nullable type Carbon\\Carbon\. Use \-\> instead\.$#'
identifier: nullsafe.neverNull
count: 1
path: app/Services/Api/ApiUsageReporter.php
@ -595,76 +745,22 @@ parameters:
path: app/Services/Api/LegacyApiAccessLogAnalyzer.php
-
message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$id\.$#'
identifier: property.notFound
count: 1
path: app/Services/Api/LegacyApiCustomerReporter.php
-
message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$invoice_date\.$#'
identifier: property.notFound
count: 1
path: app/Services/Api/LegacyApiCustomerReporter.php
-
message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$legacy_id\.$#'
identifier: property.notFound
count: 1
path: app/Services/Api/LegacyApiCustomerReporter.php
-
message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$legacy_portal\.$#'
identifier: property.notFound
count: 1
path: app/Services/Api/LegacyApiCustomerReporter.php
-
message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$number\.$#'
identifier: property.notFound
count: 1
path: app/Services/Api/LegacyApiCustomerReporter.php
-
message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$paid_at\.$#'
identifier: property.notFound
count: 1
path: app/Services/Api/LegacyApiCustomerReporter.php
-
message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$status\.$#'
identifier: property.notFound
count: 1
path: app/Services/Api/LegacyApiCustomerReporter.php
-
message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$total_cents\.$#'
identifier: property.notFound
count: 1
path: app/Services/Api/LegacyApiCustomerReporter.php
-
message: '#^Cannot access property \$value on string\.$#'
identifier: property.nonObject
count: 2
path: app/Services/Api/LegacyApiCustomerReporter.php
-
message: '#^Cannot call method toDateTimeString\(\) on string\.$#'
identifier: method.nonObject
count: 1
path: app/Services/Api/LegacyApiCustomerReporter.php
-
message: '#^Using nullsafe property access on non\-nullable type string\. Use \-\> instead\.$#'
message: '#^Using nullsafe property access on non\-nullable type App\\Enums\\Portal\. Use \-\> instead\.$#'
identifier: nullsafe.neverNull
count: 2
path: app/Services/Api/LegacyApiCustomerReporter.php
-
message: '#^Method App\\Services\\Auth\\MagicLinkGenerator\:\:createLoginLink\(\) should return array\{magic_link\: App\\Models\\MagicLink, plain_token\: string, expires_at\: Illuminate\\Support\\Carbon\} but returns array\{magic_link\: Illuminate\\Database\\Eloquent\\Model, plain_token\: string, expires_at\: Illuminate\\Support\\Carbon\}\.$#'
identifier: return.type
message: '#^Using nullsafe property access on non\-nullable type App\\Enums\\RegistrationType\. Use \-\> instead\.$#'
identifier: nullsafe.neverNull
count: 1
path: app/Services/Auth/MagicLinkGenerator.php
path: app/Services/Api/LegacyApiCustomerReporter.php
-
message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Relations\\Pivot\:\:\$role\.$#'
identifier: property.notFound
count: 1
path: app/Services/Auth/ContactAccessService.php
-
message: '#^Using nullsafe property access "\?\-\>id" on left side of \?\? is unnecessary\. Use \-\> instead\.$#'
@ -673,28 +769,40 @@ parameters:
path: app/Services/Auth/MagicLinkGenerator.php
-
message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$profile\.$#'
identifier: property.notFound
message: '#^Using nullsafe property access "\?\-\>currency" on left side of \?\? is unnecessary\. Use \-\> instead\.$#'
identifier: nullsafe.neverNull
count: 1
path: app/Services/Billing/LegacyInvoicePdfRenderer.php
path: app/Services/Billing/ManualInvoiceService.php
-
message: '#^Cannot call method betweenIncluded\(\) on string\.$#'
identifier: method.nonObject
message: '#^Using nullsafe property access "\?\-\>price_cents" on left side of \?\? is unnecessary\. Use \-\> instead\.$#'
identifier: nullsafe.neverNull
count: 1
path: app/Services/Billing/LegacyInvoicePdfRenderer.php
path: app/Services/Billing/ManualInvoiceService.php
-
message: '#^Cannot call method format\(\) on string\.$#'
identifier: method.nonObject
count: 4
path: app/Services/Billing/LegacyInvoicePdfRenderer.php
message: '#^Access to property \$id on an unknown class Laravel\\Cashier\\Concerns\\Stripe\\TaxId\.$#'
identifier: class.notFound
count: 1
path: app/Services/Billing/StripeCheckoutService.php
-
message: '#^Cannot call method label\(\) on string\.$#'
identifier: method.nonObject
count: 3
path: app/Services/Billing/LegacyInvoicePdfRenderer.php
message: '#^Access to property \$type on an unknown class Laravel\\Cashier\\Concerns\\Stripe\\TaxId\.$#'
identifier: class.notFound
count: 1
path: app/Services/Billing/StripeCheckoutService.php
-
message: '#^Access to property \$value on an unknown class Laravel\\Cashier\\Concerns\\Stripe\\TaxId\.$#'
identifier: class.notFound
count: 1
path: app/Services/Billing/StripeCheckoutService.php
-
message: '#^Using nullsafe property access "\?\-\>timestamp" on left side of \?\? is unnecessary\. Use \-\> instead\.$#'
identifier: nullsafe.neverNull
count: 1
path: app/Services/Billing/SubscriptionPeriodResolver.php
-
message: '#^Unsafe access to private property App\\Services\\CurrentPortalContext\:\:\$portal through static\:\:\.$#'
@ -702,6 +810,12 @@ parameters:
count: 4
path: app/Services/CurrentPortalContext.php
-
message: '#^Using nullsafe property access "\?\-\>role" on left side of \?\? is unnecessary\. Use \-\> instead\.$#'
identifier: nullsafe.neverNull
count: 1
path: app/Services/Customer/CustomerCompanyContext.php
-
message: '#^Offset ''filename'' on array\{dirname\?\: string, basename\: string, extension\?\: string, filename\: string\} on left side of \?\? always exists and is not nullable\.$#'
identifier: nullCoalesce.offset
@ -715,40 +829,160 @@ parameters:
path: app/Services/Import/PressReleaseImporter.php
-
message: '#^Access to an undefined property Illuminate\\Database\\Eloquent\\Model\:\:\$email\.$#'
message: '#^Using nullsafe property access "\?\-\>uuid" on left side of \?\? is unnecessary\. Use \-\> instead\.$#'
identifier: nullsafe.neverNull
count: 1
path: app/Services/Import/PressReleaseImporter.php
-
message: '#^Using nullsafe property access "\?\-\>translations" on left side of \?\? is unnecessary\. Use \-\> instead\.$#'
identifier: nullsafe.neverNull
count: 2
path: app/Services/PressRelease/BoostService.php
-
message: '#^Using nullsafe property access on non\-nullable type App\\Enums\\Portal\. Use \-\> instead\.$#'
identifier: nullsafe.neverNull
count: 1
path: app/Services/PressRelease/CorrectionService.php
-
message: '#^Instanceof between App\\Enums\\PressReleaseStatus and App\\Enums\\PressReleaseStatus will always evaluate to true\.$#'
identifier: instanceof.alwaysTrue
count: 1
path: app/Services/PressRelease/PressReleaseService.php
-
message: '#^Method App\\Services\\PressRelease\\PressReleaseService\:\:resolvePublishedAt\(\) should return Illuminate\\Support\\Carbon but returns Carbon\\Carbon\.$#'
identifier: return.type
count: 3
path: app/Services/PressRelease/PressReleaseService.php
-
message: '#^Using nullsafe property access "\?\-\>name" on left side of \?\? is unnecessary\. Use \-\> instead\.$#'
identifier: nullsafe.neverNull
count: 1
path: app/Services/PressRelease/PressReleaseService.php
-
message: '#^Comparison operation "\<" between int\<1, max\> and 1 is always false\.$#'
identifier: smaller.alwaysFalse
count: 2
path: app/Services/PressRelease/ProofPdfRenderer.php
-
message: '#^Instanceof between App\\Enums\\Portal and App\\Enums\\Portal will always evaluate to true\.$#'
identifier: instanceof.alwaysTrue
count: 1
path: app/Services/PressRelease/ProofPdfRenderer.php
-
message: '#^Result of \|\| is always false\.$#'
identifier: booleanOr.alwaysFalse
count: 1
path: app/Services/PressRelease/ProofPdfRenderer.php
-
message: '#^Access to an undefined property App\\Models\\Company\:\:\$recent_releases_count\.$#'
identifier: property.notFound
count: 1
path: app/Services/PressRelease/PressReleaseService.php
path: app/Services/Web/CategoryPageData.php
-
message: '#^Call to function in_array\(\) with arguments string, array\<App\\Enums\\PressReleaseStatus\> and true will always evaluate to false\.$#'
identifier: function.impossibleType
message: '#^Access to an undefined property App\\Models\\Company\:\:\$today_releases_count\.$#'
identifier: property.notFound
count: 1
path: app/Services/PressRelease/PressReleaseService.php
path: app/Services/Web/CategoryPageData.php
-
message: '#^Instanceof between string and App\\Enums\\PressReleaseStatus will always evaluate to false\.$#'
identifier: instanceof.alwaysFalse
count: 1
path: app/Services/PressRelease/PressReleaseService.php
message: '#^Call to an undefined method Illuminate\\Database\\Eloquent\\Builder\:\:forPortalValues\(\)\.$#'
identifier: method.notFound
count: 2
path: app/Services/Web/CategoryPageData.php
-
message: '#^Strict comparison using \!\=\= between string and App\\Enums\\PressReleaseStatus will always evaluate to true\.$#'
identifier: notIdentical.alwaysTrue
message: '#^Access to an undefined property App\\Models\\Company\:\:\$recent_releases_count\.$#'
identifier: property.notFound
count: 1
path: app/Services/PressRelease/PressReleaseService.php
path: app/Services/Web/HomePageData.php
-
message: '#^Strict comparison using \=\=\= between string and App\\Enums\\PressReleaseStatus\:\:Published will always evaluate to false\.$#'
identifier: identical.alwaysFalse
message: '#^Access to an undefined property App\\Models\\Company\:\:\$today_releases_count\.$#'
identifier: property.notFound
count: 1
path: app/Services/PressRelease/PressReleaseService.php
path: app/Services/Web/HomePageData.php
-
message: '#^Called ''env'' outside of the config directory which returns null when the config is cached, use ''config''\.$#'
identifier: larastan.noEnvCallsOutsideOfConfig
message: '#^Call to an undefined method Illuminate\\Database\\Eloquent\\Builder\:\:forPortalValues\(\)\.$#'
identifier: method.notFound
count: 5
path: app/Services/Web/HomePageData.php
-
message: '#^Call to an undefined method Illuminate\\Database\\Eloquent\\Builder\<Illuminate\\Database\\Eloquent\\Model\>\:\:forPortalValues\(\)\.$#'
identifier: method.notFound
count: 1
path: bootstrap/app.php
path: app/Services/Web/HomePageData.php
-
message: '#^Using nullsafe property access "\?\-\>name" on left side of \?\? is unnecessary\. Use \-\> instead\.$#'
identifier: nullsafe.neverNull
count: 1
path: app/Services/Web/HomePageData.php
-
message: '#^Call to an undefined method Illuminate\\Database\\Eloquent\\Builder\<Illuminate\\Database\\Eloquent\\Model\>\:\:visibleInArchive\(\)\.$#'
identifier: method.notFound
count: 1
path: app/Services/Web/HubPageData.php
-
message: '#^Call to function is_array\(\) with list\<array\{label\: string, value\: string, delta\: string, positive\: bool\}\> will always evaluate to true\.$#'
identifier: function.alreadyNarrowedType
count: 1
path: app/Services/Web/MarketDataService.php
-
message: '#^Call to an undefined method Illuminate\\Database\\Eloquent\\Builder\:\:forPortalValues\(\)\.$#'
identifier: method.notFound
count: 1
path: app/Services/Web/NewsroomDetailPageData.php
-
message: '#^Call to an undefined method Illuminate\\Database\\Eloquent\\Builder\:\:forPortalValues\(\)\.$#'
identifier: method.notFound
count: 1
path: app/Services/Web/NewsroomsPageData.php
-
message: '#^Call to an undefined method Illuminate\\Database\\Eloquent\\Builder\:\:forPortalValues\(\)\.$#'
identifier: method.notFound
count: 1
path: app/Services/Web/ReleaseDetailPageData.php
-
message: '#^Call to an undefined method Illuminate\\Support\\Collection\<\(int\|string\), mixed\>\:\:modelKeys\(\)\.$#'
identifier: method.notFound
count: 1
path: app/Services/Web/ReleaseDetailPageData.php
-
message: '#^Call to an undefined method Illuminate\\Database\\Eloquent\\Builder\:\:forPortalValues\(\)\.$#'
identifier: method.notFound
count: 1
path: app/Services/Web/VeroeffentlichenPageData.php
-
message: '#^Method App\\Support\\CategoryNavigation\:\:query\(\) should return Illuminate\\Support\\Collection\<int, array\{label\: string, slug\: string, href\: string\}\> but returns Illuminate\\Support\\Collection\<int, array\{label\: string, slug\: non\-falsy\-string, href\: string\}\>\.$#'
identifier: return.type
count: 1
path: app/Support/CategoryNavigation.php
-
message: '#^Call to function is_array\(\) with array\<string, mixed\> will always evaluate to true\.$#'
identifier: function.alreadyNarrowedType
count: 1
path: app/Support/DomainAssetContext.php
-
message: '#^Call to an undefined method Faker\\Generator\:\:secondaryAddress\(\)\.$#'

View file

@ -4,6 +4,15 @@ includes:
- phpstan-baseline.neon
parameters:
# ZWINGEND für dieses Projekt: Alle Models deklarieren ihre Casts über die
# `casts()`-METHODE (Laravel-11-Stil). Laravel führt sie erst zur Laufzeit
# mit `$this->casts` zusammen, Larastan instanziiert Models aber ohne
# Konstruktor und sieht sie deshalb nicht — ohne diesen Schalter gilt
# `$pressRelease->status` als `string` statt als `PressReleaseStatus` und
# jeder Enum-Vergleich wird als „immer falsch" gemeldet. Larastans Default
# ist `false`.
parseModelCastsMethod: true
level: 4
paths:
- app