Skip to content

irivengroup/WorldDatasets

Repository files navigation

Convention retenue

Cette version conserve volontairement :

  • WorldDatasetsService comme service principal
  • WorldDatasetsFactory comme factory principale
  • countries() comme point d’entrée de collection
  • CountriesCollection comme nom officiel de la collection de pays
  • la variable d’exemple $worldDatasets dans toute la documentation

Autrement dit :

  • on ne migre pas CountriesCollection vers DatasetsCollection
  • on garde countries()CountriesCollection

Version publique stabilisée v1

Cette archive ne contient plus d’alias de transition.
L’API publique officielle repose uniquement sur :

  • Iriven\WorldDatasets\Application\WorldDatasetsService
  • Iriven\WorldDatasets\Application\Factory\WorldDatasetsFactory
  • Iriven\WorldDatasets\Application\Config\WorldDatasetsRuntimeConfig
  • Iriven\WorldDatasets\Domain\CountriesCollection
  • Iriven\WorldDatasets\Domain\CurrencyCollection
  • Iriven\WorldDatasets\Domain\RegionCollection
  • Iriven\WorldDatasets\Application\Query\WorldDatasetsQuery
  • Iriven\WorldDatasets\Application\Stats\WorldDatasetsStats

Public API harmonisée

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

Installation

composer require irivengroup/world-datasets

Namespace principal :

use Iriven\WorldDatasets\Application\WorldDatasetsService;
use Iriven\WorldDatasets\Application\Factory\WorldDatasetsFactory;

PHP Countries Data

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.


Sommaire

  1. Présentation
  2. Architecture
  3. Sources de données
  4. Installation
  5. Démarrage rapide
  6. Inventaire complet des méthodes publiques
  7. Collections et query builder
  8. Exports
  9. Validation, checksums et pipeline data
  10. CLI et health check
  11. Intégration Symfony
  12. Intégration Laravel
  13. Tests, CI et qualité
  14. Conventions de nommage
  15. Fichiers du projet

1. Présentation

Le projet expose une API moderne autour de quatre idées :

  • un service principal de consultation
  • des CountryInfo value 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\WorldDatasets

L’alias de compatibilité historique WorldDatasets est conservé, mais déprécié.


2. Architecture

2.1 Composants principaux

  • WorldDatasets : service central
  • Countries : alias concret prêt à l’emploi
  • WorldDatasetsFactory : point d’entrée canonique
  • CountryInfo : représentation d’un pays
  • CurrencyInfo, RegionInfo, SubRegionInfo, PhoneInfo : value objects
  • CountriesCollection, CurrencyCollection, RegionCollection
  • WorldDatasetsQuery
  • DatasetValidator
  • JsonCountryRepository, CsvCountryRepository, SqliteCountryRepository, ArrayCountryRepository

2.2 Source de vérité

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

3. Sources de données

3.1 Chargement par défaut

use Iriven\WorldDatasets\Application\Factory\WorldDatasetsFactory;

require_once __DIR__ . '/vendor/autoload.php';

$worldDatasets = WorldDatasetsFactory::make();

Cela charge :

src/data/.countriesRepository.sqlite

3.2 Chargement explicite

WorldDatasetsFactory::make(Iriven\WorldDatasets\Application\Factory\WorldDatasetsFactory::defaultSqlitePath());
WorldDatasetsFactory::make(Iriven\WorldDatasets\Application\Factory\WorldDatasetsFactory::defaultJsonPath());
WorldDatasetsFactory::make(Iriven\WorldDatasets\Application\Factory\WorldDatasetsFactory::defaultCsvPath());

3.3 Configuration runtime

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);

3.4 Vérification stricte au bootstrap

$worldDatasets = WorldDatasetsFactory::makeWithValidation();

4. Installation

composer install
composer run build-data
composer run check-data
composer run doctor
composer run analyse
composer test

5. Démarrage rapide

use 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());

6. Inventaire complet des méthodes publiques

6.1 Iriven\WorldDatasets\Application\Factory\WorldDatasetsFactory

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

6.2 Iriven\WorldDatasets

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

6.3 Iriven\WorldDatasets\Domain\CountryInfo

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

6.4 Iriven\WorldDatasets\Domain\CountriesCollection

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

6.5 Iriven\CurrencyCollection

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

6.6 Iriven\RegionCollection

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

6.7 Iriven\WorldDatasets\Application\Query\WorldDatasetsQuery

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>

6.8 Value objects

Iriven\CurrencyInfo

  • code(): string
  • name(): string
  • toArray(): array
  • jsonSerialize(): array
  • __toString(): string

Iriven\RegionInfo

  • alphaCode(): string
  • numericCode(): string
  • name(): string
  • subRegion(): SubRegionInfo
  • toArray(): array
  • jsonSerialize(): array
  • __toString(): string

Iriven\SubRegionInfo

  • code(): string
  • Code(): string
  • name(): string
  • Name(): string
  • toArray(): array
  • jsonSerialize(): array
  • __toString(): string

Iriven\PhoneInfo

  • code(): string
  • internationalPrefix(): string
  • nationalPrefix(): string
  • subscriberPattern(): string
  • pattern(): string
  • toArray(): array
  • jsonSerialize(): array
  • __toString(): string

Iriven\MetaInfo

  • count(): int
  • source(): string
  • version(): string
  • lastUpdatedAt(): ?string
  • packageVersion(): string
  • datasetVersion(): string
  • checksum(): ?string
  • builtAt(): ?string
  • toArray(): array
  • jsonSerialize(): array

Iriven\WorldDatasets\Application\Stats\WorldDatasetsStats

  • total(): int
  • regions(): int
  • currencies(): int
  • toArray(): array
  • jsonSerialize(): array

6.9 Validation et configuration

Iriven\DatasetValidator

  • validate(array $worldDatasets, bool $strict = true): DatasetValidationReport

Iriven\DatasetValidationReport

  • duplicates(): array
  • invalidCodes(): array
  • warnings(): array
  • strict(): bool
  • isValid(): bool
  • toArray(): array
  • jsonSerialize(): array

Iriven\WorldDatasets\Application\Config\WorldDatasetsRuntimeConfig

  • sourcePath(): ?string
  • verifyChecksum(): bool
  • strictValidation(): bool
  • usePsr16Cache(): bool

Iriven\WorldDatasets\Application\Support\CountryCodeNormalizer

  • normalize(string $code): string
  • normalizeAlpha(string $code): string
  • normalizeNumeric(string $code): string
  • normalizePreservingNumeric(string $code): string
  • normalizeTld(string $tld): string

Iriven\PhoneCodeNormalizer

  • normalize(string $code): string

Iriven\TldNormalizer

  • normalize(string $tld): string

7. Collections et query builder

7.1 Exemples de collections

$worldDatasets->countries()->alpha2()->list();
$worldDatasets->countries()->alpha3()->list();
$worldDatasets->countries()->numeric()->list();

$worldDatasets->countries()
    ->inRegion('Europe')
    ->withCurrency('EUR')
    ->sortByName()
    ->paginate(0, 25)
    ->values();

7.2 Exemples query builder

$result = $worldDatasets->query()
    ->inRegion('Europe')
    ->withCurrency('EUR')
    ->sortByName()
    ->limit(20)
    ->get();

7.3 Exemples fonctionnels

$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
);

8. Exports

$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');

9. Validation, checksums et pipeline data

9.1 Build complet des données

composer run build-data

Cette 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

9.2 Validation

composer run check-data
composer run validate-data -- --strict

9.3 Checksum

$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);

10. CLI et health check

10.1 CLI métier

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 --strict

10.2 Doctor

composer run doctor

Cette 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

11. Intégration Symfony

11.1 Déclaration de service simple

