Skip to content

Releases: nafphp/framework

v0.2.12 — HTTP contracts, testable requests and optional PDO

Choose a tag to compare

@FloKnapp FloKnapp released this 14 Oct 21:42
4c8dc4e

HTTP routing now distinguishes an unknown path (404) from an unsupported method (405 with Allow). HEAD prefers an explicit route and otherwise uses GET without emitting content. Automatic OPTIONS returns 204 with Allow for known paths without invoking a controller; explicit OPTIONS routes still run normally. This does not configure CORS.

App::handle(ServerRequestInterface $request): ResponseInterface processes a supplied request without output or process exit. Sequential calls replace request bindings and matched-route state while retaining shared application services. Existing applications continue to start with app()->run(); adopting handle() is optional. Response preparation suppresses content for HEAD, informational statuses, 204, 205 and 304 and is reapplied after header listeners.

The core no longer requires PDO. Database integrations still need PDO and their drivers. Both built-in containers implement the new MutableContainerInterface; App and AutoResolvingContainer require it directly in their constructor types. Dispatcher execution and route URL lookup are simplified, numeric route names remain strings, and method normalization is consistent for automatic OPTIONS and the welcome response.

Request creation also preserves the browser authority from Host without adding an internal container listen port. An explicit public port is retained, and the request contains exactly one Host header. This includes the previously merged authority correction; there was no separate 0.2.11 release.

Upgrade notes:

  • Custom writable containers must implement Naf\Contracts\MutableContainerInterface, extending PSR-11 with set() and reset(). Unsupported containers fail with PHP's TypeError before boot.
  • Every URL placeholder must receive a string or integer. Missing or invalid values throw InvalidArgumentException. Encoding remains the caller's responsibility; values are substituted once.
  • Handlers must return PSR-7 responses. The undocumented -1 exit sentinel is rejected instead of terminating in-process handling.
  • GET handlers can also serve HEAD. Register explicit HEAD or OPTIONS routes where separate application behavior is needed.
  • Nyholm PSR-7 now requires 1.8.2 or newer to avoid PHP 8.4/8.5 deprecations at the minimum dependency set.

Event::REQUEST_BODY and Event::REQUEST_END remain defined as deprecated compatibility constants; the core does not dispatch them. AppHolder, CoreFileLoader, PluginBootOrder and Stopwatch are marked internal.

Validation: 201 tests / 504 assertions, PHPStan, strict Composer validation, real HTTP and published-plugin integration tests. All eight CI jobs pass on the release commit, covering PHP 8.3/8.4/8.5 with lowest/latest dependencies, the locked set and production installation without PDO.

v0.2.10 — Early configuration sources and named overrides

Choose a tag to compare

@FloKnapp FloKnapp released this 10 Oct 22:55
e8f2db3

Configuration sources can now provide persisted overrides before the first plugin bootstrap reads configuration. A plugin or host may register source classes through app/config_sources.php or src/config_sources.php; plugin sources follow boot order and host sources load last.

Config adds named literal override layers and provenance APIs: overlay(), overrides(), base(), raw() and source(). Overrides preserve false, zero and null, replace complete array values, and can be reset to resolved server defaults. This supports administration-managed configuration without forcing startup services to use stale server values.

Existing configuration files and readers continue to work. Sources must have no-argument constructors and must not resolve configuration-dependent services while loading. Updating a layer does not reconfigure services that have already been instantiated.

Validation: 177 tests with 377 assertions; a real HTTP host verifies source loading before plugin boot, plugin/host precedence, environment defaults, literal values, reset behavior, redirects and response headers. Composer validation and dependency audit pass; CI passes on the exact release commit.

v0.2.9 — Core guards in workers and composable response headers

Choose a tag to compare

@FloKnapp FloKnapp released this 10 Oct 13:59
66ff7c7

Core guard rules are now registered under CLI as well as HTTP, so View rendering and HTML escaping work in commands, queue workers and scheduled jobs after application boot. Blocklist rules read configuration when called and allow a missing list instead of throwing a TypeError.

The response.header event now passes each returned PSR-7 response to the next listener. Application headers and the language cookie from naf/i18n can coexist. Other dispatch methods retain their behaviour.

Regression coverage exercises CLI guards and response chaining. The package suite passes (175 tests), and CI passes for the released commit.

v0.2.8 — Preserve process environment configuration

Choose a tag to compare

