From 57b209a94b7492da88396c295b27af94f5f1c190 Mon Sep 17 00:00:00 2001 From: mscherer Date: Sat, 28 Mar 2026 19:51:31 +0100 Subject: [PATCH 1/2] Add missing 5.4 migration guide entries - Component-alias/default-table collision warning - PSR-13 link support on Response - New collection helpers: keys(), values(), implode(), when(), unless() --- docs/en/appendices/5-4-migration-guide.md | 69 +++++++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/docs/en/appendices/5-4-migration-guide.md b/docs/en/appendices/5-4-migration-guide.md index f8db436ca7..f4ab27c1e0 100644 --- a/docs/en/appendices/5-4-migration-guide.md +++ b/docs/en/appendices/5-4-migration-guide.md @@ -27,6 +27,27 @@ The default eager loading strategy for `HasMany` and `BelongsToMany` association has changed from `select` to `subquery`. If you need the previous behavior, explicitly set `'strategy' => 'select'` when defining associations. +### Controller + +When loading a component that has the same alias as the controller's default table, +CakePHP now triggers a warning. This helps identify situations where accessing +`$this->ComponentAlias` returns the table instead of the component: + +```php +class PaymentsController extends AppController +{ + public function initialize(): void + { + parent::initialize(); + // Triggers warning: Component alias `Payments` clashes with the default table name + $this->loadComponent('Payments'); + } +} +``` + +To resolve this, either use a different component alias or set `Controller::$defaultTable` +to an empty string if the controller doesn't use a table. + ## Deprecations - WIP @@ -60,12 +81,60 @@ explicitly set `'strategy' => 'select'` when defining associations. nested array format matching `contain()` syntax. See [Converting Request Data into Entities](../orm/saving-data#converting-request-data-into-entities). +### Http + +- Added PSR-13 Link implementation with `Cake\Http\Link\Link` and `Cake\Http\Link\LinkProvider` + classes for hypermedia link support. Links added to responses are automatically emitted + as HTTP `Link` headers. + +```php +use Cake\Http\Link\Link; + +// Add links to a response +$response = $response->withLink(new Link('/api/users', 'self')); +$response = $response->withLink( + (new Link('/api/users?page=2')) + ->withRel('next') + ->withAttribute('type', 'application/json'), +); + +// Preload resources +$response = $response->withLink( + (new Link('/css/app.css')) + ->withRel('preload') + ->withAttribute('as', 'style'), +); +``` + ### Utility - Added `Cake\Utility\Fs\Finder` class for fluent file discovery with pattern matching, depth control, and custom filters. Added `Cake\Utility\Fs\Path` for cross-platform path manipulation. +### Collection + +- Added `keys()` method to return a collection containing only the keys. +- Added `values()` method to return a collection of values re-indexed with consecutive integers. +- Added `implode()` method to concatenate elements into a string, with optional path extraction. +- Added `when()` and `unless()` methods for conditional method chaining. + +```php +// keys() and values() +$collection = new Collection(['a' => 1, 'b' => 2]); +$collection->keys()->toList(); // ['a', 'b'] +$collection->values()->toList(); // [1, 2] + +// implode() with path extraction +$collection = new Collection([['name' => 'foo'], ['name' => 'bar']]); +$collection->implode(', ', 'name'); // 'foo, bar' + +// Conditional chaining with when() and unless() +$collection = new Collection($items) + ->when($shouldFilter, fn($c) => $c->filter(fn($v) => $v['active'])) + ->unless($hasDefaults, fn($c) => $c->append($defaults)); +``` + ### View - Added `{{inputId}}` template variable to `inputContainer` and `error` templates From f326d2bd0d0003a2c0d05c9f5c1fccf843295557 Mon Sep 17 00:00:00 2001 From: mscherer Date: Sat, 28 Mar 2026 19:56:16 +0100 Subject: [PATCH 2/2] Move detailed docs to actual documentation pages Migration guide now contains concise entries with links. Detailed documentation added to: - controllers/components.md: Component alias conflicts section - controllers/request-response.md: Hypermedia Links (PSR-13) section - core-libraries/collections.md: keys(), values(), implode(), when(), unless() methods --- docs/en/appendices/5-4-migration-guide.md | 64 ++----------- docs/en/controllers/components.md | 41 ++++++++ docs/en/controllers/request-response.md | 55 +++++++++++ docs/en/core-libraries/collections.md | 108 ++++++++++++++++++++-- 4 files changed, 201 insertions(+), 67 deletions(-) diff --git a/docs/en/appendices/5-4-migration-guide.md b/docs/en/appendices/5-4-migration-guide.md index f4ab27c1e0..93ade52098 100644 --- a/docs/en/appendices/5-4-migration-guide.md +++ b/docs/en/appendices/5-4-migration-guide.md @@ -29,24 +29,8 @@ explicitly set `'strategy' => 'select'` when defining associations. ### Controller -When loading a component that has the same alias as the controller's default table, -CakePHP now triggers a warning. This helps identify situations where accessing -`$this->ComponentAlias` returns the table instead of the component: - -```php -class PaymentsController extends AppController -{ - public function initialize(): void - { - parent::initialize(); - // Triggers warning: Component alias `Payments` clashes with the default table name - $this->loadComponent('Payments'); - } -} -``` - -To resolve this, either use a different component alias or set `Controller::$defaultTable` -to an empty string if the controller doesn't use a table. +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). ## Deprecations @@ -85,26 +69,7 @@ to an empty string if the controller doesn't use a table. - Added PSR-13 Link implementation with `Cake\Http\Link\Link` and `Cake\Http\Link\LinkProvider` classes for hypermedia link support. Links added to responses are automatically emitted - as HTTP `Link` headers. - -```php -use Cake\Http\Link\Link; - -// Add links to a response -$response = $response->withLink(new Link('/api/users', 'self')); -$response = $response->withLink( - (new Link('/api/users?page=2')) - ->withRel('next') - ->withAttribute('type', 'application/json'), -); - -// Preload resources -$response = $response->withLink( - (new Link('/css/app.css')) - ->withRel('preload') - ->withAttribute('as', 'style'), -); -``` + as HTTP `Link` headers. See [Hypermedia Links](../controllers/request-response#hypermedia-links). ### Utility @@ -114,26 +79,9 @@ $response = $response->withLink( ### Collection -- Added `keys()` method to return a collection containing only the keys. -- Added `values()` method to return a collection of values re-indexed with consecutive integers. -- Added `implode()` method to concatenate elements into a string, with optional path extraction. -- Added `when()` and `unless()` methods for conditional method chaining. - -```php -// keys() and values() -$collection = new Collection(['a' => 1, 'b' => 2]); -$collection->keys()->toList(); // ['a', 'b'] -$collection->values()->toList(); // [1, 2] - -// implode() with path extraction -$collection = new Collection([['name' => 'foo'], ['name' => 'bar']]); -$collection->implode(', ', 'name'); // 'foo, bar' - -// Conditional chaining with when() and unless() -$collection = new Collection($items) - ->when($shouldFilter, fn($c) => $c->filter(fn($v) => $v['active'])) - ->unless($hasDefaults, fn($c) => $c->append($defaults)); -``` +- Added [`keys()`](../core-libraries/collections#keys) and [`values()`](../core-libraries/collections#values) methods for extracting keys or re-indexing values. +- Added [`implode()`](../core-libraries/collections#implode) method to concatenate elements into a string. +- Added [`when()`](../core-libraries/collections#when) and [`unless()`](../core-libraries/collections#unless) methods for conditional method chaining. ### View diff --git a/docs/en/controllers/components.md b/docs/en/controllers/components.md index 2d9647f85d..651904295a 100644 --- a/docs/en/controllers/components.md +++ b/docs/en/controllers/components.md @@ -98,6 +98,47 @@ class MyFlashComponent extends FlashComponent The above would *alias* `MyFlashComponent` to `$this->Flash` in your controllers. +### Component Alias Conflicts + +When a component alias matches the controller's default table name, accessing +that property will return the table instead of the component. CakePHP triggers +a warning when this conflict is detected: + +```php +class PaymentsController extends AppController +{ + public function initialize(): void + { + parent::initialize(); + // Warning: Component alias `Payments` clashes with the default table name + $this->loadComponent('Payments'); + } + + public function index() + { + // This returns PaymentsTable, not PaymentsComponent! + $this->Payments; + } +} +``` + +To resolve this conflict, either use a different component alias: + +```php +$this->loadComponent('Payments', ['className' => 'Payments', 'alias' => 'PaymentService']); +// Access via $this->PaymentService +``` + +Or set `Controller::$defaultTable` to an empty string if the controller doesn't +need a default table: + +```php +class PaymentsController extends AppController +{ + protected ?string $defaultTable = ''; +} +``` + > [!NOTE] > Aliasing a component replaces that instance anywhere that component is used, > including inside other Components. diff --git a/docs/en/controllers/request-response.md b/docs/en/controllers/request-response.md index bb23ea13df..9f2e0a64b4 100644 --- a/docs/en/controllers/request-response.md +++ b/docs/en/controllers/request-response.md @@ -817,6 +817,61 @@ You can now use the convenience method `Cake\Http\Response::withLocation()` to directly set or get the redirect location header. +### Hypermedia Links + +`method` Cake\\Http\\Response::**withLink**(LinkInterface $link): static + +CakePHP implements [PSR-13](https://www.php-fig.org/psr/psr-13/) for hypermedia +link support. You can add links to responses, and they will be automatically +emitted as HTTP `Link` headers when the response is sent: + +```php +use Cake\Http\Link\Link; + +// Add a simple link +$response = $response->withLink(new Link('/api/users/1', 'self')); + +// Add a link with multiple relations and attributes +$response = $response->withLink( + (new Link('/api/users?page=2')) + ->withRel('next') + ->withAttribute('type', 'application/json'), +); + +// Preload resources for performance +$response = $response->withLink( + (new Link('/css/app.css')) + ->withRel('preload') + ->withAttribute('as', 'style'), +); +``` + +The `Link` class implements `EvolvableLinkInterface` and provides these methods: + +- `withHref(string $href)` - Set the link URI +- `withRel(string $rel)` - Add a link relation +- `withoutRel(string $rel)` - Remove a link relation +- `withAttribute(string $name, $value)` - Add an attribute +- `withoutAttribute(string $name)` - Remove an attribute + +You can also work with multiple links using `LinkProvider`: + +```php +use Cake\Http\Link\Link; +use Cake\Http\Link\LinkProvider; + +$provider = new LinkProvider([ + new Link('/api/users/1', 'self'), + new Link('/api/users?page=2', 'next'), +]); + +// Get the link provider from a response +$links = $response->getLinks(); + +// Set a new link provider +$response = $response->withLinkProvider($provider); +``` + ### Setting the Body `method` Cake\\Http\\Response::**withStringBody**(?string $string): static diff --git a/docs/en/core-libraries/collections.md b/docs/en/core-libraries/collections.md index acad10a442..b38341f7f5 100644 --- a/docs/en/core-libraries/collections.md +++ b/docs/en/core-libraries/collections.md @@ -61,15 +61,17 @@ application as well. | `contains` | `countBy` | `each` | | `every` | `extract` | `filter` | | `first` | `firstMatch` | `groupBy` | -| `indexBy` | `insert` | `isEmpty` | -| `last` | `listNested` | `map` | -| `match` | `max` | `median` | -| `min` | `nest` | `prepend` | -| `prependItem` | `reduce` | `reject` | -| `sample` | `shuffle` | `skip` | -| `some` | `sortBy` | `stopWhen` | -| `sumOf` | `take` | `through` | -| `transpose` | `unfold` | `zip` | +| `implode` | `indexBy` | `insert` | +| `isEmpty` | `keys` | `last` | +| `listNested` | `map` | `match` | +| `max` | `median` | `min` | +| `nest` | `prepend` | `prependItem`| +| `reduce` | `reject` | `sample` | +| `shuffle` | `skip` | `some` | +| `sortBy` | `stopWhen` | `sumOf` | +| `take` | `through` | `transpose` | +| `unfold` | `unless` | `values` | +| `when` | `zip` | | ## Iterating @@ -1431,3 +1433,91 @@ foreach ($ages as $age) { } } ``` + +## Transforming Keys and Values + +### keys() + +`method` Cake\\Collection\\Collection::**keys**(): CollectionInterface + +Returns a new collection containing only the keys from the original collection: + +```php +$collection = new Collection(['a' => 1, 'b' => 2, 'c' => 3]); +$keys = $collection->keys()->toList(); // ['a', 'b', 'c'] +``` + +### values() + +`method` Cake\\Collection\\Collection::**values**(): CollectionInterface + +Returns a new collection of values re-indexed with consecutive integers, +discarding the original keys: + +```php +$collection = new Collection(['a' => 1, 'b' => 2, 'c' => 3]); +$values = $collection->values()->toList(); // [1, 2, 3] +``` + +## String Operations + +### implode() + +`method` Cake\\Collection\\Collection::**implode**(string $glue, callable|string|null $path = null): string + +Concatenates all elements in the collection into a string, separated by +the given glue string. Optionally extract values using a path before joining: + +```php +$collection = new Collection(['a', 'b', 'c']); +echo $collection->implode(', '); // 'a, b, c' + +// With path extraction +$collection = new Collection([ + ['name' => 'Alice'], + ['name' => 'Bob'], + ['name' => 'Charlie'], +]); +echo $collection->implode(', ', 'name'); // 'Alice, Bob, Charlie' + +// With a callback +$collection = new Collection([1, 2, 3]); +echo $collection->implode(' + ', fn($n) => $n * 2); // '2 + 4 + 6' +``` + +## Conditional Operations + +### when() + +`method` Cake\\Collection\\Collection::**when**(mixed $condition, callable $callback): CollectionInterface + +Conditionally applies a callback to the collection when the condition is truthy. +This enables fluent conditional chaining without breaking the method chain: + +```php +$collection = new Collection($items) + ->when($shouldFilter, function ($collection) { + return $collection->filter(fn($item) => $item['active']); + }) + ->when($sortByName, function ($collection) { + return $collection->sortBy('name'); + }); +``` + +If the condition is falsy, the collection is returned unchanged. + +### unless() + +`method` Cake\\Collection\\Collection::**unless**(mixed $condition, callable $callback): CollectionInterface + +The inverse of `when()`. Conditionally applies a callback to the collection +when the condition is falsy: + +```php +$collection = new Collection($items) + ->unless($hasDefaults, function ($collection) use ($defaults) { + return $collection->append($defaults); + }); +``` + +If the condition is truthy, the collection is returned unchanged.