Repository navigation
Release 3.0.0 - #38
Merged
Merged
Conversation
The `php forge` banner and `php forge about` always showed "0.1.0"/a
mislabeled app version regardless of the actually-installed
marrow/framework version, because Console\Kernel and AboutCommand both
read config('app.version') (the application's own version, itself
defaulting to 0.1.0 in a fresh skeleton) rather than anything
reflecting the framework package itself.
Added framework_version(), resolved from Composer\InstalledVersions,
and wired it into the Kernel's console banner and a new, correctly
labeled "Framework" row in `about` (separate from "App"). Also fixed
FrameworkExtension::getGlobals() reading $_ENV directly instead of
config('app.*'), which made a config-only (non-env) app.name/version
invisible to Twig's `app.*` global.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Thin wrapper around SymfonyStyle::choice()'s multiSelect mode, following the same pattern as the existing choice()/confirm()/ask() helpers. Used by marrow/anvil's now-interactive anvil:install. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
RedirectResponse and JsonResponse were each a sibling of Response (both extended their own Symfony ancestor directly), not a subtype of it — so a controller method returning either a redirect or a rendered view had no single Marrow type to declare, only their shared Symfony ancestor worked. marrow/warden's controllers are the clearest example of the workaround this forced. Response now holds a Symfony\...\Response internally instead of extending it; RedirectResponse/JsonResponse extend Response directly, each composing their own more specific Symfony instance. Every Symfony method not explicitly redeclared still works via __call() (documented with @mixin for IDE/PHPStan support); headers is a real public property pointing at the same ResponseHeaderBag the wrapped instance sends from, not magic. Propagated the type change through every internal consumer: Router, Pipeline, Kernel, SessionManager, all bundled middleware, and fixed ApiController's JSON helpers, which were still constructing a raw Symfony JsonResponse directly (latent bug: would have broken the moment Router::toResponse()'s now-Marrow-typed instanceof check saw one). Verified: isolated unit tests for the new Response/RedirectResponse/ JsonResponse wrapper (fluent chaining preserved on the Marrow instance, not leaked to the inner Symfony one; clone deep-copies correctly); full pest suite (261 passing) and phpstan (0 errors); and a true end-to-end pass — overlaid this code into a real skeleton app's vendor/marrow/framework, ran its Pest suite against the real Http\Kernel, and hit a real `php forge serve` instance with curl to confirm headers/cookies/status/body all still send correctly. Breaking for any app/package with its own middleware or controller typed against Symfony\Component\HttpFoundation\Response as the same workaround — see CHANGELOG.md for the fix. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
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.
[3.0.0] - 2026-10-06
Changed (breaking)
Http\Responsenow composes aSymfony\Component\HttpFoundation\Responseinstead of extending it —specifically so
Http\RedirectResponseandHttp\JsonResponsecan be genuine subclasses ofResponseinstead of independent siblings of it (each previously extended its own Symfony ancestor directly:
RedirectResponse extends Symfony\...\RedirectResponse,Response extends Symfony\...\Response— twounrelated Symfony leaf classes, so
RedirectResponsewas never aninstanceof Responseeven though both are"a Marrow response" in every way that matters to application code). A controller method that can return
either a redirect or a rendered view now simply declares
: Response— no more typing againstSymfony\Component\HttpFoundation\Responseas a workaround (seemarrow/warden's controllers, simplifiedback in the same release this motivated).
setContent(),getStatusCode(),isRedirect(),cache/cookie/vary helpers, ...) still works, forwarded via
__call()—@mixinon the class givesIDEs/PHPStan full autocomplete/type-checking for those.
$response->headersis a real public property (thesame
ResponseHeaderBagthe wrapped instance reads when sending), not magic.toSymfonyResponse()is theescape hatch for anything that specifically needs the real Symfony instance.
Symfony\Component\HttpFoundation\Response(the previous, now-unnecessary workaround) — aMarrow\Http\Responseinstance is no longer an
instanceofSymfony's Response, so PHP's own return-type enforcement will throw aTypeErrorthe first time such code returns one. Fix: change the type toMarrow\Http\Response. Everyframework-internal consumer (Router, Pipeline, Kernel, SessionManager, all bundled middleware) already made
this exact change — see docs/http.md.
ApiController's JSON helpers(
ok(),created(),notFound(), ...) constructedSymfony\Component\HttpFoundation\JsonResponsedirectly instead of
Marrow\Http\JsonResponse— harmless on its own, but would have broken the moment oneof those methods' return value reached
Router::toResponse()'s now-Marrow\Http\Response-typedinstanceofcheck.[2.5.0] - 2026-10-06
Added
framework_version()(Support\helpers.php) — the installedmarrow/frameworkversion (e.g.2.4.1),resolved from Composer's own runtime metadata (
Composer\InstalledVersions), never hand-maintained. Distinctfrom
app()->version()(config('app.version')), which is the application's own version.Fixed
php forgeCLI banner always printed "Marrow 0.1.0", regardless of whichmarrow/frameworkversionwas actually installed —
Console\Kernelwas constructed withconfig('app.version', '0.1.0'), theapplication's version config (itself defaulting to
0.1.0in a fresh skeleton), not the framework's. Afresh
composer create-project marrow/skeletonfollowed immediately byphp forge listlooked exactly likea framework frozen since an early prototype. Now built from
framework_version()instead —php forge liston a real install correctly shows e.g.
Marrow 2.4.1.php forge aboutmislabeled the application's own version as "Framework", and never showed the actualinstalled
marrow/frameworkversion anywhere. Now shows both, correctly separated:Framework(the real,Composer-resolved version) and
App(config('app.name')+config('app.version'), the app's own).Also switched its four other reads from raw
$_ENVtoconfig(), matching the rest of the command.FrameworkExtension::getGlobals()'s Twigapp.*global read$_ENVdirectly instead ofconfig('app.*')— a value set only in
config/app.php(not mirrored to an env var) was invisible to{{ app.name }}/{{ app.version }}in templates even thoughconfig('app.name')elsewhere in the same app saw it correctly.Now reads through
config()like everywhere else, andapp.version's stale0.1.0fallback is gone (emptystring when unset, same as
AboutCommand).