allow vendord
All checks were successful
Build, Push and Deploy / build-and-push (push) Successful in 3m3s
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 16s
Build, Push and Deploy / deploy-staging (push) Successful in 32s
Build, Push and Deploy / deploy-production (push) Has been skipped
Build, Push and Deploy / cleanup (push) Successful in 1s

This commit is contained in:
2026-03-30 14:54:57 +07:00
parent 66aed7c4e8
commit b5e3a778ce
21316 changed files with 2777892 additions and 13 deletions

View File

@@ -0,0 +1,59 @@
<?php
declare(strict_types=1);
namespace Money\Currencies;
use AppendIterator;
use IteratorIterator;
use Money\Currencies;
use Money\Currency;
use Money\Exception\UnknownCurrencyException;
use Traversable;
/**
* Aggregates several currency repositories.
*/
final class AggregateCurrencies implements Currencies
{
/**
* @param Currencies[] $currencies
*/
public function __construct(private readonly array $currencies)
{
}
public function contains(Currency $currency): bool
{
foreach ($this->currencies as $currencies) {
if ($currencies->contains($currency)) {
return true;
}
}
return false;
}
public function subunitFor(Currency $currency): int
{
foreach ($this->currencies as $currencies) {
if ($currencies->contains($currency)) {
return $currencies->subunitFor($currency);
}
}
throw new UnknownCurrencyException('Cannot find currency ' . $currency->getCode());
}
/** {@inheritDoc} */
public function getIterator(): Traversable
{
$iterator = new AppendIterator();
foreach ($this->currencies as $currencies) {
$iterator->append(new IteratorIterator($currencies->getIterator()));
}
return $iterator;
}
}

View File

@@ -0,0 +1,38 @@
<?php
declare(strict_types=1);
namespace Money\Currencies;
use ArrayIterator;
use Money\Currencies;
use Money\Currency;
use Money\Exception\UnknownCurrencyException;
use Traversable;
final class BitcoinCurrencies implements Currencies
{
public const CODE = 'XBT';
public const SYMBOL = "\xC9\x83";
public function contains(Currency $currency): bool
{
return $currency->getCode() === self::CODE;
}
public function subunitFor(Currency $currency): int
{
if ($currency->getCode() !== self::CODE) {
throw new UnknownCurrencyException($currency->getCode() . ' is not bitcoin and is not supported by this currency repository');
}
return 8;
}
/** {@inheritDoc} */
public function getIterator(): Traversable
{
return new ArrayIterator([new Currency(self::CODE)]);
}
}

View File

@@ -0,0 +1,79 @@
<?php
declare(strict_types=1);
namespace Money\Currencies;
use ArrayIterator;
use Cache\TagInterop\TaggableCacheItemInterface;
use CallbackFilterIterator;
use Money\Currencies;
use Money\Currency;
use Psr\Cache\CacheItemPoolInterface;
use Traversable;
use function iterator_to_array;
/**
* Cache the result of currency checking.
*/
final class CachedCurrencies implements Currencies
{
public function __construct(private readonly Currencies $currencies, private readonly CacheItemPoolInterface $pool)
{
}
public function contains(Currency $currency): bool
{
$item = $this->pool->getItem('currency|availability|' . $currency->getCode());
if ($item->isHit() === false) {
$item->set($this->currencies->contains($currency));
if ($item instanceof TaggableCacheItemInterface) {
$item->setTags(['currency.availability']);
}
$this->pool->save($item);
}
return (bool) $item->get();
}
public function subunitFor(Currency $currency): int
{
$item = $this->pool->getItem('currency|subunit|' . $currency->getCode());
if ($item->isHit() === false) {
$item->set($this->currencies->subunitFor($currency));
if ($item instanceof TaggableCacheItemInterface) {
$item->setTags(['currency.subunit']);
}
$this->pool->save($item);
}
return (int) $item->get();
}
/** {@inheritDoc} */
public function getIterator(): Traversable
{
return new CallbackFilterIterator(
new ArrayIterator(iterator_to_array($this->currencies->getIterator())),
function (Currency $currency): bool {
$item = $this->pool->getItem('currency|availability|' . $currency->getCode());
$item->set(true);
if ($item instanceof TaggableCacheItemInterface) {
$item->setTags(['currency.availability']);
}
$this->pool->save($item);
return true;
}
);
}
}

View File

