Skip to content

Matching across aggregates

You can match aggregates of some kind based on conditions from another aggregate kind.

For example if you have 2 aggregates User and Movie and you want:

All movies where the director's last name is Blomkamp.

You can write the following code:

use Innmind\Specification\{
    Comparator\Property,
    Sign,
};

$orm
    ->repository(Movie::class)
    ->matching(Property::of(
        'director',
        Sign::in,
        $orm
            ->repository(User::class)
            ->matching(Property::of(
                'lastName',
                Sign::equality,
                'Blomkamp',
            )),
    ))
    ->foreach(static fn($movie) => doSomething($movie));

For this to work the director property must be typed Formal\ORM\Id<User>.

If your storage adapter supports it this is even optimised at the storage level.

Warning

Only SQL is able to optimize this at the storage level.

It still works with Elasticsearch and Filesystem but Formal will fetch the matching ids in memory and use them as input value.

Inversed cross aggregate match

Experimental

This feature is still experimental and may change in future minor versions.

All users who directed a movie named Batman

use Innmind\Specification\{
    Comparator\Property,
    Sign,
};

$orm
    ->repository(User::class)
    ->matching(Property::of(
        'id',
        Sign::in,
        $orm
            ->repository(Movie::class)
            ->matching(Property::of(
                'name',
                Sign::contains,
                'Batman',
            ))
            ->property('director'),
    ))
    ->foreach(static fn($user) => doSomething($user));
Tip

This is an optimized version of:

use Formal\ORM\Id;
use Innmind\Specification\{
    Comparator\Property,
    Sign,
};

$orm
    ->repository(User::class)
    ->matching(Property::of(
        'id',
        Sign::in,
        $orm
            ->repository(Movie::class)
            ->matching(Property::of(
                'name',
                Sign::contains,
                'Batman',
            ))
            ->sequence()
            ->map(static fn($movie): Id => $movie->director()),
    ))
    ->foreach(static fn($user) => doSomething($user));

This loads every director Id in memory before sending it back to the new query. While the optimized version applies it directly at the adapter level.

Warning

The same warning as above applies on this feature.