Skip to content

Commit 89065c4

Browse files
committed
Add EndpointInterface and improve PHPStan checks for fake override
- Now fake override needs interface and implementation in makeEndpoint. - Add tests for fake implementation using environment interface swap
1 parent b213f37 commit 89065c4

18 files changed

+210
-53
lines changed

composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
"lint:check": "./vendor/bin/ecs",
5050
"lint:fix": "./vendor/bin/ecs --fix",
5151
"lint:stan": "./vendor/bin/phpstan",
52+
"lint:stan:baseline": "./vendor/bin/phpstan -b",
5253
"lint:upgrade:check": "vendor/bin/rector process --dry-run",
5354
"lint:upgrade": "vendor/bin/rector process",
5455
"lint": "composer lint:upgrade && composer lint:fix && composer lint:stan",

phpstan-baseline.neon

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
11
parameters:
22
ignoreErrors:
33
-
4-
message: "#^Method WrkFlow\\\\ApiSdkBuilder\\\\AbstractApi\\:\\:getOverrideEndpointClassIfCan\\(\\) should return class\\-string\\<T of WrkFlow\\\\ApiSdkBuilder\\\\Endpoints\\\\AbstractEndpoint\\> but returns class\\-string\\<WrkFlow\\\\ApiSdkBuilder\\\\Endpoints\\\\AbstractEndpoint\\>\\.$#"
4+
message: "#^Method WrkFlow\\\\ApiSdkBuilder\\\\AbstractApi\\:\\:getOverrideEndpointClassIfCan\\(\\) should return class\\-string\\<T of WrkFlow\\\\ApiSdkBuilder\\\\Interfaces\\\\EndpointInterface\\> but returns class\\-string\\<WrkFlow\\\\ApiSdkBuilder\\\\Interfaces\\\\EndpointInterface\\>\\.$#"
55
count: 1
66
path: src/AbstractApi.php
77

88
-
9-
message: "#^Method WrkFlow\\\\ApiSdkBuilder\\\\AbstractApi\\:\\:makeEndpoint\\(\\) should return T of WrkFlow\\\\ApiSdkBuilder\\\\Endpoints\\\\AbstractEndpoint but returns WrkFlow\\\\ApiSdkBuilder\\\\Endpoints\\\\AbstractEndpoint\\.$#"
9+
message: "#^Method WrkFlow\\\\ApiSdkBuilderTests\\\\TestApi\\\\Endpoints\\\\Json\\\\JsonEndpoint\\:\\:phpStanShouldReportThis\\(\\) should return WrkFlow\\\\ApiSdkBuilderTests\\\\TestApi\\\\Endpoints\\\\Json\\\\JsonResponse but returns WrkFlow\\\\ApiSdkBuilder\\\\Responses\\\\AbstractResponse\\.$#"
1010
count: 1
11-
path: src/AbstractApi.php
11+
path: tests/TestApi/Endpoints/Json/JsonEndpoint.php
1212

1313
-
14-
message: "#^Method WrkFlow\\\\ApiSdkBuilderTests\\\\TestApi\\\\Endpoints\\\\Json\\\\JsonEndpoint\\:\\:phpStanShouldReportThis\\(\\) should return WrkFlow\\\\ApiSdkBuilderTests\\\\TestApi\\\\Endpoints\\\\Json\\\\JsonResponse but returns WrkFlow\\\\ApiSdkBuilder\\\\Responses\\\\AbstractResponse\\.$#"
14+
message: "#^Method WrkFlow\\\\ApiSdkBuilderTests\\\\TestApi\\\\TestApi\\:\\:phpStanShouldReportThis\\(\\) should return WrkFlow\\\\ApiSdkBuilderTests\\\\TestApi\\\\Endpoints\\\\Json\\\\JsonEndpointInterface but returns WrkFlow\\\\ApiSdkBuilderTests\\\\TestApi\\\\Endpoints\\\\EmptyEndpoint\\|WrkFlow\\\\ApiSdkBuilderTests\\\\TestApi\\\\Endpoints\\\\Json\\\\JsonEndpointInterface\\.$#"
1515
count: 1
16-
path: tests/TestApi/Endpoints/Json/JsonEndpoint.php
16+
path: tests/TestApi/TestApi.php
1717

src/AbstractApi.php

Lines changed: 24 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -8,25 +8,25 @@
88
use JustSteveKing\UriBuilder\Uri;
99
use Psr\Http\Message\ResponseInterface;
1010
use WrkFlow\ApiSdkBuilder\Contracts\ApiFactoryContract;
11-
use WrkFlow\ApiSdkBuilder\Endpoints\AbstractEndpoint;
1211
use WrkFlow\ApiSdkBuilder\Environments\AbstractEnvironment;
1312
use WrkFlow\ApiSdkBuilder\Exceptions\BadRequestException;
1413
use WrkFlow\ApiSdkBuilder\Exceptions\ResponseException;
1514
use WrkFlow\ApiSdkBuilder\Exceptions\ServerFailedException;
1615
use WrkFlow\ApiSdkBuilder\Interfaces\ApiInterface;
16+
use WrkFlow\ApiSdkBuilder\Interfaces\EndpointInterface;
1717
use WrkFlow\ApiSdkBuilder\Interfaces\EnvironmentOverrideEndpointsInterface;
1818

1919
abstract class AbstractApi implements ApiInterface
2020
{
2121
/**
2222
* A cache map of created endpoints: class -> instance.
2323
*
24-
* @var array<string,AbstractEndpoint>
24+
* @var array<string,EndpointInterface>
2525
*/
2626
private array $cachedEndpoints = [];
2727

2828
/**
29-
* @var array<class-string, class-string<AbstractEndpoint>>
29+
* @var array<class-string<EndpointInterface>, class-string<EndpointInterface>>
3030
*/
3131
private readonly array $overrideEndpoints;
3232

@@ -70,56 +70,55 @@ public function shouldIgnoreLoggersOnException(): ?Closure
7070
}
7171

7272
/**
73-
* @template T of AbstractEndpoint
73+
* @template T of EndpointInterface
7474
*
7575
* @param class-string<T> $endpoint
76+
* @param class-string<T>|null $implementation
7677
*
7778
* @return T
7879
*/
79-
final protected function makeEndpoint(string $endpoint): AbstractEndpoint
80+
final protected function makeEndpoint(string $endpoint, string $implementation = null): EndpointInterface
8081
{
8182
if (array_key_exists($endpoint, $this->cachedEndpoints) === false) {
82-
$endpoint = $this->getOverrideEndpointClassIfCan($endpoint);
83+
$endpoint = $this->getOverrideEndpointClassIfCan($endpoint, $implementation ?? $endpoint);
8384

84-
$instance = $this->factory()
85+
$instance = $this
86+
->factory()
8587
->container()
8688
->makeEndpoint($this, $endpoint);
8789

90+
$endpointInstance = $instance;
8891
$this->cachedEndpoints[$endpoint] = $instance;
92+
} else {
93+
$endpointInstance = $this->cachedEndpoints[$endpoint];
8994
}
9095

91-
return $this->cachedEndpoints[$endpoint];
96+
assert(assertion: $endpointInstance instanceof $endpoint, description: 'Invalid cached endpoints state.');
97+
98+
return $endpointInstance;
9299
}
93100

94101
/**
95-
* @template T of AbstractEndpoint
102+
* Allow swapping implementation for original endpoint using interface. If the endpoint is in the overrideEndpoints
103+
* then we will return the override class, otherwise we will return the original $implementation.
96104
*
97-
* Allow swapping implementation for original endpoint using interface We will receive the "real" endpoint
98-
* implementation we will check if the endpoint implements any interface check the interface agains override
99-
* endpoints map
105+
* @template T of EndpointInterface
100106
*
101107
* @param class-string<T> $endpoint
108+
* @param class-string<T> $implementation
102109
*
103110
* @return class-string<T>
104111
*/
105-
private function getOverrideEndpointClassIfCan(string $endpoint): string
112+
private function getOverrideEndpointClassIfCan(string $endpoint, string $implementation): string
106113
{
107114
if ($this->overrideEndpoints === []) {
108-
return $endpoint;
109-
}
110-
111-
$implements = class_implements($endpoint);
112-
113-
if (is_array($implements) === false) {
114-
return $endpoint;
115+
return $implementation;
115116
}
116117

117-
foreach ($implements as $interface) {
118-
if (array_key_exists($interface, $this->overrideEndpoints)) {
119-
return $this->overrideEndpoints[$interface];
120-
}
118+
if (array_key_exists($endpoint, $this->overrideEndpoints)) {
119+
return $this->overrideEndpoints[$endpoint];
121120
}
122121

123-
return $endpoint;
122+
return $implementation;
124123
}
125124
}

src/Contracts/SDKContainerFactoryContract.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,21 +5,21 @@
55
namespace WrkFlow\ApiSdkBuilder\Contracts;
66

