Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Phpstan Typo3 Laravel Package

saschaegerer/phpstan-typo3

PHPStan extension for TYPO3 CMS that teaches static analysis about TYPO3 APIs, service container, Extbase, and framework classes. It adds TYPO3-specific rules and stubs to catch type errors and bugs earlier in extensions and projects.

View on GitHub
Deep Wiki
Context7

Getting Started

1. Install alongside PHPStan

composer require --dev phpstan/phpstan phpstan/extension-installer saschaegerer/phpstan-typo3

phpstan/extension-installer is optional but recommended — it auto-registers every installed PHPStan extension, so you do not have to touch your config when adding this package.

2. Create phpstan.neon (or .dist)

includes:
    - vendor/saschaegerer/phpstan-typo3/extension.neon

parameters:
    level: 5
    paths:
        - packages/my_sitepackage/Classes
        - config/system
    # TYPO3 projects routinely blow the default 128M
    # (set on the CLI instead if you prefer)

If extension-installer is active, drop the includes: line so you do not register the extension twice.

3. Confirm TYPO3 core is actually installed

The extension works by reflecting real TYPO3 classes. If typo3/cms-core (and the specific system extensions you use) are not in vendor/, PHPStan will report hundreds of "class not found" errors before it ever gets to your code. Composer-based TYPO3 v11+ projects already satisfy this.

4. First run

vendor/bin/phpstan analyse --memory-limit=2G

Where to look first — the package is small and readable; read these in order:

  • vendor/saschaegerer/phpstan-typo3/extension.neon — shows exactly which reflection extensions and rules are registered.
  • src/ — dynamic return type extensions (primarily GeneralUtility::makeInstance()) and $GLOBALS type providers.
  • src/Rules/ — the framework-specific rules. Read each rule class; they are short, and knowing what they assert tells you what the extension adds on top of plain PHPStan.

First useful case: an Extbase extension where GeneralUtility::makeInstance(MyClass::class) currently resolves to object and every call site needs /** @var MyClass $x */. After enabling the extension the return type is inferred from the class-string<T> argument, so those annotations can be deleted and the real errors underneath become visible.

Implementation Patterns

Adopt by ratcheting, not by fixing everything

Legacy TYPO3 codebases will not pass level 5 cold. The productive loop is:

vendor/bin/phpstan analyse --generate-baseline
vendor/bin/phpstan analyse          # green
# then bump level, regenerate baseline, repeat

Commit phpstan-baseline.neon so the team shares the same debt ceiling. Delete baseline entries as you touch files; never let a PR add new ones.

Scope paths deliberately

Analyse your own code, not vendor/:

parameters:
    paths:
        - packages/*/Classes
        - packages/*/Configuration
    excludePaths:
        - packages/*/Classes/Domain/Model/*

Domain models in Extbase are mostly property declarations plus getters/setters — low signal, high noise at low levels.

Lean on GeneralUtility::makeInstance() inference

// return type is MyService — no @var needed
$service = GeneralUtility::makeInstance(MyService::class);

// stays `object`: argument is not a literal class-string
$service = GeneralUtility::makeInstance($this->classNameFromTypoScript);

When you must pass a dynamic class name, carry the generic yourself:

/** @var class-string<MyService> $className */
$className = $configuration['className'];
$service = GeneralUtility::makeInstance($className); // MyService

Document Extbase magic finders with @method

The extension reflects classes; it does not teach PHPStan about Repository::__call() magic. Declare the methods explicitly:

/**
 * @method MyModel|null findOneBySlug(string $slug)
 * @method MyModel[] findByPid(int $pid)
 * @extends \TYPO3\CMS\Extbase\Persistence\Repository<MyModel>
 */
class MyModelRepository extends Repository {}

Same for @Inject properties — annotate the property type, since PHPStan cannot see the DI container.

Per-extension configs, merged at the root

Keep each sitepackage self-describing:

# packages/my_sitepackage/phpstan.neon
includes:
    - ../../vendor/saschaegerer/phpstan-typo3/extension.neon
parameters:
    level: 6
    paths:
        - Classes

and in the root phpstan.neon include each one and use paths for the union. This lets an individual extension be stricter than the legacy remainder.

Use error identifiers for surgical ignores

PHPStan attaches identifiers to its errors (and so do custom rules). Ignore by identifier, never by message regex:

