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\PropertyValuePlan;
11: use LaravelUi5\OData\Service\Contracts\EntityResolverInterface;
12: use LaravelUi5\OData\Service\Contracts\RuntimeSchemaInterface;
13:
14: /**
15: * Handles property value access: /EntitySet(key)/property[/$value]
16: *
17: * Returns the property value as JSON (wrapped) or raw (when $value suffix).
18: */
19: final readonly class PropertyValueHandler
20: {
21: public function __construct(
22: private RuntimeSchemaInterface $schema,
23: private string $serviceRoot,
24: ) {}
25:
26: public function handle(PropertyValuePlan $plan): ODataResponse
27: {
28: $resolver = $this->schema->getResolver($plan->target);
29:
30: if (!($resolver instanceof EntityResolverInterface)) {
31: throw new NotImplementedException(
32: 'entity_resolver_not_supported',
33: sprintf('The resolver for "%s" does not support single-entity lookups.', $plan->target->getName())
34: );
35: }
36:
37: // Build a minimal EntityQueryPlan to reuse resolveOne().
38: $entityPlan = new \LaravelUi5\OData\Protocol\Planning\EntityQueryPlan(
39: target: $plan->target,
40: key: $plan->key,
41: select: new \LaravelUi5\OData\Protocol\Planning\SelectList(),
42: expand: new \LaravelUi5\OData\Protocol\Planning\ExpandList(),
43: );
44:
45: $entity = $resolver->resolveOne($entityPlan);
46:
47: if ($entity === null) {
48: throw new NotFoundException(
49: 'entity_not_found',
50: sprintf('No entity found in "%s" matching the given key.', $plan->target->getName())
51: );
52: }
53:
54: $propName = $plan->property->getName();
55: $value = $entity[$propName] ?? null;
56:
57: if ($plan->rawValue) {
58: // /$value — return raw value with text/plain
59: $response = new ODataResponse(null, 200, [
60: 'Content-Type' => 'text/plain;charset=utf-8',
61: 'OData-Version' => '4.0',
62: ]);
63: $response->setCallback(static function () use ($value): void {
64: echo (string) $value;
65: });
66: return $response;
67: }
68:
69: // Property wrapped in JSON context
70: $context = $this->serviceRoot . '$metadata#' . $plan->target->getName()
71: . '(' . $propName . ')/$entity';
72:
73: $response = new ODataResponse(null, 200, [
74: 'Content-Type' => 'application/json;odata.metadata=minimal;charset=utf-8',
75: 'OData-Version' => '4.0',
76: ]);
77:
78: $response->setCallback(static function () use ($context, $value): void {
79: echo json_encode(
80: ['@odata.context' => $context, 'value' => $value],
81: JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE
82: );
83: });
84:
85: return $response;
86: }
87: }
88: