Skip to content

Commit 3bc7980

Browse files
authored
[FT] Support Guzzle 7 & PSR-18 (#47)
Fixes #39
1 parent 95dc53a commit 3bc7980

33 files changed

+765
-392
lines changed

Makefile

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ CONFLUENT_NETWORK_SUBNET ?= 172.68.0.0/24
88
SCHEMA_REGISTRY_IPV4 ?= 172.68.0.103
99
KAFKA_BROKER_IPV4 ?= 172.68.0.102
1010
ZOOKEEPER_IPV4 ?= 172.68.0.101
11+
CONFLUENT_PLATFORM_WARMUP_SECONDS ?= 30
12+
CONFLUENT_PLATFORM_SHUTDOWN_GRACE_SECONDS ?= 5
1113
COMPOSER ?= bin/composer.phar
1214
COMPOSER_VERSION ?= 1.10.13
1315
PHP ?= bin/php
@@ -71,8 +73,9 @@ install-phars:
7173

7274
platform:
7375
docker-compose down
76+
sleep $(CONFLUENT_PLATFORM_SHUTDOWN_GRACE_SECONDS)
7477
docker-compose up -d
75-
sleep 25
78+
sleep $(CONFLUENT_PLATFORM_WARMUP_SECONDS)
7679

7780
clean:
7881
rm -rf build
@@ -81,6 +84,6 @@ clean:
8184
benchmark:
8285
docker-compose down
8386
docker-compose up -d
84-
sleep 15
87+
sleep $(CONFLUENT_PLATFORM_WARMUP_SECONDS)
8588
PHP_VERSION=$(PHP_VERSION) $(PHP) ./vendor/bin/phpbench run benchmarks/AvroEncodingBench.php --report=aggregate --retry-threshold=5
8689
docker-compose down

README.md

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -77,10 +77,10 @@ major version upgrades will have incompatibilities that will be released in the
7777
<?php
7878

7979
use GuzzleHttp\Client;
80-
use FlixTech\SchemaRegistryApi\Registry\PromisingRegistry;
80+
use FlixTech\SchemaRegistryApi\Registry\GuzzlePromiseAsyncRegistry;
8181
use FlixTech\SchemaRegistryApi\Exception\SchemaRegistryException;
8282

83-
$registry = new PromisingRegistry(
83+
$registry = new GuzzlePromiseAsyncRegistry(
8484
new Client(['base_uri' => 'registry.example.com'])
8585
);
8686

@@ -140,12 +140,12 @@ $schemaId = $registry->schemaId('test-subject', $schema)->wait();
140140
```php
141141
<?php
142142

143-
use FlixTech\SchemaRegistryApi\Registry\BlockingRegistry;
144-
use FlixTech\SchemaRegistryApi\Registry\PromisingRegistry;
143+
use FlixTech\SchemaRegistryApi\Registry\Decorators\BlockingDecorator;
144+
use FlixTech\SchemaRegistryApi\Registry\GuzzlePromiseAsyncRegistry;
145145
use GuzzleHttp\Client;
146146

147-
$registry = new BlockingRegistry(
148-
new PromisingRegistry(
147+
$registry = new BlockingDecorator(
148+
new GuzzlePromiseAsyncRegistry(
149149
new Client(['base_uri' => 'registry.example.com'])
150150
)
151151
);
@@ -173,30 +173,30 @@ It supports both async and sync APIs.
173173
```php
174174
<?php
175175

176-
use FlixTech\SchemaRegistryApi\Registry\BlockingRegistry;
177-
use FlixTech\SchemaRegistryApi\Registry\PromisingRegistry;
178-
use FlixTech\SchemaRegistryApi\Registry\CachedRegistry;
176+
use FlixTech\SchemaRegistryApi\Registry\Decorators\BlockingDecorator;
177+
use FlixTech\SchemaRegistryApi\Registry\GuzzlePromiseAsyncRegistry;
178+
use FlixTech\SchemaRegistryApi\Registry\Decorators\CachingDecorator;
179179
use FlixTech\SchemaRegistryApi\Registry\Cache\AvroObjectCacheAdapter;
180180
use FlixTech\SchemaRegistryApi\Registry\Cache\DoctrineCacheAdapter;
181181
use Doctrine\Common\Cache\ArrayCache;
182182
use GuzzleHttp\Client;
183183

184-
$asyncApi = new PromisingRegistry(
184+
$asyncApi = new GuzzlePromiseAsyncRegistry(
185185
new Client(['base_uri' => 'registry.example.com'])
186186
);
187187

188-
$syncApi = new BlockingRegistry($asyncApi);
188+
$syncApi = new BlockingDecorator($asyncApi);
189189

190-
$doctrineCachedSyncApi = new CachedRegistry(
190+
$doctrineCachedSyncApi = new CachingDecorator(
191191
$asyncApi,
192192
new DoctrineCacheAdapter(
193193
new ArrayCache()
194194
)
195195
);
196196

197197
// All adapters support both APIs, for async APIs additional fulfillment callbacks will be registered.
198-
$avroObjectCachedAsyncApi = new CachedRegistry(
199-
$syncApi,
198+
$avroObjectCachedAsyncApi = new CachingDecorator(
199+
$asyncApi,
200200
new AvroObjectCacheAdapter()
201201
);
202202

@@ -212,7 +212,7 @@ $sha1HashFunction = static function (AvroSchema $schema) {
212212
};
213213

214214
// Pass the hash function as optional 3rd parameter to the CachedRegistry constructor
215-
$avroObjectCachedAsyncApi = new CachedRegistry(
215+
$avroObjectCachedAsyncApi = new CachingDecorator(
216216
$syncApi,
217217
new AvroObjectCacheAdapter(),
218218
$sha1HashFunction

composer.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,9 @@
1414
"php": "^7.3",
1515
"ext-curl": "*",
1616
"ext-json": "*",
17-
"guzzlehttp/guzzle": "~6.3",
18-
"guzzlehttp/psr7": "<1.7",
17+
"psr/http-client": "~1.0",
18+
"guzzlehttp/guzzle": "~7.0",
19+
"guzzlehttp/psr7": "^1.7",
1920
"beberlei/assert": "~2.7|~3.0",
2021
"flix-tech/avro-php": "^4.1"
2122
},

docker-compose.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ services:
1010
ZOOKEEPER_CLIENT_PORT: 2181
1111
ZOOKEEPER_TICK_TIME: 2000
1212
networks:
13-
avro-serializer-net:
13+
schema-registry-net:
1414
ipv4_address: ${ZOOKEEPER_IPV4}
1515

1616
broker:
@@ -25,7 +25,7 @@ services:
2525
KAFKA_ZOOKEEPER_CONNECT: 'zookeeper:2181'
2626
KAFKA_ADVERTISED_LISTENERS: 'PLAINTEXT://172.68.0.102:9092'
2727
networks:
28-
avro-serializer-net:
28+
schema-registry-net:
2929
ipv4_address: ${KAFKA_BROKER_IPV4}
3030

3131
schema_registry:
@@ -40,11 +40,11 @@ services:
4040
SCHEMA_REGISTRY_HOST_NAME: ${SCHEMA_REGISTRY_IPV4}
4141
SCHEMA_REGISTRY_KAFKASTORE_CONNECTION_URL: 'zookeeper:2181'
4242
networks:
43-
avro-serializer-net:
43+
schema-registry-net:
4444
ipv4_address: ${SCHEMA_REGISTRY_IPV4}
4545

4646
networks:
47-
avro-serializer-net:
47+
schema-registry-net:
4848
driver: bridge
4949
ipam:
5050
config:

src/AsynchronousRegistry.php

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,6 @@
77
use AvroSchema;
88
use GuzzleHttp\Promise\PromiseInterface;
99

10-
/**
11-
* {@inheritdoc}
12-
*/
1310
interface AsynchronousRegistry extends Registry
1411
{
1512
/**

src/Constants/Constants.php

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,7 @@
1010
const COMPATIBILITY_FULL = 'FULL';
1111
const COMPATIBILITY_FULL_TRANSITIVE = 'FULL_TRANSITIVE';
1212
const VERSION_LATEST = 'latest';
13-
const ACCEPT_HEADER = ['Accept' => 'application/vnd.schemaregistry.v1+json'];
14-
const CONTENT_TYPE_HEADER = ['Content-Type' => 'application/vnd.schemaregistry.v1+json'];
13+
const ACCEPT = 'Accept';
14+
const ACCEPT_HEADER = [ACCEPT => 'application/vnd.schemaregistry.v1+json'];
15+
const CONTENT_TYPE = 'Content-Type';
16+
const CONTENT_TYPE_HEADER = [CONTENT_TYPE => 'application/vnd.schemaregistry.v1+json'];

src/Exception/AbstractSchemaRegistryException.php

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,9 @@
44

55
namespace FlixTech\SchemaRegistryApi\Exception;
66

7-
use LogicException;
8-
use RuntimeException;
7+
use RuntimeException as PHPRuntimeException;
98

10-
abstract class AbstractSchemaRegistryException extends RuntimeException implements SchemaRegistryException
9+
abstract class AbstractSchemaRegistryException extends PHPRuntimeException implements SchemaRegistryException
1110
{
1211
public const ERROR_CODE = 0;
1312

src/Exception/ExceptionMap.php

Lines changed: 36 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,9 @@
55
namespace FlixTech\SchemaRegistryApi\Exception;
66

77
use Exception;
8-
use GuzzleHttp\Exception\RequestException;
98
use Psr\Http\Message\ResponseInterface;
10-
use RuntimeException;
119
use function array_key_exists;
10+
use function FlixTech\SchemaRegistryApi\Requests\jsonDecode;
1211
use function sprintf;
1312

1413
final class ExceptionMap
@@ -31,38 +30,56 @@ public static function instance(): ExceptionMap
3130
return self::$instance;
3231
}
3332

33+
/**
34+
* @var array<int, callable>
35+
*/
36+
private $map;
37+
3438
private function __construct()
3539
{
40+
$factoryFn = static function (string $exceptionClass): callable {
41+
return static function (int $errorCode, string $errorMessage) use ($exceptionClass): SchemaRegistryException {
42+
return new $exceptionClass($errorMessage, $errorCode);
43+
};
44+
};
45+
46+
$this->map = [
47+
IncompatibleAvroSchemaException::errorCode() => $factoryFn(IncompatibleAvroSchemaException::class),
48+
BackendDataStoreException::errorCode() => $factoryFn(BackendDataStoreException::class),
49+
OperationTimedOutException::errorCode() => $factoryFn(OperationTimedOutException::class),
50+
MasterProxyException::errorCode() => $factoryFn(MasterProxyException::class),
51+
InvalidVersionException::errorCode() => $factoryFn(InvalidVersionException::class),
52+
InvalidAvroSchemaException::errorCode() => $factoryFn(InvalidAvroSchemaException::class),
53+
SchemaNotFoundException::errorCode() => $factoryFn(SchemaNotFoundException::class),
54+
SubjectNotFoundException::errorCode() => $factoryFn(SubjectNotFoundException::class),
55+
VersionNotFoundException::errorCode() => $factoryFn(VersionNotFoundException::class),
56+
InvalidCompatibilityLevelException::errorCode() => $factoryFn(InvalidCompatibilityLevelException::class),
57+
];
3658
}
3759

3860
/**
39-
* Maps a RequestException to the internal SchemaRegistryException types.
61+
* Maps a ResponseInterface to the internal SchemaRegistryException types.
4062
*
41-
* @param RequestException $exception
63+
* @param ResponseInterface $response
4264
*
4365
* @return SchemaRegistryException
4466
*
4567
* @throws RuntimeException
4668
*/
47-
public function __invoke(RequestException $exception): SchemaRegistryException
69+
public function exceptionFor(ResponseInterface $response): SchemaRegistryException
4870
{
49-
$response = $this->guardAgainstMissingResponse($exception);
5071
$decodedBody = $this->guardAgainstMissingErrorCode($response);
5172
$errorCode = $decodedBody[self::ERROR_CODE_FIELD_NAME];
52-
$errorMessage = $decodedBody[self::ERROR_MESSAGE_FIELD_NAME];
73+
$errorMessage = $decodedBody[self::ERROR_MESSAGE_FIELD_NAME] ?? "Unknown Error";
5374

5475
return $this->mapErrorCodeToException($errorCode, $errorMessage);
5576
}
5677

57-
private function guardAgainstMissingResponse(RequestException $exception): ResponseInterface
78+
public function hasMappableError(ResponseInterface $response): bool
5879
{
59-
$response = $exception->getResponse();
80+
$statusCode = $response->getStatusCode();
6081

61-
if (!$response) {
62-
throw new RuntimeException('RequestException has no response to inspect', 0, $exception);
63-
}
64-
65-
return $response;
82+
return $statusCode >= 400 && $statusCode < 600;
6683
}
6784

6885
/**
@@ -72,7 +89,7 @@ private function guardAgainstMissingResponse(RequestException $exception): Respo
7289
private function guardAgainstMissingErrorCode(ResponseInterface $response): array
7390
{
7491
try {
75-
$decodedBody = \GuzzleHttp\json_decode((string) $response->getBody(), true);
92+
$decodedBody = jsonDecode((string) $response->getBody());
7693

7794
if (!array_key_exists(self::ERROR_CODE_FIELD_NAME, $decodedBody)) {
7895
throw new RuntimeException(
@@ -98,39 +115,10 @@ private function guardAgainstMissingErrorCode(ResponseInterface $response): arra
98115

99116
private function mapErrorCodeToException(int $errorCode, string $errorMessage): SchemaRegistryException
100117
{
101-
switch ($errorCode) {
102-
case IncompatibleAvroSchemaException::errorCode():
103-
return new IncompatibleAvroSchemaException($errorMessage, $errorCode);
104-
105-
case BackendDataStoreException::errorCode():
106-
return new BackendDataStoreException($errorMessage, $errorCode);
107-
108-
case OperationTimedOutException::errorCode():
109-
return new OperationTimedOutException($errorMessage, $errorCode);
110-
111-
case MasterProxyException::errorCode():
112-
return new MasterProxyException($errorMessage, $errorCode);
113-
114-
case InvalidVersionException::errorCode():
115-
return new InvalidVersionException($errorMessage, $errorCode);
116-
117-
case InvalidAvroSchemaException::errorCode():
118-
return new InvalidAvroSchemaException($errorMessage, $errorCode);
119-
120-
case SchemaNotFoundException::errorCode():
121-
return new SchemaNotFoundException($errorMessage, $errorCode);
122-
123-
case SubjectNotFoundException::errorCode():
124-
return new SubjectNotFoundException($errorMessage, $errorCode);
125-
126-
case VersionNotFoundException::errorCode():
127-
return new VersionNotFoundException($errorMessage, $errorCode);
128-
129-
case InvalidCompatibilityLevelException::errorCode():
130-
return new InvalidCompatibilityLevelException($errorMessage, $errorCode);
131-
132-
default:
133-
throw new RuntimeException(sprintf('Unknown error code "%d"', $errorCode));
118+
if (!array_key_exists($errorCode, $this->map)) {
119+
throw new RuntimeException(sprintf('Unknown error code "%d"', $errorCode));
134120
}
121+
122+
return $this->map[$errorCode]($errorCode, $errorMessage);
135123
}
136124
}

src/Exception/LogicException.php

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace FlixTech\SchemaRegistryApi\Exception;
6+
7+
use LogicException as PHPLogicException;
8+
9+
class LogicException extends PHPLogicException implements SchemaRegistryException
10+
{
11+
public const ERROR_CODE = 99997;
12+
13+
public static function errorCode(): int
14+
{
15+
return self::ERROR_CODE;
16+
}
17+
}

src/Exception/RuntimeException.php

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace FlixTech\SchemaRegistryApi\Exception;
6+
7+
use RuntimeException as PHPRuntimeException;
8+
9+
class RuntimeException extends PHPRuntimeException implements SchemaRegistryException
10+
{
11+
public const ERROR_CODE = 99998;
12+
13+
public static function errorCode(): int
14+
{
15+
return self::ERROR_CODE;
16+
}
17+
}

0 commit comments

Comments
 (0)