Skip to content

Commit 73a779f

Browse files
committed
Merge branch 'develop'
* develop: specify next release do not throw if the transaction fail to start add upgrade documentation update documentation typo express the transaction failures in the return type of Manager::transactional() make transactions use Attempt make Repository::put() and ::remove() return an Attempt<SideEffect> make Repository::effect() return an Attempt<SideEffect> fix type declaration remove deprecated PointInTimeType::of() test against php 8.4 update dependencies discard psalm error remove unused code
2 parents 6ea5339 + 747585b commit 73a779f

86 files changed

Lines changed: 936 additions & 788 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/ci.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ jobs:
88
strategy:
99
matrix:
1010
os: [ubuntu-latest]
11-
php-version: ['8.2', '8.3']
11+
php-version: ['8.2', '8.3', '8.4']
1212
dependency-versions: ['lowest', 'highest']
1313
mariadb: ['10', '11']
1414
elasticsearch: ['7.17.18']
@@ -60,7 +60,7 @@ jobs:
6060
strategy:
6161
matrix:
6262
os: [ubuntu-latest]
63-
php-version: ['8.2', '8.3']
63+
php-version: ['8.2', '8.3', '8.4']
6464
dependency-versions: ['lowest', 'highest']
6565
name: 'Coverage'
6666
services:
@@ -113,7 +113,7 @@ jobs:
113113
runs-on: ubuntu-latest
114114
strategy:
115115
matrix:
116-
php-version: ['8.2', '8.3']
116+
php-version: ['8.2', '8.3', '8.4']
117117
dependency-versions: ['lowest', 'highest']
118118
name: 'Psalm'
119119
steps:

CHANGELOG.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,25 @@
11
# Changelog
22

3+
## 5.0.0 - 2025-06-04
4+
5+
### Changed
6+
7+
- Requires `innmind/foundation:~1.1`
8+
- `Formal\ORM\Definition\Type\PointInTimeType\Format` has been renamed `Formats` and is now an enum
9+
- `Formal\ORM\Repository::effect()` now returns an `Innmind\Immutable\Attempt<Innmind\Immutable\SideEffect>`
10+
- `Formal\ORM\Repository::put()` now returns an `Innmind\Immutable\Attempt<Innmind\Immutable\SideEffect>`
11+
- `Formal\ORM\Repository::remove()` now returns an `Innmind\Immutable\Attempt<Innmind\Immutable\SideEffect>`
12+
- `Formal\ORM\Adapter\Transaction` methods now uses `Innmind\Immutable\Attempt` to handle errors
13+
- `Formal\ORM\Manager::transactional()` may also return a `Formal\ORM\Adapter\Transaction\Failure` as a left value
14+
15+
### Removed
16+
17+
- `Formal\ORM\Definition\Type\PointInTimeType::of()`
18+
19+
### Fixed
20+
21+
- Float properties couldn't not be loaded from Elasticsearch
22+
323
## 4.1.1 - 2025-05-02
424

525
### Fixed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,6 @@ This simple example will retrieve from the database `50` elements (from index `1
5555
5656
## Documentation
5757

