1: <?php
2:
3: declare(strict_types=1);
4:
5: namespace LaravelUi5\OData\Protocol\Planning;
6:
7: /**
8: * Rebuilds an {@see ExpandList} with every expand whose target entity set is in the
9: * dropped-set list removed — at any depth.
10: *
11: * Used by the read-authorization honest-partial model: a gated `$expand` is pruned from the
12: * response (and reported via a `sap-messages` warning) rather than failing the whole read.
13: * Read authorization is per entity SET, so a dropped set name removes every expand that points
14: * at it, however deeply nested — no path bookkeeping, and no bypass via nesting.
15: */
16: final readonly class ExpandPruner
17: {
18: /**
19: * @param list<string> $droppedSetNames entity-set names denied for read
20: */
21: public static function prune(ExpandList $list, array $droppedSetNames): ExpandList
22: {
23: if ($list->isEmpty() || $droppedSetNames === []) {
24: return $list;
25: }
26:
27: $kept = [];
28:
29: foreach ($list->items as $item) {
30: if (in_array($item->targetSet->getName(), $droppedSetNames, true)) {
31: continue; // gated set → drop this expand at this (and every) depth
32: }
33:
34: $kept[] = $item->expand->isEmpty()
35: ? $item
36: : $item->withExpand(self::prune($item->expand, $droppedSetNames));
37: }
38:
39: return new ExpandList($kept);
40: }
41: }
42: