refactor(support): flatten Integrations umbrella and rename Formatters to Formatting

Two plural outliers were the only directories in Services/ and Support/ that didn't follow the singular naming convention we normalized in commit 947d00a9 (Documents -> Document).

**Integrations/ExchangeRate/ -> ExchangeRate/.** The Integrations/ umbrella was both plural AND introduced an extra layer of nesting no other Support subdir had (Pdf/, Hashids/, Module/, Update/ are all one level deep). Dropping the umbrella fixes both problems in one move and matches the existing shape. When AI providers eventually land, they follow the same pattern as Support/Pdf/: a sibling subdir at Support/Ai/, not buried under an umbrella.

**Formatters/ -> Formatting/.** Formatter (singular noun) would have been awkward; Formatting (gerund describing the capability) reads naturally as a namespace segment. The subdir holds DateFormatter, TimeFormatter, TimeZones — classes whose common thread is 'things that do formatting', which the gerund captures better than either the singular or plural noun form.

10 files renamed, 4 consumers updated (DriverRegistryProvider, ExchangeRateProviderService, FormatsController, ExchangeRateDriverFactoryTest). 350 tests pass, Pint clean.
This commit is contained in:
Darko Gjorgjijoski
2026-04-11 18:00:00 +02:00
parent f657b53215
commit 47907f9bf3
14 changed files with 22 additions and 22 deletions

View File

@@ -0,0 +1,79 @@
<?php
namespace App\Support\ExchangeRate;
use InvalidArgumentException;
use InvoiceShelf\Modules\Registry;
class ExchangeRateDriverFactory
{
/**
* Built-in driver fallback map.
*
* Kept as a backstop so that direct calls to register() (without going through
* the module Registry) continue to work. Built-in drivers are also registered
* via the Registry by DriverRegistryProvider — that registration is the
* canonical source for driver metadata (label, website, config_fields).
*
* @var array<string, class-string<ExchangeRateDriver>>
*/
protected static array $drivers = [
'currency_freak' => CurrencyFreakDriver::class,
'currency_layer' => CurrencyLayerDriver::class,
'open_exchange_rate' => OpenExchangeRateDriver::class,
'currency_converter' => CurrencyConverterDriver::class,
];
/**
* Register a custom exchange rate driver directly with the factory.
*
* Modules should prefer Registry::registerExchangeRateDriver() instead, which
* carries metadata (label, website, config_fields) the frontend needs to render
* a configuration form for the driver.
*/
public static function register(string $name, string $driverClass): void
{
static::$drivers[$name] = $driverClass;
}
public static function make(string $driver, string $apiKey, array $config = []): ExchangeRateDriver
{
$class = static::resolveDriverClass($driver);
if (! $class) {
throw new InvalidArgumentException("Unknown exchange rate driver: {$driver}");
}
return new $class($apiKey, $config);
}
/**
* Get all known driver names — both built-in/factory-registered and Registry-registered.
*
* @return array<int, string>
*/
public static function availableDrivers(): array
{
$local = array_keys(static::$drivers);
$registry = array_keys(Registry::allDrivers('exchange_rate'));
return array_values(array_unique(array_merge($local, $registry)));
}
/**
* Resolve a driver name to its concrete class.
*
* Checks the local $drivers map first (built-ins and factory::register() calls),
* then falls back to the module Registry.
*/
protected static function resolveDriverClass(string $driver): ?string
{
if (isset(static::$drivers[$driver])) {
return static::$drivers[$driver];
}
$meta = Registry::driverMeta('exchange_rate', $driver);
return $meta['class'] ?? null;
}
}