@FloKnapp FloKnapp released this 06 Oct 17:42
cd14d83

PHP normally leaves $_ENV empty when variables_order does not contain E. Configuration values such as ENV:DB_HOST now fall back to the process environment, so values supplied by Docker Compose or a shell resolve with the standard PHP configuration.

The .env loader also preserves variables already set by the process. A value from Compose therefore takes precedence over the application's .env, as documented. Values present in $_ENV still take precedence, and an unset variable still resolves to null.

Regression coverage checks process-only values, explicit $_ENV precedence, missing variables and gap filling without overriding the process environment. The complete suite passes 172 tests with 356 assertions.

On installations using the standard GPCS setting, existing process values now win over conflicting .env entries. Review any deployment that previously relied on the file replacing those values.

v0.2.7 — Plugin boot order from package manifests

Choose a tag to compare

@FloKnapp FloKnapp released this 22 Sep 21:03
55734ed

Plugin boot order now comes from each installed plugin's composer.json. Plugins can declare extra.naf.boot.before and extra.naf.boot.after without requiring the host to maintain a complete plugins.php list. Composer dependencies remain installation requirements and do not impose bootstrap order.

The framework validates the full graph before running any plugin bootstrap. It reports invalid metadata and cycles with their declaring packages, ignores absent optional targets, and uses package names for deterministic ties. A host plugins.php can still prioritize installed plugins when its order does not conflict with declared requirements. App::getPluginBootPlan() exposes the resulting order and reasons for diagnostics.

Regression coverage exercises ordering, malformed manifests, cycles, missing targets, host preferences, and a real application boot with configuration and route precedence.

v0.2.6 — Events that are objects, and listeners sorted once

Choose a tag to compare

@FloKnapp FloKnapp released this 21 Sep 19:33
d0326f6

Two changes to EventManager, both about events being an extension mechanism rather than a convenience. Nothing that exists has to change.

An event can now be an object

event()->dispatch(new OrderShipped($order));
event()->listen(OrderShipped::class, fn(OrderShipped $e) => …);

The class is the name and the object is the payload. dispatch() accepts string|object; a string is still a name, so an application converts one event at a time or none.

listen() needed nothing at all. ::class is a string like any other name, so listen(OrderShipped::class, …) already worked before this release — there was simply nothing that would dispatch under that name. The whole change is four lines at the top of dispatch().

What it buys is what a string cannot. A misspelled class is an error where it is written, while a misspelled event name is a listener that never runs and never says so. An IDE can find every listener of an event and rename one as a refactoring. And the payload has a declared shape instead of a docblock describing variadic arguments — which matters most for the events a listener is meant to change something in, where the shape of what may be edited is the contract.

Nothing is required of an event class: no interface, no base class, no marker. Requiring one would buy nothing and would make moving an existing event over more than a one-line change.

One thing worth knowing before converting an existing event: string names and class names share one key space. That is why listen(X::class) already worked, and it means converting is a rename — a listener registered on the old string stops hearing it, silently. Convert the listeners in the same change.

Listeners are sorted when they are dispatched, not while they are registered

listen() sorted the whole list on every call, so registering n listeners for one event sorted a growing list n times — O(n² log n) to build something that is read once.

Listeners on one event before after (register + first dispatch)
25 0.027 ms 0.010 ms
100 0.531 ms 0.036 ms
500 19.15 ms 0.207 ms
1000 94.90 ms 0.465 ms

An application with a handful of listeners per event does not notice either way; at ten the difference is twenty microseconds. This is not an optimisation. Events are how this framework is extended, so how many listeners an event carries is somebody else's decision, and a quadratic ceiling nobody chose is the kind that gets found late and far from its cause.

A dirty flag per event rather than one sort, because a listener registered after an event has already been dispatched still has to take its place in the order rather than the end of the queue. That has a test of its own, as does the thing the sort has always quietly promised: listeners sharing a priority run in the order they were registered.

Upgrading

Nothing to do. Both changes are additive and the existing behaviour is unchanged — dispatch order, priorities, class listeners, return values and dispatchForResponse() all behave exactly as in v0.2.5.

143 tests, 291 assertions.

v0.2.5 — A README that no longer calls itself unreleased

Choose a tag to compare

@FloKnapp FloKnapp released this 19 Sep 10:19
6e860f3

A documentation patch. The README carried a section announcing its own release branch:

## Unreleased Nafinity integration candidate

