Skip to content

Commit 3fcb200

Browse files
authored
Merge branch 'v3' into dependabot/composer/phpstan/phpstan-tw-1.11.4or-tw-2.0.0
2 parents 72bf931 + 5123a52 commit 3fcb200

31 files changed

Lines changed: 381 additions & 87 deletions

.github/workflows/phpstan.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ jobs:
2424
- name: Setup PHP
2525
uses: shivammathur/setup-php@v2
2626
with:
27-
php-version: '8.3'
27+
php-version: '8.4'
2828
coverage: none
2929

3030
- name: Install composer dependencies

.php-cs-fixer.dist.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@
4141
'blank_line_after_opening_tag' => true,
4242
'single_import_per_statement' => true,
4343
'mb_str_functions' => true,
44-
'no_superfluous_phpdoc_tags' => true,
44+
'no_superfluous_phpdoc_tags' => ['allow_mixed' => true],
4545
'no_blank_lines_after_phpdoc' => true,
4646
'no_empty_phpdoc' => true,
4747
'phpdoc_trim' => true,

composer.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,8 @@
3131
"friendsofphp/php-cs-fixer": "^3.5",
3232
"illuminate/collections": "^9.39 || ^10.0",
3333
"league/flysystem": "^3.0",
34-
"pestphp/pest": "^2.10",
35-
"phpstan/phpstan": "^1.11.4 || ^2.0.0",
34+
"pestphp/pest": "^2.36.0 || ^3.8.2",
35+
"phpstan/phpstan": "^2.1.13",
3636
"saloonphp/xml-wrangler": "^1.1",
3737
"spatie/ray": "^1.33",
3838
"symfony/dom-crawler": "^6.0 || ^7.0",
@@ -73,7 +73,7 @@
7373
"./vendor/bin/pest -p"
7474
],
7575
"pstan": [
76-
"./vendor/bin/phpstan analyse"
76+
"./vendor/bin/phpstan analyse --memory-limit=1G"
7777
]
7878
}
7979
}

phpstan-baseline.neon

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
parameters:
2+
ignoreErrors:
3+
-
4+
message: '#^Call to an undefined method GuzzleHttp\\Promise\\PromiseInterface\|Saloon\\Http\\Response\:\:then\(\)\.$#'
5+
identifier: method.notFound
6+
count: 1
7+
path: src/Http/Connector.php
8+
9+
-
10+
message: '#^Method Saloon\\Http\\Connector\:\:defaultSender\(\) should return Saloon\\Contracts\\Sender but returns object\.$#'
11+
identifier: return.type
12+
count: 1
13+
path: src/Http/Connector.php
14+
15+
-
16+
message: '#^Parameter \#1 \$response of method Saloon\\Http\\PendingRequest\:\:executeResponsePipeline\(\) expects Saloon\\Http\\Response, GuzzleHttp\\Promise\\PromiseInterface\|Saloon\\Http\\Response given\.$#'
17+
identifier: argument.type
18+
count: 1
19+
path: src/Http/Connector.php
20+
21+
-
22+
message: '#^Parameter \#1 \$contents of static method Saloon\\Data\\RecordedResponse\:\:fromFile\(\) expects string, bool\|string given\.$#'
23+
identifier: argument.type
24+
count: 1
25+
path: src/Http/Faking/Fixture.php
26+
27+
-
28+
message: '#^Method Saloon\\Http\\Faking\\MockClient\:\:getLastResponse\(\) should return Saloon\\Http\\Response\|null but returns \(callable\)\|Saloon\\Http\\Faking\\Fixture\|Saloon\\Http\\Faking\\MockResponse\.$#'
29+
identifier: return.type
30+
count: 1
31+
path: src/Http/Faking/MockClient.php
32+
33+
-
34+
message: '#^Method Saloon\\Http\\Faking\\MockClient\:\:getRecordedResponses\(\) should return array\<Saloon\\Http\\Response\> but returns array\<\(callable\)\|Saloon\\Http\\Faking\\Fixture\|Saloon\\Http\\Faking\\MockResponse\>\.$#'
35+
identifier: return.type
36+
count: 1
37+
path: src/Http/Faking/MockClient.php
38+
39+
-
40+
message: '#^Property Saloon\\Http\\Faking\\MockClient\:\:\$recordedResponses \(array\<\(callable\(\)\: mixed\)\|Saloon\\Http\\Faking\\Fixture\|Saloon\\Http\\Faking\\MockResponse\>\) does not accept non\-empty\-array\<\(callable\(\)\: mixed\)\|Saloon\\Http\\Faking\\Fixture\|Saloon\\Http\\Faking\\MockResponse\|Saloon\\Http\\Response\>\.$#'
41+
identifier: assign.propertyType
42+
count: 1
43+
path: src/Http/Faking/MockClient.php
44+
45+
-
46+
message: '#^Parameter \#1 \$callback of function call_user_func_array expects callable\(\)\: mixed, \(callable\(\)\: mixed\)\|object given\.$#'
47+
identifier: argument.type
48+
count: 2
49+
path: src/Http/PendingRequest.php
50+
51+
-
52+
message: '#^Parameter \#1 \$object of method ReflectionMethod\:\:invoke\(\) expects object\|null, class\-string\|object given\.$#'
53+
identifier: argument.type
54+
count: 1
55+
path: src/Http/PendingRequest.php
56+
57+
-
58+
message: '#^Parameter \#1 \$callback of function call_user_func_array expects callable\(\)\: mixed, \(callable\(\)\: mixed\)\|object given\.$#'
59+
identifier: argument.type
60+
count: 2
61+
path: src/Http/Response.php
62+
63+
-
64+
message: '#^Parameter \#1 \$object of method ReflectionMethod\:\:invoke\(\) expects object\|null, class\-string\|object given\.$#'
65+
identifier: argument.type
66+
count: 1
67+
path: src/Http/Response.php