services:
  Iriven\WorldDatasets\Application\WorldDatasetsService:
    factory: ['Iriven\WorldDatasets\Application\Factory\WorldDatasetsFactory', 'make']

11.2 Avec validation stricte

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'

11.3 Exemple de contrôleur

<?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()
        );
    }
}

11.4 Exemple de commande Symfony

<?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;
    }
}

12. Intégration Laravel

12.1 Service Provider

Le provider fourni se trouve dans :

src/Bridge/Laravel/WorldDatasetsServiceProvider.php

12.2 Enregistrement

Dans config/app.php ou auto-discovery selon votre packaging :

Iriven\WorldDatasets\Bridge\Laravel\WorldDatasetsServiceProvider::class,

12.3 Utilisation dans un contrôleur

<?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()
        );
    }
}

12.4 Utilisation dans un job / service

<?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();
    }
}

12.5 Utilisation directe via le container

$worldDatasets = app(Iriven\WorldDatasets\Application\WorldDatasetsService::class);

$france = $worldDatasets->country('FR');
$list = $worldDatasets->countries()->alpha2()->list();

13. Tests, CI et qualité

13.1 Tests

composer test

Les tests couvrent :

  • résolution de pays de référence
  • stabilité des codes
  • régression de dataset
  • exports
  • query builder
  • validation

13.2 Analyse statique

composer run analyse

13.3 CI

Le 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

14. Conventions de nommage

  • 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

15. Fichiers du projet

15.1 Documentation

  • README.md
  • CHANGELOG.md
  • CONTRIBUTING.md
  • docs/compatibility.md

15.2 Scripts

  • bin/build_data.php
  • bin/check_data.php
  • bin/import_countries.php
  • bin/validate_countries.php
  • bin/countries
  • bin/countries-doctor

15.3 Données

  • src/data/.countriesRepository.sqlite
  • src/data/.countriesRepository.json
  • src/data/.countriesRepository.csv
  • src/data/.countriesRepository.meta.json
  • src/data/.countriesRepository.sha256

15.4 Schémas

  • src/data/schema/country.schema.json
  • src/data/schema/countries.sql

Commandes utiles

composer install
composer run build-data
composer run check-data
composer run doctor
composer run analyse
composer test

Utilisation avancée & chainage

Exemple complet

$worldDatasets->countries()
    ->inRegion('Europe')
    ->inSubRegion('Western Europe')
    ->withCurrency('EUR')
    ->matching('fr')
    ->sortByName()
    ->paginate(0, 10)
    ->values();

Pipeline

composer run build-data
composer run check-data
composer run doctor

Patterns

  • Query fluente
  • immutabilité
  • séparation data/build/runtime
  • source SQLite par défaut

Addendum — inventaire des chainages possibles

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 :

  • $worldDatasets est une instance de Iriven\WorldDatasets\Application\WorldDatasetsService
  • country() retourne un CountryInfo
  • countries() retourne une CountriesCollection
  • currencies() retourne une CurrencyCollection
  • regions() retourne une RegionCollection
  • query() retourne une WorldDatasetsQuery

1. Chainages depuis le service principal

Point d’entrée pays

$worldDatasets->country('FR');
$worldDatasets->findCountry('FR');

Point d’entrée collections

$worldDatasets->countries();
$worldDatasets->countries('alpha2');
$worldDatasets->countries('alpha3');
$worldDatasets->countries('numeric');

$worldDatasets->currencies();
$worldDatasets->regions();
$worldDatasets->query();
$worldDatasets->meta();

2. Chainages possibles depuis CountryInfo

2.1 Accès direct aux propriétés scalaires

$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();

2.2 Chainages vers les value objects

$worldDatasets->country('FR')->currency();
$worldDatasets->country('FR')->region();
$worldDatasets->country('FR')->phone();

2.3 Chainages métier

$worldDatasets->country('FR')->hasCurrency('EUR');
$worldDatasets->country('FR')->isInRegion('Europe');

3. Chainages possibles depuis CurrencyInfo

Point d’entrée :

