Skip to content

chore: raise PHPStan to level 8 and add PHP 8.5 to CI - #58

Merged
Fredxd merged 5 commits into
AppoloDev:mainfrom
Vondry:chore/static-analysis-level-8
Jul 27, 2026
Merged

Fredxd merged 5 commits into
AppoloDev:mainfrom
Vondry:chore/static-analysis-level-8

Conversation

@Vondry

@Vondry Vondry commented Jul 26, 2026

Copy link
Copy Markdown
Contributor

Summary

Raises static analysis from PHPStan level 5 to level 8 in three incremental steps, fixing every issue surfaced at each level. Also adds PHP 8.5 to the CI matrix.

No behavioural changes are intended. Most changes consist of improved type annotations and null guards on paths that were already unreachable or correct in practice, with two genuine robustness improvements noted below.

Changes by level

Level 6 — iterable value types & missing return types

  • ContentField::getFieldDefinition() and getField() — annotated $fields as array<int, string>.
  • SeoInjectorWidget::run() — annotated $params as array<string, mixed>.
  • Seo — typed $defaultsOverride and $seoData as array<string, mixed>, and the constructor's $config as Collection<string, mixed>.
  • SeoExtension — added @return array<string, mixed> to seoGetConfig() and Collection<string, mixed> to getExtensionConfig().
  • SeoField::getTwigValue() — declared a : Markup return type. The parent method's untyped @return string|array|Markup|bool annotation was the source of the unspecified array-type warning.
  • Seo::ogtype() and robots() — declared : string, casting the configuration-override branch, which was the only path not already guaranteed to return a string.

Level 7 — unresolved union types

  • Extension::initialize() — getContainer()->get('translator') returns object; narrowed it to TranslatorInterface via an inline annotation, matching the existing pattern used in SeoExtension.

  • Extension::install() — getParameter() returns array|bool|float|int|string|UnitEnum|null. A simple (string) cast is insufficient because arrays are not safely castable. The code now validates with is_string() and throws otherwise.

    This is a genuine robustness improvement: a non-string bolt.public_folder value would previously have caused Filesystem::mirror() to copy assets into an invalid path.

Level 8 — member access on nullable values

  • ContentField::getFieldDefinition() — Content::getDefinition() returns ?ContentType; added an early return before dereferencing ->get('fields').

  • Seo::cleanUp() — preg_replace() returns string|null (null on failure, such as malformed UTF-8 with the /u modifier), while the method declares string. The method now falls back to the original input instead of propagating null.

    This is a genuine robustness improvement rather than a PHPStan-only change.

  • SeoExtension::getExtension() — ExtensionRegistry::getExtension() returns ?ExtensionInterface; now throws if the extension is not registered instead of returning null from a non-nullable method.

CI

Added PHP 8.5 to the code_analysis matrix after verifying compatibility:

  • composer check-platform-reqs passes on PHP 8.5 (exit code 0).
  • composer why-not php 8.5 reports no dependencies with an upper PHP version constraint below 8.5.

The rector_analysis job remains pinned to PHP 8.4 because it is a single, non-matrix job and Rector's output is version-independent for this project.

Testing

  • Extension works as expected
image

@Fredxd

Fredxd commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

@Vondry Hi, Thanks for PR
There are one or more conflicts due to precedent PR
Can you resolve conflict please ?
Regards

Vondry added 5 commits July 27, 2026 13:07
Level 6 requires iterable value types and explicit return types. Annotate
the array/Collection generics on ContentField, Seo, SeoExtension and
SeoInjectorWidget, declare SeoField::getTwigValue(): Markup, and give
Seo::ogtype()/robots() a string return type (casting the config override
branch, which was the only untyped path).
Level 7 flags unresolved union types. Narrow the container lookups in
Extension: annotate the translator service as TranslatorInterface, and
guard that kernel.project_dir and bolt.public_folder are strings before
building the asset destination path.
Level 8 flags member access on nullables. Guard the nullable content
definition in ContentField::getFieldDefinition(), fall back to the
un-collapsed string when preg_replace() fails in Seo::cleanUp(), and
throw when the extension is missing from the registry instead of
returning null from SeoExtension::getExtension().

# Conflicts:
#	src/Seo/Seo.php
Rector's dry-run in CI flags the fully-qualified \RuntimeException
introduced with the level 7 and 8 null guards.
@Vondry
Vondry force-pushed the chore/static-analysis-level-8 branch from 6e13bee to b5fbc31 Compare July 27, 2026 11:10
@Vondry

Vondry commented Jul 27, 2026

Copy link
Copy Markdown
Contributor Author

@Vondry Hi, Thanks for PR There are one or more conflicts due to precedent PR Can you resolve conflict please ? Regards

Hi @Fredxd , thanks for the quick merges. Just rebased it and resolved conflicts.

Cheers 🎉

@Fredxd
Fredxd merged commit 5ec5fb4 into AppoloDev:main Jul 27, 2026
21 checks passed
@Fredxd

Fredxd commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

Thanks again.
I have release a new tag ;)
Regards

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants