Cette version conserve volontairement :
WorldDatasetsServicecomme service principalWorldDatasetsFactorycomme factory principalecountries()comme point d’entrée de collectionCountriesCollectioncomme nom officiel de la collection de pays- la variable d’exemple
$worldDatasetsdans toute la documentation
Autrement dit :
- on ne migre pas
CountriesCollectionversDatasetsCollection - on garde
countries()→CountriesCollection
Cette archive ne contient plus d’alias de transition.
L’API publique officielle repose uniquement sur :
Iriven\WorldDatasets\Application\WorldDatasetsServiceIriven\WorldDatasets\Application\Factory\WorldDatasetsFactoryIriven\WorldDatasets\Application\Config\WorldDatasetsRuntimeConfigIriven\WorldDatasets\Domain\CountriesCollectionIriven\WorldDatasets\Domain\CurrencyCollectionIriven\WorldDatasets\Domain\RegionCollectionIriven\WorldDatasets\Application\Query\WorldDatasetsQueryIriven\WorldDatasets\Application\Stats\WorldDatasetsStats
Le package est maintenant aligné de bout en bout :
- package Composer :
irivengroup/world-datasets - namespace principal :
Iriven\WorldDatasets\ - service principal :
WorldDatasetsService - factory principale :
WorldDatasetsFactory - collection principale :
CountriesCollection - query builder :
WorldDatasetsQuery
composer require irivengroup/world-datasetsNamespace principal :
use Iriven\WorldDatasets\Application\WorldDatasetsService;
use Iriven\WorldDatasets\Application\Factory\WorldDatasetsFactory;Bibliothèque PHP orientée entreprise pour consulter, filtrer, exporter et valider des données pays avec source principale SQLite, sources dérivées JSON/CSV, collections fluentes, value objects, pipeline de build et intégrations Symfony/Laravel.
- Présentation
- Architecture
- Sources de données
- Installation
- Démarrage rapide
- Inventaire complet des méthodes publiques
- Collections et query builder
- Exports
- Validation, checksums et pipeline data
- CLI et health check
- Intégration Symfony
- Intégration Laravel
- Tests, CI et qualité
- Conventions de nommage
- Fichiers du projet
Le projet expose une API moderne autour de quatre idées :
- un service principal de consultation
- des
CountryInfovalue objects - des collections immutables et chaînables
- plusieurs formats de stockage, avec SQLite comme défaut
Le nom principal de la classe de service est désormais :
Iriven\WorldDatasetsL’alias de compatibilité historique WorldDatasets est conservé, mais déprécié.
WorldDatasets: service centralCountries: alias concret prêt à l’emploiWorldDatasetsFactory: point d’entrée canoniqueCountryInfo: représentation d’un paysCurrencyInfo,RegionInfo,SubRegionInfo,PhoneInfo: value objectsCountriesCollection,CurrencyCollection,RegionCollectionWorldDatasetsQueryDatasetValidatorJsonCountryRepository,CsvCountryRepository,SqliteCountryRepository,ArrayCountryRepository
Les fichiers présents dans src/data sont la seule source de vérité du projet.
| Type | Fichier | Rôle |
|---|---|---|
| SQLite | src/data/.countriesRepository.sqlite |
source principale par défaut |
| JSON | src/data/.countriesRepository.json |
interopérabilité, debug |
| CSV | src/data/.countriesRepository.csv |
export tableur |
| Metadata | src/data/.countriesRepository.meta.json |
version de dataset, date de build, checksums |
| SHA256 | src/data/.countriesRepository.sha256 |
empreintes de contrôle |
use Iriven\WorldDatasets\Application\Factory\WorldDatasetsFactory;
require_once __DIR__ . '/vendor/autoload.php';
$worldDatasets = WorldDatasetsFactory::make();Cela charge :
src/data/.countriesRepository.sqlite
WorldDatasetsFactory::make(Iriven\WorldDatasets\Application\Factory\WorldDatasetsFactory::defaultSqlitePath());
WorldDatasetsFactory::make(Iriven\WorldDatasets\Application\Factory\WorldDatasetsFactory::defaultJsonPath());
WorldDatasetsFactory::make(Iriven\WorldDatasets\Application\Factory\WorldDatasetsFactory::defaultCsvPath());use Iriven\WorldDatasets\Application\Config\WorldDatasetsRuntimeConfig;
use Iriven\WorldDatasets\Application\Factory\WorldDatasetsFactory;
$config = new WorldDatasetsRuntimeConfig(
sourcePath: Iriven\WorldDatasets\Application\Factory\WorldDatasetsFactory::defaultSqlitePath(),
verifyChecksum: true,
strictValidation: true,
);
$worldDatasets = WorldDatasetsFactory::fromConfig($config);$worldDatasets = WorldDatasetsFactory::makeWithValidation();composer install
composer run build-data
composer run check-data
composer run doctor
composer run analyse
composer testuse Iriven\WorldDatasets\Application\Factory\WorldDatasetsFactory;
$worldDatasets = WorldDatasetsFactory::make();
echo $worldDatasets->country('FR')->name();
echo $worldDatasets->country('250')->tld();
echo $worldDatasets->country('FRA')->currency()->code();
print_r($worldDatasets->country('FRA')->data());
print_r($worldDatasets->currencies()->list());
print_r($worldDatasets->countries()->alpha3()->list());| Méthode | Retour | Description |
|---|---|---|
make(?string $sourcePath = null) |
Countries |
Construit le service principal |
fromConfig(WorldDatasetsRuntimeConfig $config, ?SimpleCacheInterface $cache = null) |
Countries |
Construit depuis une config runtime |
makeWithValidation(?string $sourcePath = null) |
Countries |
Construit avec checksum + validation stricte |
makeRepository(string $path, ?SimpleCacheInterface $cache = null) |
CountryRepositoryInterface |
Résout le repository selon la source |
defaultSqlitePath() |
string |
Chemin SQLite par défaut |
defaultJsonPath() |
string |
Chemin JSON par défaut |
defaultCsvPath() |
string |
Chemin CSV par défaut |
datasetMetaPath() |
string |
Fichier metadata dataset |
datasetShaPath() |
string |
Fichier sha256 dataset |
datasetVersion() |
string |
Version du dataset |
builtAt() |
?string |
Date de build du dataset |
checksumFor(string $path) |
?string |
SHA256 d’une source |
assertChecksum(string $path) |
void |
Vérifie l’intégrité d’une source |
| Méthode | Retour | Description |
|---|---|---|
all() |
array |
Tous les pays au format associatif |
| `iterator(int | string | CountryCodeFormat $format = CountryCodeFormat::ALPHA2)` |
count() |
int |
Nombre total de pays |
getIterator() |
Traversable |
Support natif foreach |
country(string $code) |
CountryInfo |
Résolution stricte d’un pays |
findCountry(string $code) |
?CountryInfo |
Résolution tolérante |
| `countries(int | string | CountryCodeFormat $format = CountryCodeFormat::ALPHA2)` |
currencies() |
CurrencyCollection |
Collection de devises |
regions() |
RegionCollection |
Collection de régions |
meta() |
MetaInfo |
Métadonnées package/dataset |
query() |
WorldDatasetsQuery |
Query builder fluide |
findByName(string $name) |
array<CountryInfo> |
Recherche exacte par nom |
searchCountries(string $term) |
array<CountryInfo> |
Recherche partielle |
findByCurrencyCode(string $currencyCode) |
array<CountryInfo> |
Filtre par devise |
findByRegion(string $region) |
array<CountryInfo> |
Filtre par région |
findByPhoneCode(string $phoneCode) |
array<CountryInfo> |
Filtre par indicatif |
findByTld(string $tld) |
array<CountryInfo> |
Filtre par TLD |
| Méthode | Retour |
|---|---|
alpha2() |
string |
alpha3() |
string |
numeric() |
string |
name() |
string |
capital() |
string |
tld() |
string |
language() |
string |
languages() |
string |
postalCodePattern() |
string |
currency() |
CurrencyInfo |
region() |
RegionInfo |
phone() |
PhoneInfo |
isInRegion(string $region) |
bool |
hasCurrency(string $code) |
bool |
exists() |
bool |
data() |
array |
toArray() |
array |
toIndexedArray() |
array |
all() |
array |
jsonSerialize() |
array |
__toString() |
string |
| Méthode | Retour | Description |
|---|---|---|
alpha2() |
CountriesCollection |
Format alpha2 |
alpha3() |
CountriesCollection |
Format alpha3 |
numeric() |
CountriesCollection |
Format numeric |
inRegion(string $name) |
CountriesCollection |
Filtre région |
inSubRegion(string $name) |
CountriesCollection |
Filtre sous-région |
withCurrency(string $code) |
CountriesCollection |
Filtre devise |
withPhoneCode(string $code) |
CountriesCollection |
Filtre indicatif |
withTld(string $tld) |
CountriesCollection |
Filtre TLD |
named(string $name) |
CountriesCollection |
Filtre exact nom |
matching(string $term) |
CountriesCollection |
Recherche partielle |
sortByName() |
CountriesCollection |
Tri par nom |
sortByCode() |
CountriesCollection |
Tri par code courant |
sortByNumeric() |
CountriesCollection |
Tri par numeric |
paginate(int $offset, int $limit) |
CountriesCollection |
Pagination |
first() |
?CountryInfo |
Premier pays |
last() |
?CountryInfo |
Dernier pays |
values() |
array<CountryInfo> |
Liste d’objets |
names() |
array<string,string> |
Alias de list |
codes() |
array<int,string> |
Liste des codes |
count() |
int |
Taille collection |
isEmpty() |
bool |
Collection vide |
isNotEmpty() |
bool |
Collection non vide |
contains(string $code) |
bool |
Contient un pays par code |
| `containsCountry(callable | CountryInfo | string $value)` |
chunk(int $size) |
array<CountriesCollection> |
Découpage par paquets |
stats() |
WorldDatasetsStats |
Statistiques |
groupByRegion() |
array |
Groupement région |
groupByCurrency() |
array |
Groupement devise |
pluckNames() |
array |
Liste simple des noms |
pluckCodes() |
array |
Liste simple des codes |
map(callable $callback) |
array |
Transformation fonctionnelle |
filter(callable $callback) |
CountriesCollection |
Filtrage fonctionnel |
reduce(callable $callback, mixed $initial = null) |
mixed |
Réduction fonctionnelle |
list() |
array<string,string> |
Code => nom |
exportArray() |
array |
Export tabulaire |
| `toJson(int $flags = JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE)` | string |
toCsv() |
string |
Export CSV |
exportJsonFile(string $path) |
void |
Écrit un fichier JSON |
exportCsvFile(string $path) |
void |
Écrit un fichier CSV |
toStorageArray() |
array |
Export technique |
toApiArray() |
array |
Export API |
toArray() |
array |
Alias export |
jsonSerialize() |
array |
JSON serializable |
| Méthode | Retour |
|---|---|
values() |
array<CurrencyInfo> |
list() |
array<string,string> |
countries() |
array<string,array<string,string>> |
exportArray() |
array |
| `toJson(int $flags = JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE)` |
toCsv() |
string |
exportJsonFile(string $path) |
void |
exportCsvFile(string $path) |
void |
toArray() |
array |
jsonSerialize() |
array |
| Méthode | Retour |
|---|---|
values() |
array<RegionInfo> |
list() |
array<string,string> |
countries() |
array<string,array<string,string>> |
exportArray() |
array |
| `toJson(int $flags = JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE)` |
toCsv() |
string |
exportJsonFile(string $path) |
void |
exportCsvFile(string $path) |
void |
toArray() |
array |
jsonSerialize() |
array |
| Méthode | Retour |
|---|---|
inRegion(string $name) |
WorldDatasetsQuery |
inSubRegion(string $name) |
WorldDatasetsQuery |
withCurrency(string $code) |
WorldDatasetsQuery |
withPhoneCode(string $code) |
WorldDatasetsQuery |
withTld(string $tld) |
WorldDatasetsQuery |
matching(string $term) |
WorldDatasetsQuery |
sortByName() |
WorldDatasetsQuery |
sortByCode() |
WorldDatasetsQuery |
sortByNumeric() |
WorldDatasetsQuery |
limit(int $limit) |
WorldDatasetsQuery |
offset(int $offset, int $limit = PHP_INT_MAX) |
WorldDatasetsQuery |
get() |
array<CountryInfo> |
list() |
array<string,string> |
code(): stringname(): stringtoArray(): arrayjsonSerialize(): array__toString(): string
alphaCode(): stringnumericCode(): stringname(): stringsubRegion(): SubRegionInfotoArray(): arrayjsonSerialize(): array__toString(): string
code(): stringCode(): stringname(): stringName(): stringtoArray(): arrayjsonSerialize(): array__toString(): string
code(): stringinternationalPrefix(): stringnationalPrefix(): stringsubscriberPattern(): stringpattern(): stringtoArray(): arrayjsonSerialize(): array__toString(): string
count(): intsource(): stringversion(): stringlastUpdatedAt(): ?stringpackageVersion(): stringdatasetVersion(): stringchecksum(): ?stringbuiltAt(): ?stringtoArray(): arrayjsonSerialize(): array
total(): intregions(): intcurrencies(): inttoArray(): arrayjsonSerialize(): array
validate(array $worldDatasets, bool $strict = true): DatasetValidationReport
duplicates(): arrayinvalidCodes(): arraywarnings(): arraystrict(): boolisValid(): booltoArray(): arrayjsonSerialize(): array
sourcePath(): ?stringverifyChecksum(): boolstrictValidation(): boolusePsr16Cache(): bool
normalize(string $code): stringnormalizeAlpha(string $code): stringnormalizeNumeric(string $code): stringnormalizePreservingNumeric(string $code): stringnormalizeTld(string $tld): string
normalize(string $code): string
normalize(string $tld): string
$worldDatasets->countries()->alpha2()->list();
$worldDatasets->countries()->alpha3()->list();
$worldDatasets->countries()->numeric()->list();
$worldDatasets->countries()
->inRegion('Europe')
->withCurrency('EUR')
->sortByName()
->paginate(0, 25)
->values();$result = $worldDatasets->query()
->inRegion('Europe')
->withCurrency('EUR')
->sortByName()
->limit(20)
->get();$names = $worldDatasets->countries()->map(fn (Iriven\WorldDatasets\Domain\CountryInfo $country) => $country->name());
$eurCountries = $worldDatasets->countries()->filter(
fn (Iriven\WorldDatasets\Domain\CountryInfo $country) => $country->hasCurrency('EUR')
);
$total = $worldDatasets->countries()->reduce(
fn (int $carry, Iriven\WorldDatasets\Domain\CountryInfo $country) => $carry + 1,
0
);$json = $worldDatasets->countries()->toJson();
$csv = $worldDatasets->countries()->toCsv();
$worldDatasets->countries()->exportJsonFile('/tmp/countries.json');
$worldDatasets->countries()->exportCsvFile('/tmp/countries.csv');
$worldDatasets->currencies()->exportJsonFile('/tmp/currencies.json');
$worldDatasets->regions()->exportCsvFile('/tmp/regions.csv');composer run build-dataCette commande :
- relit la source courante
- normalise les enregistrements
- régénère SQLite, JSON, CSV
- génère un rapport de validation
- génère les checksums
- régénère les métadonnées
composer run check-data
composer run validate-data -- --strict$worldDatasets = Iriven\WorldDatasets\Application\Factory\WorldDatasetsFactory::makeWithValidation();ou
$config = new Iriven\WorldDatasets\Application\Config\WorldDatasetsRuntimeConfig(
verifyChecksum: true,
strictValidation: true,
);
$worldDatasets = Iriven\WorldDatasets\Application\Factory\WorldDatasetsFactory::fromConfig($config);composer run countries -- list alpha2
composer run countries -- show FR
composer run countries -- search france
composer run countries -- export countries json
composer run countries -- validate --strictcomposer run doctorCette commande vérifie :
- la présence des fichiers de données
- la capacité à charger le service
- la résolution de pays de référence
services:
Iriven\WorldDatasets\Application\WorldDatasetsService:
factory: ['Iriven\WorldDatasets\Application\Factory\WorldDatasetsFactory', 'make']services:
Iriven\WorldDatasets\Application\Config\WorldDatasetsRuntimeConfig:
arguments:
$sourcePath: '%kernel.project_dir%/vendor/irivengroup/world-datasets/src/data/.countriesRepository.sqlite'
$verifyChecksum: true
$strictValidation: true
$usePsr16Cache: false
Iriven\WorldDatasets\Application\WorldDatasetsService:
factory: ['Iriven\WorldDatasets\Application\Factory\WorldDatasetsFactory', 'fromConfig']
arguments:
$config: '@Iriven\WorldDatasets\Application\Config\WorldDatasetsRuntimeConfig'<?php
namespace App\Controller;
use Iriven\WorldDatasets\Application\WorldDatasetsService;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\Routing\Annotation\Route;
final class CountryController extends AbstractController
{
#[Route('/countries/{code}', methods: ['GET'])]
public function show(Countries $worldDatasets, string $code): JsonResponse
{
$country = $worldDatasets->country($code);
return $this->json([
'country' => $country->toArray(),
'currency' => $country->currency()->toArray(),
'region' => $country->region()->toArray(),
'phone' => $country->phone()->toArray(),
]);
}
#[Route('/countries/europe/eur', methods: ['GET'])]
public function euroCountries(Countries $worldDatasets): JsonResponse
{
return $this->json(
$worldDatasets->countries()
->inRegion('Europe')
->withCurrency('EUR')
->alpha2()
->list()
);
}
}<?php
namespace App\Command;
use Iriven\WorldDatasets\Application\WorldDatasetsService;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
#[AsCommand(name: 'app:countries:doctor')]
final class CountriesDoctorCommand extends Command
{
public function __construct(
private readonly Countries $worldDatasets,
) {
parent::__construct();
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$output->writeln('Countries count: ' . $this->countries->count());
$output->writeln('Source: ' . $this->countries->meta()->source());
return Command::SUCCESS;
}
}Le provider fourni se trouve dans :
src/Bridge/Laravel/WorldDatasetsServiceProvider.php
Dans config/app.php ou auto-discovery selon votre packaging :
Iriven\WorldDatasets\Bridge\Laravel\WorldDatasetsServiceProvider::class,<?php
namespace App\Http\Controllers;
use Illuminate\Http\JsonResponse;
use Iriven\WorldDatasets\Application\WorldDatasetsService;
final class CountryController
{
public function show(Countries $worldDatasets, string $code): JsonResponse
{
$country = $worldDatasets->country($code);
return response()->json([
'country' => $country->toArray(),
'currency' => $country->currency()->toArray(),
'region' => $country->region()->toArray(),
'phone' => $country->phone()->toArray(),
]);
}
public function euroCountries(Countries $worldDatasets): JsonResponse
{
return response()->json(
$worldDatasets->countries()
->inRegion('Europe')
->withCurrency('EUR')
->alpha3()
->list()
);
}
}<?php
namespace App\Services;
use Iriven\WorldDatasets\Application\WorldDatasetsService;
final class ShippingService
{
public function __construct(
private readonly Countries $worldDatasets,
) {
}
public function supportedDestinations(): array
{
return $this->countries
->countries()
->withTld('.fr')
->values();
}
}$worldDatasets = app(Iriven\WorldDatasets\Application\WorldDatasetsService::class);
$france = $worldDatasets->country('FR');
$list = $worldDatasets->countries()->alpha2()->list();composer testLes tests couvrent :
- résolution de pays de référence
- stabilité des codes
- régression de dataset
- exports
- query builder
- validation
composer run analyseLe workflow GitHub Actions :
- installe les dépendances
- reconstruit les données
- vérifie les checksums
- lance le doctor
- exécute PHPStan
- exécute PHPUnit
- échoue si des fichiers générés diffèrent du repo
- classe de service principale :
WorldDatasets - alias concret recommandé :
Countries - factory canonique :
WorldDatasetsFactory - source par défaut : SQLite
- source JSON et CSV : dérivées, mais maintenues
README.mdCHANGELOG.mdCONTRIBUTING.mddocs/compatibility.md
bin/build_data.phpbin/check_data.phpbin/import_countries.phpbin/validate_countries.phpbin/countriesbin/countries-doctor
src/data/.countriesRepository.sqlitesrc/data/.countriesRepository.jsonsrc/data/.countriesRepository.csvsrc/data/.countriesRepository.meta.jsonsrc/data/.countriesRepository.sha256
src/data/schema/country.schema.jsonsrc/data/schema/countries.sql
composer install
composer run build-data
composer run check-data
composer run doctor
composer run analyse
composer test$worldDatasets->countries()
->inRegion('Europe')
->inSubRegion('Western Europe')
->withCurrency('EUR')
->matching('fr')
->sortByName()
->paginate(0, 10)
->values();composer run build-data
composer run check-data
composer run doctor- Query fluente
- immutabilité
- séparation data/build/runtime
- source SQLite par défaut
Cette section complète le README avec un inventaire explicite des enchaînements autorisés sur les value objects, les filtres, les collections et le query builder.
Convention utilisée ci-dessous :
$worldDatasetsest une instance deIriven\WorldDatasets\Application\WorldDatasetsServicecountry()retourne unCountryInfocountries()retourne uneCountriesCollectioncurrencies()retourne uneCurrencyCollectionregions()retourne uneRegionCollectionquery()retourne uneWorldDatasetsQuery
$worldDatasets->country('FR');
$worldDatasets->findCountry('FR');$worldDatasets->countries();
$worldDatasets->countries('alpha2');
$worldDatasets->countries('alpha3');
$worldDatasets->countries('numeric');
$worldDatasets->currencies();
$worldDatasets->regions();
$worldDatasets->query();
$worldDatasets->meta();$worldDatasets->country('FR')->alpha2();
$worldDatasets->country('FR')->alpha3();
$worldDatasets->country('FR')->numeric();
$worldDatasets->country('FR')->name();
$worldDatasets->country('FR')->capital();
$worldDatasets->country('FR')->tld();
$worldDatasets->country('FR')->language();
$worldDatasets->country('FR')->languages();
$worldDatasets->country('FR')->postalCodePattern();
$worldDatasets->country('FR')->exists();
$worldDatasets->country('FR')->data();
$worldDatasets->country('FR')->all();
$worldDatasets->country('FR')->toArray();
$worldDatasets->country('FR')->toIndexedArray();
$worldDatasets->country('FR')->jsonSerialize();$worldDatasets->country('FR')->currency();
$worldDatasets->country('FR')->region();
$worldDatasets->country('FR')->phone();$worldDatasets->country('FR')->hasCurrency('EUR');
$worldDatasets->country('FR')->isInRegion('Europe');Point d’entrée :
$currency = $worldDatasets->country('FR')->currency();$currency->code();
$currency->name();
$currency->toArray();
$currency->jsonSerialize();
(string) $currency;$worldDatasets->country('FR')->currency()->code();
$worldDatasets->country('FR')->currency()->name();
$worldDatasets->country('FR')->currency()->toArray();Point d’entrée :
$region = $worldDatasets->country('FR')->region();$region->alphaCode();
$region->numericCode();
$region->name();
$region->subRegion();
$region->toArray();
$region->jsonSerialize();
(string) $region;$worldDatasets->country('FR')->region()->alphaCode();
$worldDatasets->country('FR')->region()->numericCode();
$worldDatasets->country('FR')->region()->name();
$worldDatasets->country('FR')->region()->toArray();
$worldDatasets->country('FR')->region()->jsonSerialize();$worldDatasets->country('FR')->region()->subRegion();
$worldDatasets->country('FR')->region()->subRegion()->code();
$worldDatasets->country('FR')->region()->subRegion()->code();
$worldDatasets->country('FR')->region()->subRegion()->name();
$worldDatasets->country('FR')->region()->subRegion()->name();
$worldDatasets->country('FR')->region()->subRegion()->toArray();
$worldDatasets->country('FR')->region()->subRegion()->jsonSerialize();Point d’entrée :
$subRegion = $worldDatasets->country('FR')->region()->subRegion();$subRegion->code();
$subRegion->code();
$subRegion->name();
$subRegion->name();
$subRegion->toArray();
$subRegion->jsonSerialize();
(string) $subRegion;Point d’entrée :
$phone = $worldDatasets->country('FR')->phone();$phone->code();
$phone->internationalPrefix();
$phone->nationalPrefix();
$phone->subscriberPattern();
$phone->pattern();
$phone->toArray();
$phone->jsonSerialize();
(string) $phone;$worldDatasets->country('FR')->phone()->code();
$worldDatasets->country('FR')->phone()->internationalPrefix();
$worldDatasets->country('FR')->phone()->nationalPrefix();
$worldDatasets->country('FR')->phone()->subscriberPattern();
$worldDatasets->country('FR')->phone()->pattern();
$worldDatasets->country('FR')->phone()->toArray();Point d’entrée :
$collection = $worldDatasets->countries();$worldDatasets->countries()->alpha2();
$worldDatasets->countries()->alpha3();
$worldDatasets->countries()->numeric();Chainages usuels :
$worldDatasets->countries()->alpha2()->list();
$worldDatasets->countries()->alpha3()->list();
$worldDatasets->countries()->numeric()->list();
$worldDatasets->countries()->alpha2()->codes();
$worldDatasets->countries()->alpha3()->codes();
$worldDatasets->countries()->numeric()->codes();Tous ces filtres peuvent s’enchaîner librement entre eux, puis avec les méthodes de tri, pagination, extraction ou export.
$worldDatasets->countries()->inRegion('Europe');
$worldDatasets->countries()->inSubRegion('Western Europe');
$worldDatasets->countries()->withCurrency('EUR');
$worldDatasets->countries()->withPhoneCode('+33');
$worldDatasets->countries()->withTld('.fr');
$worldDatasets->countries()->named('France');
$worldDatasets->countries()->matching('fr');$worldDatasets->countries()->sortByName();
$worldDatasets->countries()->sortByCode();
$worldDatasets->countries()->sortByNumeric();
$worldDatasets->countries()->paginate(0, 10);$worldDatasets->countries()->first();
$worldDatasets->countries()->last();
$worldDatasets->countries()->count();
$worldDatasets->countries()->isEmpty();
$worldDatasets->countries()->isNotEmpty();
$worldDatasets->countries()->contains('FR');
$worldDatasets->countries()->containsCountry('FR');
$worldDatasets->countries()->containsCountry($worldDatasets->country('FR'));
$worldDatasets->countries()->containsCountry(fn ($country) => $country->hasCurrency('EUR'));
$worldDatasets->countries()->chunk(50);$worldDatasets->countries()->values();
$worldDatasets->countries()->names();
$worldDatasets->countries()->codes();
$worldDatasets->countries()->list();
$worldDatasets->countries()->exportArray();
$worldDatasets->countries()->toStorageArray();
$worldDatasets->countries()->toApiArray();
$worldDatasets->countries()->toArray();
$worldDatasets->countries()->jsonSerialize();$worldDatasets->countries()->stats();
$worldDatasets->countries()->groupByRegion();
$worldDatasets->countries()->groupByCurrency();
$worldDatasets->countries()->pluckNames();
$worldDatasets->countries()->pluckCodes();$worldDatasets->countries()->map(fn ($country) => $country->name());
$worldDatasets->countries()->filter(fn ($country) => $country->hasCurrency('EUR'));
$worldDatasets->countries()->reduce(fn ($carry, $country) => $carry + 1, 0);$worldDatasets->countries()->toJson();
$worldDatasets->countries()->toCsv();
$worldDatasets->countries()->exportJsonFile('/tmp/countries.json');
$worldDatasets->countries()->exportCsvFile('/tmp/countries.csv');$worldDatasets->countries()
->inRegion('Europe')
->withCurrency('EUR')
->alpha2()
->sortByName()
->list();$worldDatasets->countries()
->inRegion('Europe')
->inSubRegion('Western Europe')
->withCurrency('EUR')
->matching('fr')
->sortByCode()
->paginate(0, 20)
->values();$worldDatasets->countries()
->withTld('.fr')
->withPhoneCode('+33')
->alpha3()
->codes();$worldDatasets->countries()
->matching('united')
->sortByNumeric()
->pluckNames();$worldDatasets->countries()
->filter(fn ($country) => $country->region()->name() === 'Europe')
->map(fn ($country) => [
'code' => $country->alpha2(),
'name' => $country->name(),
'currency' => $country->currency()->code(),
]);$worldDatasets->countries()
->inRegion('Asia')
->sortByName()
->exportJsonFile('/tmp/asia.json');$worldDatasets->countries()
->inRegion('Americas')
->groupByCurrency();Point d’entrée :
$currencies = $worldDatasets->currencies();$currencies->values();
$currencies->list();
$currencies->countries();
$currencies->exportArray();
$currencies->toJson();
$currencies->toCsv();
$currencies->exportJsonFile('/tmp/currencies.json');
$currencies->exportCsvFile('/tmp/currencies.csv');
$currencies->toArray();
$currencies->jsonSerialize();$worldDatasets->currencies()->list();
$worldDatasets->currencies()->values();
$worldDatasets->currencies()->countries();
$worldDatasets->currencies()->toJson();
$worldDatasets->currencies()->exportCsvFile('/tmp/currencies.csv');Point d’entrée :
$regions = $worldDatasets->regions();$regions->values();
$regions->list();
$regions->countries();
$regions->exportArray();
$regions->toJson();
$regions->toCsv();
$regions->exportJsonFile('/tmp/regions.json');
$regions->exportCsvFile('/tmp/regions.csv');
$regions->toArray();
$regions->jsonSerialize();$worldDatasets->regions()->list();
$worldDatasets->regions()->values();
$worldDatasets->regions()->countries();
$worldDatasets->regions()->toJson();
$worldDatasets->regions()->exportCsvFile('/tmp/regions.csv');Point d’entrée :
$query = $worldDatasets->query();$query->inRegion('Europe');
$query->inSubRegion('Western Europe');
$query->withCurrency('EUR');
$query->withPhoneCode('+33');
$query->withTld('.fr');
$query->matching('fr');
$query->sortByName();
$query->sortByCode();
$query->sortByNumeric();
$query->limit(20);
$query->offset(0, 20);$query->get();
$query->list();$worldDatasets->query()
->inRegion('Europe')
->withCurrency('EUR')
->sortByName()
->limit(20)
->get();$worldDatasets->query()
->inRegion('Europe')
->inSubRegion('Western Europe')
->matching('fr')
->sortByCode()
->offset(0, 10)
->list();$worldDatasets->query()
->withTld('.fr')
->withPhoneCode('+33')
->get();Point d’entrée :
$meta = $worldDatasets->meta();$meta->count();
$meta->source();
$meta->version();
$meta->lastUpdatedAt();
$meta->packageVersion();
$meta->datasetVersion();
$meta->checksum();
$meta->builtAt();
$meta->toArray();
$meta->jsonSerialize();$worldDatasets->meta()->source();
$worldDatasets->meta()->datasetVersion();
$worldDatasets->meta()->checksum();
$worldDatasets->meta()->builtAt();
$worldDatasets->meta()->toArray();Iriven\WorldDatasets\Application\Factory\WorldDatasetsFactory::make();
Iriven\WorldDatasets\Application\Factory\WorldDatasetsFactory::make(Iriven\WorldDatasets\Application\Factory\WorldDatasetsFactory::defaultSqlitePath());
Iriven\WorldDatasets\Application\Factory\WorldDatasetsFactory::make(Iriven\WorldDatasets\Application\Factory\WorldDatasetsFactory::defaultJsonPath());
Iriven\WorldDatasets\Application\Factory\WorldDatasetsFactory::make(Iriven\WorldDatasets\Application\Factory\WorldDatasetsFactory::defaultCsvPath());$config = new Iriven\WorldDatasets\Application\Config\WorldDatasetsRuntimeConfig(
sourcePath: Iriven\WorldDatasets\Application\Factory\WorldDatasetsFactory::defaultSqlitePath(),
verifyChecksum: true,
strictValidation: true,
);
$worldDatasets = Iriven\WorldDatasets\Application\Factory\WorldDatasetsFactory::fromConfig($config);Iriven\WorldDatasets\Application\Factory\WorldDatasetsFactory::makeWithValidation();Iriven\WorldDatasets\Application\Factory\WorldDatasetsFactory::assertChecksum(
Iriven\WorldDatasets\Application\Factory\WorldDatasetsFactory::defaultSqlitePath()
);$worldDatasets->country('FR')->region()->subRegion()->name();
$worldDatasets->country('FR')->currency()->code();
$worldDatasets->country('FR')->phone()->pattern();$worldDatasets->countries()
->inRegion('Europe')
->withCurrency('EUR')
->sortByName()
->list();$worldDatasets->countries()
->filter(fn ($country) => $country->hasCurrency('EUR'))
->map(fn ($country) => $country->name());$worldDatasets->query()
->matching('fr')
->limit(10)
->get();$worldDatasets->currencies()->exportJsonFile('/tmp/currencies.json');
$worldDatasets->regions()->exportCsvFile('/tmp/regions.csv');Les chainages ci-dessus sont donnés sans restriction artificielle :
tout ce qui est exposé publiquement par les objets peut être combiné dans l’ordre logique du type retourné.
En pratique :
- un
CountryInfopeut chaîner vers ses value objects - une
CountriesCollectionpeut enchaîner filtres, tri, pagination, extraction, agrégation, export - un
WorldDatasetsQuerypeut enchaîner filtres, tri et pagination avantget()oulist() CurrencyCollectionetRegionCollectionpeuvent aller jusqu’à l’export final
Sans modification de la logique métier ni de l’API publique, cette version ajoute :
- index mémoire pour les lookups
alpha2,alpha3,numeric - cache interne des résultats de collections :
list()codes()names()exportArray()stats()groupByRegion()groupByCurrency()
- réduction des instanciations répétées des normalizers et value objects
- factorisation des transformations de tableaux via
CountryArrayTransformer - lecture unique des métadonnées et checksums dans
WorldDatasetsFactory - optimisation des index SQLite
- support de requêtes partielles côté repository SQLite via :
iterateAllLazy(int $limit = 500)iterateByRegionLazy(string $region, int $limit = 500)
Ces méthodes sont internes au repository SQLite et permettent de parcourir les données par lots, sans charger l’intégralité du dataset d’un coup.
Exemple conceptuel :
$repository = Iriven\WorldDatasets\Application\Factory\WorldDatasetsFactory::makeRepository(
Iriven\WorldDatasets\Application\Factory\WorldDatasetsFactory::defaultSqlitePath()
);
if ($repository instanceof Iriven\Infrastructure\Persistence\SqliteCountryRepository) {
foreach ($repository->iterateAllLazy(100) as $country) {
// traitement batch
}
}Le workflow CI active maintenant explicitement Node 24 pour anticiper la dépréciation Node 20 :
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: trueLe script bin/build_data.php produit désormais des artefacts déterministes :
- ordre stable des enregistrements
- JSON avec fin de ligne normalisée
- métadonnées
built_atconservées si les checksums n’ont pas changé
Cela évite les diffs parasites lors du contrôle git diff --exit-code après génération.
Le script bin/build_data.php est maintenant idempotent pour la CI :
- les fichiers texte ne sont réécrits que si leur contenu change
- la base SQLite n’est pas reconstruite quand elle est déjà la source par défaut
built_atreste stable si les checksums restent identiques
Cela évite les échecs sur git diff --exit-code après composer run build-data.
Le projet inclut maintenant un fichier .scrutinizer.yml aligné sur la pipeline principale :
composer installcomposer run build-datacomposer run check-datacomposer run doctorcomposer run analyseXDEBUG_MODE=coverage vendor/bin/phpunit --coverage-clover build/logs/clover.xmlcomposer testgit diff --exit-code
Le bridge Laravel est exclu de l’analyse statique Scrutinizer tant que les dépendances framework ne sont pas installées.
- PHPStan est exécuté avec
--memory-limit=512M - la couverture PHPUnit utilise maintenant un filtre
sourceexplicite dansphpunit.xml - le bridge Laravel reste exclu de l’analyse statique et de la couverture par défaut
La couverture n'est plus seulement générée : elle est maintenant déclarée explicitement à Scrutinizer via :
build:
nodes:
coverage:
tests:
override:
- command: "XDEBUG_MODE=coverage vendor/bin/phpunit --configuration phpunit.xml --coverage-clover build/logs/clover.xml"
coverage:
file: "build/logs/clover.xml"
format: "clover"C'est cette structure qui permet d'éviter un statut coverage à unknown.
DatasetValidator::validate() a été factorisée en petites méthodes privées pour réduire la complexité cyclomatique et améliorer la lisibilité, sans changer la logique métier :
- validation des codes ISO
- détection des doublons
- collecte des warnings
- gestion du mode strict
Les annotations PHPDoc du projet ont été uniformisées pour privilégier array<int, ...> à la place de array<int, ...> lorsque cela améliore la compatibilité entre outils d'analyse statique.
Toutes les annotations array<int, ...> du projet ont été remplacées par des formes array<int, ...> pour une meilleure compatibilité avec les outils d'analyse statique et de qualité de code.
SqliteCountryRepository a été allégé par extraction de composants dédiés :
SqliteConnectionFactorySqliteCountryHydratorSqliteCountryQueryBuilderSqliteStatementExecutor
L'API publique du repository reste inchangée.
CountriesCollection a été allégée par extraction de composants dédiés :
CountriesCollectionFilterCountriesCollectionSorterCountriesCollectionAggregatorCountriesCollectionExporter
L'API publique de la collection reste inchangée.
Le constructeur de CsvCountryRepository a été allégé par extraction de méthodes privées dédiées :
openFile()readHeaders()readCountries()
Le comportement reste identique, avec fermeture du handle garantie via finally.
Les annotations PHPDoc exotiques problématiques pour certains environnements CI ont été éliminées ou remplacées par :
- des types simples compatibles
- des validations runtime explicites lorsque nécessaire
Objectif : maximiser la compatibilité avec PHPStan, Scrutinizer et les pipelines CI stricts.
CountriesCollection a été encore allégée avec de nouveaux composants internes :
CountriesCollectionCacheCountriesCollectionReadModelCountriesCollectionSequence
La classe conserve la même API publique, mais délègue désormais la lecture, la séquence et la gestion du cache.
Un premier lot de tests PHPUnit a été ajouté pour améliorer rapidement le coverage sur les classes extraites :
CountriesCollectionFilterTestCountriesCollectionAggregatorTestCountriesCollectionSequenceTestCountriesCollectionReadModelTestSqliteCountryQueryBuilderTestSqliteCountryHydratorTest
Un trait CountryFactoryTrait a aussi été ajouté pour factoriser les jeux de données de test.
Un second lot de tests PHPUnit a été ajouté pour continuer à remonter le coverage :
CountriesCollectionExporterTestCountriesCollectionSorterTestCountriesCollectionFacadeTestSqliteConnectionFactoryTestSqliteStatementExecutorTestCsvCountryRepositoryTest
Les points d'entrée publics et les composants critiques sont maintenant couverts aussi par des tests d'intégration ciblés :
DatasetValidatorTestSqliteCountryRepositoryIntegrationTestWorldDatasetsFactoryIntegrationTest
Une fixture SQLite temporaire a été ajoutée via SqliteFixtureTrait.
Le dossier src a été réorganisé pour réduire l'encombrement à la racine :
- les repositories fichiers (
ArrayCountryRepository,CsvCountryRepository,JsonCountryRepository) sont désormais danssrc/Infrastructure/Persistence/ - les composants sont désormais regroupés dans
src/components/ - dans
src/components/, les fichiers principaux de composant sont placés au niveau du dossier - les fichiers secondaires d'un composant sont regroupés dans un sous-dossier portant le nom du composant
L'autoload du projet est désormais aligné sur une structure PSR-4 cohérente avec l'arborescence réelle.
Les namespaces sont maintenant alignés sur les dossiers réels du projet. Les imports et les tests ont été mis à jour en conséquence.
Le projet est désormais structuré autour de Domain, Application et Infrastructure.