$currency = $worldDatasets->country('FR')->currency();

Méthodes publiques

$currency->code();
$currency->name();
$currency->toArray();
$currency->jsonSerialize();
(string) $currency;

Exemples

$worldDatasets->country('FR')->currency()->code();
$worldDatasets->country('FR')->currency()->name();
$worldDatasets->country('FR')->currency()->toArray();

4. Chainages possibles depuis RegionInfo

Point d’entrée :

$region = $worldDatasets->country('FR')->region();

Méthodes publiques

$region->alphaCode();
$region->numericCode();
$region->name();
$region->subRegion();
$region->toArray();
$region->jsonSerialize();
(string) $region;

Chainages complets

$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();

Passage vers SubRegionInfo

$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();

5. Chainages possibles depuis SubRegionInfo

Point d’entrée :

$subRegion = $worldDatasets->country('FR')->region()->subRegion();

Méthodes publiques

$subRegion->code();
$subRegion->code();
$subRegion->name();
$subRegion->name();
$subRegion->toArray();
$subRegion->jsonSerialize();
(string) $subRegion;

6. Chainages possibles depuis PhoneInfo

Point d’entrée :

$phone = $worldDatasets->country('FR')->phone();

Méthodes publiques

$phone->code();
$phone->internationalPrefix();
$phone->nationalPrefix();
$phone->subscriberPattern();
$phone->pattern();
$phone->toArray();
$phone->jsonSerialize();
(string) $phone;

Exemples détaillés

$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();

7. Chainages possibles depuis CountriesCollection

Point d’entrée :

$collection = $worldDatasets->countries();

7.1 Sélection du format de code

$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();

7.2 Filtres chaînables

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');

7.3 Tri et pagination

$worldDatasets->countries()->sortByName();
$worldDatasets->countries()->sortByCode();
$worldDatasets->countries()->sortByNumeric();
$worldDatasets->countries()->paginate(0, 10);

7.4 Accès ponctuel

$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);

7.5 Extraction / restitution

$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();

7.6 Agrégations

$worldDatasets->countries()->stats();
$worldDatasets->countries()->groupByRegion();
$worldDatasets->countries()->groupByCurrency();
$worldDatasets->countries()->pluckNames();
$worldDatasets->countries()->pluckCodes();

7.7 Fonctionnel

$worldDatasets->countries()->map(fn ($country) => $country->name());
$worldDatasets->countries()->filter(fn ($country) => $country->hasCurrency('EUR'));
$worldDatasets->countries()->reduce(fn ($carry, $country) => $carry + 1, 0);

7.8 Export

$worldDatasets->countries()->toJson();
$worldDatasets->countries()->toCsv();
$worldDatasets->countries()->exportJsonFile('/tmp/countries.json');
$worldDatasets->countries()->exportCsvFile('/tmp/countries.csv');

7.9 Exemples de chainages libres

Exemple 1

$worldDatasets->countries()
    ->inRegion('Europe')
    ->withCurrency('EUR')
    ->alpha2()
    ->sortByName()
    ->list();

Exemple 2

$worldDatasets->countries()
    ->inRegion('Europe')
    ->inSubRegion('Western Europe')
    ->withCurrency('EUR')
    ->matching('fr')
    ->sortByCode()
    ->paginate(0, 20)
    ->values();

Exemple 3

$worldDatasets->countries()
    ->withTld('.fr')
    ->withPhoneCode('+33')
    ->alpha3()
    ->codes();

Exemple 4

$worldDatasets->countries()
    ->matching('united')
    ->sortByNumeric()
    ->pluckNames();

Exemple 5

$worldDatasets->countries()
    ->filter(fn ($country) => $country->region()->name() === 'Europe')
    ->map(fn ($country) => [
        'code' => $country->alpha2(),
        'name' => $country->name(),
        'currency' => $country->currency()->code(),
    ]);

Exemple 6

$worldDatasets->countries()
    ->inRegion('Asia')
    ->sortByName()
    ->exportJsonFile('/tmp/asia.json');

