Lines
70.58%
24 / 34
Methods
87.50%
7 / 8
Classes
0.00%
0 / 1
| Name | Lines | Methods | CRAP | ||||
|---|---|---|---|---|---|---|---|
| fromRequest | 100.00% | 2 / 2 | 100.00% | 1 / 1 | 2 | ||
| set | 100.00% | 1 / 1 | 100.00% | 1 / 1 | 1 | ||
| get | 100.00% | 1 / 1 | 100.00% | 1 / 1 | 2 | ||
| getTyped | 100.00% | 2 / 2 | 100.00% | 1 / 1 | 2 | ||
| requireTyped | 100.00% | 10 / 10 | 100.00% | 1 / 1 | 3 | ||
| matchesType | 37.50% | 6 / 16 | 0.00% | 0 / 1 | 78.50 | ||
| has | 100.00% | 1 / 1 | 100.00% | 1 / 1 | 1 | ||
| all | 100.00% | 1 / 1 | 100.00% | 1 / 1 | 1 | ||
| 23 | class RequestContext | |
| 24 | { | |
| 25 | /** @var array<string, mixed> */ | |
| 26 | private array $data = []; | |
| 27 | ||
| 28 | /** | |
| 29 | * Retrieve the context bag attached to a request, if any. | |
| 30 | * | |
| 31 | * Works with any {@see ServerRequestInterface}: Lucent's ServerRequest | |
| 32 | * carries a RequestContext as its 'context' attribute, and other | |
| 33 | * implementations may do the same. Returns null when the request has no | |
| 34 | * context bag attached. | |
| 35 | * | |
| 36 | * @param ServerRequestInterface $request The request to inspect | |
| 37 | * @return self|null The attached context bag, or null if none | |
| 38 | */ | |
| 39 | public static function fromRequest(ServerRequestInterface $request): ?self | |
| 40 | { | |
| 41 | $context = $request->getAttribute('context'); | |
| 42 | ||
| 43 | return $context instanceof self ? $context : null; | |
| 44 | } | |
| 45 | ||
| 46 | /** | |
| 47 | * Store a value in the context. | |
| 48 | * | |
| 49 | * @param string $key The context key | |
| 50 | * @param mixed $value The value to store | |
| 51 | * @return void | |
| 52 | */ | |
| 53 | public function set(string $key, mixed $value): void | |
| 54 | { | |
| 55 | $this->data[$key] = $value; | |
| 56 | } | |
| 57 | ||
| 58 | /** | |
| 59 | * Read a value from the context. | |
| 60 | * | |
| 61 | * @param string $key The context key | |
| 62 | * @param mixed $default Default value if the key is not set | |
| 63 | * @return mixed | |
| 64 | */ | |
| 65 | public function get(string $key, mixed $default = null): mixed | |
| 66 | { | |
| 67 | return array_key_exists($key, $this->data) ? $this->data[$key] : $default; | |
| 68 | } | |
| 69 | ||
| 70 | /** | |
| 71 | * Read a value from the context, guaranteed to match a given type. | |
| 72 | * | |
| 73 | * Returns the stored value when it matches the type, otherwise returns | |
| 74 | * the default. This is the type-safe counterpart to {@see get()}: it lets | |
| 75 | * middleware and rules stash objects (a User, a Session, a request id) or | |
| 76 | * scalars and read them back without a manual instanceof / is_* check at | |
| 77 | * every call site. | |
| 78 | * | |
| 79 | * The type may be a class or interface name (checked with instanceof) or | |
| 80 | * a builtin type name: string, int, float, bool, array, object, callable, | |
| 81 | * iterable, numeric, scalar, resource, or null. | |
| 82 | * | |
| 83 | * @template T | |
| 84 | * @param string $key The context key | |
| 85 | * @param class-string<T>|string $type The expected type of the stored | |
| 86 | * value (class name or builtin type) | |
| 87 | * @param T|null $default Default value if the key is not set or holds a | |
| 88 | * value that does not match $type | |
| 89 | * @return ($default is null ? T|null : T) The stored value when it matches | |
| 90 | * $type, otherwise $default. When $default is non-null the return is always T. | |
| 91 | */ | |
| 92 | public function getTyped(string $key, string $type, mixed $default = null): mixed | |
| 93 | { | |
| 94 | $value = $this->data[$key] ?? null; | |
| 95 | ||
| 96 | return $this->matchesType($value, $type) ? $value : $default; | |
| 97 | } | |
| 98 | ||
| 99 | /** | |
| 100 | * Read a value from the context, guaranteed to match a given type, or | |
| 101 | * throw if it is missing or of the wrong type. | |
| 102 | * | |
| 103 | * This is the fail-fast counterpart to {@see getTyped()}: it is intended | |
| 104 | * for values the code genuinely cannot proceed without (an authenticated | |
| 105 | * user, a session, a request id). Instead of returning a default and | |
| 106 | * forcing the caller to null-check, it throws a descriptive | |
| 107 | * {@see \RuntimeException} so the failure surfaces at the point of use | |
| 108 | * with a clear message rather than as a confusing "call to member | |
| 109 | * function on null" deeper in the stack. | |
| 110 | * | |
| 111 | * The type may be a class or interface name (checked with instanceof) or | |
| 112 | * a builtin type name: string, int, float, bool, array, object, callable, | |
| 113 | * iterable, numeric, scalar, resource, or null. | |
| 114 | * | |
| 115 | * @template T | |
| 116 | * @param string $key The context key | |
| 117 | * @param class-string<T>|string $type The expected type of the stored | |
| 118 | * value (class name or builtin type) | |
| 119 | * @return T The stored value, guaranteed to match $type | |
| 120 | * @throws \RuntimeException When the key is not set or holds a value that | |
| 121 | * does not match $type | |
| 122 | */ | |
| 123 | public function requireTyped(string $key, string $type): mixed | |
| 124 | { | |
| 125 | $value = $this->getTyped($key, $type); | |
| 126 | ||
| 127 | if ($value === null && !$this->matchesType(null, $type)) { | |
| 128 | throw new \RuntimeException( | |
| 129 | sprintf( | |
| 130 | 'Context key "%s" is missing or does not hold a value of type "%s".', | |
| 131 | $key, | |
| 132 | $type | |
| 133 | ) | |
| 134 | ); | |
| 135 | } | |
| 136 | ||
| 137 | return $value; | |
| 138 | } | |
| 139 | ||
| 140 | /** | |
| 141 | * Determine whether a value matches a class name or builtin type. | |
| 142 | * | |
| 143 | * @param mixed $value The value to check | |
| 144 | * @param string $type A class/interface name or a builtin type name | |
| 145 | * @return bool | |
| 146 | */ | |
| 147 | private function matchesType(mixed $value, string $type): bool | |
| 148 | { | |
| 149 | if (class_exists($type) || interface_exists($type)) { | |
| 150 | return $value instanceof $type; | |
| 151 | } | |
| 152 | ||
| 153 | return match ($type) { | |
| 154 | 'string' => is_string($value), | |
| 155 | 'int', 'integer' => is_int($value), | |
| 156 | 'float', 'double' => is_float($value), | |
| 157 | 'bool', 'boolean' => is_bool($value), | |
| 158 | 'array' => is_array($value), | |
| 159 | 'object' => is_object($value), | |
| 160 | 'callable' => is_callable($value), | |
| 161 | 'iterable' => is_iterable($value), | |
| 162 | 'numeric' => is_numeric($value), | |
| 163 | 'scalar' => is_scalar($value), | |
| 164 | 'resource' => is_resource($value), | |
| 165 | 'null' => $value === null, | |
| 166 | default => false, | |
| 167 | }; | |
| 168 | } | |
| 169 | ||
| 170 | /** | |
| 171 | * Determine whether a key is set in the context. | |
| 172 | * | |
| 173 | * @param string $key The context key | |
| 174 | * @return bool | |
| 175 | */ | |
| 176 | public function has(string $key): bool | |
| 177 | { | |
| 178 | return array_key_exists($key, $this->data); | |
| 179 | } | |
| 180 | ||
| 181 | /** | |
| 182 | * Get all context values. | |
| 183 | * | |
| 184 | * @return array<string, mixed> | |
| 185 | */ | |
| 186 | public function all(): array | |
| 187 | { | |
| 188 | return $this->data; | |
| 189 | } | |
| 190 | } |