Skip to content

Prepare Objectiveweb Router v3.0.0 - #1

Open
objectivebot wants to merge 60 commits into
masterfrom
devel
Open

objectivebot wants to merge 60 commits into
masterfrom
devel

Conversation

@objectivebot

@objectivebot objectivebot commented Sep 28, 2026 •

Copy link
Copy Markdown

Summary

Prepare Objectiveweb Router 3.0.0 as an intentionally breaking release.

This PR modernizes the supported runtime and test matrix, hardens controller/middleware/response behavior, fixes template handling, and adds the release metadata and verification needed for a public v3 release.

Breaking changes

  • Require PHP 8.1+.
  • Router now composes Dice instead of extending Dice\\Dice.
    • addRule() and create(string $name, array $args = []): object are the supported Router DI API.
    • Dice's internal share argument is not exposed by Router.
    • Other methods previously inherited from Dice are no longer part of the Router API.
  • Replace controller before() / beforePost() hooks with attribute-based middleware.
  • Middleware before() must return the complete controller argument array.
  • Controller object responses no longer enter array-only template rendering.
  • All routed responses now go through the common respond() pipeline.
  • Default templates resolve from <composer project root>/templates.

See CHANGELOG.md for detailed migration notes from 2.x.

Runtime and response handling

  • Restore late-static respond() dispatch so Router subclasses can override response handling.
  • Route registered serializers and exceptions through the normal response pipeline instead of bypassing respond().
  • JSON-encode non-string response values before output.
  • Fix root controller matching for nested paths.
  • Keep non-array controller responses out of template lookup.

Middleware

  • Preserve repeated middleware attributes using the same class.
  • Keep precedence as defaults < class < method.
  • Preserve all repeated middleware in the winning scope.
  • Run before() hooks in declaration order.
  • Run implemented after() hooks in reverse order.
  • Do not require an after() hook.
  • Reject non-array before() results with a clear 500 error.
  • Update MiddlewareInterface and middleware documentation accordingly.

Templates

  • Fix the HTTP-method fallback typo caused by the accidental variable-variable expression.
  • Resolve the default template root from Composer's root package.
  • Add regression coverage for default root and method fallback behavior.

Dependencies and CI

  • Composer resolves objectiveweb/dice from the published package; the temporary VCS repository override has been removed.

  • Require PHP 8.1+.

  • Require published Objectiveweb Dice ^4.1.0.

  • Support PHPUnit 10-12.

  • Update JMS Serializer development compatibility to ^3.32.

  • Remove Travis CI.

  • Add GitHub Actions CI for PHP 8.1, 8.2, 8.3, 8.4, and 8.5.

  • Run composer validate --strict in CI.

  • Add a release verification workflow for v* tags and manual runs, including:

    • strict Composer validation,
    • the full supported PHP test matrix,
    • a production --no-dev dependency install check.

Release metadata

  • Add MIT LICENSE.
  • Add CHANGELOG.md with v3 changes and 2.x → 3.x migration notes.
  • Update README installation and DI/controller-hook references for v3.

Verification

Current devel CI is green on:

  • PHP 8.1
  • PHP 8.2
  • PHP 8.3
  • PHP 8.4
  • PHP 8.5

This release does not attempt to maintain backwards compatibility with Router 2.x.

Content negotiation

  • Parse Accept media ranges with q-values and wildcards.
  • Respect explicit q=0 exclusions and return 406 when no supported representation is acceptable.
  • Prefer HTML for controller routes with templates when Accept is missing or equally weighted.
  • Return JSON for explicit JSON preference.
  • Emit explicit text/html; charset=utf-8 or application/json; charset=utf-8 instead of inferring content type from the first response byte.
  • Emit Vary: Accept when a response has multiple possible representations.
  • Encode string values as valid JSON scalars when JSON is requested.

Error boundary

  • Catch all PHP Throwable failures at the route boundary, including TypeError, ArgumentCountError, ValueError, and Error.
  • Include dependency-injection and callback resolution inside the boundary.
  • Preserve valid HTTP status codes from 400 through 599.
  • Normalize missing/invalid throwable codes such as 0 to HTTP 500.
  • Format unhandled Throwable values with the standard JSON error envelope.

