Release Notes
Version history for the Melodic PHP Framework. Each release is tagged in the melodic-php repository.
v4.0.0 latest
July 6, 2026
Review remediation — secure by default
Completes the security & correctness remediation started in 3.0, resolving the remaining findings from a multi-agent code review. Several defaults tighten toward “secure by default.” See the migration guide for upgrade steps.
Security
- Secure cookies by default:
Response::withCookie()now defaults toSecure, matching the auth and session cookies. Pass['secure' => false]for plain-HTTP local development. - Open redirect closed: only local paths (a single leading
/, not//or/\) are stored and honored as the redirect-after-login target. NewRedirectResponse::local()rejects off-site targets. - Mass assignment defense: the new
#[Guarded]attribute marks properties that never bind fromfromArray()input and never entertoUpdateArray(). - OIDC hardening: full
nonceround-trip (sent at login, verified against theid_tokenon callback); the callback now requires anid_tokenand no longer falls back to the access token; the discovery issuer host must match the discovery URL host. - Error-detail masking:
SecurityExceptionmessages are replaced with a genericAuthentication failed.outside debug mode; the refresh-token endpoint no longer lets a client distinguish “reuse detected” from “invalid token.” - CSRF: a mismatched token no longer consumes the stored token, so a forged POST cannot invalidate the user’s open form.
- Authorization: routes declaring required entitlements now reject anonymous requests with
401even whenrequireAuthenticationisfalse. - Hardened defaults:
FileCachetightens an existing cache directory to0700and writes entries0600;FileLoggeruses0750/0640with whole-line log-injection sanitization; session logout expires the browser session cookie; scaffolding rejects non-identifier names (path-traversal guard).
Fixed
- 400 instead of 500 for uncoercible client input: backed-enum and
DateTime/DateTimeImmutablemodel properties, and typed route params (show(int $id)with a non-numeric id) now return400with a field-keyed error body instead of an uncaughtTypeError. - Config list merges now replace wholesale instead of merging by index, so a shorter environment override no longer inherits stale trailing base elements (e.g. CORS
allowedOrigins). - Backed-enum and date hydration from the database is now supported on models.
- Route patterns escape literal segments (
/v1.0/no longer treats.as a wildcard);ExceptionHandlertreats only/apiand/api/…as API paths (/apiaryno longer gets JSON errors). - DI container: exceptions thrown by your factories/constructors propagate instead of silently becoming a parameter default; resolution failures throw
ContainerException/CircularDependencyException(both extendRuntimeException);singleton(Interface, Concrete)aliases the concrete class so direct type-hints share the singleton. ViewEngineunwinds all output buffers when a template throws mid-section;#[Required]rejects empty arrays;ArrayCachetreats non-positive TTLs as already expired;DbContextappliesERRMODE_EXCEPTION+FETCH_ASSOCto injected PDO instances.
Added
#[Guarded]attribute (Melodic\Data\Guarded) andRedirectResponse::local()/isLocalPath().RefreshTokenService::validateAndRotate()— atomic validate + rotate, run in a DB transaction when constructed with aDbContextInterface.EventDispatcher::removeListener()to deregister a listener, plus reflection caches inModelandContainer.
kingdom_refresh → melodic_refresh (set auth.refreshToken.cookieName to keep in-flight sessions valid). Cookies now default to Secure, so set ['secure' => false] and the auth/session cookieSecure config to false for plain-HTTP local dev. Environment config files must now list every element of an overridden array. The empty Service::__destruct() was removed — drop any parent::__destruct() call.
v3.0.0
June 1, 2026
Security & correctness hardening
A focused hardening release addressing issues found in a cross-checked code review. See the migration guide for upgrade steps.
Security
- JWT/OIDC validation now requires a matching issuer (
iss) and a presentexp, and validates the audience (falling back toclient_id). Local HS256 tokens also requireexp. - Logout is now a CSRF-protected
POST /auth/logout(was a CSRF-ableGET). A non-POST returns405; a POST without a validcsrf_tokenreturns403. - Local signing keys must be non-empty and at least 32 characters for HMAC (
HS*) algorithms —LocalAuthConfignow throws on weak keys. - Cookies (auth and PHP session) default to
Secure+HttpOnly+SameSite=Lax, sourced from config. - Generic OAuth2 login now sends
response_type=codeand uses PKCE (S256). - FileCache hardened:
0700directory, atomicLOCK_EXwrites, corruption-safe reads, and aclear()scoped to its own.cachefiles. - Log injection prevented (CR/LF stripped from interpolated context),
ApiAuthenticationMiddlewarereturns a generic401, and the CSRF token is reused across login renders. - Views: new
$this->e()HTML-escaping helper (templates are not auto-escaped).
Fixed
- Microsoft Entra: OIDC JWKS now parse with a configurable default algorithm (
auth.providers.*.signingAlg, defaultRS256), so providers that omit the per-keyalgvalidate instead of failing. Verified end to end against a live Entra tenant. - Validation is nullable-by-default: optional fields with format rules no longer fail when omitted — only
#[Required]rejects a missing value. - Model binding coerces scalars to declared types and returns a
400(field error) on uncoercible input instead of an uncaught500; non-Modelaction parameters resolve from the container. - Routing returns
405with anAllowheader when a path exists under a different method (was404). HEADrequests no longer emit a response body (RFC 9110), andDbContextbool hydration handles driver string forms.
Changed
- New config keys:
auth.cookieSecure/cookieSameSite/cookiePath/cookieDomain,auth.oidcCacheDir,auth.providers.*.signingAlg, andsession.*equivalents. - Static analysis is now clean — PHPStan level 6 reports 0 errors.
Secure, login will appear not to work over plain HTTP until you set auth.cookieSecure and session.cookieSecure to false in your dev config.
v1.7.2
March 15, 2026
Include Claude Code assets in Composer distribution
- The
.claude/directory (agents and skills) is now included in the Composer dist package, soclaude:installworks immediately aftercomposer require
v1.7.1
March 15, 2026
Update documentation for Claude Code integration
- Updated framework docs to cover the
claude:installcommand, agents, and skills
v1.7.0
March 15, 2026
Add Claude Code agents, skills, and claude:install command
- New
claude:installCLI command installs Melodic-specific Claude Code agents and skills into your project - Includes
melodic-expertagent for framework architecture, patterns, and debugging assistance - Includes three skills:
/melodic:scaffold-app,/melodic:scaffold-resource, and/melodic:add-middleware - Generates a
CLAUDE.mdproject template with framework conventions, naming patterns, and architecture overview - Run
vendor/bin/melodic claude:installin any Melodic project to get started
v1.6.0
March 15, 2026
Add toPascalArray() and toUpdateArray() to Model
toPascalArray()returns all initialized public properties with their original PascalCase names and converts booleans to integers for PDO compatibility — ideal for INSERT parameter arraystoUpdateArray()returns only non-null initialized properties with PascalCase keys and boolean-to-int conversion — ideal for partial UPDATE parameter arrays where null means “not provided”
v1.5.1
March 11, 2026
Fix DbContext hydration and documentation improvements
- Fix
DbContext::hydrate()to correctly handlestdClassresult rows - Add route registration examples to model binding documentation
v1.5.0
March 10, 2026
Add automatic request model binding with validation
- Controller action parameters typed as a
Modelsubclass are now automatically hydrated from the request body and validated by theRoutingMiddleware - If validation fails, a
400JSON response with field-keyed errors is returned before the controller action is called - Route parameters (e.g.
$idfrom/users/{id}) and model parameters work together — route params are matched by name first - Uses
ReflectionMethodto inspect action parameters,Model::fromArray()for hydration, and the DI-resolvedValidatorfor validation - Expanded test coverage with 20+ new test classes covering controllers, middleware, routing, security, data, views, logging, and more
v1.4.0
March 1, 2026
Add refresh token support with rotation and reuse detection
RefreshTokenmodel with family-based token chains and generation trackingRefreshTokenServicefor creating, validating, and rotating tokens — automatically revokes the entire token family on reuse detectionRefreshTokenRepositoryInterfacethat consuming apps implement for storage (find, store, revoke by family/user, delete expired)RefreshTokenMiddlewarereads the token from an HTTP-only cookie, validates it, and setsrefreshTokenandrefreshTokenUserIdrequest attributesRefreshTokenCookieHelperfor setting and clearing the secure, HTTP-only refresh token cookieRefreshTokenConfigwith configurable lifetime (default 7 days), cookie name, domain, path, secure flag, and SameSite policySecurityServiceProviderauto-registers all refresh token services whensecurity.refreshTokenconfig is present
v1.3.2
February 28, 2026
Serialize Model properties as camelCase JSON
- Model now implements
JsonSerializableand converts PascalCase PHP properties to camelCase intoArray()andjson_encode()output fromArray()accepts both PascalCase (DB) and camelCase (frontend) input
v1.3.1
February 28, 2026
Fix Response with*() methods breaking subclasses
- Use
cloneinstead ofnew self()sowith*()methods preserve the actual subclass type (e.g.JsonResponse) - Remove
readonlyfrom constructor properties to allow mutation on cloned instances - Return
staticinstead ofself
v1.3.0
February 28, 2026
Add environment configuration support
- Layered config loading:
config.json→config.{APP_ENV}.json→config.dev.json - New
loadEnvironmentConfig()method on Application replaces manualloadConfig()+file_existspattern - New
make:configCLI command for generating config files - Updated project scaffolding with QA/PD config stubs
v1.2.0
February 27, 2026
Add full project type as default with MVC + API routing
- Default
make:projecttype is now “full”, generating both MVC views with a HomeController and API route scaffolding - Use
--type=apior--type=mvcfor single-purpose projects
v1.1.1
February 27, 2026
Centralized version tracking
- Add
Framework::VERSIONas single source of truth for the package version - Console class now reads the version automatically instead of a hardcoded default
- Updated
PUBLISHING.mdwith the new release workflow
v1.1.0
February 27, 2026
Add project and entity scaffolding CLI
- New
bin/melodicCLI withmake:projectandmake:entitycommands make:projectcreates API or MVC project withcomposer.json, config, public entry point, service provider, and directory scaffoldingmake:entitygenerates 8 files per entity: DTO model, 2 queries, 3 commands, service, and API controller- Add
Stubutility for template rendering and case conversion - 38 tests covering all new functionality
v1.0.1
February 18, 2026
Security fix: upgrade firebase/php-jwt
- Upgrade
firebase/php-jwtto^7.0to resolve Packagist security advisory (PKSA-y2cr-5h3j-g3ys) - The entire v6.x line was flagged, causing Composer to block installation
- Fix
phpstan.neonexclude path for removed example directory
firebase/php-jwt vulnerability.
v1.0.0
February 18, 2026
Initial public release
- Published as
melodicdev/frameworkon Packagist - CQRS data access with Query and Command objects via DbContext
- Auto-wiring dependency injection container with interface bindings and service providers
- PSR-15-style middleware pipeline (CORS, authentication, body parsing)
- JWT authentication with OIDC, OAuth2, and local providers
- MVC views with layouts, sections, and ViewBag
- Attribute-based DTO validation
- Centralized exception handling with JSON/HTML detection
- PHP 8.2+ with modern language features throughout
Stay up to date
Follow the repository for new releases, or get started with the latest version today.