Skip to content

Commit

Permalink
feat!: Bump circuit breaker (#10)
Browse files Browse the repository at this point in the history
  • Loading branch information
ksaveras authored Jun 24, 2023
1 parent 255d7ea commit 5560f59
Show file tree
Hide file tree
Showing 21 changed files with 568 additions and 619 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/continuous-integration.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ jobs:
- dependencies: lowest
stability: stable
php-version: 8.1
symfony: 5.4.*
symfony: 6.3.*

steps:
- name: Checkout
Expand Down
15 changes: 6 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,18 +51,15 @@ return [
ksaveras_circuit_breaker:
circuit_breakers:
cb_name:
storage: 'apcu'
storage: 'cache'
failure_threshold: 3
retry_policy:
type: 'exponential'
options:
retry_policy:
exponential:
reset_timeout: 60
maximum_timeout: 86400

storage:
apcu: ~
in_memory: ~
redis: 'client_service_id'
psr_cache: 'pool_service_id'
predis: 'predis_service_id'
my_storage: 'storage_service'
cache: 'pool_service_id'
my_storage: '@storage_service'
```
6 changes: 4 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,14 @@
],
"require": {
"php": "^8.1",
"ksaveras/circuit-breaker": "^1.0",
"ksaveras/circuit-breaker": "dev-release-v2.0.0",
"symfony/framework-bundle": "^5.0|^6.0|^7.0"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "^3.0",
"phpstan/phpstan": "^1.6",
"phpstan/phpstan-phpunit": "^1.0",
"phpstan/phpstan-strict-rules": "^1.5",
"phpunit/phpunit": "^10.2",
"rector/rector": "^0.17.0"
},
Expand All @@ -39,7 +40,8 @@
"rector": "./vendor/bin/rector process --dry-run",
"rector:fix": "./vendor/bin/rector process",
"test": "./vendor/bin/phpunit",
"test:coverage": "@php -dapc.enable_cli=1 ./vendor/bin/phpunit --coverage-clover=coverage/clover.xml"
"test:coverage": "./vendor/bin/phpunit --coverage-clover=coverage/clover.xml",
"static-analysis": ["@phpcs", "@phpstan", "@rector"]
},
"config": {
"sort-packages": true
Expand Down
7 changes: 6 additions & 1 deletion phpstan.neon
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,15 @@ parameters:
count: 1
path: src/DependencyInjection/Configuration.php
-
message: "#^Cannot call method integerNode\\(\\) on Symfony\\\\Component\\\\Config\\\\Definition\\\\Builder\\\\NodeParentInterface\\|null\\.$#"
message: "#^Call to an undefined method Symfony\\\\Component\\\\Config\\\\Definition\\\\Builder\\\\NodeDefinition\\:\\:addDefaultsIfNotSet\\(\\)\\.$#"
count: 3
path: src/DependencyInjection/Configuration.php
-
message: "#^Call to an undefined method Symfony\\\\Component\\\\Config\\\\Definition\\\\Builder\\\\NodeDefinition\\:\\:useAttributeAsKey\\(\\)\\.$#"
count: 1
path: src/DependencyInjection/Configuration.php

includes:
- vendor/phpstan/phpstan-phpunit/extension.neon
- vendor/phpstan/phpstan-phpunit/rules.neon
- vendor/phpstan/phpstan-strict-rules/rules.neon
14 changes: 11 additions & 3 deletions rector.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@
use Rector\CodingStyle\Rector\ClassMethod\NewlineBeforeNewAssignSetRector;
use Rector\CodingStyle\Rector\Stmt\NewlineAfterStatementRector;
use Rector\Config\RectorConfig;
use Rector\PHPUnit\Rector\Class_\AddSeeTestAnnotationRector;
use Rector\PHPUnit\Rector\Class_\PreferPHPUnitSelfCallRector;
use Rector\PHPUnit\Rector\Class_\PreferPHPUnitThisCallRector;
use Rector\PHPUnit\Set\PHPUnitLevelSetList;
use Rector\PHPUnit\Set\PHPUnitSetList;
use Rector\Set\ValueObject\LevelSetList;
Expand All @@ -20,9 +23,8 @@

return static function (RectorConfig $rectorConfig): void {
$rectorConfig->paths([
__DIR__.'/DependencyInjection',
__DIR__.'/Resources',
__DIR__.'/Tests',
__DIR__.'/src',
__DIR__.'/tests',
]);

// define sets of rules
Expand All @@ -40,9 +42,15 @@
PHPUnitSetList::PHPUNIT_CODE_QUALITY,
]);

$rectorConfig->rules([
PreferPHPUnitSelfCallRector::class,
]);

$rectorConfig->skip([
AddReturnTypeDeclarationFromYieldsRector::class,
AddSeeTestAnnotationRector::class,
FlipTypeControlToUseExclusiveTypeRector::class,
PreferPHPUnitThisCallRector::class,
NewlineAfterStatementRector::class,
NewlineBeforeNewAssignSetRector::class,
VarConstantCommentRector::class,
Expand Down
167 changes: 132 additions & 35 deletions src/DependencyInjection/Configuration.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
namespace Ksaveras\CircuitBreakerBundle\DependencyInjection;

use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition;
use Symfony\Component\Config\Definition\Builder\NodeDefinition;
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
use Symfony\Component\Config\Definition\ConfigurationInterface;

Expand All @@ -30,11 +31,12 @@ public function getConfigTreeBuilder()
private function addStorageSection(ArrayNodeDefinition $rootNode): void
{
$rootNode
->fixXmlConfig('storage')
->children()
->arrayNode('storage')
->arrayNode('storages')
->useAttributeAsKey('name')
->beforeNormalization()
->always(static function ($config) {
->always(static function ($config): array {
if (!\is_array($config)) {
return [];
}
Expand All @@ -50,9 +52,8 @@ private function addStorageSection(ArrayNodeDefinition $rootNode): void
$config[$name] = ['type' => 'service', 'id' => substr($v, 1)];
} else {
$config[$name] = match ($name) {
'apcu', 'in_memory' => ['type' => $v],
'redis', 'predis' => ['type' => $name, 'client' => $v],
'psr_cache' => ['type' => $name, 'pool' => $v],
'in_memory' => ['type' => $v],
'cache' => ['type' => $name, 'pool' => $v],
default => ['type' => 'service', 'id' => $v],
};
}
Expand All @@ -68,24 +69,15 @@ private function addStorageSection(ArrayNodeDefinition $rootNode): void
->thenInvalid('You must specify service "id" for storage type "service".')
->end()
->validate()
->ifTrue(static fn($v): bool => 'redis' === $v['type'] && !isset($v['client']))
->thenInvalid('You must specify "client" for storage type "redis".')
->end()
->validate()
->ifTrue(static fn($v): bool => 'predis' === $v['type'] && !isset($v['client']))
->thenInvalid('You must specify "client" for storage type "predis".')
->end()
->validate()
->ifTrue(static fn($v): bool => 'psr_cache' === $v['type'] && !isset($v['pool']))
->thenInvalid('You must specify "pool" for storage type "psr_cache".')
->ifTrue(static fn($v): bool => 'cache' === $v['type'] && !isset($v['pool']))
->thenInvalid('You must specify "pool" for storage type "cache".')
->end()
->children()
->enumNode('type')
->values(['service', 'apcu', 'in_memory', 'redis', 'psr_cache', 'predis'])
->values(['service', 'in_memory', 'cache'])
->isRequired()
->end()
->scalarNode('id')->end()
->scalarNode('client')->end()
->scalarNode('pool')->end()
->end()
->end()
Expand All @@ -100,6 +92,17 @@ private function addCircuitBreakerSection(ArrayNodeDefinition $rootNode): void
->fixXmlConfig('circuit_breaker')
->children()
->arrayNode('circuit_breakers')
->beforeNormalization()
->always(static function (array $values): array {
foreach ($values as $name => $config) {
if (!isset($config['retry_policy'])) {
$values[$name]['retry_policy']['exponential'] = ['enabled' => true];
}
}

return $values;
})
->end()
->useAttributeAsKey('name')
->arrayPrototype()
->children()
Expand All @@ -114,31 +117,125 @@ private function addCircuitBreakerSection(ArrayNodeDefinition $rootNode): void
->min(1)
->end()
->arrayNode('retry_policy')
->beforeNormalization()
->always(static function (array $values): array {
foreach ($values as $type => $value) {
if (!is_array($value)) {
continue;
}
if (isset($value['enabled'])) {
continue;
}
$values[$type]['enabled'] = true;
}

return $values;
})
->end()
->addDefaultsIfNotSet()
->children()
->enumNode('type')
->values(['exponential', 'constant', 'linear'])
->defaultValue('exponential')
->cannotBeEmpty()
->end()
->arrayNode('options')
->addDefaultsIfNotSet()
->children()
->integerNode('reset_timeout')
->defaultValue(60)
->min(1)
->end()
->integerNode('maximum_timeout')
->defaultValue(86400)
->min(1)
->end()
->end()
->end()
->append($this->addExponentialRetryPolicyNode())
->append($this->addLinearRetryPolicyNode())
->append($this->addConstantRetryPolicyNode())
->end()
->validate()
->ifTrue(static fn($v): bool => 1 !== count(array_filter(
array_values($v),
static fn ($i) => $i['enabled'] ?? false
)))
->thenInvalid('Only one retry policy can be configured per circuit breaker.')
->end()
->end()
->arrayNode('header_policy')
->info('List of header policies')
->defaultValue(['retry_after', 'rate_limit'])
->treatNullLike([])
->treatFalseLike([])
->treatTrueLike(['retry_after', 'rate_limit'])
->enumPrototype()
->values(['retry_after', 'rate_limit'])
->end()
->end()
->end()
->end()
->end()
;
}

private function addExponentialRetryPolicyNode(): NodeDefinition
{
$treeBuilder = new TreeBuilder('exponential');

return $treeBuilder->getRootNode()
->treatFalseLike(['enabled' => false])
->treatTrueLike(['enabled' => true])
->treatNullLike(['enabled' => true])
->addDefaultsIfNotSet()
->children()
->booleanNode('enabled')->defaultFalse()->end()
->integerNode('reset_timeout')
->info('Number of seconds the circuit breaker will be in open state')
->defaultValue(10)
->min(1)
->end()
->floatNode('base')
->info('Base value for exponential function')
->defaultValue(2.0)
->min(1.01)
->end()
->integerNode('maximum_timeout')
->info('Maximum number of seconds for open circuit')
->defaultValue(86400)
->min(10)
->end()
->end();
}

private function addLinearRetryPolicyNode(): NodeDefinition
{
$treeBuilder = new TreeBuilder('linear');

return $treeBuilder->getRootNode()
->treatFalseLike(['enabled' => false])
->treatTrueLike(['enabled' => true])
->treatNullLike(['enabled' => true])
->addDefaultsIfNotSet()
->children()
->booleanNode('enabled')->defaultFalse()->end()
->integerNode('reset_timeout')
->info('Number of seconds the circuit breaker will be in open state')
->defaultValue(10)
->min(1)
->end()
->integerNode('step')
->info('Step size in seconds for increasing the open circuit TTL')
->defaultValue(60)
->min(1)
->end()
->integerNode('maximum_timeout')
->info('Maximum number of seconds for open circuit')
->defaultValue(86400)
->min(10)
->end()
->end();
}

private function addConstantRetryPolicyNode(): NodeDefinition
{
$treeBuilder = new TreeBuilder('constant');

return $treeBuilder->getRootNode()
->treatFalseLike(['enabled' => false])
->treatTrueLike(['enabled' => true])
->treatNullLike(['enabled' => true])
->addDefaultsIfNotSet()
->children()
->booleanNode('enabled')->defaultFalse()->end()
->integerNode('reset_timeout')
->info('Number of seconds the circuit breaker will be in open state')
->defaultValue(10)
->min(1)
->end()
->end();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,21 +9,23 @@
*/
namespace Ksaveras\CircuitBreakerBundle\DependencyInjection\Factory\Storage;

use Symfony\Component\DependencyInjection\ChildDefinition;
use Ksaveras\CircuitBreaker\Storage\CacheStorage;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;

final class ApcuStorageFactory extends AbstractStorageFactory
final class CacheStorageFactory extends AbstractStorageFactory
{
public function create(ContainerBuilder $container, string $name, array $config = []): string
{
$id = $this->serviceId($name);
$container->setDefinition($id, new ChildDefinition('ksaveras_circuit_breaker.storage.apcu.abstract'));
$definition = $container->register($id, CacheStorage::class);
$definition->setArguments([new Reference($config['pool'])]);

return $id;
}

public function getType(): string
{
return 'apcu';
return 'cache';
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,15 @@
*/
namespace Ksaveras\CircuitBreakerBundle\DependencyInjection\Factory\Storage;

use Symfony\Component\DependencyInjection\ChildDefinition;
use Ksaveras\CircuitBreaker\Storage\InMemoryStorage;
use Symfony\Component\DependencyInjection\ContainerBuilder;

final class InMemoryStorageFactory extends AbstractStorageFactory
{
public function create(ContainerBuilder $container, string $name, array $config = []): string
{
$id = $this->serviceId($name);
$container->setDefinition($id, new ChildDefinition('ksaveras_circuit_breaker.storage.in_memory.abstract'));
$container->register($id, InMemoryStorage::class);

return $id;
}
Expand Down
Loading

0 comments on commit 5560f59

Please sign in to comment.