diff --git a/setup/installation.md b/setup/installation.md index 8aa14f73..43384ab0 100644 --- a/setup/installation.md +++ b/setup/installation.md @@ -16,7 +16,7 @@ Before you proceed, check that your server meets the minimum system requirements Winter CMS has some server requirements for web hosting: -- PHP version 8.1 or above. (we recommend at least PHP 8.2) +- PHP version 8.2 or above. - The following PHP extensions installed and enabled: - cURL - GD @@ -30,10 +30,10 @@ We also recommend the installation of the PDO SQLite extension, regardless of yo ### Supported Databases -- MariaDB 10.2+ ([Version Policy](https://mariadb.org/about/#maintenance-policy)) +- MariaDB 10.3+ ([Version Policy](https://mariadb.org/about/#maintenance-policy)) - MySQL 5.7+ ([Version Policy](https://en.wikipedia.org/wiki/MySQL#Release_history)) -- PostgreSQL 9.6+ ([Version Policy](https://www.postgresql.org/support/versioning/)) -- SQLite 3.8.8+ +- PostgreSQL 10.0+ ([Version Policy](https://www.postgresql.org/support/versioning/)) +- SQLite 3.35.0+ - SQL Server 2017+ ([Version Policy](https://docs.microsoft.com/en-us/lifecycle/products/?products=sql-server)) When using the SQL Server database engine, you will need to install the [group concatenation](https://github.com/orlando-colamatteo/ms-sql-server-group-concat-sqlclr) user-defined aggregate. diff --git a/setup/upgrade-guide.md b/setup/upgrade-guide.md index ac8e6bae..f7a5f01c 100644 --- a/setup/upgrade-guide.md +++ b/setup/upgrade-guide.md @@ -1,40 +1,47 @@ --- title: "Getting Started: Upgrade Guide" -description: "Upgrading your Winter CMS installation from v1.1 to v1.2." +description: "Upgrading your Winter CMS installation from v1.2 to v1.3." --- -# Upgrade Guide (v1.1 to v1.2) +# Upgrade Guide (v1.2 to v1.3) -With Winter's core goal of stability, upgrading your Winter CMS v1.1 site to the new v1.2 branch should be relatively straight-forward. However, as with all major releases, there may be some breaking changes or new requirements that may mean some adjustments to your projects. +Winter CMS v1.3 moves the platform from Laravel 9 to Laravel 12. Most of the work has been done inside Winter itself, so for many projects the upgrade comes down to updating `composer.json`, reviewing a few configuration files and running `composer update`. However, jumping three major versions of Laravel also brings in new major versions of Symfony, Carbon, Monolog and PHPUnit, and some of their changes reach your own code and plugins. + +This guide lists the new requirements and the breaking changes in Winter, together with the changes from Laravel 10, 11 and 12 and their dependencies that affect Winter projects. Changes from those upgrade guides that do not apply to Winter (for example, the new Laravel 11 application structure) are left out. We make every effort to document all new requirements and breaking changes, however, your project may have some very unique edge cases that we have not accounted for. If your project does not work after following the guide below, please feel free to [submit an issue on Github](https://github.com/wintercms/winter/issues/new/choose) or reach out to the [community on Discord](https://discord.gg/D5MFSPH6Ux) for assistance. -> **IMPORTANT:** If your site runs a version of Winter CMS v1.0, please upgrade to v1.1 first before going through this upgrade guide. +> **IMPORTANT:** If your site runs a version of Winter CMS older than v1.2, please upgrade to v1.2 first before going through this upgrade guide. ## New requirements ### Application server -- PHP 8.0.2 is now the minimum supported version. [PHP 7.4 and below is now unsupported](https://www.php.net/supported-versions.php). +- PHP 8.2 is now the minimum supported version. +- Composer 2.2 or above is required. +- If you use the HTTP client (`Http` facade), curl 7.34.0 or above is required. ### Database server - The following versions of the database servers supported by Winter CMS are now the minimum versions required: - MySQL 5.7+ ([Version Policy](https://en.wikipedia.org/wiki/MySQL#Release_history)) - - MariaDB 10.2+ ([Version Policy](https://mariadb.org/about/#maintenance-policy)) - - PostgreSQL 9.6+ ([Version Policy](https://www.postgresql.org/support/versioning/)) - - SQLite 3.8.8+ + - MariaDB 10.3+ ([Version Policy](https://mariadb.org/about/#maintenance-policy)) + - PostgreSQL 10.0+ ([Version Policy](https://www.postgresql.org/support/versioning/)) + - SQLite 3.35.0+ - SQL Server 2017+ ([Version Policy](https://docs.microsoft.com/en-us/lifecycle/products/?products=sql-server)) +> **NOTE:** Laravel 12 documents SQLite 3.26.0 as its minimum, but Winter requires SQLite 3.35.0. The `winter:up` command stops with an error on older SQLite versions. This includes plugin test runs, which use an in-memory SQLite database. + ### Dependencies -- The following dependencies are now used in Winter CMS 1.2. These will automatically be installed when upgrading. - - Laravel: 9.x - - Laravel Tinker: 2.7 - - Twig: 3.x - - Symfony\Yaml: 5.1 - - PHPUnit: 9.5.8 - - Mockery: 1.4.4 - - Assetic: 3.0 +- The following dependencies are now used in Winter CMS 1.3. These will automatically be installed when upgrading. + - Laravel: 12.x + - Laravel Tinker: 2.8 + - Symfony: 7.x (Console, Process, HTTP Foundation and the other components installed by Laravel) + - Carbon: 3.x + - Monolog: 3.x + - Doctrine DBAL: 3.x + - PHPUnit: 11.x + - Twig: 3.x (unchanged) ## Composer updates @@ -45,249 +52,335 @@ If you installed your project via Composer, you must make the following changes Within the `require` section: ```json -"php": "^8.0.2", -"winter/storm": "~1.2.0", -"winter/wn-system-module": "~1.2.0", -"winter/wn-backend-module": "~1.2.0", -"winter/wn-cms-module": "~1.2.0", -"laravel/framework": "^9.1", -"wikimedia/composer-merge-plugin": "~2.0.1" +"php": "^8.2", +"winter/storm": "~1.3.0", +"winter/wn-system-module": "~1.3.0", +"winter/wn-backend-module": "~1.3.0", +"winter/wn-cms-module": "~1.3.0", +"laravel/framework": "^12.0", +"wikimedia/composer-merge-plugin": "~2.1.0" ``` Within the `require-dev` section: ```json -"phpunit/phpunit": "^9.5.8", -"mockery/mockery": "^1.4.4", +"phpunit/phpunit": "^11.0", +"mockery/mockery": "^1.6", "fakerphp/faker": "^1.9.2", "squizlabs/php_codesniffer": "^3.2", -"php-parallel-lint/php-parallel-lint": "^1.0", -"dms/phpunit-arraysubset-asserts": "^0.1.0|^0.2.1" +"php-parallel-lint/php-parallel-lint": "^1.0" ``` -You must also remove the entire `autoload-dev` section below, unless you are using this section for your own requirements. +The `dms/phpunit-arraysubset-asserts` package does not have a release that supports PHPUnit 11. Remove it unless your own tests use `assertArraySubset()`. -```json -"autoload-dev": { - "classmap": [ - "tests/concerns/InteractsWithAuthentication.php", - "tests/fixtures/backend/models/UserFixture.php", - "tests/TestCase.php", - "tests/PluginTestCase.php" - ] -}, -``` +Other Laravel packages you require may also need a new version that supports Laravel 12 (see [Laravel packages](#laravel-packages) below). -Once done, run `composer update` to install all the required dependencies and upgrade Winter CMS to version 1.2. +Once done, run `composer update` to install all the required dependencies and upgrade Winter CMS to version 1.3. -## Tests folder +## Configuration file changes -> **Impacts:** All users. +> **Impacts:** Most users. -The `tests` folder in the root folder of your project is no longer required, as all Winter CMS tests are now stored in the modules that those tests relate to. You may remove the entire folder, unless you are using the folder to store your own application tests. +There are only a few changes to the default configuration files. You should review the [default configuration files](https://github.com/wintercms/winter/tree/1.3/config) and implement any changes as desired. -## Server script +### config/app.php -> **Impacts:** All users. +- A new `env` setting holds the application environment (`APP_ENV`). +- The `locale`, `fallback_locale` and `faker_locale` settings can now be set with the `APP_LOCALE`, `APP_FALLBACK_LOCALE` and `APP_FAKER_LOCALE` environment variables. +- A new `previous_keys` setting allows you to rotate your application key. List your old keys, separated by commas, in the `APP_PREVIOUS_KEYS` environment variable, and data encrypted with them can still be decrypted. -The `server.php` file located in your project root folder is no longer required for `php artisan serve` to function. You may remove this if you wish, however, there is no harm to your project if you leave it there. +```php +'env' => env('APP_ENV', 'production'), -## Configuration file changes +'locale' => env('APP_LOCALE', 'en'), +'fallback_locale' => env('APP_FALLBACK_LOCALE', 'en'), +'faker_locale' => env('APP_FAKER_LOCALE', 'en_US'), -> **Impacts:** Most users. +'key' => env('APP_KEY'), -There have been significant changes to the default configuration files, mainly in those that are based on the Laravel framework's configuration files. You should review the changes to the [default configuration files](https://github.com/wintercms/winter/tree/1.2/config) and implement any changes as desired. +'previous_keys' => [ + ...array_filter( + explode(',', (string) env('APP_PREVIOUS_KEYS', '')) + ), +], +``` -The following items have the highest likelihood of having an impact on your projects: +### config/hashing.php -### config/app.php +- Add `'rehash_on_login' => false`. Laravel 11 introduced automatic password rehashing on login, which is enabled by default when this setting is missing. Winter's backend and frontend authentication do not use it, but it would apply to any Laravel authentication guard used by a plugin. -- An application key is no longer provided by default. If your application does not yet have one set, run `php artisan key:generate` in your project root folder. -- `Illuminate\Http\Request::HTTP_X_FORWARDED_ALL` has been removed in Symfony 6, but it's referenced with no intermediate layer in the default `app.php` config file for the `testedProxyHeaders` configuration from v1.1.4 to v1.1.8. See https://github.com/wintercms/storm/commit/fcecefda3fd3966310306b5799852c53d6330a64 for more details. If you are using this value for `trustedProxyHeaders`, change this to the string `HEADER_X_FORWARDED_ALL`. +### Settings that are missing from your configuration -### config/database.php +Winter only reads your own configuration files. It does not merge in the default configuration files of the Laravel framework, so a setting that was added in Laravel 10, 11 or 12 is not active until you add it to your own configuration files. Please keep the following in mind when you compare your configuration with the Laravel 12 defaults: -- The `database.useConfigForTesting` configuration is no longer supported. Use `config/testing/database.php` to override testing defaults instead. -- Setting a `varcharmax` on `mysql` database connections is no longer supported as the minimum required version of MySQL now supports 255 character UTF8 strings by default. +- Do not add `'serve' => true` to the `local` disk in `config/filesystems.php`. Laravel 12 uses this setting to serve the files of a disk through a route. Because Winter's `local` disk is public, Laravel would serve every file in `storage/app`, including protected uploads, without checking a signature. +- Do not add `'verify' => true` to the `bcrypt` or `argon` settings in `config/hashing.php` unless every password hash in your database uses that algorithm. With this setting, `Hash::check()` throws an exception for any hash made with another algorithm instead of returning `false`. -### config/mail.php +### Cache key prefixes -- The structure has changed - update your local copy to match the one [now provided on the 1.2 branch](https://github.com/wintercms/winter/blob/1.2/config/mail.php). The old structure may work, but it has been reported in some cases that it does not, so it is recommended to update it to reflect the new structure. +> **Impacts:** Users of the Redis, Memcached and DynamoDB cache stores. -### config/filesystems.php +Laravel no longer adds a `:` to the end of the cache prefix. With Winter's default `CACHE_PREFIX`, a cache key such as `winter_cache:settings` becomes `winter_cachesettings` after the upgrade, which means your application starts with an empty cache and the old keys stay in the store until they expire. If you want to keep the old key names, set the `CACHE_PREFIX` environment variable to a value that ends with a `:`. -- The `local` disk currently needs a `visibility => 'public'` setting in order for the files and folders under `/storage/app/resized` to be publicly accessible when using the image resizer. +## Console commands + +> **Impacts:** Plugin developers. -## Third-party email delivery providers +### Command names and registration -> **Impacts:** Users of third-party email providers, plugin developers. +Symfony Console 7 no longer uses the static `$defaultName` property of a command, and the `getDefaultName()` method now returns `null` unless the command class has an `#[AsCommand]` attribute. Commands still get their name from their `$signature` or `$name` property, so the `$defaultName` property can simply be removed. -- Built-in support for SES, Postmark, Mailgun, Mandrill, SendGrid, & SparkPost mail drivers has been removed. Use the applicable first-party driver plugins as needed: - - [Winter.DriverAWS](https://github.com/wintercms/wn-driveraws-plugin) - - [Winter.DriverMailgun](https://github.com/wintercms/wn-drivermailgun-plugin) - - [Winter.DriverMandrill](https://github.com/wintercms/wn-drivermandrill-plugin) - - [Winter.DriverPostmark](https://github.com/wintercms/wn-driverpostmark-plugin) - - [Winter.DriverSendGrid](https://github.com/wintercms/wn-driversendgrid-plugin) - - [Winter.DriverSparkPost](https://github.com/wintercms/wn-driversparkpost-plugin) -- Review the [SymfonyMailer upgrade guide](https://laravel.com/docs/9.x/upgrade#symfony-mailer) if you interact with mail message objects directly, as several methods and return types have changed. +However, if your plugin registers commands using `getDefaultName()`, every one of those commands is registered under the same key and only the last one registered will be available. Nothing warns you about this: the other commands are simply missing from `php artisan list`, and scheduled tasks that run them fail. -## Storage & files +```php +// Before Winter v1.3: each command was registered under its own name +$this->registerConsoleCommand(ImportProducts::getDefaultName(), ImportProducts::class); +$this->registerConsoleCommand(ExportProducts::getDefaultName(), ExportProducts::class); -> **Impacts:** Rackspace storage users, plugin developers. +// From Winter v1.3: use a unique key for each command +$this->registerConsoleCommand('acme.import-products', ImportProducts::class); +$this->registerConsoleCommand('acme.export-products', ExportProducts::class); +``` -- The Rackspace Flysystem driver / adapter is no longer supported. -- `Symfony\Component\HttpFoundation\File\UploadedFile->getClientSize()` has been removed. Use `getSize()` instead. -- The `getAdapter()` method on Storage disks is no longer present. `getPathPrefix()` & `setPathPrefix()` methods have been added to the `$disk` instances if desired. +The `getDefaultName()` method is deprecated and will be removed in Symfony 8, so avoid it even if you add the `#[AsCommand]` attribute to your commands. -## Localization changes +### The `-s` shortcut for `--silent` -> **Impacts:** Plugin developers. +Symfony Console 7.2 added a global `--silent` option to all commands. The `-s` shortcut for the `--silent` option of the `mix:compile`, `mix:create`, `mix:install`, `mix:watch`, `npm:install`, `npm:run`, `npm:update`, `npm:version`, `vite:compile`, `vite:create`, `vite:install` and `vite:watch` commands has therefore been removed. Use `--silent` instead, for example in your deployment scripts. -The `translator.beforeResolve` event has been removed for performance reasons. `Lang::set($key, $value, $locale)` can be used as a replacement. +### Command signatures -### Overriding a specific key +Laravel now parses command signatures more strictly. An option written as `{?--option}` is no longer recognized as an option and makes the command fail. Write options as `{--option}` instead. -Before Winter v1.2: +If your command handles signals by implementing Symfony's `SignalableCommandInterface`, or overrides the `handleSignal()` method of Winter's `HandlesCleanup` trait, the method must now have the following signature: ```php - Event::listen('translator.beforeResolve', function ($key, $replaces, $locale) { - if ($key === 'validation.reserved') { - return Lang::get('winter.builder::lang.validation.reserved'); - } -}); +public function handleSignal(int $signal, int|false $previousExitCode = 0): int|false ``` -From Winter v1.2: +## Database + +> **Impacts:** Plugin developers, some users. + +### Raw expressions + +The value returned by `DB::raw()` is an `Expression` object that can no longer be converted to a string. Code that concatenates a raw expression with a string, or casts it to a string, now throws an error. Pass plain strings to the `*Raw()` query methods instead, or call the `getValue()` method on the expression: ```php -Lang::set('validation.reserved', Lang::get('winter.builder::lang.validation.reserved')); +$sql = DB::raw('COUNT(*)')->getValue(DB::connection()->getQueryGrammar()); ``` -### Overriding an entire namespace +### Modifying columns -If you were using the event to extend / override an entire namespaced key (for example in order to share localization overrides that would normally be present in a project's `lang` override folder at the project root between multiple projects via a plugin), then you could switch your code to use the following example: +In Laravel 11, calling `->change()` on a column in a migration resets any column attribute that you do not state again. Winter keeps the `nullable`, `default` and `comment` attributes of the existing column when you do not specify them, so most existing migrations keep working. Other attributes, such as `unsigned`, the character set and collation, `autoIncrement` and generated columns, are still reset. We recommend that you state the full column definition when you modify a column: ```php -Lang::set('winter.builder::lang', require __DIR__ . '/lang/en-ca/lang.php', 'en-ca'); +$table->integer('votes')->unsigned()->default(1)->comment('The vote count')->change(); ``` -## Facades +### Column types -> **Impacts:** Plugin developers. +The following changes to the schema builder affect new migrations, but also your existing migrations whenever they run on an empty database, for example on a fresh installation or in your test suite: -- Facades must return string keys rather than objects (See https://github.com/laravel/framework/pull/38017). -- `Winter\Storm\Support\Facades\Str` facade has been removed. Use `Winter\Storm\Support\Str` directly instead. The `Str` alias now points to `Winter\Storm\Support\Str` instead. +- The `float()` column type now takes a single `$precision` argument and creates a double-precision column when no precision is given. The `double()` column type no longer accepts a precision and scale. Use `decimal()` for amounts that need a fixed number of decimals. +- The `unsignedDecimal()`, `unsignedDouble()` and `unsignedFloat()` methods have been removed. Use `->unsigned()` on the column instead. +- The spatial column types (`point()`, `lineString()`, `polygon()` and the other spatial types) have been removed. Use `geometry()` or `geography()` instead. -## Laravel packages +### Limits in eager loads + +In Laravel 9, a `limit()` or `take()` inside an eager load constraint limited the total number of related records loaded for all parents together. In Laravel 12, the limit applies to each parent: + +```php +// Before Winter v1.3: loads 3 posts in total, spread across the categories +// From Winter v1.3: loads 3 posts for each category +$categories = Category::with(['posts' => function ($query) { + $query->latest()->limit(3); +}])->get(); +``` + +Check any eager loads that use a limit, as they may now load more records than before. On database servers that support window functions, the limit is applied with a `ROW_NUMBER()` window function. MySQL 9 rejects a random order (`inRandomOrder()`) in that window with error 3587, while MySQL 8.4 accepts it. If you need random related records, load the relation without the limit and use `shuffle()` and `take()` on the collection instead, or query the relation for a single parent. + +### MariaDB + +Winter now includes a dedicated `mariadb` database driver. The `mysql` driver continues to work with MariaDB servers, so switching is optional. + +### Doctrine DBAL + +Laravel 11 no longer uses Doctrine DBAL. Winter still requires `doctrine/dbal` and still provides the following methods on its database connections: `getDoctrineConnection()`, `getDoctrineSchemaManager()`, `getDoctrineColumn()`, `registerDoctrineType()`, `isDoctrineAvailable()` and `usingNativeSchemaOperations()`. + +The `dbal.types` configuration, `DB::registerDoctrineType()` and the `Schema::getAllTables()`, `Schema::getAllViews()` and `Schema::getAllTypes()` methods have been removed. Use `Schema::getTables()`, `Schema::getViews()` and `Schema::getTypes()` instead. `Schema::getColumnType()` now returns the column type as the database reports it. + +### Schema inspection + +- On MySQL, MariaDB and PostgreSQL, `Schema::getTables()` now returns the tables of every database or schema that the database user can access, not only the default one. Pass the `schema` argument to limit the results. +- `Schema::getTableListing()` now returns table names prefixed with their schema. Pass `schemaQualified: false` to get the old result. + +### Date attributes + +The `$dates` property on models is still supported by Winter's `Model` class, so you do not need to change existing models. If you have models that extend Laravel's `Illuminate\Database\Eloquent\Model` class directly, move their `$dates` to the `$casts` property with the `datetime` cast. + +## Dates and times + +> **Impacts:** Most users, plugin developers. + +Carbon has been updated from version 2 to version 3. Carbon is also used for the date attributes of your models, so these changes can affect any code or template that works with dates. + +- The `diffInSeconds()`, `diffInMinutes()`, `diffInHours()`, `diffInDays()` and other `diffIn*()` methods now return a float instead of an integer, and the result is negative when the given date is before the date you call the method on. A countdown such as `$expiresAt->diffInDays($now)` now returns a negative number with decimals. To get the old result, pass `true` as the second argument to get an absolute value and convert the result to an integer: `(int) $expiresAt->diffInDays($now, true)`. This also applies to Twig templates, where `{{ post.published_at.diffInDays() }}` now prints decimals. +- `Carbon::createFromTimestamp()` now creates the date in the UTC timezone, instead of in the default timezone of your application. Pass the timezone as the second argument if you need a different one. +- `startOfWeek()` and `endOfWeek()` without an argument now follow the first day of the week of the current locale. For example, with the `en_US` locale the week now starts on Sunday. Pass the day to keep a fixed week start: `startOfWeek(\Carbon\WeekDay::Monday)`. +- The `formatLocalized()`, `setUtf8()`, `setWeekStartsAt()` and `setWeekEndsAt()` methods have been removed. Use `isoFormat()` to format dates in the current locale. +- Comparison methods such as `isSameDay()` now require an argument. + +The [Carbon 3 migration guide](https://carbon.nesbot.com/guide/getting-started/migration.html) lists all the changes. + +## Helper functions + +> **Impacts:** All users, plugin developers. + +### `array_first()` and `array_last()` + +Winter no longer provides the `array_first()` and `array_last()` helper functions, because PHP 8.5 includes its own functions with the same names. These functions only accept the array: on PHP 8.2 to 8.4 (through the Symfony polyfill), a callback or default value is ignored without any error, and on PHP 8.5 passing one throws an error. + +```php +// Before Winter v1.3: returns 2 +// From Winter v1.3: returns 1 on PHP 8.4, throws an error on PHP 8.5 +$first = array_first([1, 2, 3], fn ($value) => $value > 1); + +// Use the Arr class instead: returns 2 +$first = Arr::first([1, 2, 3], fn ($value) => $value > 1); +``` + +Replace every call that passes a callback or a default value with `Arr::first()` or `Arr::last()`. + +### Other helpers + +- The `$seed` argument of the `array_shuffle()` helper has been removed and is ignored if you pass it. +- `Form::selectMonth()` now formats the month names with the PHP `date()` function instead of `strftime()`, and its default format is `F`. If you pass a format to this method, use the [`date()` format characters](https://www.php.net/manual/en/datetime.format.php) (for example `M` instead of `%b`). The month names are always in English. + +## Service container > **Impacts:** Plugin developers. -The version of Laravel has been changed from 6.x LTS to 9.x. If you are using packages made for Laravel, you may have to go through and update them to a version compatible with Laravel 9.x. +When the service container resolves a class, it now uses the default value of a constructor parameter instead of resolving the type of that parameter. A dependency that has a default value of `null` is therefore now `null`. This applies to all classes that are resolved from the container, such as components, controllers and jobs. -## Unit testing +```php +// Before Winter v1.3: $users is an instance of the Users class +// From Winter v1.3: $users is null +public function __construct(?CodeBase $cmsObject = null, $properties = [], ?Users $users = null) -> **Impacts:** Plugin developers that use test cases. +// Give the dependency no default value so that it is always resolved +public function __construct(Users $users, ?CodeBase $cmsObject = null, $properties = []) +``` -Unit testing should now be conducted using the `php artisan winter:test` command, as opposed to running unit tests directly on `phpunit`. This ensures that the correct bootstrap is used, as well as the necessary environment configuration is created. +## Authentication -If you have unit tests that extend either the base `TestCase` or `PluginTestCase` classes, these must now extend `\System\Tests\Bootstrap\TestCase` and `\System\Tests\Bootstrap\PluginTestCase` instead, respectively. +> **Impacts:** Plugin developers. -If your plugin is intended to work (and test) on both Winter 1.1 and 1.2, you may create a stub class in your test cases that will use the corresponding base test case class depending on the Winter CMS version in use. +- Classes that implement Laravel's `Authenticatable` contract must now have a `getAuthPasswordName()` method, which returns the name of the password attribute. Winter's `Winter\Storm\Auth\Models\User` class already provides this method. +- Classes that implement Laravel's `UserProvider` contract must now have a `rehashPasswordIfRequired()` method. +- If you extend `Winter\Storm\Auth\Manager` and override the `setUser()` method, the method must now return `static`. + +## Mail + +> **Impacts:** Plugin developers that use test cases. + +The `Mail::fake()` method now requires the mail manager as an argument: ```php -// Create a stub BaseTestCase that extends the correct plugin test case file depending on Winter version -if (class_exists('System\Tests\Bootstrap\PluginTestCase')) { - class BaseTestCase extends \System\Tests\Bootstrap\PluginTestCase - { - } -} else { - class BaseTestCase extends \PluginTestCase - { - } -} - -class MyTestCase extends BaseTestCase +Mail::fake(app('mail.manager')); ``` -## Storm library internals +## Localization + +> **Impacts:** Plugin developers. + +- Translation strings of plugins and modules can now also be overridden in the Laravel style, with a `lang/vendor/{namespace}/{locale}/{group}.php` file in the root folder of your project, in addition to the Winter style (`lang/{locale}/{vendor}/{plugin}/{group}.php`). +- `Lang::choice()` now falls back to the fallback locale when a key is missing in the current locale. +- If you extend Winter's translation classes, the `FileLoader::loadPath()` method has been replaced with `loadPaths(array $paths, ...)`, and `Translator::localeForChoice()` now receives the translation key as its first argument. + +## Logging + +> **Impacts:** Users with custom log handlers, processors or formatters. + +Monolog has been updated to version 3. Log records are now `Monolog\LogRecord` objects instead of arrays, and log levels are an enum. If you have custom log handlers, processors, formatters or `tap` classes, update them according to the [Monolog 3 upgrade notes](https://github.com/Seldaek/monolog/blob/main/UPGRADE.md). + +## Requests -> **Impacts:** Some users and plugin developers. +> **Impacts:** Plugin developers. + +In Symfony 7, the `getInt()`, `getBoolean()` and `filter()` methods of a request's parameter bags (for example `$request->query->getInt('page')`) throw an exception for a value that cannot be converted, which results in a "400 Bad Request" response. The `Input::get()` method and Laravel's `$request->integer()` and `$request->boolean()` methods are not affected. + +## Laravel packages + +> **Impacts:** Plugin developers. -Version 1.2 of Winter includes a large code refactoring and documentation cleanup for the Storm library, to ensure that our base functionality is fully documented and works in a consistent and expected way. In addition, this brings our Storm library closer to the base Laravel functionality, which should make upgrades quicker and less painful in the future. +The version of Laravel has been changed from 9.x to 12.x. If you are using packages made for Laravel, you may have to go through and update them to a version compatible with Laravel 12.x. -While we have ensured that the potential for breaking changes is low, there may be some cases of breaking functionality if the functionality used an undocumented or incorrectly-documented API. We will list all the changes below, grouped by the "package" within the Storm library: +- The facades that Laravel added after version 9, such as `Context`, `Number`, `Process`, `Schedule` and `Uri`, do not have a global alias in Winter. Import them with their full class name, for example `use Illuminate\Support\Facades\Process;`. +- If a plugin requires the `spatie/once` package, remove it: Laravel now includes its own `once()` function, which conflicts with the one from this package. -- [\Winter\Storm\Database](#database) -- [\Winter\Storm\Extension](#extension) -- [\Winter\Storm\Filesystem](#filesystem) -- [\Winter\Storm\Foundation](#foundation) -- [\Winter\Storm\Halcyon](#halcyon) -- [\Winter\Storm\Html](#html) -- [\Winter\Storm\Parse](#parse) -- [\Winter\Storm\Support\Testing](#supporttesting) +## Unit testing -### Database +> **Impacts:** Plugin developers that use test cases. -- To prevent unsafe model instantiating, the model constructors are now forced to only allow a single `$attributes` parameter via an interface (`\Winter\Storm\Database\ModelInterface` and `\Winter\Storm\Halcyon\ModelInterface`). This will ensure that calls like `Model::make()` or `Model::create()` will execute correctly. It is possible that some people might have used additional parameters for their model constructors - these will no longer work and must be moved to another method. -- Due to the above, Pivot model construction has been rewritten. The constructor used to allow 4 parameters but now only allows the one `$attributes` parameter as per the Model class. Construction now happens more closely to Laravel's format of calling a `Pivot::fromAttributes()` static method. If you previously used `new Pivot()` to create a pivot model, switch to using `Pivot::fromAttributes()` instead. -- The `MorphToMany` class now extends the `MorphToMany` class from Laravel, as opposed to the `BelongsToMany` class in Winter. This prevents repeated code in the Winter `MorphToMany` class and maintains covariance with Laravel. This will mean that it will no longer inherit from the Winter `BelongsToMany` class. To allow for this, we have converted most of the overridden `BelongsToMany` functionality in Winter into a trait (`Concerns\BelongsOrMorphsToMany`). The `BelongsToMany` relation class now also uses this trait. -- The relation traits found in `src/Database/Relations` have been moved to `src/Database/Relations/Concerns`, in order to keep just the actual relation classes within the `src/Database/Relations` directory. In the unlikely event that you are using a relation trait directly, please rename the trait classes to the following: - - `Winter\Storm\Database\Relations\AttachOneOrMany` to `Winter\Storm\Database\Relations\Concerns\AttachOneOrMany` - - `Winter\Storm\Database\Relations\DeferOneOrMany` to `Winter\Storm\Database\Relations\Concerns\DeferOneOrMany` - - `Winter\Storm\Database\Relations\DefinedConstraints` to `Winter\Storm\Database\Relations\Concerns\DefinedConstraints` - - `Winter\Storm\Database\Relations\HasOneOrMany` to `Winter\Storm\Database\Relations\Concerns\HasOneOrMany` - - `Winter\Storm\Database\Relations\MorphOneOrMany` to `Winter\Storm\Database\Relations\Concerns\MorphOneOrMany` +Winter now uses PHPUnit 11. The base test case classes (`\System\Tests\Bootstrap\TestCase` and `\System\Tests\Bootstrap\PluginTestCase`) are still the same, but PHPUnit 11 has changed the way tests are written in several ways. -### Extension +### Test configuration -- Previously, the `Winter\Storm\Extension\Extendable::extendClassWith()` method returned the current class if the extension name provided was an empty string. This appears to be a code smell, so this has been changed to throw an Exception. +The format of the `phpunit.xml` file has changed. Run the following command in the folder of each `phpunit.xml` file to convert it to the new format: -### Filesystem +```bash +../../../vendor/bin/phpunit --migrate-configuration +``` -- The `Filesystem::symbolizePath` method's `$default` parameter now accepts a `string`, `bool` or `null`. Only in the case of `null` will the method return the original path - the given `$default` will be used in all other cases. This is the same as the original functionality, but we're documenting it in case you have customised this method in some fashion. -- The `Filesystem::isPathSymbol` method previously returned the path symbol used, as a string, if one was found and `false` if not found. However, the docblock stipulated, as well as the method name itself implied, that this is a boolean check function, so we have converted this to a straight boolean response - `true` if a path symbol is used, otherwise `false`. -- Many classes within the `Filesystem` namespace have had type hinting and return types added to enforce functionality and clarify documentation. If you extend any of these classes in your plugins, you may need to update your method signatures. +The command replaces the `` section with a `` section and removes the settings that no longer exist, such as `convertErrorsToExceptions`. It also renames `backupStaticAttributes` to `backupStaticProperties`. -### Foundation +### Writing tests -- The `Winter\Storm\Foundation\Application` class callback methods `before` and `after` were documented as `void` methods, but returned a value. These no longer return a value. +- Data provider methods must be `public static`. +- Test metadata in docblock comments, such as `@test`, `@dataProvider` and `@depends`, is deprecated and will no longer work in PHPUnit 12. Use attributes instead, such as `#[Test]`, `#[DataProvider('provideValues')]` and `#[Depends('testCreate')]` from the `PHPUnit\Framework\Attributes` namespace. When a test method has at least one attribute, PHPUnit ignores all of its docblock metadata, so convert all of the metadata of a method at once. +- Most methods of PHPUnit's `TestCase` class are now `final`, and a test class that declares a method with the same name, even a private one, causes a fatal error. Rename helper methods in your tests that are called, for example, `status()`, `name()`, `size()`, `groups()`, `result()`, `output()`, `count()`, `run()`, `provides()`, `requires()`, `any()`, `once()`, `never()` or `setLocale()`. +- The `withConsecutive()`, `setMethods()` and `at()` mock methods have been removed. Use `onlyMethods()` instead of `setMethods()`. The `getMockForAbstractClass()`, `getMockForTrait()`, `getObjectForTrait()` and `returnValue()` methods are deprecated. +- Laravel's `expectsEvents()`, `expectsJobs()`, `expectsNotifications()`, `withoutEvents()` and `withoutJobs()` test methods have been removed. Use `Event::fake()`, `Bus::fake()` and `Notification::fake()` instead. +- If your test case has a `tearDown()` method, make sure that it calls `parent::tearDown()`. +- Winter's base `TestCase` class still provides the `assertFileNotExists()`, `assertRegExp()` and `assertObjectHasAttribute()` assertions, but you should switch to `assertFileDoesNotExist()`, `assertMatchesRegularExpression()` and `assertObjectHasProperty()`. -### Halcyon +### Running tests -- To prevent unsafe model instantiating, the model constructors are now forced to only allow a single `$attributes` parameter via an interface (`\Winter\Storm\Database\ModelInterface` and `\Winter\Storm\Halcyon\ModelInterface`). This will ensure that calls like `Model::make()` or `Model::create()` will execute correctly. It is possible that some people might have used additional parameters for their model constructors - these will no longer work and must be moved to another method. -- The Halcyon Builder `insert` method now returns an `int` representing the created model's filesize, not a `bool`. -- The Halcyon Builder `insert` method requires the `$values` parameter to actually contain variables and will throw an Exception if an empty array is provided. Previously, this was silently discarded and returned `true` (although this would not actually save the file). +The `winter:test` command keeps its options. Running it with `-m` or `-p` but no module or plugin name now runs the tests of all modules or all plugins. Pass any other PHPUnit arguments after `--`, for example `php artisan winter:test -p Acme.Blog -- --stop-on-failure`. -### HTML +## Storm library internals -- The `Winter\Storm\Html\FormBuilder` class has had type hinting and return types added to enforce functionality and clarify documentation. If you extend this class in your plugin, you may need to make changes to your method signatures if you overwrite the base functionality. +> **Impacts:** Plugin developers that extend Storm classes. -### Parse +Some methods of the Storm library have new signatures to stay compatible with Laravel 12. If your plugin extends one of the following classes and overrides the method, update the method signature to match: -- The `Bracket` class constructor is now `final` to prevent unsafe static calls to the `parse` static method. -- The `Bracket::parseString()` method previously returned `false` if a string was not provided for parsing, or the string was empty after trimming. Since the signature calls for a string, we won't check for this anymore. If the string is empty, an empty string will be returned. -- The `markdown.beforeParse` event in the `Markdown` class sent a single `MarkdownData` instance as a parameter to the listeners. It now sends an array with the `MarkdownData` instance as the only value, to meet the signature requirements for events. -- The `Syntax\FieldParser` class constructor has been made `final` to prevent unsafe static calls to the `parse` statuc method. -- The `$template` parameter for the `Syntax\FieldParser` class constructor was originally optional. Since there is no purpose for this, and no way to populate the template after the fact, this has been made required. -- The `Syntax\Parser` class constructor has been made `final` to prevent unsafe static calss to the `parse` static method. -- The `$template` parameter for the `Syntax\Parser` class constructor was originally optional. Since there is no purpose for this, and no way to populate the template after the fact, this has been made required. +- `Winter\Storm\Auth\Manager::setUser(Authenticatable $user): static` +- `Winter\Storm\Console\Traits\HandlesCleanup::handleSignal(int $signal, int|false $previousExitCode = 0): int|false` +- `Winter\Storm\Database\Builder::paginate($perPage = null, $currentPage = null, $columns = ['*'], $pageName = 'page', $total = null)`: the new `$total` argument allows you to skip the count query. +- `Winter\Storm\Database\Model::hasAttribute($key)`: Winter's own version has been removed in favor of Laravel's. +- `Winter\Storm\Database\Relations\Concerns\DeferOneOrMany::getWithDeferredQualifiedKeyName(): string` +- `Winter\Storm\Halcyon\MemoryCacheManager::repository(Store $store, array $config = [])` +- `Winter\Storm\Translation\FileLoader::loadPaths(array $paths, $locale, $group)` replaces `loadPath()`. +- `Winter\Storm\Translation\Translator::localeForChoice($key, $locale)` +- The `Winter\Storm\Config\Repository::load()`, `afterLoading()` and `callAfterLoad()` methods now have a `string` type for the namespace. -### Support\Testing +The database connection classes have also changed. Winter's MySQL, PostgreSQL, SQLite and SQL Server connections now extend Laravel's connection classes directly and share their Winter functionality through the `Winter\Storm\Database\Connections\HasConnection` trait. The old `Winter\Storm\Database\Connections\Connection` base class is deprecated. If you have a custom database connection, grammar or schema blueprint: -- The `Winter\Storm\Support\Testing\Fakes\MailFake::queue()` method has had its signature re-arranged to remain compatible with Laravel's `MailFake` class, with the `$queue` parameter being moved to the second parameter. The new signature is `($view, $queue, $data, $callback)`. +- Laravel's `Illuminate\Database\PDO` classes have been removed. Winter provides replacements in the `Winter\Storm\Database\PDO` namespace. +- Query and schema grammars now receive the database connection in their constructor, and `Grammar::setConnection()` and `Connection::withTablePrefix()` have been removed. +- The `Blueprint` class now receives the database connection as its first constructor argument. ## Upgrade guides for dependencies > **Impacts:** Informational only. -- [PHP 8.0](https://www.php.net/manual/en/migration80.php) -- [PHP 8.1](https://www.php.net/manual/en/migration81.php) -- [Laravel 7](https://laravel.com/docs/7.x/upgrade) -- [Laravel 8](https://laravel.com/docs/8.x/upgrade) -- [Laravel 9](https://laravel.com/docs/9.x/upgrade) -- [SwiftMailer to Symfony Mailer](https://github.com/laravel/framework/pull/38481) -- [Symfony v5](https://github.com/symfony/symfony/blob/5.4/UPGRADE-5.0.md) -- [Symfony v6](https://github.com/symfony/symfony/blob/6.0/UPGRADE-6.0.md) -- [Twig 3.0](https://twig.symfony.com/doc/2.x/deprecated.html) ([changelog](https://github.com/twigphp/Twig/blob/3.x/CHANGELOG) & [announcement](https://symfony.com/blog/preparing-your-applications-for-twig-3)) -- [Flysystem v2.0](https://flysystem.thephpleague.com/docs/upgrade-from-1.x/) & https://github.com/laravel/framework/pull/33612 -- [Flysystem v3.0](https://flysystem.thephpleague.com/docs/what-is-new/) & https://github.com/laravel/framework/pull/40411 +- [PHP 8.2](https://www.php.net/manual/en/migration82.php) +- [Laravel 10](https://laravel.com/docs/10.x/upgrade) +- [Laravel 11](https://laravel.com/docs/11.x/upgrade) +- [Laravel 12](https://laravel.com/docs/12.x/upgrade) +- [Symfony v7](https://github.com/symfony/symfony/blob/7.0/UPGRADE-7.0.md) +- [Carbon 3](https://carbon.nesbot.com/guide/getting-started/migration.html) +- [Monolog 3](https://github.com/Seldaek/monolog/blob/main/UPGRADE.md) +- [Doctrine DBAL 3](https://github.com/doctrine/dbal/blob/3.0.x/UPGRADE.md) +- [PHPUnit 10](https://github.com/sebastianbergmann/phpunit/blob/10.0.0/ChangeLog-10.0.md) & [PHPUnit 11](https://github.com/sebastianbergmann/phpunit/blob/11.0.0/ChangeLog-11.0.md)