hrabo/symfony-ares

Symfony 7+ bundle for the Czech ARES (ARES2) REST API: PSR-18 client, search + detail endpoints, retry, endpoint failover, optional cache and rate limiting.

Installs: 0

Dependents: 0

Suggesters: 0

Security: 0

Stars: 0

Watchers: 0

Forks: 0

Open Issues: 0

Type:symfony-bundle

pkg:composer/hrabo/symfony-ares

dev-master 2026-02-21 10:57 UTC

This package is auto-updated.

Last update: 2026-02-21 11:10:36 UTC


README

Symfony 7+ bundle for the Czech ARES (ARES2) REST API.

It provides:

  • Hrabo\SymfonyAres\AresClient – low-level client returning raw ARES payloads
  • Hrabo\SymfonyAres\Application\EconomicSubjectService – typed/DDD-friendly facade
  • Hrabo\SymfonyAres\Application\AresLustrationService – multi-source “lustration” orchestrator

Built-in resilience & safeguards:

  • Exponential backoff retries (429/5xx by default)
  • Client-side base URI failover (round-robin + cooldown)
  • Optional PSR-16 cache for GET endpoints (via Symfony Cache)
  • Optional in-memory fixed-window rate limiter

Install

composer require hrabo/symfony-ares

If Symfony Flex doesn’t auto-enable the bundle, add it manually:

// config/bundles.php
return [
    // ...
    Hrabo\SymfonyAres\HraboAresBundle::class => ['all' => true],
];

Configuration

Create config/packages/hrabo_ares.yaml:

hrabo_ares:
  base_uri: 'https://ares.gov.cz/ekonomicke-subjekty-v-be/rest/'
  # Optional list for client-side failover / load balancing
  base_uris: []
  endpoint_cooldown_seconds: 10

  search_path: 'ekonomicke-subjekty/vyhledat'

  default_headers:
    User-Agent: 'my-app/1.0 (+https://example.com)'

  cache:
    enabled: true
    pool: 'cache.app'
    ttl_seconds: 300

  rate_limit:
    enabled: false
    key: 'ares2'
    max_requests: 450
    window_seconds: 60

  retry:
    max_retries: 2
    base_delay_ms: 200
    max_delay_ms: 2000
    jitter: 0.2
    retry_http_status_codes: [429, 500, 502, 503, 504]

Usage

Low-level client (raw ARES payloads)

use Hrabo\SymfonyAres\AresClient;
use Hrabo\SymfonyAres\DTO\Pagination;
use Hrabo\SymfonyAres\DTO\SearchRequest;

final class AresDemo
{
    public function __construct(private AresClient $ares) {}

    public function run(): void
    {
        $detail = $this->ares->getEconomicSubject('00006947');

        $res = $this->ares->searchEconomicSubjects(new SearchRequest(
            criteria: ['obchodniJmeno' => 'Ministerstvo financí'],
            pagination: new Pagination(start: 0, count: 10),
            sort: [],
        ));

        // $detail is array<string,mixed>
        // $res->items is list<array<string,mixed>>
    }
}

Typed API (DDD-friendly)

use Hrabo\SymfonyAres\Application\EconomicSubjectService;
use Hrabo\SymfonyAres\DTO\Pagination;

final class SubjectDemo
{
    public function __construct(private EconomicSubjectService $subjects) {}

    public function run(): void
    {
        $subject = $this->subjects->getByIco('00006947');

        $result = $this->subjects->search(
            ['obchodniJmeno' => 'Ministerstvo'],
            new Pagination(0, 20),
        );

        foreach ($result->items as $item) {
            echo (string) $item->ico . ' ' . ($item->name ?? '') . PHP_EOL;
        }
    }
}

Lustration (multi-source due diligence)

use Hrabo\SymfonyAres\Application\AresLustrationService;
use Hrabo\SymfonyAres\DTO\Lustration\LustrationOptions;
use Hrabo\SymfonyAres\DTO\Lustration\LustrationQuery;

final class LustrationDemo
{
    public function __construct(private AresLustrationService $lustration) {}

    public function run(): void
    {
        $result = $this->lustration->run(
            LustrationQuery::forCompanyName('Ministerstvo financí'),
            new LustrationOptions(
                maxTargets: 5,
                relevantSourcesOnly: true, // uses CORE->seznamRegistraci to avoid needless 404s
            )
        );

        foreach ($result->subjects as $subject) {
            echo $subject->ico . PHP_EOL;
            foreach ($subject->bySource as $sourceKey => $sourceResult) {
                echo " - {$sourceKey}: {$sourceResult->status->value}" . PHP_EOL;
            }
        }
    }
}

Person name search – important limitation

ARES CORE does not expose a public “person → companies” search across statutory bodies.

So LustrationQuery::forPersonName() is implemented as a best-effort OSVČ / name-as-trade-name lookup: it tries a few obchodniJmeno variants like "Jan Novák", "Novák Jan", "Novák, Jan" and then proceeds with the same multi-source fetch.

If you need true person-to-company linking, you will need another legally available source (e.g. licensed commercial registry data) and treat ARES as a data enrichment layer.

License

MIT