77
use Psr\Http\Message\ResponseInterface;
8-
use WrkFlow\ApiSdkBuilder\Endpoints\AbstractEndpoint;
98
use WrkFlow\ApiSdkBuilder\Interfaces\ApiInterface;
9+
use WrkFlow\ApiSdkBuilder\Interfaces\EndpointInterface;
1010
use WrkFlow\ApiSdkBuilder\Responses\AbstractResponse;
1111
use Wrkflow\GetValue\GetValue;
1212

1313
interface SDKContainerFactoryContract
1414
{
1515
/**
16-
* @template T of AbstractEndpoint
16+
* @template T of EndpointInterface
1717
*
1818
* @param class-string<T> $endpointClass
1919
*
2020
* @return T
2121
*/
22-
public function makeEndpoint(ApiInterface $api, string $endpointClass): AbstractEndpoint;
22+
public function makeEndpoint(ApiInterface $api, string $endpointClass): EndpointInterface;
2323

2424
/**
2525
* Dynamically creates an instance of the given class.

src/Endpoints/AbstractEndpoint.php

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,10 @@
1212
use WrkFlow\ApiSdkBuilder\Contracts\ApiFactoryContract;
1313
use WrkFlow\ApiSdkBuilder\Entities\EndpointDIEntity;
1414
use WrkFlow\ApiSdkBuilder\Interfaces\ApiInterface;
15+
use WrkFlow\ApiSdkBuilder\Interfaces\EndpointInterface;
1516
use WrkFlow\ApiSdkBuilder\Interfaces\HeadersInterface;
1617
use WrkFlow\ApiSdkBuilder\Interfaces\OptionsInterface;
18+
use WrkFlow\ApiSdkBuilder\Log\Contracts\FileLoggerContract;
1719
use WrkFlow\ApiSdkBuilder\Responses\AbstractResponse;
1820

1921
/**
@@ -22,7 +24,7 @@
2224
*
2325
* @phpstan-import-type IgnoreLoggersOnExceptionClosure from ApiInterface
2426
*/
25-
abstract class AbstractEndpoint
27+
abstract class AbstractEndpoint implements EndpointInterface
2628
{
2729
/**
2830
* @var IgnoreLoggersOnExceptionClosure
@@ -34,10 +36,6 @@ public function __construct(
3436
) {
3537
}
3638

37-
/**
38-
* Returns a copy of endpoint with ability to prevent loggers from logging failed responses for given
39-
* exception.
40-
*/
4139
final public function setShouldIgnoreLoggersForExceptions(Closure $closure): static
4240
{
4341
$cloned = clone $this;

src/Interfaces/EndpointInterface.php

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace WrkFlow\ApiSdkBuilder\Interfaces;
6+
7+
use Closure;
8+
use Throwable;
9+
10+
/**
11+
* Endpoint names that contains all API endpoint methods that sends a request.
12+
* This class should be immutable because it is cached.
13+
*
14+
* @phpstan-import-type IgnoreLoggersOnExceptionClosure from ApiInterface
15+
*/
16+
interface EndpointInterface
17+
{
18+
/**
19+
* Returns a copy of endpoint with ability to prevent loggers from logging failed responses for given
20+
* exception.
21+
*/
22+
public function setShouldIgnoreLoggersForExceptions(Closure $closure): static;
23+
24+
/**
25+
* Returns a copy of endpoint that will not log to file when the request fails with given exception.
26+
*
27+
* @param array<class-string<Throwable>> $exceptions
28+
*/
29+
public function dontReportExceptionsToFile(array $exceptions): static;
30+
}

src/Interfaces/EnvironmentOverrideEndpointsInterface.php

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,10 @@
44

55
namespace WrkFlow\ApiSdkBuilder\Interfaces;
66

7-
use WrkFlow\ApiSdkBuilder\Endpoints\AbstractEndpoint;
8-
97
interface EnvironmentOverrideEndpointsInterface
108
{
119
/**
12-
* @return array<class-string, class-string<AbstractEndpoint>>
10+
* @return array<class-string<EndpointInterface>, class-string<EndpointInterface>>
1311
*/
1412
public function endpoints(): array;
1513
}

src/Laravel/LaravelContainerFactory.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@
77
use Illuminate\Contracts\Container\Container;
88
use Psr\Http\Message\ResponseInterface;
99
use WrkFlow\ApiSdkBuilder\Contracts\SDKContainerFactoryContract;
10-
use WrkFlow\ApiSdkBuilder\Endpoints\AbstractEndpoint;
1110
use WrkFlow\ApiSdkBuilder\Entities\EndpointDIEntity;
1211
use WrkFlow\ApiSdkBuilder\Interfaces\ApiInterface;
12+
use WrkFlow\ApiSdkBuilder\Interfaces\EndpointInterface;
1313
use WrkFlow\ApiSdkBuilder\Responses\AbstractResponse;
1414
use Wrkflow\GetValue\GetValue;
1515

@@ -21,12 +21,12 @@ public function __construct(
2121
}
2222

2323
/**
24-
* @template T of AbstractEndpoint
24+
* @template T of EndpointInterface
2525
* @param class-string<T> $endpointClass
2626
*
2727
* @return T
2828
*/
29-
public function makeEndpoint(ApiInterface $api, string $endpointClass): AbstractEndpoint
29+
public function makeEndpoint(ApiInterface $api, string $endpointClass): EndpointInterface
3030
{
3131
$endpoint = $this->container->make($endpointClass, [
3232
'di' => $this->container->make(EndpointDIEntity::class, [

src/Testing/Factories/TestSDKContainerFactory.php

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,18 @@
88
use Illuminate\Contracts\Container\BindingResolutionException;
99
use Psr\Http\Message\ResponseInterface;
1010
use WrkFlow\ApiSdkBuilder\Contracts\SDKContainerFactoryContract;
11-
use WrkFlow\ApiSdkBuilder\Endpoints\AbstractEndpoint;
1211
use WrkFlow\ApiSdkBuilder\Entities\EndpointDIEntity;
1312
use WrkFlow\ApiSdkBuilder\Interfaces\ApiInterface;
13+
use WrkFlow\ApiSdkBuilder\Interfaces\EndpointInterface;
1414
use WrkFlow\ApiSdkBuilder\Responses\AbstractResponse;
1515
use Wrkflow\GetValue\GetValue;
1616

1717
final class TestSDKContainerFactory implements SDKContainerFactoryContract
1818
{
1919
/**
20-
* @param array<class-string<object>, object|Closure():(object|null)> $makeBindings
20+
* @param array<class-string<object>, object|Closure():(object|null)> $makeBindings
2121
* A map of closures mapped to a class that should create the instance.
22-
* @param array<class-string<AbstractEndpoint>, Closure(EndpointDIEntity):(AbstractEndpoint|null)> $makeEndpointBindings
22+
* @param array<class-string<EndpointInterface>, Closure(EndpointDIEntity):(EndpointInterface|null)> $makeEndpointBindings
2323
* A map of closures mapped to a class that should create the instance.
2424
* @param array<class-string<AbstractResponse>, Closure(ResponseInterface, ?GetValue):(AbstractResponse|null)> $makeResponseBindings A map of closures mapped to a class that should create the instance.
2525
*/
@@ -30,14 +30,14 @@ public function __construct(
3030
) {
3131
}
3232

33-
public function makeEndpoint(ApiInterface $api, string $endpointClass): AbstractEndpoint
33+
public function makeEndpoint(ApiInterface $api, string $endpointClass): EndpointInterface
3434
{
3535
$result = $this->makeFrom(
3636
abstract: $endpointClass,
3737
bindings: $this->makeEndpointBindings,
3838
makeGiven: static fn (Closure $make) => $make(EndpointDIEntityFactory::make(api: $api)),
3939
);
40-
assert($result instanceof $endpointClass, 'Binding must be instance of ' . AbstractEndpoint::class);
40+
assert($result instanceof $endpointClass, 'Binding must be instance of ' . EndpointInterface::class);
4141
return $result;
4242
}
4343

tests/Laravel/ApiTestCase.php

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
use Http\Mock\Client;
88
use Psr\Http\Client\ClientInterface;
99
use WrkFlow\ApiSdkBuilder\Contracts\ApiFactoryContract;
10+
use WrkFlow\ApiSdkBuilder\Environments\AbstractEnvironment;
1011
use WrkFlow\ApiSdkBuilder\Testing\Environments\TestingEnvironment;
1112
use WrkFlow\ApiSdkBuilderTests\TestApi\TestApi;
1213

@@ -21,7 +22,7 @@ protected function setUp(): void
2122
$this->mockBeforeApiFactory();
2223

2324
$this->api = new TestApi(
24-
environment: new TestingEnvironment(),
25+
environment: $this->createApiEnvironment(),
2526
factory: $this->make(ApiFactoryContract::class),
2627
);
2728
}
@@ -31,6 +32,11 @@ protected function mockBeforeApiFactory(): void
3132
$this->useMockApiClient();
3233
}
3334

35+
protected function createApiEnvironment(): AbstractEnvironment
36+
{
37+
return new TestingEnvironment();
38+
}
39+
3440
private function useMockApiClient(): void
3541
{
3642
$client = new Client();

0 commit comments

Comments
 (0)