Skip to content

Commit

Permalink
Merge pull request #14 from FrontEndCoffee/add-wsdl-caching-to-tax-re…
Browse files Browse the repository at this point in the history
…solver

Add optional CacheInterface to cache VAT rates.
  • Loading branch information
arnowest authored Mar 13, 2023
2 parents fa5c42d + aa53a71 commit 38c127c
Show file tree
Hide file tree
Showing 7 changed files with 128 additions and 9 deletions.
15 changes: 14 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,22 @@ $vatService->validateEuropeanVatNumber("YOURVATNUMBERHERE", "COUNTRYCODE"); // t

$vatService->countryInEurope('NL'); // true

$vatService->europeanVatRate("COUNTRYCODE"); // 0.0
$vatService->europeanVatRate("YOURVATNUMBERHERE"); // 0.0
```

### Caching

If you resolve VAT rates for a country quite often, it can be a little slow. If you want to, you can cache the results
by passing a `Psr\SimpleCache\CacheInterface` to the `Vat()` service. The implementation might differ based on your
application, but all major frameworks implement this interface on their cache.

```php
/** @var Psr\SimpleCache\CacheInterface $cache */

$vatService = new \SandwaveIo\Vat\Vat(cache: $cache);
```


## External documentation

* [VIES API](https://ec.europa.eu/taxation_customs/vies/technicalInformation.html)
Expand Down
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@
"require": {
"php": "^8.1",
"ext-soap": "*",
"divineomega/php-countries": "^2.3"
"divineomega/php-countries": "^2.3",
"psr/simple-cache": "^1.0"
},
"require-dev": {
"phpunit/phpunit": "^10.0.15",
Expand Down
7 changes: 6 additions & 1 deletion src/Vat.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace SandwaveIo\Vat;

use DateTimeImmutable;
use Psr\SimpleCache\CacheInterface;
use SandwaveIo\Vat\Countries\Iso2;
use SandwaveIo\Vat\Countries\ResolvesCountries;
use SandwaveIo\Vat\VatNumbers\ValidatesVatNumbers;
Expand All @@ -21,11 +22,15 @@ final class Vat
public function __construct(
?ResolvesCountries $countryResolver = null,
?ResolvesVatRates $vatRateResolver = null,
?ValidatesVatNumbers $vatNumberVerifier = null
?ValidatesVatNumbers $vatNumberVerifier = null,
?CacheInterface $cache = null
) {
$this->countryResolver = $countryResolver ?? new Iso2();
$this->vatRateResolver = $vatRateResolver ?? new TaxesEuropeDatabaseClient();
$this->vatNumberVerifier = $vatNumberVerifier ?? new ViesClient();
if ($cache !== null) {
$this->vatRateResolver->setCache($cache);
}
}

public function validateEuropeanVatNumber(string $vatNumber, string $countryCode): bool
Expand Down
3 changes: 3 additions & 0 deletions src/VatRates/ResolvesVatRates.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,11 @@
namespace SandwaveIo\Vat\VatRates;

use DateTimeImmutable;
use Psr\SimpleCache\CacheInterface;

interface ResolvesVatRates
{
public function setCache(?CacheInterface $cache): void;

public function getDefaultVatRateForCountry(string $countryCode, ?DateTimeImmutable $dateTime = null): ?float;
}
37 changes: 31 additions & 6 deletions src/VatRates/TaxesEuropeDatabaseClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace SandwaveIo\Vat\VatRates;

use DateTimeImmutable;
use Psr\SimpleCache\CacheInterface;
use SandwaveIo\Vat\Exceptions\VatFetchFailedException;
use SoapClient;
use SoapFault;
Expand All @@ -15,11 +16,17 @@ final class TaxesEuropeDatabaseClient implements ResolvesVatRates
const RATE_TYPE_STANDARD = 'STANDARD';
const RATE_VALUE_TYPE_DEFAULT = 'DEFAULT';

private ?SoapClient $client;
private ?CacheInterface $cache;

public function __construct(?SoapClient $client = null)
public function __construct(private ?SoapClient $client = null)
{
$this->client = $client;
$this->cache = null;
}

public function setCache(?CacheInterface $cache): void
{
$this->cache = $cache;
}

/**
Expand Down Expand Up @@ -86,18 +93,36 @@ private function parseRetrieveVatRateResponse(object $response, string $countryC
*/
private function call(string $call, array $params): ?object
{
$cacheKey = $this->generateCacheKey($call, $params);
if ($this->cache !== null && $this->cache->has($cacheKey)) {
return $this->cache->get($cacheKey);
}
try {
$response = $this->getClient()->__soapCall($call, $params);
if (! is_object($response)) {
return null;
}
return $response;
} catch (SoapFault $fault) {
throw new VatFetchFailedException(
'Could not fetch VAT rate from TEDB: ' . $fault->faultstring,
$params
);
}
if (! is_object($response)) {
return null;
}
if ($this->cache !== null) {
$this->cache->set($cacheKey, $response);
}
return $response;
}

/**
* @param string $call
* @param array<string,array<mixed>> $params
*
* @return string
*/
private function generateCacheKey(string $call, array $params): string
{
return 'eu_taxes_response_' . md5(serialize(['call' => $call, 'params' => $params]));
}

private function getClient(): SoapClient
Expand Down
37 changes: 37 additions & 0 deletions tests/VatRates/TedbClientTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
use PHPUnit\Framework\Assert;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
use Psr\SimpleCache\CacheInterface;
use SandwaveIo\Vat\Exceptions\VatFetchFailedException;
use SandwaveIo\Vat\VatRates\TaxesEuropeDatabaseClient;
use SoapClient;
Expand Down Expand Up @@ -48,4 +49,40 @@ public static function soapTestData(): Generator
yield ['NL', (object) [], null];
yield ['NL', (object) ['vatRateResults' => []], null];
}

public function testWithColdCache(): void
{
/** @var MockObject&SoapClient $mockedSoapClient */
$mockedSoapClient = $this->getMockFromWsdl(TaxesEuropeDatabaseClient::WSDL);
$mockedSoapClient
->expects(TestCase::once())
->method('__soapCall')
->with('retrieveVatRates')
->willReturn(unserialize(include 'nl_rates_snapshot.php'));

$cache = $this->createMock(CacheInterface::class);
$cache->expects(TestCase::once())->method('has')->willReturn(false);
$cache->expects(TestCase::never())->method('get');
$cache->expects(TestCase::once())->method('set');

$client = new TaxesEuropeDatabaseClient($mockedSoapClient);
$client->setCache($cache);
$client->getDefaultVatRateForCountry('NL');
}

public function testWithWarmCache(): void
{
/** @var MockObject&SoapClient $mockedSoapClient */
$mockedSoapClient = $this->getMockFromWsdl(TaxesEuropeDatabaseClient::WSDL);
$mockedSoapClient->expects(TestCase::never())->method('__soapCall');

$cache = $this->createMock(CacheInterface::class);
$cache->expects(TestCase::once())->method('has')->willReturn(true);
$cache->expects(TestCase::once())->method('get')->willReturn(unserialize(include 'nl_rates_snapshot.php'));
$cache->expects(TestCase::never())->method('set');

$client = new TaxesEuropeDatabaseClient($mockedSoapClient);
$client->setCache($cache);
$client->getDefaultVatRateForCountry('NL');
}
}
35 changes: 35 additions & 0 deletions tests/VatServiceTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use Generator;
use PHPUnit\Framework\Assert;
use PHPUnit\Framework\TestCase;
use Psr\SimpleCache\CacheInterface;
use SandwaveIo\Vat\Countries\ResolvesCountries;
use SandwaveIo\Vat\Vat;
use SandwaveIo\Vat\VatNumbers\ValidatesVatNumbers;
Expand Down Expand Up @@ -87,4 +88,38 @@ public static function euVatRateTestData(): Generator
yield ['CA', true, false, null, 0.0];
yield ['XX', false, false, null, 0.0];
}

public function testWithoutCache(): void
{
$vat = new Vat();
// Get Vat::vatRateResolver property.
$vatReflection = new \ReflectionClass($vat);
$resolverProperty = $vatReflection->getProperty('vatRateResolver');
$resolverProperty->setAccessible(true);
$resolver = $resolverProperty->getValue($vat);
// Get TaxesEuropeDatabaseClient::cache property.
$resolverReflection = new \ReflectionClass($resolver);
$cacheProperty = $resolverReflection->getProperty('cache');
$cacheProperty->setAccessible(true);
$cache = $cacheProperty->getValue($resolver);

Assert::assertNull($cache);
}

public function testWithCache(): void
{
$vat = new Vat(null, null, null, $this->createStub(CacheInterface::class));
// Get Vat::vatRateResolver property.
$vatReflection = new \ReflectionClass($vat);
$resolverProperty = $vatReflection->getProperty('vatRateResolver');
$resolverProperty->setAccessible(true);
$resolver = $resolverProperty->getValue($vat);
// Get TaxesEuropeDatabaseClient::cache property.
$resolverReflection = new \ReflectionClass($resolver);
$cacheProperty = $resolverReflection->getProperty('cache');
$cacheProperty->setAccessible(true);
$cache = $cacheProperty->getValue($resolver);

Assert::assertInstanceOf(CacheInterface::class, $cache);
}
}

0 comments on commit 38c127c

Please sign in to comment.