Skip to content

Commit 113b2ca

Browse files
Merge pull request #50 from MacPaw/fix/fix-request-content
enhance request handling for JSON content, drop PHP 7.4 support
2 parents 8e938c5 + fd787fb commit 113b2ca

17 files changed

Lines changed: 376 additions & 108 deletions

.github/workflows/ci.yaml

Lines changed: 7 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -15,36 +15,25 @@ jobs:
1515
- '8.3'
1616
coverage: ['none']
1717
symfony-versions:
18-
- '4.4.*'
1918
- '5.4.*'
20-
- '6.0.*'
21-
- '6.2.*'
22-
- '7.0.*'
19+
- '6.4.*'
20+
- '7.1.*'
2321
exclude:
2422
- php: '8.1'
25-
symfony-versions: '7.0.*'
23+
symfony-versions: '7.1.*'
2624
include:
27-
- php: '7.4'
28-
symfony-versions: '^4.4'
29-
coverage: 'none'
30-
- php: '7.4'
31-
symfony-versions: '^5.4'
32-
coverage: 'none'
33-
- php: '8.0'
34-
symfony-versions: '^4.4'
35-
coverage: 'none'
36-
- php: '8.0'
25+
- php: '8.1'
3726
symfony-versions: '^5.4'
3827
coverage: 'none'
3928
- description: 'Log Code Coverage'
40-
php: '8.2'
29+
php: '8.4'
4130
coverage: 'xdebug'
42-
symfony-versions: '^7.0'
31+
symfony-versions: '^7.1'
4332

4433
name: PHP ${{ matrix.php }} Symfony ${{ matrix.symfony-versions }} ${{ matrix.description }}
4534
steps:
4635
- name: Checkout
47-
uses: actions/checkout@v2
36+
uses: actions/checkout@v4
4837

4938
- uses: actions/cache@v4
5039
with:
@@ -83,9 +72,6 @@ jobs:
8372
- name: Install dependencies
8473
run: composer install
8574

86-
- name: Add doctrine/orm
87-
run: composer require --no-progress --no-interaction --prefer-dist doctrine/orm:^2.0
88-
8975
- name: Run PHPUnit tests
9076
run: composer phpunit
9177
if: matrix.coverage == 'none'

.github/workflows/security.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ jobs:
99
runs-on: ubuntu-latest
1010
steps:
1111
- name: Checkout
12-
uses: actions/checkout@v2
12+
uses: actions/checkout@v4
1313

1414
- name: Setup PHP
1515
uses: shivammathur/setup-php@v2

.github/workflows/static-analysis.yaml

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,17 +11,14 @@ jobs:
1111
runs-on: ubuntu-latest
1212
steps:
1313
- name: Checkout
14-
uses: actions/checkout@v2
14+
uses: actions/checkout@v4
1515

1616
- name: Setup PHP
1717
uses: shivammathur/setup-php@v2
1818

1919
- name: Install dependencies
2020
run: composer install --no-progress --no-interaction --prefer-dist
2121

22-
- name: Add doctrine/orm
23-
run: composer require --no-progress --no-interaction --prefer-dist doctrine/orm:^2.0
24-
2522
- name: Run script
2623
run: composer code-style
2724

@@ -30,17 +27,14 @@ jobs:
3027
runs-on: ubuntu-latest
3128
steps:
3229
- name: Checkout
33-
uses: actions/checkout@v2
30+
uses: actions/checkout@v4
3431

3532
- name: Setup PHP
3633
uses: shivammathur/setup-php@v2
3734

3835
- name: Install dependencies
3936
run: composer install --no-progress --no-interaction --prefer-dist
4037

41-
- name: Add doctrine/orm
42-
run: composer require --no-progress --no-interaction --prefer-dist doctrine/orm:^2.0
43-
4438
- name: Run script
4539
run: composer phpstan
4640

@@ -49,7 +43,7 @@ jobs:
4943
runs-on: ubuntu-latest
5044
steps:
5145
- name: Checkout
52-
uses: actions/checkout@v2
46+
uses: actions/checkout@v4
5347

5448
- name: Setup PHP
5549
uses: shivammathur/setup-php@v2