phpstan.baseline.neon

Lines changed: 0 additions & 51 deletions
This file was deleted.

phpstan.dist.neon

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
#############################################################################################
55

66
includes:
7-
- phpstan.baseline.neon
7+
- phpstan-baseline.neon
88

99
parameters:
1010
# Add one of the editorUrl's in your phpstan.neon file for direct editor/IDE links in the terminal.
@@ -18,3 +18,7 @@ parameters:
1818

1919
ignoreErrors:
2020
- "#^Unsafe usage of new static\\(\\)\\.$#"
21+
22+
# Rules
23+
24+
treatPhpDocTypesAsCertain: false

src/Helpers/ArrayHelpers.php

Lines changed: 41 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ final class ArrayHelpers
1616
/**
1717
* Determine whether the given value is array accessible.
1818
*
19-
* @phpstan-assert-if-true array|ArrayAccess $value
19+
* @phpstan-assert-if-true array<array-key, mixed>|ArrayAccess<array-key, mixed> $value
2020
*/
2121
private static function accessible(mixed $value): bool
2222
{
@@ -49,10 +49,6 @@ private static function exists(array|ArrayAccess $array, string|int|float $key):
4949
*/
5050
public static function get(array $array, string|int|null $key, mixed $default = null): mixed
5151
{
52-
if (! static::accessible($array)) {
53-
return Helpers::value($default);
54-
}
55-
5652
if (is_null($key)) {
5753
return $array;
5854
}
@@ -75,4 +71,44 @@ public static function get(array $array, string|int|null $key, mixed $default =
7571

7672
return $array;
7773
}
74+
75+
/**
76+
* Set an array item to a given value using "dot" notation.
77+
*
78+
* If no key is given to the method, the entire array will be replaced.
79+
*
80+
* @param array<array-key, mixed> $array
81+
* @param string|int|null $key
82+
* @param mixed $value
83+
* @return array<array-key, mixed>
84+
*/
85+
public static function set(&$array, $key, $value)
86+
{
87+
if (is_null($key)) {
88+
return $array = $value;
89+
}
90+
91+
$keys = explode('.', (string)$key);
92+
93+
foreach ($keys as $i => $key) {
94+
if (count($keys) === 1) {
95+
break;
96+
}
97+
98+
unset($keys[$i]);
99+
100+
// If the key doesn't exist at this depth, we will just create an empty array
101+
// to hold the next value, allowing us to create the arrays to hold final
102+
// values at the correct depth. Then we'll keep digging into the array.
103+
if (! isset($array[$key]) || ! is_array($array[$key])) {
104+
$array[$key] = [];
105+
}
106+
107+
$array = &$array[$key];
108+
}
109+
110+
$array[array_shift($keys)] = $value;
111+
112+
return $array;
113+
}
78114
}

src/Http/Faking/FakeResponse.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
use Saloon\Contracts\FakeResponse as FakeResponseContract;
2020

2121
/**
22-
* @method static static make(mixed $body = [], int $status = 200, array $headers = [])
22+
* @method static static make(mixed $body = [], int $status = 200, array<string, string|int|float|bool> $headers = [])
2323
*/
2424
class FakeResponse implements FakeResponseContract
2525
{

src/Http/Faking/Fixture.php

Lines changed: 71 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,12 @@
66

77
use Saloon\MockConfig;
88
use Saloon\Helpers\Storage;
9+
use Saloon\Helpers\ArrayHelpers;
910
use Saloon\Data\RecordedResponse;
1011
use Saloon\Helpers\FixtureHelper;
1112
use Saloon\Exceptions\FixtureException;
1213
use Saloon\Exceptions\FixtureMissingException;
14+
use Saloon\Repositories\Body\StringBodyRepository;
1315

1416
class Fixture
1517
{
@@ -28,6 +30,18 @@ class Fixture
2830
*/
2931
protected Storage $storage;
3032

33+
/**
34+
* Data to merge in the mocked response.
35+
*
36+
* @var array<array-key, mixed>|null
37+
*/
38+
protected ?array $merge = null;
39+
40+
/**
41+
* Closure to modify the returned data with.
42+
*/
43+
protected ?\Closure $through = null;
44+
3145
/**
3246
* Constructor
3347
*/
@@ -37,6 +51,28 @@ public function __construct(string $name = '', ?Storage $storage = null)
3751
$this->storage = $storage ?? new Storage(MockConfig::getFixturePath(), true);
3852
}
3953

54+
/**
55+
* Specify data to merge with the mock response data.
56+
*
57+
* @param array<array-key, mixed> $merge
58+
*/
59+
public function merge(array $merge = []): static
60+
{
61+
$this->merge = $merge;
62+
63+
return $this;
64+
}
65+
66+
/**
67+
* Specify a closure to modify the mock response data with.
68+
*/
69+
public function through(\Closure $through): static
70+
{
71+
$this->through = $through;
72+
73+
return $this;
74+
}
75+
4076
/**
4177
* Attempt to get the mock response from the fixture.
4278
*/
@@ -46,7 +82,41 @@ public function getMockResponse(): ?MockResponse
4682
$fixturePath = $this->getFixturePath();
4783

4884
if ($storage->exists($fixturePath)) {
49-
return RecordedResponse::fromFile($storage->get($fixturePath))->toMockResponse();
85+
$response = RecordedResponse::fromFile($storage->get($fixturePath))->toMockResponse();
86+
87+
if (is_null($this->merge) && is_null($this->through)) {
88+
return $response;
89+
}
90+
91+
// First, we get the body as an array. If we're dealing with
92+
// a `StringBodyRepository`, we have to encode it first.
93+
if (! is_array($body = $response->body()->all())) {
94+
$body = json_decode($body ?: '[]', associative: true, flags: \JSON_THROW_ON_ERROR);
95+
}
96+
97+
// We can then merge the data in the body usingthrough
98+
// the ArrayHelpers for dot-notation support.
99+
if (is_array($this->merge)) {
100+
foreach ($this->merge as $key => $value) {
101+
ArrayHelpers::set($body, $key, $value);
102+
}
103+
}
104+
105+
// If specified, we pass the body through a function that
106+
// may modify the mock response data.
107+
if (! is_null($this->through)) {
108+
$body = call_user_func($this->through, $body);
109+
}
110+
111+
// We then set the mutated data back in the repository. If we're dealing
112+
// with a `StringBodyRepository`, we need to encode it back to string.
113+
$response->body()->set(
114+
$response->body() instanceof StringBodyRepository
115+
? json_encode($body)
116+
: $body
117+
);
118+
119+
return $response;
50120
}
51121

52122
if (MockConfig::isThrowingOnMissingFixtures() === true) {

0 commit comments

Comments
 (0)