SilverStripe 6 compatibility, dev infra, and PHPUnit suite#16
Open
erikfrerejean wants to merge 14 commits into
Open
SilverStripe 6 compatibility, dev infra, and PHPUnit suite#16erikfrerejean wants to merge 14 commits into
erikfrerejean wants to merge 14 commits into
Conversation
Replaces the legacy single-container php-cs-fixer-only Docker setup with the FrankenPHP + MySQL stack pattern used in silverstripe-media-field and silverstripe-grid. Adds GitHub Actions for code-style, static analysis, and a PHPUnit matrix; adds dependabot, .gitattributes export-ignore, and .dockerignore. Module composer.json is intentionally untouched — SS6 compat and the PHPUnit suite are followup tasks. The dev env and CI are pinned to PHP 8.2+ because FrankenPHP doesn't publish a PHP 8.1 image.
Establishes the module's first tests on top of the FrankenPHP dev infra, covering both DataObjects' behavior — slug auto-fill, protected_menus config gating, permission delegation, getLink across all LinkType branches (with QueryString/AnchorText config flips), LinkingMode, getLevel, and LastEdited cascade on write/delete. Adds a `coverage` make target and declares the package as silverstripe-vendormodule so SilverStripe's class manifest scans the module's tests/ in test mode.
Bumps PHPStan from level 9 to max and removes treatPhpDocTypesAsCertain: false (which weakened analysis below max). Adds cambis/silverstan ^2.1 so PHPStan understands SilverStripe's config conventions (private static $db / $has_one / $has_many config properties, has_one method/property typing, Config_ForClass access), which was the root cause of the bulk of the original 61 errors. Code fixes: - Specify HasManyList<MenuItem> generic on @method Items() - Add @method Menu Menu() on MenuItem (silverstan resolves via has_one) - Use ::create() instead of new GridFieldConfig_RelationEditor() - Replace self::config()->prop with self::config()->get('prop') (silverstan.configurationProperty.unresolvableType / .unsafe) - Guard $this->LinkedPage()/$this->File() with ->exists() before ->Link() - Drop dead `?? ''` after exhaustive match in getLink() - Add ?Member docblocks on canX() (parent in SS5 is untyped, so narrowing the typehint in PHP would violate LSP) and pass Permission::checkMember($member ?? 0, ...) to satisfy int|Member — the framework's `if (!$member)` branch produces identical runtime behaviour for 0 and null. - Type onAfterWrite() return as void - Add @return array<int, string> on get_template_global_variables() - Use is_array() guard in IsProtected() so in_array() haystack is typed, and switch from $this->Config() (deprecated ViewableData) to static::config().
- Module: PHP ^8.3, silverstripe/cms ^6, silverstripe/framework ^6, silverstripe/admin ^3, unclecheese/display-logic ^4 (was misnamed silverstripe/display-logic ^3 — code imports UncleCheese\…), symbiote/silverstripe-gridfieldextensions ^5. Drop branch-alias to follow the SilverStripe `6` branch convention. - Dev infra: Dockerfile defaults to PHP 8.3; dev composer pulls recipe-cms ^6, phpunit ^11, cambis/silverstan, phpstan-deprecation-rules, type-coverage, wernerkrauss/silverstripe-rector. PHPStan: level max + 100% type coverage + deprecation rules. CI matrix: PHP 8.3/8.4/8.5, triggers on `6`. Drop php-cs-fixer entirely (Rector handles code style). - Source: declare(strict_types=1) + #[Override] across the module. Menu::forTemplate() returns string to match SS6 ModelData; ViewableData → ModelData (Rector). dataFieldByName() and Controller::curr() null- guarded for SS6's narrower return types. canX permission methods typed `mixed $member` to satisfy 100% type coverage without violating LSP against DataObject's untyped parent — same pattern as silverstripe-grid.
Promote MenuItem::LinkType to a first-class WeDevelop\Menustructure\Model\LinkType backed enum and constrain the column with MySQL Enum. Drops the private const string + $link_types static array + Varchar trio that was only there to support runtime extension via updateLinkTypes — that hook is now gone. Adds a new "breakpoint" link type so downstream templates can render structural markers without the ambiguity of overloading no-link. BREAKING CHANGE: updateLinkTypes extension and link_types config array removed; LinkType column changes from Varchar to Enum (dev/build performs an in-place ALTER TABLE — existing values are preserved).
Replace Menu::onBeforeDelete() manual loop with $cascade_deletes config and add the same on MenuItem so deleting a sub-tree root no longer orphans its descendants. Drop MenuItem's $owns = ['File'] — neither model is Versioned, so the publish cascade never runs and the config was dead. Pin the MenuItem cascade with a regression test that deletes the middle of a 3-level chain and asserts descendants are removed while parent and siblings stay intact. Rename Menu's existing cascade test to drop the now-stale onBeforeDelete reference.
PHPDoc test metadata is deprecated in PHPUnit 10+ and removed in PHPUnit 12; switch to the supported attribute form.
Splits the single docs/configuration.md into a grid-style layout sized for
this module: docs/usage/{templates,configuration}.md, docs/architecture/
data-model.md, and docs/contributing.md. README rewritten as a concise
index with Requirements / Installation / Usage / Documentation pointers.
CHANGELOG.md adopts Keep a Changelog format with a single 6.0.0-rc.1
entry aggregating the SS6 baseline, the LinkType enum BC, the
updateLinkTypes hook removal, the cascade_deletes refactor, the
FrankenPHP dev infra, the PHPStan max bump, and the new test suite.
Pre-RC1 history continues to live as GitHub releases.
Updates project guidance to reflect three changes already on the branch: LinkType is now a backed PHP enum with a Breakpoint case, hierarchy cleanup runs through cascade_deletes on both models, and CHANGELOG.md is actively maintained alongside the new docs/ layout.
Adds export-ignore entries for .claude/, .dockerignore, .vscode/, and CLAUDE.md so they no longer ship in Packagist tarballs alongside the runtime sources.
Aligns Menu's permission code construction with MenuItem so both models build the CMS_ACCESS_ string from the class constant rather than a hardcoded FQCN literal — refactor-safe and statically verifiable.
BREAKING CHANGE: getLink() return type widens from string to ?string. no-link, breakpoint, page/file with no relation, and url with empty Url all now return null instead of ''. The updateLink extension hook signature becomes updateLink(?string &$link). Also collapses the duplicated LinkType::Page checks for query-string and anchor decoration into a single guarded block, and replaces sprintf with .= for the trivial concat.
Documentation should describe current state — migration history is preserved in the CHANGELOG and git log.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
First cut of the
6.0.0-rc.1release line. Rolls together the SilverStripe 6 / PHP 8.3 baseline, a fresh FrankenPHP-based dev environment, the module's first PHPUnit suite, and supporting CI/static-analysis. SeeCHANGELOG.mdfor the full breakdown — highlights below.Breaking
silverstripe/framework ^6.0,silverstripe/cms ^6.0,silverstripe/admin ^3.0,unclecheese/display-logic ^4.0,symbiote/silverstripe-gridfieldextensions ^5.0. The previoussilverstripe/display-logic ^3constraint was wrong (the code importsUncleCheese\…) and is corrected here.MenuItem::LinkTypeis now a backed PHP enum (WeDevelop\Menustructure\Model\LinkType). TheLINK_TYPE_*constants are gone; compare againstLinkType::Page->valueor useLinkType::tryFrom(). DB column moves fromVarcharto MySQLEnum('page,url,file,no-link,breakpoint', 'no-link')—dev/buildmigrates in place, existing rows preserved.updateLinkTypeshook +link_typesconfig removed. Valid link types are closed at the PHP level — custom types now require a code change to this module. Trade-off against runtime extensibility for verifiable static analysis.MenuItem::getLink()returns?string(wasstring).no-link,breakpoint, unset relations, and empty URLs now returnnullinstead of''.updateLinkextension hook signature changes accordingly.Added
breakpointlink type for structural markers (dividers, mega-menu column breaks) — distinct from the labelledno-linkcase.tests/Model/Menu*Test.php) covering slug auto-fill,protected_menus, permission delegation,getLink()across everyLinkTypebranch (withQueryString/AnchorTextconfig flips),LinkingMode,getLevel, and theLastEditedcascade on write/delete.make analyse+make rector-dry) and a PHPUnit matrix across PHP 8.3 / 8.4 / 8.5 on every push and PR to6..docker/, replacing the legacy single-container php-cs-fixer setup.maketargets fortest,coverage,analyse,rector,rector-dry,flush,dev-build,sh.make rector/make rector-dry) targeting SS5→SS6 + PHP 8.3 + standard presets — also enforces code style, replacingphp-cs-fixerentirely.docs/—usage/,architecture/,contributing.md. CHANGELOG adopts Keep a Changelog format.maxwithcambis/silverstan,phpstan/phpstan-deprecation-rules, and 100% type coverage.Internal cleanup
$cascade_deleteson bothMenuandMenuIteminstead of a manualonBeforeDeleteloop. Deleting a sub-tree root no longer orphans descendants.MenuItem::$ownsremoved — neither model isVersioned, so the publish cascade never ran (dead config).strict_types=1and uses#[Override]on overridden methods.mixed $member = nullto satisfy 100% type-coverage without narrowing the parentDataObjectcontract (LSP).Test plan
6target: static-analysis job + PHPUnit matrix (8.3 / 8.4 / 8.5)make analysepasses at level maxmake testpassesmake rector-dryreports no pending changesmake up && make dev-build, create aMenuwith eachLinkType(includingbreakpoint), confirm CMS field display-logic toggles correctly anddev/buildmigrates theLinkTypecolumn on an existing DBprotected_menusconfig blocks deletion and forcesSlugread-only