Skip to content

Commit

Permalink
Added Symfony Cache support
Browse files Browse the repository at this point in the history
  • Loading branch information
Mickaël Andrieu committed Jan 7, 2019
1 parent 85be647 commit aa15280
Show file tree
Hide file tree
Showing 9 changed files with 410 additions and 12 deletions.
11 changes: 7 additions & 4 deletions composer.json
Expand Up @@ -15,15 +15,18 @@
],
"require": {
"php": ">=5.6",
"guzzlehttp/guzzle": "^6.3",
"symfony/workflow": "^3.4"
"guzzlehttp/guzzle": "^6.3"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "^2.12",
"symfony/cache": "^3.4.0",
"phpunit/phpunit": "~5.7",
"mockery/mockery": "^1.2",
"symfony/lts": "v3",
"symfony/phpunit-bridge": "^3.4.0"
"symfony/lts": "v3"
},
"suggest": {
"symfony/cache": "Allows use of Symfony Cache adapters to store transactions",
"ext-apcu": "Allows use of APCu adapter (performant) to store transactions"
},
"autoload": {
"psr-4": {
Expand Down
16 changes: 15 additions & 1 deletion src/Contracts/Storage.php
Expand Up @@ -21,7 +21,7 @@ interface Storage
public function saveTransaction($service, Transaction $transaction);

/**
* Retrieve the CircuitBreaker transaction.
* Retrieve the CircuitBreaker transaction for a specific service.
*
* @var string the service name
*
Expand All @@ -31,6 +31,13 @@ public function saveTransaction($service, Transaction $transaction);
*/
public function getTransaction($service);

/**
* Retrieve all the CircuitBreaker transactions.
*
* @return TransactionCollection
*/
public function getTransactions();

/**
* Checks if the transaction exists.
*
Expand All @@ -39,4 +46,11 @@ public function getTransaction($service);
* @return bool
*/
public function hasTransaction($service);

/**
* Clear the Circuit Breaker storage.
*
* @return bool
*/
public function clear();
}
35 changes: 35 additions & 0 deletions src/Contracts/TransactionCollection.php
@@ -0,0 +1,35 @@
<?php

namespace PrestaShop\CircuitBreaker\Contracts;

use PrestaShop\CircuitBreaker\Exceptions\TransactionNotFound;

/**
* Helper to manipulate a list of transactions.
*/
interface TransactionCollection extends \Countable
{
/**
* @param string $service the Transaction service URI
*
* @throws TransactionNotFound
*
* @return Transaction
*/
public function findOneByService($service);

/**
* Returns all transactions of the storage.
*
* @return array
*/
public function findAll();

/**
* Returns all transactions of the storage
* using a callable to filter results.
*
* @return array
*/
public function findAllUsing(callable $function);
}
17 changes: 17 additions & 0 deletions src/Storages/SimpleArray.php
Expand Up @@ -4,6 +4,7 @@

use PrestaShop\CircuitBreaker\Contracts\Storage;
use PrestaShop\CircuitBreaker\Contracts\Transaction;
use PrestaShop\CircuitBreaker\Transactions\SimpleCollection;
use PrestaShop\CircuitBreaker\Exceptions\TransactionNotFound;

/**
Expand Down Expand Up @@ -39,6 +40,14 @@ public function getTransaction($service)
throw new TransactionNotFound();
}

/**
* {@inheritdoc}
*/
public function getTransactions()
{
return SimpleCollection::create(self::$transactions);
}

/**
* {@inheritdoc}
*/
Expand All @@ -49,6 +58,14 @@ public function hasTransaction($service)
return array_key_exists($key, self::$transactions);
}

/**
* {@inheritdoc}
*/
public function clear()
{
self::$transactions = [];
}