parameters:
    ignoreErrors:
        - identifier: typo3.someRuleName
          path: packages/legacy_ext/*

Run with --error-format=raw and -v to surface identifiers when you are unsure.

CI wiring

- run: composer install --no-progress
- run: vendor/bin/phpstan analyse --no-progress --error-format=github --memory-limit=2G

Cache the tmpDir (default .phpstan in the project root, or whatever you set via parameters.tmpDir) between runs — on large TYPO3 instances the result cache is the difference between a 40-second and a 6-minute job. Set tmpDir outside vendor/.

Gotchas and Tips

Pin the package against your PHPStan major

This is the single biggest failure mode. The package's composer.json constrains both phpstan/phpstan and the PHP versions it supports. Upgrading PHPStan 1 → 2 without checking the constraint will either fail to install or silently drop reflection services. Read the constraint before any PHPStan bump, and run a full analysis on the upgrade branch — a missing reflection extension shows up as a flood of new object/mixed related errors, not as a crash.

Class aliases and legacy names do not resolve

TYPO3 ships a class alias map for renamed classes. PHPStan only sees what Composer's autoloader can resolve. If you analyse old code using pre-v9 class names, either migrate the names or help the analyser find them:

parameters:
    scanFiles:
        - typo3/sysext/core/Classes/Core/ClassAliasMap.php

Prefer migrating to FQCNs — aliases resolve at runtime but produce weak or wrong types.

Extensions outside Composer are invisible

typo3conf/ext/* in a non-Composer (legacy) install is not autoloaded. Either add PSR-4 autoload entries to the root composer.json, or point PHPStan at the sources:

parameters:
    scanDirectories:
        - typo3conf/ext/my_ext/Classes

Without this, every reference to that extension's classes becomes an error.

XCLASS overrides are not modelled

TYPO3's XCLASS mechanism replaces a core class at runtime. PHPStan reflects the original class only. Code that relies on an XCLASS-modified signature will type-check against the core definition and can fail at runtime. Treat XCLASS-affected call sites as needing manual review; consider them a candidate for migration to real DI/events anyway.

$GLOBALS['TCA'] typing is coarse

The extension gives $GLOBALS a useful shape, but array keys are not table- or column-precise. Inside a TCA file you will still get mixed for column config. Narrow locally:

/** @var array{type: string, config?: array<string, mixed>} $column */
$column = $GLOBALS['TCA']['tt_content']['columns']['my_field'];

Do not chase perfect TCA typing — it is a data structure, not an API.

makeInstance() falls back to object silently

No error, no warning — you just lose types downstream. Symptoms: Cannot call method foo() on object far away from the real cause. When you see that, grep for makeInstance( and check whether the argument is a literal ::class, a class-string<T>-annotated variable, or something untyped.

Double registration

If phpstan/extension-installer is installed and you also includes: extension.neon manually, rules can be registered twice and behave oddly (duplicate errors, or ignore rules not matching). Pick one mechanism. Teams that vendor a shared config often disable the installer ("phpstan/extension-installer": {"allow": false} style config) and include explicitly for reproducibility.

Baseline hygiene

--generate-baseline writes messages, and messages change when you upgrade PHPStan or this package, producing "ignored error pattern was not matched" failures. Set reportUnmatchedIgnoredErrors: false while upgrading, then regenerate the baseline and turn the check back on.

Debugging a surprising type

Drop into the code you are analysing:

\PHPStan\dumpType($service); // prints the inferred type, then fails the build

Combine with vendor/bin/phpstan analyse -v --debug path/to/File.php to see which reflection extension handled the expression. If a TYPO3 class comes back as object or mixed when you expected better, the cause is almost always (a) core not installed, (b) class not autoloadable, or (c) a non-literal class-string argument.

Extensions points worth knowing about

  • parameters.tmpDir — move off the default if your CI mounts the workspace read-only or if you want a clean cache per job.
  • parameters.bootstrapFiles — for a TYPO3 Environment bootstrap or constant definitions your code relies on (e.g. TYPO3_MODE, TYPO3_REQUESTTYPE constants in older versions); define them via define() in a bootstrap file to avoid false "undefined constant" errors.
  • Rule toggles — the shipped rules are registered in extension.neon. To disable one, remove the corresponding service entry from a copy of the config, or ignore its identifier in ignoreErrors. Read the rule source first; most are one-screen checks and easy to reason about.
  • --memory-limit — TYPO3's class graph is large. 2G is a sane default for CI; some monorepos need more.
  • Fluid templates, TypoScript, and TCA runtime behaviour are entirely outside PHPStan's reach — do not expect this extension, or any PHPStan extension, to cover them. Pair it with functional tests for anything template- or TypoScript-driven.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
se7enxweb/flysystem
lampwick/laravel
jeslage/statamic-emojis
micahdshackelford/rmap-laravel
draftsmaninc/draftsman
square-bit/laravel-volition
himito/laravel-mailman
htinlynn/genius-helper
maco-studios/hephaestus
elmogy/larafast
xeviant/laravel-paystack
gorilladash/laravel-website-sdk
martialbe/flysystem-oss
chondal/3312client
jeroen-g/flickr
opscale-co/nova-genbi
itsjustvita/laravel-retention
tequilarapido/trackit
gregsphotography/site-builder-filament
cabaoctavio/laravel-query-log