Skip to content

Commit 2772f7e

Browse files
committed
Merge remote-tracking branch 'origin/5.next' into docs-string-agg
# Conflicts: # docs/en/appendices/5-4-migration-guide.md
2 parents 83a104f + 5d904e0 commit 2772f7e

5 files changed

Lines changed: 138 additions & 14 deletions

File tree

docs/en/appendices/5-4-migration-guide.md

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,11 @@ version is reported as `unknown`), the header is omitted.
4545
has changed from `select` to `subquery`. If you need the previous behavior,
4646
explicitly set `'strategy' => 'select'` when defining associations.
4747
See [Associations](../orm/associations#has-many-associations) for more details.
48+
- `Model.afterSaveCommit` and `Model.afterDeleteCommit` events are now fired
49+
when `save()` or `delete()` is called inside an outer transaction. Previously,
50+
these events were silently suppressed. They are now deferred until the
51+
outermost transaction commits, and discarded on rollback.
52+
See [Table Objects](../orm/table-objects#aftersavecommit) for more details.
4853

4954
### Controller
5055

@@ -72,6 +77,12 @@ version is reported as `unknown`), the header is omitted.
7277
`league/container` implementation by setting `App.container` to `cake` inside your `config/app.php`.
7378
See [Dependency Injection Container](../development/dependency-injection) for more details.
7479

80+
### Collection
81+
82+
- Added [`keys()`](../core-libraries/collections#keys) and [`values()`](../core-libraries/collections#values) methods for extracting keys or re-indexing values.
83+
- Added [`implode()`](../core-libraries/collections#implode) method to concatenate elements into a string.
84+
- Added [`when()`](../core-libraries/collections#when) and [`unless()`](../core-libraries/collections#unless) methods for conditional method chaining.
85+
7586
### Commands
7687

7788
- You can use `$this->io` and `$this->args` inside your commands to access input/output and argument objects
@@ -102,6 +113,9 @@ version is reported as `unknown`), the header is omitted.
102113
- Added `FunctionsBuilder::stringAgg()` for portable string aggregation.
103114
Translates to `STRING_AGG` or `GROUP_CONCAT` per driver.
104115
See [Query Builder](../orm/query-builder#string-aggregation).
116+
- Added `Connection::afterCommit()` to register callbacks that run after the
117+
outermost transaction commits. Callbacks are discarded on rollback.
118+
See [Database Basics](../orm/database-basics#aftercommit) for more details.
105119
- Added `except()` and `exceptAll()` methods on `SelectQuery` for `EXCEPT`
106120
and `EXCEPT ALL` set operations. `EXCEPT ALL` is supported on PostgreSQL
107121
and recent MySQL/MariaDB versions; it is not supported on SQLite or SQL Server.
@@ -113,6 +127,12 @@ version is reported as `unknown`), the header is omitted.
113127
`Index::BRIN`, `Index::HASH`) for these access methods.
114128
See [Reading Indexes and Constraints](../orm/schema-system#reading-indexes-and-constraints).
115129

130+
### Http
131+
132+
- Added PSR-13 Link implementation with `Cake\Http\Link\Link` and `Cake\Http\Link\LinkProvider`
133+
classes for hypermedia link support. Links added to responses are automatically emitted
134+
as HTTP `Link` headers. See [Hypermedia Links](../controllers/request-response#hypermedia-links).
135+
116136
### I18n
117137

118138
- `Number::toReadableSize()` now uses decimal units (KB = 1000 bytes) by default.
@@ -124,11 +144,9 @@ version is reported as `unknown`), the header is omitted.
124144
nested array format matching `contain()` syntax.
125145
See [Converting Request Data into Entities](../orm/saving-data#converting-request-data-into-entities).
126146

127-
### Http
147+
### Testsuite
128148

129-
- Added PSR-13 Link implementation with `Cake\Http\Link\Link` and `Cake\Http\Link\LinkProvider`
130-
classes for hypermedia link support. Links added to responses are automatically emitted
131-
as HTTP `Link` headers. See [Hypermedia Links](../controllers/request-response#hypermedia-links).
149+
- `TestCase::mockModel()` has been added to allow mocking of model classes in tests using Mockery mocks.
132150

133151
### Utility
134152

@@ -137,12 +155,7 @@ version is reported as `unknown`), the header is omitted.
137155
path manipulation. See [Filesystem Utilities](../core-libraries/filesystem.md).
138156
- `Security::encrypt()` can now be configured to use longer keys with separate encryption and authentication keys that are derived from the provided key.
139157
You can set `Security.encryptWithRawKey` to enable this behavior. See [here](https://github.com/cakephp/cakephp/pull/19325) for more details.
140-
141-
### Collection
142-
143-
- Added [`keys()`](../core-libraries/collections#keys) and [`values()`](../core-libraries/collections#values) methods for extracting keys or re-indexing values.
144-
- Added [`implode()`](../core-libraries/collections#implode) method to concatenate elements into a string.
145-
- Added [`when()`](../core-libraries/collections#when) and [`unless()`](../core-libraries/collections#unless) methods for conditional method chaining.
158+
- Added `Text::mask()` method which masks a portion of a string with a repeated character. See [Text Masking](../core-libraries/text.md#text-masking) for more details.
146159

147160
### View
148161

docs/en/core-libraries/text.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -453,4 +453,32 @@ Output:
453453

454454
red, orange, yellow, green, blue, indigo and violet
455455

456+
## Text Masking
457+
458+
### Text::mask()
459+
460+
`method` Cake\\Utility\\Text::**mask**(string $string, int $offset, ?int $length = null, string $maskCharacter = '*'): string
461+
462+
Masks a portion of a string with a repeated character.
463+
464+
Replaces characters starting at `$offset` for `$length` characters with `$maskCharacter`.
465+
If `$length` is `null`, masking continues to the end of the string.
466+
Negative offsets are supported and are calculated from the end of the string.
467+
468+
```php
469+
$creditCardNumber = '4909090909091234';
470+
471+
// Called as TextHelper
472+
echo $this->Text->mask($creditCardNumber, 0, 12, '*');
473+
474+
// Called as Text
475+
use Cake\Utility\Text;
476+
477+
echo Text::mask($creditCardNumber, 0, 12, '*');
478+
```
479+
480+
Output:
481+
482+
************1234
483+
456484
<!-- end-text -->

docs/en/development/testing.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1043,6 +1043,23 @@ In your `tearDown()` method be sure to remove the mock with:
10431043
$this->getTableLocator()->clear();
10441044
```
10451045

1046+
::: info Added in version 5.4
1047+
:::
1048+
1049+
If you prefer Mockery mocks you can use `mockModel()` instead of `getMockForModel()`.
1050+
1051+
```php
1052+
public function testSendingEmails(): void
1053+
{
1054+
$model = $this->mockModel('EmailVerification');
1055+
$mock->shouldReceive('send')
1056+
->once()
1057+
->andReturn(true);
1058+
1059+
$model->verifyEmail('test@example.com');
1060+
}
1061+
```
1062+
10461063
<a id="integration-testing"></a>
10471064

10481065
## Controller Integration Testing

docs/en/orm/database-basics.md

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1107,6 +1107,53 @@ do the following:
11071107
- If the closure returns `false`, a rollback will be issued.
11081108
- If the closure executes successfully, the transaction will be committed.
11091109

1110+
### afterCommit
1111+
1112+
`method` Cake\\Database\\Connection::**afterCommit**(callable $callback): void
1113+
1114+
You can register callbacks to run after the outermost transaction commits using
1115+
``afterCommit()``. This is useful for deferring side effects like sending
1116+
emails, dispatching jobs, or invalidating caches until you know the data has
1117+
been persisted:
1118+
1119+
```php
1120+
$connection->begin();
1121+
$connection->execute('UPDATE articles SET published = ? WHERE id = ?', [true, 2]);
1122+
$connection->afterCommit(function () {
1123+
// Send notification email — only runs if the transaction commits.
1124+
$this->mailer->send('article-published');
1125+
});
1126+
$connection->commit(); // Callback fires here.
1127+
```
1128+
1129+
Callbacks are discarded if the transaction is rolled back. When nested
1130+
transactions are in use, callbacks registered at any depth are deferred until
1131+
the outermost transaction commits:
1132+
1133+
```php
1134+
$connection->begin();
1135+
$connection->afterCommit(function () {
1136+
// This fires after the outermost commit.
1137+
});
1138+
1139+
$connection->begin(); // Nested (savepoint)
1140+
$connection->afterCommit(function () {
1141+
// Also deferred to outermost commit.
1142+
});
1143+
$connection->commit(); // Releases savepoint — callbacks don't fire yet.
1144+
1145+
$connection->commit(); // Outermost commit — both callbacks fire now.
1146+
```
1147+
1148+
If ``afterCommit()`` is called when no transaction is active, the callback
1149+
executes immediately. This matches the semantics of the ORM's
1150+
``Model.afterSaveCommit`` event, which also fires immediately for non-atomic
1151+
saves.
1152+
1153+
::: info Added in version 5.4.0
1154+
`Connection::afterCommit()` was added.
1155+
:::
1156+
11101157
## Interacting with Statements
11111158

11121159
When using the lower level database API, you will often encounter statement

docs/en/orm/table-objects.md

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -320,8 +320,18 @@ The `Model.afterSave` event is fired after an entity is saved.
320320
The `Model.afterSaveCommit` event is fired after the transaction in which the
321321
save operation is wrapped has been committed. It's also triggered for non atomic
322322
saves where database operations are implicitly committed. The event is triggered
323-
only for the primary table on which `save()` is directly called. The event is
324-
not triggered if a transaction is started before calling save.
323+
only for the primary table on which `save()` is directly called.
324+
325+
When `save()` is called inside an outer transaction (e.g. one started with
326+
`Connection::begin()`), the event is deferred until the outermost transaction
327+
commits. If the outer transaction is rolled back, the event is discarded. This
328+
ensures the event only fires after data has been persisted to the database.
329+
330+
::: info Changed in version 5.4.0
331+
Previously, this event was not triggered if a transaction was started before
332+
calling `save()`. It is now deferred and fires after the outermost
333+
transaction commits.
334+
:::
325335

326336
### beforeDelete
327337

@@ -342,10 +352,19 @@ The `Model.afterDelete` event is fired after an entity has been deleted.
342352
`method` Cake\\ORM\\Table::**afterDeleteCommit**(EventInterface $event, EntityInterface $entity, ArrayObject $options): void
343353

344354
The `Model.afterDeleteCommit` event is fired after the transaction in which the
345-
delete operation is wrapped has been is committed. It's also triggered for non
355+
delete operation is wrapped has been committed. It's also triggered for non
346356
atomic deletes where database operations are implicitly committed. The event is
347357
triggered only for the primary table on which `delete()` is directly called.
348-
The event is not triggered if a transaction is started before calling delete.
358+
359+
When `delete()` is called inside an outer transaction, the event is deferred
360+
until the outermost transaction commits. If the outer transaction is rolled
361+
back, the event is discarded.
362+
363+
::: info Changed in version 5.4.0
364+
Previously, this event was not triggered if a transaction was started before
365+
calling `delete()`. It is now deferred and fires after the outermost
366+
transaction commits.
367+
:::
349368

350369
### Stopping Table Events
351370

0 commit comments

Comments
 (0)