Request body handling

  • Parse request bodies according to Content-Type instead of guessing from the payload shape.
  • Support application/json and structured application/*+json media types.
  • Return 400 for malformed JSON.
  • Require JSON media types for class-typed controller body parameters that use JMS deserialization.
  • Return 415 for missing or unsupported media types on class-typed body parameters.

Error response negotiation

  • Default Throwable responses support both text/html and application/json.
  • Preserve the original 4xx/5xx status when either representation is acceptable.
  • Escape exception class/message content in the HTML representation.
  • Keep 406 only for requests that reject every available error representation.

Deprecation policy

  • Remove the remaining PHP 8.2+ dynamic-property deprecation from the example controller.
  • Run PHPUnit with --fail-on-deprecation --display-deprecations so CI fails if PHP/PHPUnit deprecations reappear.
  • Current PHP 8.1-8.5 matrix is deprecation-clean.

Release workflow verification

  • Update both CI workflows to actions/checkout@v7.
  • Remove the deprecated Node 20 checkout runtime warning.
  • Exercise the release-verification workflow successfully on PHP 8.1-8.5.
  • Verify the production composer install --no-dev job against published dependencies.
  • Final release workflow remains limited to v* tags and manual dispatch.

Verb helper dispatch

  • Route GET(), POST(), PUT(), and DELETE() through the same callback resolver and Throwable boundary as route().
  • Support Dice-instantiated [ClassName::class, 'method'] callbacks consistently from verb helpers.
  • Prepare query/body arguments only after a route matches and inside the error boundary.
  • Keep malformed request bodies and invalid callbacks as controlled HTTP errors instead of uncaught failures.

Documentation and examples

  • Rewrite the README examples against the v3 API and fix invalid PHP snippets.
  • Remove legacy manual level-2/dice includes and use Composer autoloading in runnable examples.
  • Remove obsolete controller before() / beforePost() examples in favor of attribute middleware.
  • Align middleware signatures and ordering documentation with MiddlewareInterface.
  • Replace application-specific controller documentation with the Router's actual method-resolution rules.
  • Fix example repository data so it contains Product objects rather than incompatible arrays.

Middleware DI instantiation

  • Remove the synthetic string passed through Dice's internal share argument when constructing middleware.
  • Keep repeated middleware instances distinct by default.
  • Verify normal Dice sharing still works for dependencies configured with shared => true, including the same shared dependency being reused by the controller and repeated middleware instances.

DI API boundary

  • Narrow Router::create() to create(string $name, array $args = []): object.
  • Do not expose Dice's internal object-graph share argument through the Router API.
  • Add a regression test that locks the supported method signature for v3.

Global request middleware and CORS

  • Add DI-backed global request middleware whose before() hooks run once before route matching and whose after() hooks unwind when a route produces a response.
  • Add RequestMiddlewareInterface with request-level before(method, path) and after(method, path, response) hooks.
  • Run request before() hooks once per incoming request in declaration order, before route matching; run after() hooks in reverse order around the routed response.
  • Add addRequestMiddleware() and request.middlewares constructor configuration.
  • Reuse the same Dice container/rules as controllers and controller middleware.
  • Add built-in CorsMiddleware.
  • Make setCors() register CorsMiddleware instead of using controller-specific CORS branching.
  • Handle CORS preflight as a terminating request-middleware flow with HTTP 204.
  • Cover request/controller middleware nesting, response transformation, shared DI dependencies, exception behavior, normal CORS headers, and preflight headers.

Basic HTTP method semantics

  • Add a PATCH() helper with the same request-body parsing as POST/PUT.
  • Make GET() accept HEAD requests and make controller HEAD requests resolve through GET/index/custom GET methods.
  • Preserve the actual HEAD method for middleware while using GET semantics for controller resolution.
  • Suppress response bodies for HEAD while preserving GET representation metadata.
  • Suppress bodies and skip representation negotiation for 1xx, 204, 205, and 304 responses.
  • Document that Objectiveweb Router is an immediate regex dispatcher, not a route-table dispatcher; raw route() patterns remain literal regex behavior, and route-table features such as synthesized global 405/Allow and generic OPTIONS are not inferred automatically.

Template hardening

  • Type the template root and make Template::render(): string explicit.
  • Use extract(..., EXTR_SKIP) so template data cannot overwrite renderer locals.
  • Protect layout $_contents from user-supplied data.
  • Centralize PHP include rendering in one helper for templates and layouts.
  • Restore the exact output-buffer depth when template or layout execution throws, including nested buffers opened by template code.
  • Add regression coverage for variable collisions, layout contents, and exception-safe cleanup.

GuilhermeBarile and others added 30 commits September 9, 2025 21:49
While initializing Attributes with Dice, linting got broken since the Attribute misses the arguments that will be injected by DI.

This changes syntax to calling #[Middleware(class, ...args)] instead of referencing the Attribute directly.
 - Instantiate from Router
 - Set template.root and default middlewares in Router
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