Exemple 7

$worldDatasets->countries()
    ->inRegion('Americas')
    ->groupByCurrency();

8. Chainages possibles depuis CurrencyCollection

Point d’entrée :

$currencies = $worldDatasets->currencies();

Méthodes publiques

$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();

Exemples

$worldDatasets->currencies()->list();
$worldDatasets->currencies()->values();
$worldDatasets->currencies()->countries();
$worldDatasets->currencies()->toJson();
$worldDatasets->currencies()->exportCsvFile('/tmp/currencies.csv');

9. Chainages possibles depuis RegionCollection

Point d’entrée :

$regions = $worldDatasets->regions();

Méthodes publiques

$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();

Exemples

$worldDatasets->regions()->list();
$worldDatasets->regions()->values();
$worldDatasets->regions()->countries();
$worldDatasets->regions()->toJson();
$worldDatasets->regions()->exportCsvFile('/tmp/regions.csv');

10. Chainages possibles depuis WorldDatasetsQuery

Point d’entrée :

$query = $worldDatasets->query();

Filtres / tri / pagination

$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);

Résolution finale

$query->get();
$query->list();

Exemples complets

$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();

11. Chainages possibles depuis MetaInfo

Point d’entrée :

$meta = $worldDatasets->meta();

Méthodes publiques

$meta->count();
$meta->source();
$meta->version();
$meta->lastUpdatedAt();
$meta->packageVersion();
$meta->datasetVersion();
$meta->checksum();
$meta->builtAt();
$meta->toArray();
$meta->jsonSerialize();

Exemples

$worldDatasets->meta()->source();
$worldDatasets->meta()->datasetVersion();
$worldDatasets->meta()->checksum();
$worldDatasets->meta()->builtAt();
$worldDatasets->meta()->toArray();

12. Chainages liés au factory et à la config runtime

Factory directe

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());

Factory avec config

$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);

Validation stricte

Iriven\WorldDatasets\Application\Factory\WorldDatasetsFactory::makeWithValidation();

Checksum

Iriven\WorldDatasets\Application\Factory\WorldDatasetsFactory::assertChecksum(
    Iriven\WorldDatasets\Application\Factory\WorldDatasetsFactory::defaultSqlitePath()
);

13. Résumé des familles de chainage

13.1 Service -> CountryInfo -> Value Object -> propriété

$worldDatasets->country('FR')->region()->subRegion()->name();
$worldDatasets->country('FR')->currency()->code();
$worldDatasets->country('FR')->phone()->pattern();

13.2 Service -> CountriesCollection -> filtres -> tri -> sortie

$worldDatasets->countries()
    ->inRegion('Europe')
    ->withCurrency('EUR')
    ->sortByName()
    ->list();

13.3 Service -> CountriesCollection -> fonctionnel -> sortie

$worldDatasets->countries()
    ->filter(fn ($country) => $country->hasCurrency('EUR'))
    ->map(fn ($country) => $country->name());

13.4 Service -> Query -> résultat

$worldDatasets->query()
    ->matching('fr')
    ->limit(10)
    ->get();

13.5 Service -> collection spécialisée -> export

$worldDatasets->currencies()->exportJsonFile('/tmp/currencies.json');
$worldDatasets->regions()->exportCsvFile('/tmp/regions.csv');

14. Important

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 CountryInfo peut chaîner vers ses value objects
  • une CountriesCollection peut enchaîner filtres, tri, pagination, extraction, agrégation, export
  • un WorldDatasetsQuery peut enchaîner filtres, tri et pagination avant get() ou list()
  • CurrencyCollection et RegionCollection peuvent aller jusqu’à l’export final

Optimisations internes appliquées

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)

Notes sur le lazy loading SQLite

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
    }
}

GitHub Actions

Le workflow CI active maintenant explicitement Node 24 pour anticiper la dépréciation Node 20 :

env:
  FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true

Build determinism

