1: <?php
2:
3: declare(strict_types=1);
4:
5: namespace LaravelUi5\OData\Protocol\Execution;
6:
7: use LaravelUi5\OData\Http\ODataResponse;
8: use LaravelUi5\OData\Exception\NotFoundException;
9: use LaravelUi5\OData\Exception\NotImplementedException;
10: use LaravelUi5\OData\Protocol\Planning\EntityQueryPlan;
11: use LaravelUi5\OData\Service\Contracts\EntityResolverInterface;
12: use LaravelUi5\OData\Service\Contracts\RuntimeSchemaInterface;
13:
14: /**
15: * Handles EntityQueryPlan — single entity lookup by key.
16: *
17: * Emits:
18: * {"@odata.context":"<serviceRoot>$metadata#<SetName>/$entity","id":1,"name":"..."}
19: *
20: * Returns 404 when no entity with the given key exists.
21: * Throws NotImplementedException when the resolver does not implement
22: * EntityResolverInterface (driver capability check).
23: */
24: final readonly class EntityHandler
25: {
26: public function __construct(
27: private RuntimeSchemaInterface $schema,
28: private string $serviceRoot,
29: ) {}
30:
31: public function handle(EntityQueryPlan $plan): ODataResponse
32: {
33: $resolver = $this->schema->getResolver($plan->target);
34:
35: if (!($resolver instanceof EntityResolverInterface)) {
36: throw new NotImplementedException(
37: 'entity_resolver_not_supported',
38: sprintf(
39: 'The resolver for "%s" does not implement EntityResolverInterface.',
40: $plan->target->getName()
41: )
42: );
43: }
44:
45: $context = $this->serviceRoot . '$metadata#' . $plan->target->getName()
46: . SelectHelper::contextFragment($plan->select) . '/$entity';
47: $entity = $resolver->resolveOne($plan);
48:
49: if ($entity === null) {
50: throw new NotFoundException(
51: 'entity_not_found',
52: sprintf('No entity found in "%s" matching the given key.', $plan->target->getName())
53: );
54: }
55:
56: $selectKeys = SelectHelper::allowedKeys($plan->select, $plan->expand);
57: $entity = (new RowCoercion($plan->target->getEntityType()))->apply($entity);
58:
59: $response = new ODataResponse(null, 200, [
60: 'Content-Type' => 'application/json;odata.metadata=minimal;charset=utf-8',
61: 'OData-Version' => '4.0',
62: ]);
63:
64: $response->setCallback(static function () use ($context, $entity, $selectKeys): void {
65: $entity = $selectKeys !== null ? array_intersect_key($entity, $selectKeys) : $entity;
66: $payload = array_merge(['@odata.context' => $context], $entity);
67: echo json_encode($payload, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
68: });
69:
70: return $response;
71: }
72: }
73: