Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions docs/en/appendices/5-4-migration-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ Running `bin/cake` without providing a command name no longer displays the
"No command provided" error message. Instead, the `help` command is shown
directly.

Unknown tokens after a parent command with subcommands are now rejected
(e.g. `bin/cake i18n nonsense`) instead of silently invoking the parent.
See [Subcommand Validation](../console-commands/commands#subcommand-validation).

The `help` command is now hidden from command listings (via
`CommandHiddenInterface`). It remains accessible by running `bin/cake help` or
`bin/cake help <command>`.
Expand Down Expand Up @@ -68,6 +72,16 @@ See [Application and Plugin Events](../core-libraries/events#registering-event-l
- Loading a component with the same alias as the controller's default table now
triggers a warning. See [Component Alias Conflicts](../controllers/components#component-alias-conflicts).

### View

- `FormHelper` now wraps hidden form blocks (CSRF, FormProtection,
`postLink()` / `postButton()`) with the HTML5 boolean `hidden` attribute
instead of an inline `style="display:none;"`. This makes the default markup
compatible with a strict Content-Security-Policy (no need for
`style-src 'unsafe-inline'`). If you previously selected those wrappers via
CSS (e.g. `div[style="display:none;"]`), switch to `[hidden]` or set the
`hiddenClass` template option to opt out and emit a class instead.

## Deprecations

### Command Helpers
Expand Down Expand Up @@ -145,6 +159,11 @@ See [Application and Plugin Events](../core-libraries/events#registering-event-l
provides constants (`Index::GIN`, `Index::GIST`, `Index::SPGIST`,
`Index::BRIN`, `Index::HASH`) for these access methods.
See [Reading Indexes and Constraints](../orm/schema-system#reading-indexes-and-constraints).
- Added `Cake\Database\Type\EnumLabelTrait` and the
`Cake\Database\Type\Attribute\Label` attribute. The trait provides a default
`label()` implementation backed by the translator and the attribute lets
individual cases override the derived label. See
[EnumLabelTrait and the Label Attribute](../orm/database-basics#enumlabeltrait-and-the-label-attribute).

### Http

Expand All @@ -166,6 +185,10 @@ See [Application and Plugin Events](../core-libraries/events#registering-event-l
- Added `I18n::setCacheConfig()` to route translator persistence to a Cache
config other than the default `_cake_translations_`.
- The `cake i18n extract` command now also extracts enum labels using the #[Label] attribute.
- Added `PluralRules::setRule()` to register a custom Gettext plural rule for
a locale whose built-in form is missing or differs from the layout used by
your .po/.mo files. See
[Customizing Plural Rules](../core-libraries/internationalization-and-localization#customizing-plural-rules).

### ORM

Expand All @@ -191,3 +214,6 @@ See [Application and Plugin Events](../core-libraries/events#registering-event-l

- Added `{{inputId}}` template variable to `inputContainer` and `error` templates
in FormHelper. See [Built-in Template Variables](../views/helpers/form#built-in-template-variables).
- `FormHelper::enumOptions()` is now public. This lets you build `select`
options from a backed enum class even when the form was created without
an entity context. See [Creating Select Pickers](../views/helpers/form#creating-select-pickers).
24 changes: 24 additions & 0 deletions docs/en/console-commands/commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,30 @@ Usage:
cake user [-h] [-q] [-v]
```

## Subcommand Validation

::: info Added in version 5.4.0
Strict validation for unknown subcommands was added in 5.4.0.
:::

When a parent command has registered subcommands (e.g. `i18n extract`,
`i18n init`), CakePHP rejects unknown positional tokens that follow the
parent name. Previously, typos such as `bin/cake i18n nonsense` silently
invoked the parent command and discarded the trailing token; now you get a
clear error listing the available subcommands:

```text
$ bin/cake i18n nonsense
Error: Unknown command `cake i18n nonsense`.
Available subcommands: `i18n extract`, `i18n init`.
Run `cake i18n --help` to see usage.
```

This only kicks in when the parent command has sibling subcommands. Commands
that accept arbitrary positional arguments (e.g. `routes generate`) are
unaffected, and option-like tokens (`--help`, `-v`) following the command
name continue to be forwarded to the parser.

## Grouping Commands

By default, in the help output CakePHP will group commands into core, app, and
Expand Down
59 changes: 59 additions & 0 deletions docs/en/core-libraries/internationalization-and-localization.md
Original file line number Diff line number Diff line change
Expand Up @@ -425,6 +425,65 @@ msgstr[2] "{0} datoteka je uklonjeno"
Please visit the [Launchpad languages page](https://translations.launchpad.net/+languages)
for a detailed explanation of the plural form numbers for each language.

#### Customizing Plural Rules

::: info Added in version 5.4.0
`PluralRules::setRule()` and `PluralRules::resetRules()` were added in 5.4.0.
:::

When `__n()` / `__dn()` and the other Gettext-style plural functions resolve a
message, CakePHP picks the plural form via `Cake\I18n\PluralRules::calculate()`.
The built-in rules cover most CLDR locales, but they can lag behind upstream
CLDR releases and they do not cover every minority language. If you hit a
locale whose plural form is missing or wrong, you can register a custom rule
without patching CakePHP:

```php
use Cake\I18n\PluralRules;

// Breton: 5 plural forms (CLDR)
PluralRules::setRule('br', function (int $n): int {
if ($n % 10 === 1 && $n % 100 !== 11 && $n % 100 !== 71 && $n % 100 !== 91) {
return 0;
}
if ($n % 10 === 2 && $n % 100 !== 12 && $n % 100 !== 72 && $n % 100 !== 92) {
return 1;
}
if (in_array($n % 10, [3, 4, 9], true)
&& !in_array($n % 100, [13, 14, 19, 73, 74, 79, 93, 94, 99], true)
) {
return 2;
}
if ($n !== 0 && $n % 1_000_000 === 0) {
return 3;
}

return 4;
});
```

The closure receives the integer count and must return the zero-based plural
form index that matches the `msgstr[N]` entries in your **.po** / **.mo**
files. Custom rules take precedence over the built-in map, so they can also
be used to override a built-in rule that does not match the form layout used
by your translation files.

Register rules in **config/bootstrap.php** so they are available before any
translation is requested. The locale string is normalized via
`Locale::canonicalize()` and an invalid locale throws an
`InvalidArgumentException`. To drop all registered custom rules (typically
between tests), call:

```php
PluralRules::resetRules();
```

> [!NOTE]
> `PluralRules` is only consulted for Gettext-style messages
> (`__n()`, `__dn()`, `msgstr[0]` / `msgstr[1]` / …). The ICU plural selector
> shown above resolves its own forms via `MessageFormatter` and is unaffected
> by `setRule()`.

## Creating Your Own Translators

If you need to diverge from CakePHP conventions regarding where and how
Expand Down
49 changes: 48 additions & 1 deletion docs/en/orm/database-basics.md
Original file line number Diff line number Diff line change
Expand Up @@ -596,7 +596,54 @@ enum ArticleStatus: string implements EnumLabelInterface
```

This can be useful if you want to use your enums in `FormHelper` select
inputs. You can use [bake](../bake) to generate an enum class:
inputs.

#### EnumLabelTrait and the Label Attribute

::: info Added in version 5.4.0
`Cake\Database\Type\EnumLabelTrait` and the
`Cake\Database\Type\Attribute\Label` attribute were added in 5.4.0.
:::

Writing the `label()` `match` block by hand becomes repetitive once an enum
grows past a few cases. `EnumLabelTrait` provides a default `label()`
implementation that derives the label from the case name and resolves it
through the translator. Cases can override the derived label with the
`#[Label]` attribute:

```php
namespace App\Model\Enum;

use Cake\Database\Type\Attribute\Label;
use Cake\Database\Type\EnumLabelInterface;
use Cake\Database\Type\EnumLabelTrait;

enum ArticleStatus: string implements EnumLabelInterface
{
use EnumLabelTrait;

case Published = 'Y';

#[Label('Not yet published')]
case Unpublished = 'N';

#[Label('Archived', domain: 'articles', context: 'status')]
case Archived = 'A';
}
```

For a case **without** a `#[Label]` attribute, the trait humanizes the case
name (`Unpublished` → `Unpublished`, `InReview` → `In review`) and runs it
through the translator. For cases **with** a `#[Label]`, the explicit label
string is used and is translated using the optional `domain` and `context`
constructor arguments. Labels are extracted by `cake i18n extract`, which
detects the `#[Label]` attribute and emits one msgid per case.

> [!TIP]
> Pair `EnumLabelTrait` with `EnumLabelInterface` so type-aware consumers
> (e.g. `FormHelper`'s automatic enum support) keep working.

You can use [bake](../bake) to generate an enum class:

```bash
# generate an enum class with two cases and stored as an integer
Expand Down
32 changes: 29 additions & 3 deletions docs/en/views/helpers/form.md
Original file line number Diff line number Diff line change
Expand Up @@ -1462,6 +1462,24 @@ Output:
</select>
```

To build `$options` from a backed enum, you can use `enumOptions()`:

```php
use App\Model\Enum\ArticleStatus;

echo $this->Form->select('status', $this->Form->enumOptions(ArticleStatus::class));
```

When `ArticleStatus` implements `EnumLabelInterface` (or uses
`EnumLabelTrait`), the option text is taken from `label()`; otherwise the
case name is used. This is useful when the form was created without an
entity context, where the automatic enum detection on `control()` does not
apply.

::: info Added in version 5.4.0
`FormHelper::enumOptions()` was made public in 5.4.0.
:::

**Controlling Select Pickers via Attributes**

By using specific options in the `$attributes` parameter you can control
Expand Down Expand Up @@ -2140,14 +2158,22 @@ echo $this->Form->end(['data-type' => 'hidden']);
Will output:

```html
<div style="display:none;">
<div hidden="hidden">
<input type="hidden" name="_Token[fields]" data-type="hidden"
value="2981c38990f3f6ba935e6561dc77277966fabd6d%3AAddresses.id">
<input type="hidden" name="_Token[unlocked]" data-type="hidden"
value="address%7Cfirst_name">
</div>
```

::: info Added in version 5.4.0
The wrapper around hidden security tokens now defaults to the HTML5
`hidden` boolean attribute instead of `style="display:none;"`. This avoids
needing `style-src 'unsafe-inline'` under a strict Content-Security-Policy.
You can still opt for a CSS class instead by setting the `hiddenClass`
template option on `FormHelper`.
:::

> [!NOTE]
> If you are using
> `Cake\Controller\Component\FormProtectionComponent` in your
Expand Down Expand Up @@ -2190,11 +2216,11 @@ Will output HTML similar to:

```html
<form method="post" accept-charset="utf-8" action="/Rtools/tickets/delete/5">
<div style="display:none;">
<div hidden="hidden">
<input name="_method" value="POST" type="hidden">
</div>
<button type="submit">Delete Record</button>
<div style="display:none;">
<div hidden="hidden">
<input name="_Token[fields]" value="186cfbfc6f519622e19d1e688633c4028229081f%3A" type="hidden">
<input name="_Token[unlocked]" value="" type="hidden">
<input name="_Token[debug]" value="%5B%22%5C%2FRtools%5C%2Ftickets%5C%2Fdelete%5C%2F1%22%2C%5B%5D%2C%5B%5D%5D" type="hidden">
Expand Down