Le 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_at conservé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.

Build idempotent

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_at reste stable si les checksums restent identiques

Cela évite les échecs sur git diff --exit-code après composer run build-data.

Scrutinizer CI

Le projet inclut maintenant un fichier .scrutinizer.yml aligné sur la pipeline principale :

  • composer install
  • composer run build-data
  • composer run check-data
  • composer run doctor
  • composer run analyse
  • XDEBUG_MODE=coverage vendor/bin/phpunit --coverage-clover build/logs/clover.xml
  • composer test
  • git diff --exit-code

Le bridge Laravel est exclu de l’analyse statique Scrutinizer tant que les dépendances framework ne sont pas installées.

Scrutinizer notes

  • PHPStan est exécuté avec --memory-limit=512M
  • la couverture PHPUnit utilise maintenant un filtre source explicite dans phpunit.xml
  • le bridge Laravel reste exclu de l’analyse statique et de la couverture par défaut

Scrutinizer coverage mapping

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.

Refactoring quality

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

Static analysis compatibility

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.

Uniformisation des annotations

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.

Refactoring interne du repository SQLite

SqliteCountryRepository a été allégé par extraction de composants dédiés :

  • SqliteConnectionFactory
  • SqliteCountryHydrator
  • SqliteCountryQueryBuilder
  • SqliteStatementExecutor

L'API publique du repository reste inchangée.

Refactoring interne de CountriesCollection

CountriesCollection a été allégée par extraction de composants dédiés :

  • CountriesCollectionFilter
  • CountriesCollectionSorter
  • CountriesCollectionAggregator
  • CountriesCollectionExporter

L'API publique de la collection reste inchangée.

Refactoring interne de CsvCountryRepository

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.

Compatibilité PHPStan stricte

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.

Refactoring approfondi de CountriesCollection

CountriesCollection a été encore allégée avec de nouveaux composants internes :

  • CountriesCollectionCache
  • CountriesCollectionReadModel
  • CountriesCollectionSequence

La classe conserve la même API publique, mais délègue désormais la lecture, la séquence et la gestion du cache.

Couverture de tests initiale ajoutée

Un premier lot de tests PHPUnit a été ajouté pour améliorer rapidement le coverage sur les classes extraites :

  • CountriesCollectionFilterTest
  • CountriesCollectionAggregatorTest
  • CountriesCollectionSequenceTest
  • CountriesCollectionReadModelTest
  • SqliteCountryQueryBuilderTest
  • SqliteCountryHydratorTest

Un trait CountryFactoryTrait a aussi été ajouté pour factoriser les jeux de données de test.

Couverture de tests complémentaire ajoutée

Un second lot de tests PHPUnit a été ajouté pour continuer à remonter le coverage :

  • CountriesCollectionExporterTest
  • CountriesCollectionSorterTest
  • CountriesCollectionFacadeTest
  • SqliteConnectionFactoryTest
  • SqliteStatementExecutorTest
  • CsvCountryRepositoryTest

Tests complémentaires d'intégration ajoutés

Les points d'entrée publics et les composants critiques sont maintenant couverts aussi par des tests d'intégration ciblés :

  • DatasetValidatorTest
  • SqliteCountryRepositoryIntegrationTest
  • WorldDatasetsFactoryIntegrationTest

Une fixture SQLite temporaire a été ajoutée via SqliteFixtureTrait.

Structure src réorganisée

Le dossier src a été réorganisé pour réduire l'encombrement à la racine :

  • les repositories fichiers (ArrayCountryRepository, CsvCountryRepository, JsonCountryRepository) sont désormais dans src/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.

Migration PSR-4

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.

Clean Architecture légère

Le projet est désormais structuré autour de Domain, Application et Infrastructure.

About

All useful information about every country packaged as convenient little country objects. It includes data from ISO 3166 (countries and states/subdivisions ), ISO 4217 (currency), and E.164 (phone numbers).

Topics

Resources

License

Contributing

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages