Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Ares Bundle Laravel Package

arodax/ares-bundle

A Symfony bundle for querying the Czech ARES business register. Look up company details by IČO, including name, address, legal form, and registration data, with a simple service layer for easy integration into Symfony applications.

View on GitHub
Deep Wiki
Context7

Getting Started

First, the honest framing. arodax/ares-bundle is a Symfony bundle, not a Laravel package. There is no service provider, no facade, no config/ares.php — registration happens in config/bundles.php and configuration in YAML. So "day-to-day leverage" in a Laravel codebase means one of two things, and you must pick before writing any code:

  1. You are actually on Symfony. Then composer require arodax/ares-bundle, confirm the bundle is registered, and inject the ARES service into your controller/command. Day-to-day usage is a single service call returning a record by IČO. Everything below still applies conceptually.
  2. You are on Laravel (the likely case). Do not attempt composer require. Read the bundle as reference source — it will be small — and build a thin client against the current ARES REST API behind an interface you own.

Minimal steps for the Laravel path

# Do not install the bundle. Probe the live API first.
curl -s "https://ares.gov.cz/ekonomicke-subjekty-v-be/rest/ekonomicke-subjekty/00006947" | jq .

Probe at least three IČO values before designing anything: a normal active company, a defunct/inactive subject, and one with no DIČ (VAT number). The response shapes differ and that difference drives your DTO.

Where to look first in the bundle source

  • The class that actually builds the request and parses the response — that is the only transferable asset.
  • Which API generation it targets (legacy SOAP/WSDL vs. the current REST endpoints). If it's SOAP, the code is functionally dead; stop reading and build thin.
  • Its transport: PSR-18 (psr/http-client) is portable; symfony/http-client bindings mean you rewrite the transport anyway.
  • Its handling of "IČO not found" versus "IČO found but subject inactive" — these are the two error cases you will get wrong on your first attempt.

First use case to ship: IČO autofill on onboarding. User types 8 digits, you fetch the record, populate legal name + registered address + DIČ, and let them correct it. It is the highest-value, lowest-risk slice — it degrades to manual entry for free, which makes it the right place to prove the integration.

Implementation Patterns

Bind an interface, not the package. The whole point of a registry lookup is that the provider is swappable later (VIES for VAT, ISIR for insolvency, Cribis for credit risk). One interface, three implementations.

// app/Contracts/CompanyRegistry.php
interface CompanyRegistry
{
    public function findByIc(string $ico): ?CompanyRecord;
}

// app/Data/CompanyRecord.php  (a spatie/laravel-data or readonly DTO)
final readonly class CompanyRecord
{
    public function __construct(
        public string $ico,
        public string $name,
        public ?string $dic,
        public ?string $legalForm,
        public ?Address $registeredAddress,
        public bool $active,
        public ?string $raw,           // keep the payload; schemas change
    ) {}
}

The client. Plain Http facade, explicit timeout, no retries inside the client (retry belongs to the caller's policy, not a government endpoint you might hammer).

final class AresClient implements CompanyRegistry
{
    public function findByIc(string $ico): ?CompanyRecord
    {
        $response = Http::baseUrl(config('services.ares.base_url'))
            ->timeout(3)
            ->connectTimeout(2)
            ->acceptJson()
            ->get("/ekonomicke-subjekty/{$ico}");

        if ($response->status() === 404) {
            return null;                     // unknown IČO — not an error
        }

        $response->throw();                  // 5xx/timeouts bubble up to the caller's policy

        return AresMapper::toRecord($response->json());
    }
}

Cache decorator, not caching in the client. Registry data changes on a scale of days-to-weeks. Bind the cached decorator as the default and keep the raw client injectable for a manual refresh path.

final class CachedCompanyRegistry implements CompanyRegistry
{
    public function __construct(
        private CompanyRegistry $inner,
        private Cache $cache,
    ) {}

    public function findByIc(string $ico): ?CompanyRecord
    {
        // Cache nulls too, with a shorter TTL, so unknown IČOs don't hammer ARES.
        return $this->cache->remember(
            "ares:ico:{$ico}",
            now()->addHours(12),
            fn () => $this->inner->findByIc($ico),
        );
    }
}

Fake for tests, contract tests for reality. Two different jobs:

// Feature tests — no network.
$this->app->bind(CompanyRegistry::class, fn () => new FakeCompanyRegistry([
    '00006947' => CompanyRecord::fixture('00006947'),
]));

// Contract tests — recorded real payloads, run in CI.
it('parses a real ARES payload', function () {
    Http::fake(['*' => Http::response(json_decode(
        file_get_contents(base_path('tests/fixtures/ares/00006947.json')), true
    ))]);
    expect(app(CompanyRegistry::class)->findByIc('00006947'))->not->toBeNull();
});

The contract test is the single most valuable thing you will write here. ARES payloads change without notice; a recorded fixture turns an upstream schema change into a red CI run instead of silent production nulls.

Validate IČO before you call. Eight digits with a modulo-11 checksum:

function isValidIco(string $ico): bool
{
    if (!preg_match('/^\d{8}$/', $ico)) return false;
    $weights = [8, 7, 6, 5, 4, 3, 2];
    $sum = 0;
    for ($i = 0; $i < 7; $i++) $sum += (int) $ico[$i] * $weights[$i];
    $c = 11 - ($sum % 11);
    if ($c === 10) $c = 0;
    if ($c === 11) $c = 1;
    return $c === (int) $ico[7];
}

Reject locally, skip the HTTP call, return a form error. This alone removes a meaningful slice of junk traffic.

Where the pattern shows up day to day

  • Controllers / form requests: inject CompanyRegistry into a small CompanyLookupController returning JSON for the autofill widget. Never call the client from Blade or Livewire components directly.
  • Jobs: bulk enrichment, nightly reconciliation, and backfills go on a queue with rate limiting (Redis::throttle), not into a request cycle. ARES has no published rate limit; assume it will throttle cloud egress.
  • Console commands: a companies:refresh {ico} command that bypasses the cache and warms it — this is your manual-refresh escape hatch, and your on-call tool during an ARES incident.
  • Validation rules: a ValidIco rule plus an optional exists-in-ares rule used only on flows where a real registry hit is warranted.

Gotchas and Tips

API generation is the first question, not a detail. ARES retired the legacy SOAP services in favour of REST. If this bundle targets the old generation, it returns errors or empty results and its code quality is irrelevant. Probe the live endpoint with curl before you invest a day reading the source.

Silent failure looks like "no results". The worst failure mode: the wrapper can't reach or no longer understands the API and your code interprets that as "company not found" — so users are told their real, registered IČO is invalid. Distinguish 404 (genuinely unknown IČO → empty, cached briefly) from transport/5xx errors (→ return null with a different UI message: "we couldn't verify right now, enter details manually"). Log the distinction.

null DIČ is normal, not an error. Many Czech entities are not VAT-registered. Do not treat a missing dic as a failed lookup, and do not assume a non-null DIČ means VAT-registered today — ARES reflects registry state, not VIES validity. If VAT status gates invoicing, validate against VIES separately.

Defunct and historical subjects still return records. ARES returns entities that are dissolved or in liquidation. Your DTO needs an explicit active/status field, and any onboarding flow must decide what to do with it. Do not infer "valid supplier" from "record found."

Do not install the bundle "just to see". Any hard requirement on symfony/http-kernel or symfony/framework-bundle will fight Laravel's pinned Symfony components. If someone insists, run it in a scratch branch and composer why-not laravel/framework before committing. The adaptation cost exceeds the cost of writing ~200 lines against Http.

Provenance and advisories. Bitbucket-hosted, zero stars, zero dependents, no Packagist release cadence worth relying on. Vulnerabilities will not surface in composer audit the way Packagist packages do. Budget for an internal owner and assume no upstream fixes arrive on your timeline — this is a governance cost, not a code-quality judgement.

Instrument coverage, not just errors. Log every IČO that returns null (distinct from errors) with the calling context. That log is how you answer "are we actually serving the Czech users we built this for" and how you spot a checksum bug versus genuinely bad user input.

Extension points worth preserving from a wrapper like this: the IČO validator and the request/response mapping. Everything else — the DI extension, the config tree, the service definitions — is Symfony scaffolding with no meaning in Laravel. Lift the mapping, discard the container wiring, and keep the ARES payload in your DTO (raw) so you can backfill new fields without a re-fetch.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
htinlynn/genius-helper
maco-studios/hephaestus
elmogy/larafast
xeviant/laravel-paystack
gorilladash/laravel-website-sdk
martialbe/flysystem-oss
chondal/3312client
jeroen-g/flickr
opscale-co/nova-genbi
itsjustvita/laravel-retention
tequilarapido/trackit
gregsphotography/site-builder-filament
cabaoctavio/laravel-query-log
gregsphotography/site-builder-components
io-digital/clickatell
snowsoft/laravel-admin
oxygen/auth
relicsoftware/barakah-payment-gateway
tobya/work-with-composer
equipc/laravel-email-validator