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 to Secure, 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. New RedirectResponse::local() rejects off-site targets.
  • Mass assignment defense: the new #[Guarded] attribute marks properties that never bind from fromArray() input and never enter toUpdateArray().
  • OIDC hardening: full nonce round-trip (sent at login, verified against the id_token on callback); the callback now requires an id_token and no longer falls back to the access token; the discovery issuer host must match the discovery URL host.
  • Error-detail masking: SecurityException messages are replaced with a generic Authentication 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 401 even when requireAuthentication is false.
  • Hardened defaults: FileCache tightens an existing cache directory to 0700 and writes entries 0600; FileLogger uses 0750/0640 with 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/DateTimeImmutable model properties, and typed route params (show(int $id) with a non-numeric id) now return 400 with a field-keyed error body instead of an uncaught TypeError.
  • 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); ExceptionHandler treats only /api and /api/… as API paths (/apiary no 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 extend RuntimeException); singleton(Interface, Concrete) aliases the concrete class so direct type-hints share the singleton.
  • ViewEngine unwinds all output buffers when a template throws mid-section; #[Required] rejects empty arrays; ArrayCache treats non-positive TTLs as already expired; DbContext applies ERRMODE_EXCEPTION + FETCH_ASSOC to injected PDO instances.

Added

  • #[Guarded] attribute (Melodic\Data\Guarded) and RedirectResponse::local() / isLocalPath().
  • RefreshTokenService::validateAndRotate() — atomic validate + rotate, run in a DB transaction when constructed with a DbContextInterface.
  • EventDispatcher::removeListener() to deregister a listener, plus reflection caches in Model and Container.
Upgrade notes: the default refresh cookie name changed kingdom_refreshmelodic_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 present exp, and validates the audience (falling back to client_id). Local HS256 tokens also require exp.
  • Logout is now a CSRF-protected POST /auth/logout (was a CSRF-able GET). A non-POST returns 405; a POST without a valid csrf_token returns 403.
  • Local signing keys must be non-empty and at least 32 characters for HMAC (HS*) algorithms — LocalAuthConfig now 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=code and uses PKCE (S256).
  • FileCache hardened: 0700 directory, atomic LOCK_EX writes, corruption-safe reads, and a clear() scoped to its own .cache files.
  • Log injection prevented (CR/LF stripped from interpolated context), ApiAuthenticationMiddleware returns a generic 401, 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, default RS256), so providers that omit the per-key alg validate 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 uncaught 500; non-Model action parameters resolve from the container.
  • Routing returns 405 with an Allow header when a path exists under a different method (was 404).
  • HEAD requests no longer emit a response body (RFC 9110), and DbContext bool hydration handles driver string forms.

Changed

  • New config keys: auth.cookieSecure / cookieSameSite / cookiePath / cookieDomain, auth.oidcCacheDir, auth.providers.*.signingAlg, and session.* equivalents.
  • Static analysis is now clean — PHPStan level 6 reports 0 errors.
Heads up for local dev: because cookies now default to 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, so claude:install works immediately after composer require

v1.7.1

March 15, 2026

Update documentation for Claude Code integration

  • Updated framework docs to cover the claude:install command, agents, and skills

v1.7.0

March 15, 2026

Add Claude Code agents, skills, and claude:install command

  • New claude:install CLI command installs Melodic-specific Claude Code agents and skills into your project
  • Includes melodic-expert agent for framework architecture, patterns, and debugging assistance
  • Includes three skills: /melodic:scaffold-app, /melodic:scaffold-resource, and /melodic:add-middleware
  • Generates a CLAUDE.md project template with framework conventions, naming patterns, and architecture overview
  • Run vendor/bin/melodic claude:install in 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 arrays
  • toUpdateArray() 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 handle stdClass result 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 Model subclass are now automatically hydrated from the request body and validated by the RoutingMiddleware
  • If validation fails, a 400 JSON response with field-keyed errors is returned before the controller action is called
  • Route parameters (e.g. $id from /users/{id}) and model parameters work together — route params are matched by name first
  • Uses ReflectionMethod to inspect action parameters, Model::fromArray() for hydration, and the DI-resolved Validator for 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

  • RefreshToken model with family-based token chains and generation tracking
  • RefreshTokenService for creating, validating, and rotating tokens — automatically revokes the entire token family on reuse detection
  • RefreshTokenRepositoryInterface that consuming apps implement for storage (find, store, revoke by family/user, delete expired)
  • RefreshTokenMiddleware reads the token from an HTTP-only cookie, validates it, and sets refreshToken and refreshTokenUserId request attributes
  • RefreshTokenCookieHelper for setting and clearing the secure, HTTP-only refresh token cookie
  • RefreshTokenConfig with configurable lifetime (default 7 days), cookie name, domain, path, secure flag, and SameSite policy
  • SecurityServiceProvider auto-registers all refresh token services when security.refreshToken config is present

v1.3.2

February 28, 2026

Serialize Model properties as camelCase JSON

  • Model now implements JsonSerializable and converts PascalCase PHP properties to camelCase in toArray() and json_encode() output
  • fromArray() accepts both PascalCase (DB) and camelCase (frontend) input

v1.3.1

February 28, 2026

Fix Response with*() methods breaking subclasses

  • Use clone instead of new self() so with*() methods preserve the actual subclass type (e.g. JsonResponse)
  • Remove readonly from constructor properties to allow mutation on cloned instances
  • Return static instead of self

v1.3.0

February 28, 2026

Add environment configuration support

  • Layered config loading: config.jsonconfig.{APP_ENV}.jsonconfig.dev.json
  • New loadEnvironmentConfig() method on Application replaces manual loadConfig() + file_exists pattern
  • New make:config CLI 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:project type is now “full”, generating both MVC views with a HomeController and API route scaffolding
  • Use --type=api or --type=mvc for single-purpose projects

v1.1.1

February 27, 2026

Centralized version tracking

  • Add Framework::VERSION as single source of truth for the package version
  • Console class now reads the version automatically instead of a hardcoded default
  • Updated PUBLISHING.md with the new release workflow

v1.1.0

February 27, 2026

Add project and entity scaffolding CLI

  • New bin/melodic CLI with make:project and make:entity commands
  • make:project creates API or MVC project with composer.json, config, public entry point, service provider, and directory scaffolding
  • make:entity generates 8 files per entity: DTO model, 2 queries, 3 commands, service, and API controller
  • Add Stub utility 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-jwt to ^7.0 to resolve Packagist security advisory (PKSA-y2cr-5h3j-g3ys)
  • The entire v6.x line was flagged, causing Composer to block installation
  • Fix phpstan.neon exclude path for removed example directory
Security advisory: If you are on v1.0.0, upgrade to v1.0.1 or later to resolve the firebase/php-jwt vulnerability.

v1.0.0

February 18, 2026

Initial public release

  • Published as melodicdev/framework on 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.