diff --git a/docs/en/appendices/5-4-migration-guide.md b/docs/en/appendices/5-4-migration-guide.md index f8db436ca7..93ade52098 100644 --- a/docs/en/appendices/5-4-migration-guide.md +++ b/docs/en/appendices/5-4-migration-guide.md @@ -27,6 +27,11 @@ 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 + +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 - WIP @@ -60,12 +65,24 @@ 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. See [Hypermedia Links](../controllers/request-response#hypermedia-links). + ### 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()`](../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 - Added `{{inputId}}` template variable to `inputContainer` and `error` templates 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.