@@ -0,0 +1,95 @@
<?php
declare(strict_types=1);
namespace Money\Currencies;
use ArrayIterator;
use Money\Currencies;
use Money\Currency;
use Money\Exception\UnknownCurrencyException;
use RuntimeException;
use Traversable;
use function array_keys;
use function array_map;
use function is_file;
/**
* List of supported Crypto Currencies codes and names using Binance as resource.
*/
final class CryptoCurrencies implements Currencies
{
/**
* Map of known currencies indexed by code.
*
* @phpstan-var non-empty-array<non-empty-string, array{
* symbol: non-empty-string,
* minorUnit: non-negative-int
* }>|null
*/
private static array|null $currencies = null;
public function contains(Currency $currency): bool
{
return isset($this->getCurrencies()[$currency->getCode()]);
}
public function subunitFor(Currency $currency): int
{
if (! $this->contains($currency)) {
throw new UnknownCurrencyException('Cannot find ISO currency ' . $currency->getCode());
}
return $this->getCurrencies()[$currency->getCode()]['minorUnit'];
}
/**
* @phpstan-return Traversable<int, Currency>
*/
public function getIterator(): Traversable
{
return new ArrayIterator(
array_map(
static function ($code) {
return new Currency($code);
},
array_keys($this->getCurrencies())
)
);
}
/**
* Returns a map of known currencies indexed by code.
*
* @phpstan-return non-empty-array<non-empty-string, array{
* symbol: non-empty-string,
* minorUnit: non-negative-int
* }>
*/
private function getCurrencies(): array
{
if (self::$currencies === null) {
self::$currencies = $this->loadCurrencies();
}
return self::$currencies;
}
/**
* @phpstan-return non-empty-array<non-empty-string, array{
* symbol: non-empty-string,
* minorUnit: non-negative-int
* }>
*/
private function loadCurrencies(): array
{
$file = __DIR__ . '/../../resources/binance.php';
if (is_file($file)) {
return require $file;
}
throw new RuntimeException('Failed to load currency ISO codes.');
}
}

View File

@@ -0,0 +1,60 @@
<?php
declare(strict_types=1);
namespace Money\Currencies;
use ArrayIterator;
use Money\Currencies;
use Money\Currency;
use Money\Exception\UnknownCurrencyException;
use Traversable;
use function array_keys;
use function array_map;
/**
* A list of custom currencies.
*/
final class CurrencyList implements Currencies
{
/**
* Map of currencies and their sub-units indexed by code.
*
* @phpstan-var array<non-empty-string, int>
*/
private array $currencies;
/** @phpstan-param array<non-empty-string, non-negative-int> $currencies */
public function __construct(array $currencies)
{
$this->currencies = $currencies;
}
public function contains(Currency $currency): bool
{
return isset($this->currencies[$currency->getCode()]);
}
public function subunitFor(Currency $currency): int
{
if (! $this->contains($currency)) {
throw new UnknownCurrencyException('Cannot find currency ' . $currency->getCode());
}
return $this->currencies[$currency->getCode()];
}
/** {@inheritDoc} */
public function getIterator(): Traversable
{
return new ArrayIterator(
array_map(
static function ($code) {
return new Currency($code);
},
array_keys($this->currencies)
)
);
}
}

View File

@@ -0,0 +1,115 @@
<?php
declare(strict_types=1);
namespace Money\Currencies;
use ArrayIterator;
use Money\Currencies;
use Money\Currency;
use Money\Exception\UnknownCurrencyException;
use RuntimeException;
use Traversable;
use function array_keys;
use function array_map;
use function is_file;
/**
* List of supported ISO 4217 currency codes and names.
*/
final class ISOCurrencies implements Currencies
{
/**
* Map of known currencies indexed by code.
*
* @phpstan-var non-empty-array<non-empty-string, array{
* alphabeticCode: non-empty-string,
* currency: non-empty-string,
* minorUnit: non-negative-int,
* numericCode: positive-int
* }>|null
*/
private static array|null $currencies = null;
public function contains(Currency $currency): bool
{
return isset($this->getCurrencies()[$currency->getCode()]);
}
public function subunitFor(Currency $currency): int
{
if (! $this->contains($currency)) {
throw new UnknownCurrencyException('Cannot find ISO currency ' . $currency->getCode());
}
return $this->getCurrencies()[$currency->getCode()]['minorUnit'];
}
/**
* Returns the numeric code for a currency.
*
* @throws UnknownCurrencyException If currency is not available in the current context.
*/
public function numericCodeFor(Currency $currency): int
{
if (! $this->contains($currency)) {
throw new UnknownCurrencyException('Cannot find ISO currency ' . $currency->getCode());
}
return $this->getCurrencies()[$currency->getCode()]['numericCode'];
}
/**
* @phpstan-return Traversable<int, Currency>
*/
public function getIterator(): Traversable
{
return new ArrayIterator(
array_map(
static function ($code) {
return new Currency($code);
},
array_keys($this->getCurrencies())
)
);
}
/**
* Returns a map of known currencies indexed by code.
*
* @phpstan-return non-empty-array<non-empty-string, array{
* alphabeticCode: non-empty-string,
* currency: non-empty-string,
* minorUnit: non-negative-int,
* numericCode: positive-int
* }>
*/
private function getCurrencies(): array
{
if (self::$currencies === null) {
self::$currencies = $this->loadCurrencies();
}
return self::$currencies;
}
/**
* @phpstan-return non-empty-array<non-empty-string, array{
* alphabeticCode: non-empty-string,
* currency: non-empty-string,
* minorUnit: non-negative-int,
* numericCode: positive-int
* }>
*/
private function loadCurrencies(): array
{
$file = __DIR__ . '/../../resources/currency.php';
if (is_file($file)) {
return require $file;
}
throw new RuntimeException('Failed to load currency ISO codes.');
}
}