composer.json

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -27,20 +27,21 @@
2727
"license": "MIT",
2828
"require": {
2929
"ext-json": "*",
30-
"php": "^7.4 || ^8.0",
30+
"php": "^8.1",
3131
"behat/behat": "^3.0",
32-
"symfony/config": "^4.4 || ^5.4 || ^6.0 || ^7.0",
33-
"symfony/dependency-injection": "^4.4 || ^5.4.34 || ^6.0 || ^7.0.2",
34-
"symfony/http-client": "^4.4 || ^5.4 || ^6.0 || ^7.0",
35-
"symfony/http-kernel": "^4.4 || ^5.4 || ^6.0 || ^7.0",
36-
"symfony/routing": "^4.4 || ^5.4 || ^6.0 || ^7.0",
32+
"symfony/config": "^5.4 || ^6.4 || ^7.0",
33+
"symfony/dependency-injection": "^5.4.34 || ^6.4 || ^7.1",
34+
"symfony/http-client": "^5.4 || ^6.4 || ^7.1",
35+
"symfony/http-kernel": "^5.4 || ^6.4 || ^7.1",
36+
"symfony/routing": "^5.4 || ^6.4 || ^7.1",
3737
"macpaw/similar-arrays": "^1.0"
3838
},
3939
"require-dev": {
40-
"phpstan/phpstan": "^1.4",
41-
"phpunit/phpunit": "^9.3",
40+
"phpstan/phpstan": "^2.0",
41+
"phpunit/phpunit": "^10.0",
4242
"slevomat/coding-standard": "^7.0",
43-
"squizlabs/php_codesniffer": "^3.6"
43+
"squizlabs/php_codesniffer": "^3.6",
44+
"doctrine/orm": "^2.0"
4445
},
4546
"autoload": {
4647
"psr-4": {
@@ -57,7 +58,7 @@
5758
"phpstan": "./vendor/bin/phpstan analyse -l max",
5859
"code-style": "./vendor/bin/phpcs",
5960
"code-style-fix": "./vendor/bin/phpcbf",
60-
"phpunit": "./vendor/bin/phpunit",
61+
"phpunit": "./vendor/bin/phpunit --no-coverage",
6162
"phpunit-html-coverage": "XDEBUG_MODE=coverage ./vendor/bin/phpunit --coverage-html=coverage",
6263
"dev-checks": [
6364
"composer validate",

phpstan.neon.dist

Lines changed: 0 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
parameters:
2-
excludes_analyse:
32
paths:
43
- src
54
level: max
@@ -9,29 +8,9 @@ parameters:
98
message: '#Parameter \#1 \$json of function json_decode expects string, string\|false given.*#'
109
count: 3
1110
path: ./src/Context/ApiContext.php
12-
-
13-
message: '#Call to an undefined method Symfony\\Component\\HttpKernel\\KernelInterface::terminate\(\).*#'
14-
count: 1
15-
path: ./src/Context/ApiContext.php
1611
-
1712
message: '#Parameter \#3 \$actualJSON of method BehatApiContext\\Context\\ApiContext::compareStructureResponse\(\) expects string, string\|false given.*#'
1813
count: 1
1914
path: ./src/Context/ApiContext.php
20-
-
21-
message: '#Call to an undefined method Symfony\\Component\\Config\\Definition\\Builder\\NodeDefinition::children\(\).#'
22-
count: 1
23-
path: ./src/DependencyInjection
24-
-
25-
message: '#.*NodeParentInterface|null.*#'
26-
count: 1
27-
path: ./src/DependencyInjection
28-
-
29-
message: '#Call to an undefined method object::clear().*#'
30-
count: 1
31-
path: ./src/Service/ResetManager/DoctrineResetManager
32-
-
33-
message: '#Call to an undefined method object::getConnection().*#'
34-
count: 1
35-
path: ./src/Service/ResetManager/DoctrineResetManager
3615
-
3716
identifier: missingType.iterableValue

src/Context/ApiContext.php

Lines changed: 50 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
use Symfony\Component\HttpFoundation\RequestStack;
1515
use Symfony\Component\HttpFoundation\Response;
1616
use Symfony\Component\HttpKernel\KernelInterface;
17+
use Symfony\Component\HttpKernel\TerminableInterface;
1718
use Symfony\Component\Routing\RouterInterface;
1819
use Throwable;
1920

@@ -23,8 +24,12 @@ class ApiContext implements Context
2324
private StringManager $stringManager;
2425
private RouterInterface $router;
2526
private RequestStack $requestStack;
26-
private ?Response $response;
27-
private KernelInterface $kernel;
27+
private Response $response;
28+
private KernelInterface&TerminableInterface $kernel;
29+
30+
/**
31+
* @var list<ResetManagerInterface>
32+
*/
2833
private array $resetManagers = [];
2934

3035
/**
@@ -38,19 +43,19 @@ class ApiContext implements Context
3843
protected array $serverParams = [];
3944

4045
/**
41-
* @var array<mixed> $requestParams
46+
* @var array<string, mixed> $requestParams
4247
*/
4348
protected array $requestParams = [];
4449

4550
/**
46-
* @var array<mixed> $savedValues
51+
* @var array<string, string|list<string>> $savedValues
4752
*/
4853
protected array $savedValues = [];
4954

5055
public function __construct(
5156
RouterInterface $router,
5257
RequestStack $requestStack,
53-
KernelInterface $kernel
58+
KernelInterface&TerminableInterface $kernel
5459
) {
5560
$this->router = $router;
5661
$this->requestStack = $requestStack;
@@ -116,8 +121,13 @@ public function theRequestContainsParams(PyStringNode $params): void
116121

117122
$newRequestParams = (array) json_decode($processedParams, true, 512, JSON_THROW_ON_ERROR);
118123
$newRequestParams = $this->convertRunnableCodeParams($newRequestParams);
119-
$this->requestParams = array_merge($this->requestParams, $newRequestParams);
120-
$this->savedValues = array_merge($this->savedValues, $newRequestParams);
124+
/** @var array<string, mixed> $requestParams */
125+
$requestParams = array_merge($this->requestParams, $newRequestParams);
126+
$this->requestParams = $requestParams;
127+
128+
/** @var array<string, mixed> $savedValues */
129+
$savedValues = array_merge($this->savedValues, $newRequestParams);
130+
$this->savedValues = $savedValues;
121131
}
122132

123133
/**
@@ -130,19 +140,32 @@ public function iSendRequestToRoute(
130140
$routeParams = $this->popRouteAttributesFromRequestParams($route, $this->requestParams);
131141
$postFields = [];
132142
$queryString = '';
143+
$content = null;
133144

134145
$url = $this->router->generate($route, $routeParams);
135-
$url = preg_replace('|^/app[^\.]*\.php|', '', $url);
146+
$url = preg_replace('|^/app[^.]*\.php|', '', $url);
136147

137148
if (Request::METHOD_GET === $method) {
138149
$queryString = http_build_query($this->requestParams);
139150
}
140151

141152
if (in_array($method, [Request::METHOD_POST, Request::METHOD_PATCH, Request::METHOD_PUT], true)) {
142-
$postFields = $this->requestParams;
153+
$isJsonRequest = array_key_exists('Content-Type', $this->headers) &&
154+
str_contains(strtolower($this->headers['Content-Type']), 'application/json');
155+
156+
if ($isJsonRequest) {
157+
$content = json_encode($this->requestParams, JSON_THROW_ON_ERROR);
158+
} else {
159+
$postFields = $this->requestParams;
160+
}
143161
}
144162

145-
$request = Request::create($url . '?' . $queryString, $method, $postFields);
163+
$request = Request::create(
164+
uri: $url . '?' . $queryString,
165+
method: $method,
166+
parameters: $postFields,
167+
content: $content
168+
);
146169
$request->headers->add($this->headers);
147170
$request->server->add($this->serverParams);
148171

@@ -171,19 +194,21 @@ private function handleRequestWithKernel(Request $request): Response
171194
}
172195

173196
/**
174-
* @param array<string,string> $requestParams
197+
* @param array<string, mixed> $requestParams
175198
*
176-
* @return array<string,string>
199+
* @return array<string, mixed>
177200
*/
178201
private function popRouteAttributesFromRequestParams(string $route, array &$requestParams): array
179202
{
180203
$routeParams = [];
204+
$routeDecl = $this->router->getRouteCollection()->get($route);
181205

182-
if (is_array($requestParams) && ($routeDecl = $this->router->getRouteCollection()->get($route))) {
206+
if ($routeDecl !== null) {
207+
/** @var array<string, string> $requirements */
183208
$requirements = $routeDecl->getRequirements();
184209

185210
foreach ($requirements as $attribute => $requirement) {
186-
if (isset($requestParams[$attribute]) && strpos($attribute, '_') !== 0) {
211+
if (isset($requestParams[$attribute]) && !str_starts_with($attribute, '_')) {
187212
$routeParams[$attribute] = $requestParams[$attribute];
188213
unset($requestParams[$attribute]);
189214
}
@@ -282,15 +307,20 @@ public function responseShouldBeJsonWithVariableFields(string $variableFields, P
282307
$this->compareStructureResponse($variableFields, $string, $this->getResponse()->getContent());
283308
}
284309

285-
protected function compareStructureResponse(string $variableFields, PyStringNode $string, string $actualJSON): void
286-
{
310+
protected function compareStructureResponse(
311+
string $variableFieldsString,
312+
PyStringNode $string,
313+
string $actualJSON
314+
): void {
287315
if ($actualJSON === '') {
288316
throw new RuntimeException('Response is not JSON');
289317
}
290318

291-
$expectedResponse = (array) json_decode(trim($string->getRaw()), true);
292-
$actualResponse = (array) json_decode($actualJSON, true);
293-
$variableFields = $variableFields ? array_map('trim', explode(',', $variableFields)) : [];
319+
$expectedResponse = json_decode(trim($string->getRaw()), true, 512, JSON_THROW_ON_ERROR);
320+
$actualResponse = json_decode($actualJSON, true, 512, JSON_THROW_ON_ERROR);
321+
$variableFields = $variableFieldsString
322+
? array_map('trim', explode(',', $variableFieldsString))
323+
: [];
294324

295325
if (!$this->similarArrayManager->isArraysSimilar($expectedResponse, $actualResponse, $variableFields)) {
296326
$prettyJSON = json_encode($actualResponse, JSON_PRETTY_PRINT);
@@ -343,7 +373,7 @@ protected function checkResponseHeader(string $headerName, string $headerValue):
343373

344374
$responseHeaderValue = $response->headers->get($givenHeaderName);
345375

346-
if (null === $responseHeaderValue || !substr_count($responseHeaderValue, $givenHeaderValue) > 0) {
376+
if (null === $responseHeaderValue || substr_count($responseHeaderValue, $givenHeaderValue) < 1) {
347377
$message = sprintf(
348378
'Response header %s does not match. Expected: %s, given value: %s',
349379
$givenHeaderName,
@@ -413,10 +443,6 @@ private function resetRequestOptions(): void
413443

414444
protected function getResponse(): Response
415445
{
416-
if ($this->response === null) {
417-
throw new RuntimeException('Response is null.');
418-
}
419-
420446
return $this->response;
421447
}
422448

src/DependencyInjection/BehatApiContextExtension.php

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,10 @@
1212

1313
class BehatApiContextExtension extends Extension
1414
{
15-
/**
16-
* @param array<array> $configs
17-
*
18-
* {@inheritdoc}
19-
*/
2015
public function load(array $configs, ContainerBuilder $container): void
2116
{
2217
$configuration = new Configuration();
18+
/** @var array<string, mixed> $config */
2319
$config = $this->processConfiguration($configuration, $configs);
2420

2521
$loader = new XmlFileLoader($container, new FileLocator(__DIR__ . '/../Resources/config'));
@@ -28,7 +24,7 @@ public function load(array $configs, ContainerBuilder $container): void
2824
}
2925

3026
/**
31-
* @param array<array> $config
27+
* @param array<string, mixed> $config
3228
*/
3329
private function loadApiContext(
3430
array $config,
@@ -44,6 +40,10 @@ private function loadApiContext(
4440
);
4541
}
4642

43+
/**
44+
* @param array<string, mixed> $config
45+
* @param class-string $contextClass
46+
*/
4747
private function configureKernelResetManagers(
4848
array $config,
4949
ContainerBuilder $container,

0 commit comments

Comments
 (0)