58-
Full documentation available in the [here](https://formal-php.github.io/orm/).
58+
Full documentation available [here](https://formal-php.github.io/orm/).
5959

6060
[^1]: Object Relational Mapping

composer.json

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -16,18 +16,11 @@
1616
},
1717
"require": {
1818
"php": "~8.2",
19-
"innmind/immutable": "^5.14.3",
20-
"innmind/specification": "~4.1",
19+
"innmind/foundation": "~1.1",
2120
"ramsey/uuid": "~4.7",
22-
"innmind/reflection": "~5.1",
23-
"innmind/filesystem": "~7.4",
24-
"innmind/json": "~1.3",
25-
"innmind/time-continuum": "~3.3",
2621
"innmind/type": "~1.2",
2722
"formal/access-layer": "~4.2",
28-
"innmind/http-transport": "~7.2",
29-
"innmind/url-template": "~3.1",
30-
"innmind/validation": "~1.4"
23+
"innmind/url-template": "~3.1"
3124
},
3225
"autoload": {
3326
"psr-4": {
@@ -43,7 +36,6 @@
4336
"require-dev": {
4437
"innmind/static-analysis": "^1.2.1",
4538
"innmind/black-box": "~6.1",
46-
"innmind/coding-standard": "~2.0",
47-
"innmind/operating-system": "~5.2"
39+
"innmind/coding-standard": "~2.0"
4840
}
4941
}

documentation/blog/posts/mass-update.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,15 @@ In the latest release Formal now offers a way to update multiple aggregates dire
1212
Until now to update multiple aggregates you'd have to fetch all of them, update each one and then put them back in the repository. It would look something like this:
1313

1414
```php
15+
use Innmind\Immutable\SideEffect;
16+
1517
$users = $orm->repository(User::class);
1618
$users
1719
->all() #(1)
1820
->map(static fn(User $user): User => $user->rename('Alice'))
19-
->foreach($users->put(...)); #(2)
21+
->sink(SideEffect::identity())
22+
->attempt(static fn($_, User $user) => $users->put($user))
23+
->unwrap(); #(2)
2024
```
2125

2226
1. or `->matching()` with some [specification](../../specifications/index.md)

documentation/getting-started/persist.md

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,12 @@ In order to persist an aggregate you need 3 things:
99
Translated into code this gives:
1010

1111
```php
12-
use Innmind\Immutable\Either;
13-
1412
$user = User::new('alice');
1513
$users = $orm->repository(User::class);
1614
$result = $orm->transactional(
17-
static function() use ($repository, $user) {
18-
$repository->put($user);
19-
20-
return Either::right(null);
21-
};
15+
static fn() $repository
16+
->put($user)
17+
->either(),
2218
);
2319
```
2420

@@ -33,14 +29,17 @@ The `$users` repository is the abstraction that _represent all users_ in the sto
3329

3430
Use your best judgment to choose the best option for your need.
3531

36-
`$orm->transactional()` will start a transaction via the storage used by the ORM. It will then call the callable you passed to it. If the returned value is an `Either` with a value on the right side it will commit the transaction and return the `Either` object as the `$result` variable. If the `Either` contains a value on the left side it will rollback the transaction and return the `Either` object as the `$result` variable. If the callable throws an exception it will rollback as well an rethrow the exception.
32+
`$orm->transactional()` will start a transaction via the storage used by the ORM. It will then call the callable you passed to it. If the returned value is an `Either` with a value on the right side it will commit the transaction and return the `Either` object as the `$result` variable. If the `Either` contains a value on the left side it will rollback the transaction and return the `Either` object as the `$result` variable. If the callable throws an exception it will rollback as well and rethrow the exception.
33+
34+
??? note
35+
The left side of the `$result` `Either` may also contain a `Formal\ORM\Adapter\Transaction\Failure` if the commit or rollback failed itself.
3736

3837
`$repository->put()` will call the storage to persist the aggregate. At this point the ORM knows to create the aggregate because it's unaware before that of its `Id` object reference. After the `put` the ORM is now aware of this id and if you do another `put` it will try to update the same entry in the storage. (1)
3938
{ .annotate }
4039

4140
1. That's why you can't clone an `Id` object as the ORM would no longer know if it needs to insert or update the aggregate.
4241

43-
Finally we return `Either::right(null)` to tell the ORM to commit the transaction. We use `null` because there's no business logic done here. But you could imagine returning a computed value instead, or return a business error object on the left side of the `Either`.
42+
Finally we return `->either()` to tell the ORM to commit the transaction. We use the `SideEffect` returned by the `->put()` call because there's no business logic done here. But you could imagine returning a computed value instead, or return a business error object on the left side of the `Either`.
4443

4544
This example only persist one aggregate but you can persist as many as you want.
4645

documentation/getting-started/remove.md

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,19 +9,15 @@ In order to remove an aggregate you need 3 things:
99
Translated into code this gives:
1010

1111
```php
12-
use Innmind\Immutable\Either;
13-
1412
$users = $orm->repository(User::class);
1513
$result = $orm->transactional(
16-
static function() use ($repository) {
17-
$repository->remove(Id::of(User::class, 'alice-uuid'));
18-
19-
return Either::right(null);
20-
};
14+
static fn() => $repository
15+
->remove(Id::of(User::class, 'alice-uuid'))
16+
->either(),
2117
);
2218
```
2319

24-
If alice exists in the storage it will remove it and if it doesn't then nothing will happen. And like for [persisting](persist.md) the `Either::right(null)` will indicate to `transactional` to commit the transaction.
20+
If alice exists in the storage it will remove it and if it doesn't then nothing will happen. And like for [persisting](persist.md) the `->either()` will indicate to `transactional` to commit the transaction.
2521

2622
??? note
2723
If you want to remove multiple aggregates at once corresponding to a set of criteria head to the [Specification chapter](../specifications/index.md).

documentation/getting-started/update.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ $orm->transactional(
5959
->get(Id::of(User::class, 'alice-uuid'))
6060
->map(static fn(User $alice) => $alice->rename('bob'))
6161
->match(
62-
static fn(User $bob) => $repository->put($bob),
62+
static fn(User $bob) => $repository->put($bob)->unwrap(),
6363
static fn() => null,
6464
);
6565

@@ -88,16 +88,17 @@ And like for persisting an aggregate we return `Either::right(null)` to commit t
8888
static fn() => $repository
8989
->get(Id::of(User::class, 'alice-uuid'))
9090
->map(static fn(User $alice) => $alice->rename('bob'))
91-
->map($repository->put(...))
91+
->attempt(static fn() => new \Exception('User not found'))
92+
->flatMap($repository->put(...))
9293
->either(),
9394
},
9495
);
9596
```
9697

9798
This does the same thing except one thing. If alice doesn't exist it will rollback the transaction instead of committing it, but this doesn't change the end result.
9899

99-
`->map($repository->put(...))` this will call the `put` method if there was an alice that has been renamed to bob on the line before.
100+
`->flatMap($repository->put(...))` this will call the `put` method if there was an alice that has been renamed to bob on the line before.
100101

101-
`->either()` this transforms the `Maybe<void>` to an `Either<null, void>`. The `void` type is the return type of the `put` method and the `null` is when alice doesn't exist. That's why we rollback if alice doesn't exist, the returned `Either` contains `null` on the left side.
102+
`->either()` this transforms the `Attempt<SideEffect>` to an `Either<\Throwable, SideEffect>`. That's why we rollback if alice doesn't exist, the returned `Either` contains `\Throwable` on the left side.
102103

103104
Also note that all this is lazy evaluated, the `get` and eventually `put` occur when `transactional` checks the `Either` returned by the callable.

documentation/specifications/remove.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,6 @@ This is pretty straightforward:
55
```php
66
$orm
77
->repository(User::class)
8-
->remove(SearchByName::of(Name::of('alice')));
8+
->remove(SearchByName::of(Name::of('alice')))
9+
->unwrap();
910
```

documentation/upgrade/v4-to-v5.md

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
# V4 to V5
2+
3+
## `Repository->put()`
4+
5+
=== "Before"
6+
```php
7+
$repository->put($aggregate);
8+
```
9+
10+
=== "After"
11+
```php
12+
$repository->put($aggregate)->unwrap();
13+
```
14+
15+
## `Repository->remove()`
16+
17+
=== "Before"
18+
```php
19+
$repository->remove($idOrSpecification);
20+
```
21+
22+
=== "After"
23+
```php
24+
$repository->remove($idOrSpecification)->unwrap();
25+
```
26+
27+
## `Repository->effect()`
28+
29+
=== "Before"
30+
```php
31+
$repository->effect($effect);
32+
```
33+
34+
=== "After"
35+
```php
36+
$repository->effect($effect)->unwrap();
37+
```
38+
39+
## Transactions
40+
41+
=== "Before"
42+
```php
43+
use Innmind\Immutable\Either;
44+
45+
$manager
46+
->transactional(static function() {
47+
if (/* some condition */) {
48+
return Either::right(new SomeValue);
49+
}
50+
51+
return Either::left(new SomeError);
52+
})
53+
->match(
54+
static fn(SomeValue $value) => domSomething($value),
55+
static fn(SomeError $value) => domSomething($value),
56+
);
57+
```
58+
59+
=== "After"
60+
```php hl_lines="14-15"
61+
use Formal\ORM\Adapter\Transaction\Failure
62+
use Innmind\Immutable\Either;
63+
64+
$manager
65+
->transactional(static function() {
66+
if (/* some condition */) {
67+
return Either::right(new SomeValue);
68+
}
69+
70+
return Either::left(new SomeError);
71+
})
72+
->match(
73+
static fn(SomeValue $value) => domSomething($value),
74+
static fn(SomeError|Failure $value) => match (true) {
75+
$value instanceof Failure => throw $value->unwrap(),
76+
default => domSomething($value),
77+
},
78+
);
79+
```
80+
81+
Errors happening during the transaction commit/rollback are now returned on the left side instead of thrown to let you decide if you prefer using exceptions or a monadic style.

0 commit comments

Comments
 (0)