diff --git a/docs/index.md b/docs/index.md index 6151dc8c..e33ae05b 100644 --- a/docs/index.md +++ b/docs/index.md @@ -24,6 +24,8 @@ applications — the HTTP response. ## Upgrading -* [Updating from v1 to v2](v1-to-v2-update.md) -* [Updating from v2 to v3](v2-to-v3-update.md) -* [Updating from v3 to v4](v3-to-v4-update.md) +* [Updating from v1 to v2](v1-to-v2-update.md) — CLI and daemon classes removed, PSR-7 responses, + application events, interfaces +* [Updating from v2 to v3](v2-to-v3-update.md) — PHP 8.1, PSR-12, no API changes +* [Updating from v3 to v4](v3-to-v4-update.md) — PHP 8.3, the `$app->input` getter and the boolean + redirect status removed diff --git a/docs/v1-to-v2-update.md b/docs/v1-to-v2-update.md index f90e074b..9597653e 100644 --- a/docs/v1-to-v2-update.md +++ b/docs/v1-to-v2-update.md @@ -1,42 +1,238 @@ -## Updating from v1 to v2 +# Updating from v1 to v2 -The following changes were made to the Application package between v1 and v2. +Release 2.0.0 is the largest change in the package's history. It removes the CLI and daemon +application classes, introduces PSR-7 responses, adds the event lifecycle, and splits the +application contract into interfaces. -### Minimum supported PHP version raised +## At a glance -All Framework packages now require PHP 7.2 or newer. +| | v1 (1.9.3) | v2 (2.0.0) | +|---|---|---| +| PHP | `^5.3.10 \| ^7.0 \| ^8.0` | `^7.2.5` | +| Application types | Web, CLI, Daemon | Web only | +| Response | Internal `stdClass` | PSR-7 `ResponseInterface` | +| Events | Comment placeholders only | Dispatched via `joomla/event` | +| Interfaces | none | four, see below | +| `joomla/input` | required | suggested | -### PSR-7 Responses now supported -In order to support PSR-7 responses there is a single break in backwards incompatibility. The `\Joomla\Application\AbstractWebApplication::getBody()` method does not have a `toBody` parameter. +## Minimum supported PHP version raised -The package internals use the Laminas [Diactoros package](https://github.com/laminas/laminas-diactoros) for building the Response object. If you wish to use another PSR-7 compatible library you will need to extend the `setHeader`, `getHeaders`, `clearHeaders`, `setBody`, `prependBody`, and `appendBody` methods. +All Framework packages now require **PHP 7.2.5** or newer. -### AbstractWebApplication::checkToken now validates a token -The method `\Joomla\Application\AbstractWebApplication::checkToken` has been changed to validate a token in addition to checking if it is present in the request. Additionally, the homepage redirect on an invalid token has been removed. +## CLI and daemon classes removed -### CLI Classes Removed +The following were removed with no replacement in this package: -The `\Joomla\Application\AbstractCliApplication` and all `Joomla\Application\Cli` namespace classes have been removed. The new `joomla/console` package should be used going forward. +* `Joomla\Application\AbstractCliApplication` +* `Joomla\Application\AbstractDaemonApplication` +* the entire `Joomla\Application\Cli` namespace — `CliInput`, `CliOutput`, `ColorProcessor`, + `ColorStyle`, `Output\Stdout`, `Output\Xml`, `Output\Processor\ColorProcessor`, + `Output\Processor\ProcessorInterface` -### Added a concrete web application +Use the [`joomla/console`](https://github.com/joomla-framework/console) package for command line +applications. It builds on `AbstractApplication`, so configuration, logging and events work the +same way. -There is a new `\Joomla\Application\WebApplication` class available which serves as a minimal but functional web application class. This class extends `\Joomla\Application\AbstractWebApplication` and implements `\Joomla\Application\SessionAwareWebApplicationInterface` and therefore makes all application features available out-of-the-box. +There is no replacement for `AbstractDaemonApplication`. Its `pcntl` based process handling +(`daemonize()`, `fork()`, `detach()`, `restart()`, `stop()`, `writeProcessIdFile()`, +`loadConfiguration()` and the signal handlers) is gone entirely. Run long living processes under a +supervisor such as systemd or Supervisor instead. -### `$input` property moved to web application classes +## `AbstractApplication::__construct()` signature changed -The `$input` property of `\Joomla\Application\AbstractApplication` has been moved to `\Joomla\Application\AbstractWebApplication` and is no longer required to create a minimal application. With the introduction of the `joomla/console` package, which does not use the `joomla/input` package to read the console input, it is no longer practical to require all application classes support this input API. +The input object is no longer a concern of the base application class, so it was dropped from the +constructor: -Additionally, direct access to the property has been deprecated. To access the input, you should use the `\Joomla\Application\AbstractWebApplication::getInput()` method. Direct read access to the property will be removed in 3.0. +```php +// v1 +public function __construct(Input $input = null, Registry $config = null) -### Session functionality moved to new application interface/trait +// v2 +public function __construct(Registry $config = null) +``` -As sessions are not a mandatory function of web applications, session related functionality has been moved to a new `\Joomla\Application\SessionAwareWebApplicationInterface` extending `\Joomla\Application\WebApplicationInterface`. `\Joomla\Application\SessionAwareWebApplicationTrait` is provided to simplify implementing this interface in your application. +If you extend `AbstractApplication` directly and call `parent::__construct($input, $config)`, the +config object is now silently passed as the input. Update the call. -### Interfaces for application classes +`AbstractWebApplication::__construct()` keeps its input argument and gains a fourth one for the +response: -Interfaces have been created for the application classes with the following structure: +```php +// v1 +public function __construct(Input $input = null, Registry $config = null, WebClient $client = null) -- `\Joomla\Application\ApplicationInterface` defines the base requirements for all applications -- `\Joomla\Application\ConfigurationAwareApplicationInterface` defines an application which is aware of a configuration object -- `\Joomla\Application\WebApplicationInterface` defines a web application handling HTTP requests and serving HTTP responses -- `\Joomla\Application\SessionAwareWebApplicationInterface` defines a web application which requires session support +// v2 +public function __construct( + Input $input = null, + Registry $config = null, + WebClient $client = null, + ResponseInterface $response = null +) +``` + +## `$input` property moved to the web application classes + +The `$input` property moved from `AbstractApplication` to `AbstractWebApplication`. With +`joomla/console` handling console input differently, requiring every application to carry a +`joomla/input` object was no longer practical. `joomla/input` therefore moved from `require` to +`suggest` in `composer.json`. + +Direct access to the property was deprecated in favour of the new `getInput()` method: + +```php +// Deprecated in 2.0, still works via a magic getter +$app->input->getInt('id'); + +// Correct +$app->getInput()->getInt('id'); +``` + +> The magic getter was announced for removal in 3.0 but actually survived the whole 3.x series. It +> was removed in **4.0.0** — see [Updating from v3 to v4](v3-to-v4-update.md). + +## PSR-7 responses + +The response is now a PSR-7 `ResponseInterface` instead of an internal `stdClass`. Two new methods +expose it: + +```php +$response = $app->getResponse(); +$app->setResponse($response->withStatus(201)); +``` + +This causes one backwards incompatible change in the public API: **`getBody()` lost its +parameter.** + +```php +// v1 — could return the internal array of body parts +public function getBody($asArray = false) + +// v2 — always returns a string +public function getBody() +``` + +Replace `getBody(true)` with `getBody()` and split the string yourself if you relied on the array +form. + +The package uses [Laminas Diactoros](https://github.com/laminas/laminas-diactoros) to build the +response. To use a different PSR-7 implementation, override `setHeader()`, `getHeaders()`, +`clearHeaders()`, `setBody()`, `prependBody()` and `appendBody()`. + +## Application events + +v1 marked the lifecycle with `// @event onBeforeExecute` comments and dispatched nothing. v2 +dispatches real events through `joomla/event`, which became a required dependency. + +```php +use Joomla\Application\ApplicationEvents; +use Joomla\Event\Dispatcher; + +$app->setDispatcher(new Dispatcher()); +``` + +New classes: + +* `Joomla\Application\ApplicationEvents` — the event name constants `BEFORE_EXECUTE`, + `AFTER_EXECUTE`, `ERROR`, `BEFORE_RESPOND`, `AFTER_RESPOND` +* `Joomla\Application\Event\ApplicationEvent` — carries the application +* `Joomla\Application\Event\ApplicationErrorEvent` — carries the application and the `Throwable` + +`AbstractApplication::execute()` now catches every `Throwable` from `doExecute()` and dispatches +`ApplicationEvents::ERROR` instead of letting it bubble up. **If you do not register a listener for +that event, an exception produces an empty response with status 200.** See +[Lifecycle and events](lifecycle-and-events.md#error-handling). + +Attaching a dispatcher is optional: `dispatchEvent()` returns `null` when none is set. + +## Controller resolvers + +New in v2, for turning a route into a callable: + +* `Joomla\Application\Controller\ControllerResolverInterface` +* `Joomla\Application\Controller\ControllerResolver` +* `Joomla\Application\Controller\ContainerControllerResolver` — resolves from a PSR-11 container + +See [Routing and controllers](routing-and-controllers.md). + +## A concrete web application + +`Joomla\Application\WebApplication` is a minimal but functional web application. It extends +`AbstractWebApplication`, implements `SessionAwareWebApplicationInterface`, and dispatches a +request to a controller using a router and a controller resolver: + +```php +$app = new WebApplication( + new ContainerControllerResolver($container), + $router, + $input, + $config +); + +$app->execute(); +``` + +## Session functionality moved to an interface and trait + +Sessions are not mandatory for a web application, so the session methods moved out of +`AbstractWebApplication` into: + +* `Joomla\Application\SessionAwareWebApplicationInterface` — extends `WebApplicationInterface` +* `Joomla\Application\SessionAwareWebApplicationTrait` — the implementation + +The type hint also changed from the concrete class to the interface: + +```php +// v1 +public function setSession(Session $session) + +// v2 +public function setSession(SessionInterface $session) +``` + +## `checkToken()` now validates the token + +`checkToken()` previously only checked whether the token was *present* in the request. It now +validates it. The redirect to `index.php` on a new session was removed — the method returns a +boolean and leaves the reaction to you: + +```php +// v1 behaviour: could redirect and close the application +$app->checkToken(); + +// v2 behaviour: returns false, you decide +if (!$app->checkToken()) { + $app->setHeader('Status', '403', true); + $app->setBody('Invalid CSRF token.'); + + return; +} +``` + +## Interfaces for application classes + +The application contract is now expressed as four interfaces: + +| Interface | Defines | +|---|---| +| `ApplicationInterface` | The base requirements for all applications | +| `ConfigurationAwareApplicationInterface` | An application aware of a configuration object | +| `WebApplicationInterface` | A web application handling HTTP requests and serving HTTP responses | +| `SessionAwareWebApplicationInterface` | A web application which requires session support | + +Type your own services against these rather than against the abstract classes. + +## Dependency changes + +| Package | v1 (1.9.3) | v2 (2.0.0) | +|---|---|---| +| `php` | `^5.3.10 \| ^7.0 \| ^8.0` | `^7.2.5` | +| `joomla/input` | `^1.2` (required) | moved to `suggest` | +| `joomla/registry` | `^1.4.5 \| ^2.0` | `^1.4.5 \| ^2.0` | +| `psr/log` | `^1.0` | `^1.0` | +| `joomla/event` | — | `^2.0` (new) | +| `laminas/laminas-diactoros` | — | `^2.2.2` (new) | +| `psr/http-message` | — | `^1.0` (new) | +| `symfony/deprecation-contracts` | — | `^2.1` (new) | + +New optional dependencies (`suggest`): `joomla/controller`, `joomla/input`, `joomla/router`, +`joomla/session`, `joomla/uri`, `psr/container`. diff --git a/docs/v2-to-v3-update.md b/docs/v2-to-v3-update.md index 1e9ea89c..ab0ea427 100644 --- a/docs/v2-to-v3-update.md +++ b/docs/v2-to-v3-update.md @@ -1,7 +1,88 @@ -## Updating from v2 to v3 +# Updating from v2 to v3 -The following changes were made to the Application package between v1 and v2. +Release 3.0.0 is a small upgrade. It raises the PHP requirement, moves the codebase to PSR-12, and +removes one response header. **No public or protected method signature changed**, so code written +against 2.x keeps working as long as it runs on PHP 8.1. -### Minimum supported PHP version raised +## At a glance -All Framework packages now require PHP 8.1 or newer. +| | v2 (2.0.4) | v3 (3.0.0) | +|---|---|---| +| PHP | `^7.2.5` | `^8.1.0` | +| Public API | — | unchanged | +| Coding style | Joomla Coding Standard | PSR-12 | +| `X-Content-Encoded-By` header | sent when compressing | removed | + +## Minimum supported PHP version raised + +All Framework packages now require **PHP 8.1** or newer. + +## The `X-Content-Encoded-By` header is no longer sent + +`AbstractWebApplication::compress()` used to add a third header alongside the encoding headers: + +```php +$this->setHeader('Content-Encoding', $encoding); +$this->setHeader('Vary', 'Accept-Encoding'); +$this->setHeader('X-Content-Encoded-By', 'Joomla'); // removed in 3.0.0 +``` + +The header carried no functional meaning and had been considered obsolete since 2013. It is simply +gone — compressed responses now carry `Content-Encoding` and `Vary` only. + +This matters if you assert on response headers in tests, or if a downstream system reads +`X-Content-Encoded-By` to identify the application. Set it yourself if you still need it: + +```php +$dispatcher->addListener( + ApplicationEvents::BEFORE_RESPOND, + static fn ($event) => $event->getApplication()->setHeader('X-Content-Encoded-By', 'Joomla') +); +``` + +## Codebase converted to PSR-12 + +The whole package was reformatted from the Joomla Coding Standard to PSR-12 (tabs to spaces, brace +placement, import ordering). This touches nearly every line but changes no behaviour. + +Two practical consequences: + +* A `git diff` between 2.x and 3.x is almost entirely noise. Use `git diff -w` or compare method + signatures when looking for real changes. +* If you maintain patches against this package, expect all of them to conflict. Reapply rather than + rebase. + +The `joomla/coding-standards` dev dependency was replaced by `squizlabs/php_codesniffer` with a +PSR-12 ruleset. + +## No API changes + +Every `public` and `protected` method in `src/` has the same name and signature in 3.0.0 as in +2.0.4. The `Web\WebClient` constants are unchanged too. Upgrading is a matter of satisfying the PHP +and dependency requirements. + +## Dependency changes + +| Package | v2 (2.0.4) | v3 (3.0.0) | +|---|---|---| +| `php` | `^7.2.5` | `^8.1.0` | +| `joomla/event` | `^2.0` | `^3.0` | +| `joomla/registry` | `^1.4.5 \| ^2.0` | `^3.0` | +| `psr/log` | `^1.0` | `^1.0 \| ^2.0 \| ^3.0` | +| `psr/http-message` | `^1.0` | `^1.0` | +| `laminas/laminas-diactoros` | `^2.2.2` | `^2.24.0` | +| `symfony/deprecation-contracts` | `^2.1` | `^2 \| ^3` | + +The optional packages in `suggest` moved to their 3.x releases: `joomla/controller`, +`joomla/input`, `joomla/router`, `joomla/session` and `joomla/uri` are all `^3.0`. +`psr/container` stays at `^1.0`. + +## Deprecations still in place + +The following were deprecated in 2.0 and **still work in 3.x**. Both were removed in 4.0.0 — see +[Updating from v3 to v4](v3-to-v4-update.md): + +* Reading `$app->input` directly instead of calling `$app->getInput()` +* Passing a boolean as the `$status` argument of `redirect()` + +Fixing them while still on 3.x makes the move to 4.0 a no-op. diff --git a/docs/v3-to-v4-update.md b/docs/v3-to-v4-update.md index 2f2b2539..79cc95e4 100644 --- a/docs/v3-to-v4-update.md +++ b/docs/v3-to-v4-update.md @@ -1,23 +1,115 @@ -## Updating from v3 to v4 +# Updating from v3 to v4 -The following changes were made to the Application package between v3 and v4. +Release 4.0.0 raises the PHP requirement and removes the two backwards compatibility shims that +were deprecated in 2.0. Both removals are mechanical to fix, and both can be fixed while still on +3.x. -### Minimum supported PHP version raised +## At a glance -All Framework packages now require PHP 8.3 or newer. +| | v3 (3.0.4) | v4 (4.0.0) | +|---|---|---| +| PHP | `^8.1.0` | `^8.3.0` | +| `$app->input` magic getter | deprecated, works | **removed** | +| Boolean `$status` in `redirect()` | deprecated, works | **removed** | +| `psr/http-message` | `^1.0` | `^2.0` | +| `laminas/laminas-diactoros` | `^2.24.0` | `^3.6.0` | -### Access to input data +## Minimum supported PHP version raised -Accessing the input data in the input attribute of the application can now only be done via the `getInput()`. +All Framework packages now require **PHP 8.3** or newer. + +## Access to input data + +`AbstractWebApplication::__get()` has been removed. It existed solely as a compatibility proxy that +mapped `$app->input` to `getInput()` and emitted a deprecation notice. Reading the property now +raises a PHP warning and evaluates to `null`, so the failure surfaces later as a call on `null`: + +```php +// Old — removed in 4.0.0 +$app->input->getInt('id'); + +// New +$app->getInput()->getInt('id'); +``` + +Because the magic getter also handled every other property name with an `E_USER_NOTICE`, any other +undefined property read on the application now follows normal PHP semantics. + +To find the call sites before upgrading: + +```bash +grep -rn -- '->input->' src/ +``` + +## Status on redirect + +`redirect()` no longer accepts a boolean `$status`. In 1.x, `true` meant "301 Moved Permanently" +and `false` meant "303 See Other"; 2.0 deprecated that and 4.0 removed the translation. Pass the +HTTP status code: ```php -// Old -$app->input->getInt(); +// Old — removed in 4.0.0 +$app->redirect('/target', true); // meant 301 +$app->redirect('/target', false); // meant 303 // New -$app->getInput()->getInt(); +$app->redirect('/target', 301); +$app->redirect('/target', 303); ``` -### Status on redirect +The docblock type narrowed from `integer|boolean` to `integer` in both +`AbstractWebApplication::redirect()` and `WebApplicationInterface::redirect()`. + +A boolean now fails loudly: the guard is +`if (!\is_int($status) && !$this->isRedirectState($status))`, and `isRedirectState()` casts to int +first, so `true` becomes `1`, fails the `> 299` test, and an `InvalidArgumentException` is thrown. + +> Note that the same guard accepts **any** integer, including non-redirect codes — `redirect($url, 200)` +> passes and sends a `Location` header with status 200. Only booleans and other non-integers are +> rejected. Pass a real 3xx code. + +## PSR-7 2.0 + +`psr/http-message` moved from `^1.0` to `^2.0`, and `laminas/laminas-diactoros` from `^2.24` to +`^3.6`. + +PSR-7 2.0 adds return types to every interface method. This affects you if you **implement** any +PSR-7 interface yourself — a custom `ResponseInterface` passed into the application constructor, +or a decorator around one: + +```php +// PSR-7 1.x +public function withStatus($code, $reasonPhrase = '') + +// PSR-7 2.0 +public function withStatus(int $code, string $reasonPhrase = ''): ResponseInterface +``` + +Consuming a PSR-7 object needs no change. If you only pass Diactoros objects around, upgrading the +dependency is enough. + +## Dependency changes + +| Package | v3 (3.0.4) | v4 (4.0.0) | +|---|---|---| +| `php` | `^8.1.0` | `^8.3.0` | +| `psr/http-message` | `^1.0` | `^2.0` | +| `laminas/laminas-diactoros` | `^2.24.0` | `^3.6.0` | +| `joomla/event` | `^3.0` | `^4.0` | +| `joomla/registry` | `^3.0` | `^4.0` | +| `psr/log` | `^1.0 \| ^2.0 \| ^3.0` | unchanged | +| `symfony/deprecation-contracts` | `^2 \| ^3` | unchanged | + +Optional packages in `suggest`: + +| Package | v3 (3.0.4) | v4 (4.0.0) | +|---|---|---| +| `joomla/controller` | `^3.0` | `^4.0` | +| `joomla/input` | `^3.0` | `^4.0` | +| `joomla/router` | `^3.0` | `^4.0` | +| `joomla/session` | `^3.0` | `^4.0` | +| `joomla/uri` | `^3.0` | `^4.0` | +| `psr/container` | `^1.0` | `^2.0` | -When calling `$app->redirect()`, you can not hand over a boolean value for the status, but always have to use an integer representing the HTTP status code. +`psr/container` 2.0 also adds return types (`has(string $id): bool`, `get(string $id): mixed`). +This matters only if you pass a hand-written container to `ContainerControllerResolver`.