/**
* Helper method to properly store the transaction
*
Expand Down
101 changes: 101 additions & 0 deletions src/Storages/SymfonyCache.php
@@ -0,0 +1,101 @@
<?php

namespace PrestaShop\CircuitBreaker\Storages;

use PrestaShop\CircuitBreaker\Contracts\Storage;
use PrestaShop\CircuitBreaker\Contracts\Transaction;
use Symfony\Component\Cache\Adapter\AbstractAdapter;
use PrestaShop\CircuitBreaker\Exceptions\TransactionNotFound;
use PrestaShop\CircuitBreaker\Transactions\SimpleCollection;

/**
* Implementation of Storage using the Symfony Cache Component.
*/
final class SymfonyCache implements Storage
{
/**
* @var AbstractAdapter the Symfony Cache adapter
*/
private $symfonyCacheAdapter;

/**
* @var string the Symfony Cache namespace
*/
private $namespace;

public function __construct(AbstractAdapter $symfonyCacheAdapter, $namespace)
{
$this->symfonyCacheAdapter = $symfonyCacheAdapter;
$this->namespace = $namespace;
}

/**
* {@inheritdoc}
*/
public function saveTransaction($service, Transaction $transaction)
{
$key = $this->getKey($service);
$cacheItem = $this->symfonyCacheAdapter->getItem($key);

$cacheItem->set($transaction);

return $this->symfonyCacheAdapter->save($cacheItem);
}

/**
* {@inheritdoc}
*/
public function getTransaction($service)
{
$key = $this->getKey($service);

if ($this->hasTransaction($service)) {
return $this->symfonyCacheAdapter->getItem($key)->get();
}

throw new TransactionNotFound();
}

/**
* {@inheritdoc}
*/
public function getTransactions()
{
$transactions = [];
foreach ($this->symfonyCacheAdapter->getItems([$this->namespace]) as $item) {
$transactions[] = $item->get();
}

return SimpleCollection::create($transactions);
}

/**
* {@inheritdoc}
*/
public function hasTransaction($service)
{
$key = $this->getKey($service);

return $this->symfonyCacheAdapter->hasItem($key);
}

/**
* {@inheritdoc}
*/
public function clear()
{
return $this->symfonyCacheAdapter->clear();
}

/**
* Helper method to properly store the transaction
*
* @param string $service the service URI
*
* @return string the transaction unique identifier
*/
private function getKey($service)
{
return md5($service);
}
}
70 changes: 70 additions & 0 deletions src/Transactions/SimpleCollection.php
@@ -0,0 +1,70 @@
<?php

namespace PrestaShop\CircuitBreaker\Transactions;

use PrestaShop\CircuitBreaker\Contracts\TransactionCollection;
use PrestaShop\CircuitBreaker\Exceptions\TransactionNotFound;
use PrestaShop\CircuitBreaker\Contracts\Transaction;

final class SimpleCollection implements TransactionCollection
{
/**
* @var Transaction[] the list of transactions
*/
private $transactions;

public function __construct(array $transactions = [])
{
$this->transactions = $transactions;
}

/**
* {@inheritdoc}
*/
public function findOneByService($service)
{
foreach ($this->transactions as $transaction) {
if ($transaction->getService() === $service) {
return $transaction;
}
}

throw new TransactionNotFound();
}

/**
* {@inheritdoc}
*/
public function findAll()
{
return $this->transactions;
}

/**
* {@inheritdoc}
*/
public function findAllUsing(callable $function)
{
return array_filter($this->transactions, $function);
}

/**
* {@inheritdoc}
*/
public function count()
{
return count($this->transactions);
}

/**
* Helper static constructor
*
* @param array $transactions
*
* @return self
*/
public static function create(array $transactions)
{
return new self($transactions);
}
}
14 changes: 7 additions & 7 deletions tests/SimpleCircuitBreakerTest.php
Expand Up @@ -20,7 +20,7 @@ public function testCircuitBreakerIsInClosedStateAtStart()
$circuitBreaker = $this->createCircuitBreaker();

$this->assertSame(States::CLOSED_STATE, $circuitBreaker->getState());
$this->assertNull($circuitBreaker->call('https://httpbin.org/get/foo', $this->createFallback()));
$this->assertNull($circuitBreaker->call('https://httpbin.org/get/foo', $this->createFallbackResponse()));
}

/**
Expand All @@ -30,14 +30,14 @@ public function testCircuitBreakerWillBeOpenedInCaseOfFailures()
{
$circuitBreaker = $this->createCircuitBreaker();
// CLOSED
$circuitBreaker->call('https://httpbin.org/get/foo', $this->createFallback());
$circuitBreaker->call('https://httpbin.org/get/foo', $this->createFallbackResponse());

$this->assertSame(States::OPEN_STATE, $circuitBreaker->getState());
$this->assertSame(
'{}',
$circuitBreaker->call(
'https://httpbin.org/get/foo',
$this->createFallback()
$this->createFallbackResponse()
)
);
}
Expand All @@ -50,17 +50,17 @@ public function testAfterTheThresholdTheCircuitBreakerMovesInHalfOpenState()
{
$circuitBreaker = $this->createCircuitBreaker();
// CLOSED
$circuitBreaker->call('https://httpbin.org/get/foo', $this->createFallback());
$circuitBreaker->call('https://httpbin.org/get/foo', $this->createFallbackResponse());
// OPEN
$circuitBreaker->call('https://httpbin.org/get/foo', $this->createFallback());
$circuitBreaker->call('https://httpbin.org/get/foo', $this->createFallbackResponse());

sleep(2);
// NOW HALF OPEN
$this->assertSame(
'{}',
$circuitBreaker->call(
'https://httpbin.org/get/foo',
$this->createFallback()
$this->createFallbackResponse()
)
);
$this->assertSame(States::HALF_OPEN_STATE, $circuitBreaker->getState());
Expand All @@ -81,7 +81,7 @@ private function createCircuitBreaker()
/**
* @return callable the fallback callable
*/
private function createFallback()
private function createFallbackResponse()
{
return function () {
return '{}';
Expand Down

0 comments on commit aa15280

Please sign in to comment.