Skip to content

Commit 9ad611e

Browse files
authored
Use actual properties instead of arrays (#8)
* Use actual properties instead of arrays * Re-format methods
1 parent 4126072 commit 9ad611e

File tree

2 files changed

+89
-67
lines changed

2 files changed

+89
-67
lines changed

src/CorsService.php

Lines changed: 70 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -34,54 +34,61 @@
3434
* 'max_age'?: int|bool|null
3535
* }
3636
*
37-
* @phpstan-type CorsNormalizedOptions array{
38-
* 'allowedOrigins': string[],
39-
* 'allowedOriginsPatterns': string[],
40-
* 'supportsCredentials': bool,
41-
* 'allowedHeaders': string[],
42-
* 'allowedMethods': string[],
43-
* 'exposedHeaders': string[],
44-
* 'maxAge': int|bool|null,
45-
* 'allowAllOrigins': bool,
46-
* 'allowAllHeaders': bool,
47-
* 'allowAllMethods': bool,
48-
* }
4937
*/
5038
class CorsService
5139
{
52-
/** @var CorsNormalizedOptions */
53-
private $options;
40+
/** @var string[] */
41+
private $allowedOrigins = [];
42+
/** @var string[] */
43+
private $allowedOriginsPatterns = [];
44+
/** @var string[] */
45+
private $allowedMethods = [];
46+
/** @var string[] */
47+
private $allowedHeaders = [];
48+
/** @var string[] */
49+
private $exposedHeaders = [];
50+
/** @var bool */
51+
private $supportsCredentials = false;
52+
/** @var null|int */
53+
private $maxAge = 0;
54+
55+
/** @var bool */
56+
private $allowAllOrigins = false;
57+
/** @var bool */
58+
private $allowAllMethods = false;
59+
/** @var bool */
60+
private $allowAllHeaders = false;
5461

5562
/**
5663
* @param CorsInputOptions $options
5764
*/
5865
public function __construct(array $options = [])
5966
{
60-
$this->options = $this->normalizeOptions($options);
61-
}
62-
63-
/**
64-
* @param CorsInputOptions $options
65-
* @return CorsNormalizedOptions
66-
*/
67-
private function normalizeOptions(array $options = []): array
68-
{
69-
$options['allowedOrigins'] = $options['allowedOrigins'] ?? $options['allowed_origins'] ?? [];
70-
$options['allowedOriginsPatterns'] =
71-
$options['allowedOriginsPatterns'] ?? $options['allowed_origins_patterns'] ?? [];
72-
$options['allowedMethods'] = $options['allowedMethods'] ?? $options['allowed_methods'] ?? [];
73-
$options['allowedHeaders'] = $options['allowedHeaders'] ?? $options['allowed_headers'] ?? [];
74-
$options['exposedHeaders'] = $options['exposedHeaders'] ?? $options['exposed_headers'] ?? [];
75-
$options['supportsCredentials'] = $options['supportsCredentials'] ?? $options['supports_credentials'] ?? false;
76-
77-
if (!array_key_exists('maxAge', $options)) {
78-
$options['maxAge'] = array_key_exists('max_age', $options) ? $options['max_age'] : 0;
67+
$this->allowedOrigins = $options['allowedOrigins'] ?? $options['allowed_origins'] ?? $this->allowedOrigins;
68+
$this->allowedOriginsPatterns =
69+
$options['allowedOriginsPatterns'] ?? $options['allowed_origins_patterns'] ?? $this->allowedOriginsPatterns;
70+
$this->allowedMethods = $options['allowedMethods'] ?? $options['allowed_methods'] ?? $this->allowedMethods;
71+
$this->allowedHeaders = $options['allowedHeaders'] ?? $options['allowed_headers'] ?? $this->allowedHeaders;
72+
$this->supportsCredentials =
73+
$options['supportsCredentials'] ?? $options['supports_credentials'] ?? $this->supportsCredentials;
74+
75+
$maxAge = $this->maxAge;
76+
if (array_key_exists('maxAge', $options)) {
77+
$maxAge = $options['maxAge'];
78+
} elseif (array_key_exists('max_age', $options)) {
79+
$maxAge = $options['max_age'];
7980
}
81+
$this->maxAge = $maxAge === null ? null : (int)$maxAge;
8082

81-
if ($options['exposedHeaders'] === false) {
82-
$options['exposedHeaders'] = [];
83-
}
83+
$exposedHeaders = $options['exposedHeaders'] ?? $options['exposed_headers'] ?? $this->exposedHeaders;
84+
$this->exposedHeaders = $exposedHeaders === false ? [] : $exposedHeaders;
85+
86+
$this->validateOptions();
87+
$this->normalizeOptions();
88+
}
8489

90+
private function validateOptions(): void
91+
{
8592
$arrayHeaders = [
8693
'allowedOrigins',
8794
'allowedOriginsPatterns',
@@ -90,28 +97,29 @@ private function normalizeOptions(array $options = []): array
9097
'exposedHeaders',
9198
];
9299
foreach ($arrayHeaders as $key) {
93-
if (!is_array($options[$key])) {
100+
if (!is_array($this->{$key})) {
94101
throw new InvalidOptionException("CORS option `{$key}` should be an array");
95102
}
96103
}
104+
}
97105

106+
private function normalizeOptions(): void
107+
{
98108
// Transform wildcard pattern
99-
foreach ($options['allowedOrigins'] as $origin) {
109+
foreach ($this->allowedOrigins as $origin) {
100110
if (strpos($origin, '*') !== false) {
101-
$options['allowedOriginsPatterns'][] = $this->convertWildcardToPattern($origin);
111+
$this->allowedOriginsPatterns[] = $this->convertWildcardToPattern($origin);
102112
}
103113
}
104114

105115
// Normalize case
106-
$options['allowedHeaders'] = array_map('strtolower', $options['allowedHeaders']);
107-
$options['allowedMethods'] = array_map('strtoupper', $options['allowedMethods']);
116+
$this->allowedHeaders = array_map('strtolower', $this->allowedHeaders);
117+
$this->allowedMethods = array_map('strtoupper', $this->allowedMethods);
108118

109119
// Normalize ['*'] to true
110-
$options['allowAllOrigins'] = in_array('*', $options['allowedOrigins']);
111-
$options['allowAllHeaders'] = in_array('*', $options['allowedHeaders']);
112-
$options['allowAllMethods'] = in_array('*', $options['allowedMethods']);
113-
114-
return $options;
120+
$this->allowAllOrigins = in_array('*', $this->allowedOrigins);
121+
$this->allowAllHeaders = in_array('*', $this->allowedHeaders);
122+
$this->allowAllMethods = in_array('*', $this->allowedMethods);
115123
}
116124

117125
/**
@@ -171,17 +179,17 @@ public function addPreflightRequestHeaders(Response $response, Request $request)
171179

172180
public function isOriginAllowed(Request $request): bool
173181
{
174-
if ($this->options['allowAllOrigins'] === true) {
182+
if ($this->allowAllOrigins === true) {
175183
return true;
176184
}
177185

178186
$origin = (string) $request->headers->get('Origin');
179187

180-
if (in_array($origin, $this->options['allowedOrigins'])) {
188+
if (in_array($origin, $this->allowedOrigins)) {
181189
return true;
182190
}
183191

184-
foreach ($this->options['allowedOriginsPatterns'] as $pattern) {
192+
foreach ($this->allowedOriginsPatterns as $pattern) {
185193
if (preg_match($pattern, $origin)) {
186194
return true;
187195
}
@@ -205,12 +213,12 @@ public function addActualRequestHeaders(Response $response, Request $request): R
205213

206214
private function configureAllowedOrigin(Response $response, Request $request): void
207215
{
208-
if ($this->options['allowAllOrigins'] === true && !$this->options['supportsCredentials']) {
216+
if ($this->allowAllOrigins === true && !$this->supportsCredentials) {
209217
// Safe+cacheable, allow everything
210218
$response->headers->set('Access-Control-Allow-Origin', '*');
211219
} elseif ($this->isSingleOriginAllowed()) {
212220
// Single origins can be safely set
213-
$response->headers->set('Access-Control-Allow-Origin', array_values($this->options['allowedOrigins'])[0]);
221+
$response->headers->set('Access-Control-Allow-Origin', array_values($this->allowedOrigins)[0]);
214222
} else {
215223
// For dynamic headers, set the requested Origin header when set and allowed
216224
if ($this->isCorsRequest($request) && $this->isOriginAllowed($request)) {
@@ -223,54 +231,54 @@ private function configureAllowedOrigin(Response $response, Request $request): v
223231

224232
private function isSingleOriginAllowed(): bool
225233
{
226-
if ($this->options['allowAllOrigins'] === true || count($this->options['allowedOriginsPatterns']) > 0) {
234+
if ($this->allowAllOrigins === true || count($this->allowedOriginsPatterns) > 0) {
227235
return false;
228236
}
229237

230-
return count($this->options['allowedOrigins']) === 1;
238+
return count($this->allowedOrigins) === 1;
231239
}
232240

233241
private function configureAllowedMethods(Response $response, Request $request): void
234242
{
235-
if ($this->options['allowAllMethods'] === true) {
243+
if ($this->allowAllMethods === true) {
236244
$allowMethods = strtoupper((string) $request->headers->get('Access-Control-Request-Method'));
237245
$this->varyHeader($response, 'Access-Control-Request-Method');
238246
} else {
239-
$allowMethods = implode(', ', $this->options['allowedMethods']);
247+
$allowMethods = implode(', ', $this->allowedMethods);
240248
}
241249

242250
$response->headers->set('Access-Control-Allow-Methods', $allowMethods);
243251
}
244252

245253
private function configureAllowedHeaders(Response $response, Request $request): void
246254
{
247-
if ($this->options['allowAllHeaders'] === true) {
255+
if ($this->allowAllHeaders === true) {
248256
$allowHeaders = (string) $request->headers->get('Access-Control-Request-Headers');
249257
$this->varyHeader($response, 'Access-Control-Request-Headers');
250258
} else {
251-
$allowHeaders = implode(', ', $this->options['allowedHeaders']);
259+
$allowHeaders = implode(', ', $this->allowedHeaders);
252260
}
253261
$response->headers->set('Access-Control-Allow-Headers', $allowHeaders);
254262
}
255263

256264
private function configureAllowCredentials(Response $response, Request $request): void
257265
{
258-
if ($this->options['supportsCredentials']) {
266+
if ($this->supportsCredentials) {
259267
$response->headers->set('Access-Control-Allow-Credentials', 'true');
260268
}
261269
}
262270

263271
private function configureExposedHeaders(Response $response, Request $request): void
264272
{
265-
if ($this->options['exposedHeaders']) {
266-
$response->headers->set('Access-Control-Expose-Headers', implode(', ', $this->options['exposedHeaders']));
273+
if ($this->exposedHeaders) {
274+
$response->headers->set('Access-Control-Expose-Headers', implode(', ', $this->exposedHeaders));
267275
}
268276
}
269277

270278
private function configureMaxAge(Response $response, Request $request): void
271279
{
272-
if ($this->options['maxAge'] !== null) {
273-
$response->headers->set('Access-Control-Max-Age', (string) $this->options['maxAge']);
280+
if ($this->maxAge !== null) {
281+
$response->headers->set('Access-Control-Max-Age', (string) $this->maxAge);
274282
}
275283
}
276284

tests/CorsServiceTest.php

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,18 @@
1717
use PHPUnit\Framework\TestCase;
1818

1919
/**
20-
* @phpstan-import-type CorsNormalizedOptions from CorsService
20+
* @phpstan-type CorsNormalizedOptions array{
21+
* 'allowedOrigins': string[],
22+
* 'allowedOriginsPatterns': string[],
23+
* 'supportsCredentials': bool,
24+
* 'allowedHeaders': string[],
25+
* 'allowedMethods': string[],
26+
* 'exposedHeaders': string[],
27+
* 'maxAge': int|bool|null,
28+
* 'allowAllOrigins': bool,
29+
* 'allowAllHeaders': bool,
30+
* 'allowAllMethods': bool,
31+
* }
2132
*/
2233
class CorsServiceTest extends TestCase
2334
{
@@ -206,12 +217,15 @@ private function getOptionsFromService(CorsService $service): array
206217
{
207218
$reflected = new \ReflectionClass($service);
208219

209-
$property = $reflected->getProperty('options');
210-
$property->setAccessible(true);
220+
$properties = $reflected->getProperties(\ReflectionProperty::IS_PRIVATE);
211221

212-
/** @var CorsNormalizedOptions $options */
213-
$options = $property->getValue($service);
222+
$options = [];
223+
foreach ($properties as $property) {
224+
$property->setAccessible(true);
225+
$options[$property->getName()] = $property->getValue($service);
226+
}
214227

228+
/** @var CorsNormalizedOptions $options */
215229
return $options;
216230
}
217231
}

0 commit comments

Comments
 (0)