Target branch: `v0.2.4-rc`. This behavior is not a published release yet.

v0.2.4 was tagged from that file, so everyone who installed it read that the version they had
just installed was unreleased. A README ships inside the Composer package, and a tag cannot
move, so reaching existing installations needs this release.

The paragraph underneath it is gone as well. It sat after the License, where the document
ends, and read like release notes in prose: four unrelated internal changes, each only
meaningful to someone who knew the previous behaviour. Those facts are in the
v0.2.4 notes, where a reader looks
for what changed, and the behaviour they describe is unchanged here.

No code changes. Runtime behaviour, public APIs and dependency constraints are identical
to v0.2.4.

Validation: the tagged commit passed CI, and its tree passed the suite with 137 tests and
280 assertions.

v0.2.4 — Routes an application can take back, and safer streamed output

Choose a tag to compare

@FloKnapp FloKnapp released this 19 Sep 07:26
1fa07cc

An application could add routes but never take one back, and a controller bound in the container was ignored: the dispatcher always constructed a fresh instance. Naf\Core\Route::remove(string $name): bool now removes a named route from matching and reports whether it existed, and the dispatcher prefers a bound target class before falling back to building an unbound one. A bound object that does not have the route's action is reported as a DispatcherException instead of failing obscurely. Together these let a host or a plugin replace a route and its controller deliberately, rather than by registering something on top and hoping for the ordering.

This release also carries the integration hardening prepared for v0.2.4:

  • A container service whose factory legitimately returns null was treated as absent, because presence was checked with isset(). It is now array_key_exists(), so a null service stays registered and resolvable. ContainerException additionally chains the original throwable instead of only copying its message.
  • ResponseEmitter materialised the whole response body into memory before writing it. It now streams the body in 8 KiB chunks, rewinding a seekable stream first, so a large download no longer grows the PHP heap by its own size.
  • An object callable registered with the event manager could be mistaken for a class/method pair and break. The check now requires a string first element and an otherwise non-callable array before treating it as a class reference.

Debug output is narrower, and this can change what you see. ErrorHandler previously showed the debug view for any environment that was neither PROD nor TEST, so a misspelled or custom APP_ENV silently opened detailed error pages. It now shows that view only for Environment::DEV. If you run under an environment name other than dev and relied on debug output, switch that environment to dev; no other action is required.

Validation: the tagged commit passed framework CI, and its tree passed the suite with 137 tests and 280 assertions. The new behaviour is covered in RouteTest, DispatcherTest and IntegrationHardeningTest.

Version note: Route::remove() is additive. The dispatcher change is visible only where a controller class is bound in the container, which previously had no effect. Dependency constraints are unchanged.

v0.2.3 — Agent guidance in Composer installations

Choose a tag to compare

@FloKnapp FloKnapp released this 13 Sep 20:49
acabfb5

Composer installations now include AGENTS.md with the framework source map, core/plugin boundaries, boot order, container semantics, route parameters, response events and the established contribution/release workflow.

A complete core-only HTTP host example demonstrates named-route redirects, JSON responses and a response-header listener. CONTRIBUTING.md now documents the existing PHPUnit suite and correct helper paths/autoloading instead of saying tests are only planned.

Validation: the released commit passed framework CI. Local checks passed with 124 PHPUnit tests and 235 assertions, plus four HTTP checks using the guide’s complete example files.

Version note: this is a documentation and package-content patch from v0.2.2 to v0.2.3. Runtime code, public APIs and dependency constraints are unchanged; no application-code migration is required.

Verified CI · Changes since v0.2.2

v0.2.2 — Redirects on the PHP development server

Choose a tag to compare

@FloKnapp FloKnapp released this 13 Sep 18:24
0a44c10

Fixes redirects emitted by PHP's built-in development server.

redirect() constructed responses with the protocol version hard-coded to 2. The response emitter wrote that into the status line, so the HTTP/1.x development server sent an invalid HTTP/2 302 Found response that HTTP clients could reject. refresh() inherited the same problem because it uses redirect().

Redirects now use the same response factory as other responses, with its HTTP/1.1 default. The response also gets the correct reason phrase for its status — for example, See Other for a 303 redirect. The requested status and Location header are preserved, and the body is empty.

Regression tests cover the protocol version, destination, empty body and status-specific reason phrase, including the protocol returned by refresh().

The package README now points to the central NAF documentation, and an unused legacy logo has been removed.