Laravel BIR REGON separates its fluent API, GUS protocol gateway, and SOAP transport behind public contracts. The default bindings are scoped, so a session may be reused within one Laravel request or worker scope without becoming global state in a long-running process.
Replace BirClientInterface when an application needs to change the complete
package-level behavior, including searches, reports, service information,
diagnostics, and logout:
<?php
namespace App\Providers;
use App\Bir\CustomBirClient;
use cieplik206\BirRegon\BirClientInterface;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
public function register(): void
{
$this->app->scoped(
BirClientInterface::class,
CustomBirClient::class,
);
}
}
Register the replacement before BirRegonService or the BirRegon facade is
first resolved in the current scope.
The singular-identifier client methods are list-valued despite their names:
searchByNip(), searchByRegon(), and searchByKrs() each return a
list<CompanyData>, because one identifier may represent several GUS silos.
Batch search methods use the same list contract. The plural full-report methods
getFullReportsByNip(), getFullReportsByKrs(), and getFullReports() return a
list<FullCompanyReportData>. The corresponding singular full-report methods
must throw BirAmbiguousResultException rather than silently choosing one of
several distinct compatible report REGON values.
Implement BirGatewayInterface to keep the package’s BirClient, fluent
builders, validation, and public data objects while replacing access to the GUS
operations:
use App\Bir\CustomBirGateway;
use cieplik206\BirRegon\Contracts\BirGatewayInterface;
$this->app->scoped(
BirGatewayInterface::class,
CustomBirGateway::class,
);
The gateway contract works with package protocol values rather than vendor objects:
search(SearchCriteria $criteria) returns a list of SearchResult objects;fullReport(string $regon, ReportType $reportType) returns decoded report
rows as a list<array<string, string>>, including an empty or multi-row
list;bulkReport(DateTimeImmutable $date, BulkReportType $reportType) returns a
list of REGON strings;getValue(GetValueParameter $parameter) returns the requested scalar value;diagnostics() returns a DiagnosticsSnapshot containing the message code,
message, and session status;logout() ends the current session and returns the remote boolean result.All three diagnostic values in DiagnosticsSnapshot must belong to one
captured session SID. If a custom gateway renews an expired session while
collecting diagnostics, it must repeat the complete snapshot instead of mixing
values from the previous and renewed sessions. BirClient validates and maps
this protocol snapshot to the public DiagnosticsData object.
This is the preferred boundary for deterministic integration fakes and for a custom non-SOAP backend that can provide equivalent GUS BIR semantics.
Replacing the gateway bypasses the native transport graph, including its local request limiter. A gateway that still calls GUS must provide equivalent quota coordination; a deterministic fake that never performs network I/O does not need it.
SearchResult uses EntityType, Silo, and nullable NipStatus enums for
closed protocol vocabularies. A custom gateway must construct those enums and
must not pass arbitrary response strings through as public types. Applications
that need stricter input policy may configure
BIR_IDENTIFIER_VALIDATION=checksum, call PolishIdentifierChecksum
directly, or use the optional checksum argument on the NIP and REGON
SearchCriteria factories. Direct BirClient construction accepts
IdentifierValidationMode as its second constructor argument. Format-only
validation remains the default because that is the GUS request contract. KRS
has no checksum rule.
Implement BirSoapTransportInterface when requests need a different HTTP or
SOAP execution layer but the native authentication, session recovery, report
decoding, and exception translation should remain in place:
use App\Bir\CustomBirSoapTransport;
use cieplik206\BirRegon\Contracts\BirSoapTransportInterface;
$this->app->scoped(
BirSoapTransportInterface::class,
CustomBirSoapTransport::class,
);
The transport receives a BirOperation and its typed package parameters and
must return a TransportResponse. It owns the selected environment’s credential
and must report whether that credential is configured through
isAuthenticationConfigured(). A call(BirOperation::Login) arrives without
the credential in its parameter array, so the transport must serialize its own
safely stored key, apply the current session set through useSession(), and
decode the SOAP boundary. Implementations must not expose the API key, session
identifier, request XML, or raw response content in exception messages, stack
traces, debug output, or serialized state.
Native public and downstream methods mark NIP, REGON, KRS, SearchCriteria,
and operation parameter arrays with #[\SensitiveParameter]. PHP does not
inherit parameter attributes from an interface declaration. Every custom
client, gateway, transport, limiter, validator, or fake must repeat the
attribute on its own implementation parameters that can carry these values;
annotating only the package interface does not redact the custom stack frame.
The native implementation reserves the operation’s limiter cost after local
request construction and immediately before every HTTP exchange. Replacing
BirSoapTransportInterface bypasses that implementation, so the custom
transport must coordinate the GUS quota itself. It should propagate
BirRateLimitException without converting it to a generic transport failure.
See Request limits.
The native transport classifies failures at the boundary that produced them.
An exception from a custom HTTP sender becomes a transport failure, while an
unexpected exception from a custom limiter becomes
BirRateLimitException::limiterUnavailable(). Envelope or response-decoding
failures remain protocol failures. A completed HTTP 200 response with an
unsupported media type, including a text/html maintenance page, is a
transport failure. Completed HTTP 400 and 500 exchanges are decoded only when
they carry a supported SOAP media type; a valid SOAP 1.2 Fault is preserved as
a safe typed fault code without retaining its raw Reason, Detail, or
response body.
A decoded transport result is private and is available only through
TransportResponse::result(). Its debug representation is redacted. A
serialized response restores only as an inert tombstone and result() then
throws LogicException; inspecting that tombstone reports unavailable fields
without accessing discarded state. Transport responses must not be queued or
cached for later processing. A successful custom transport response for
GetValue or logout must not set resultWasNil, because those result elements
are non-nillable in the official WSDL. If a custom decoder encounters such a
response, return
TransportResponse::failure(TransportFailureType::Protocol, resultWasNil: true)
so the gateway reports the protocol violation without attempting session
renewal.
Custom gateways must implement both diagnostics(): DiagnosticsSnapshot and
logout(): bool. Custom clients must expose public diagnostics through
getDiagnostics() and also implement logout(): bool. Logout implementations
should make the no-session case idempotent and clear local session state even
when the remote operation fails.
Custom client, gateway, and transport bindings affect only the production graph
resolved from the container. BirRegon::sandbox() deliberately constructs an
isolated native sandbox graph so production extensions cannot accidentally
receive the sandbox credential or session. Replace BirRegonService as a whole
if an application also needs custom sandbox behavior.
When constructing BirRegonService manually, pass distinct production and
sandbox client instances and do not share stateful credential, session,
gateway, transport, or limiter objects between their graphs. Supplying the same
client object for both environments is rejected, because it would route
sandbox() calls through production state.
NativeSoapTransport requires an explicit BirRequestLimiterInterface when it
is instantiated directly. Laravel’s service provider passes
CacheBirRequestLimiter by default. Pass UnlimitedBirRequestLimiter only as
a deliberate opt-out when another layer coordinates every caller using the
credential, or in an isolated test that cannot perform network I/O.
The Laravel provider also passes one explicit proxy configuration to both
native environments when BIR_PROXY_URL is set. A direct
NativeSoapTransport construction can use its proxyUrl, proxyUsername, and
proxyPassword arguments. The URL accepts only an http or https scheme,
host, and optional port; credentials must use the separate arguments and the
username and password must be supplied together. The sender uses CONNECT,
keeps target TLS verification enabled with a TLS 1.2 minimum, and applies the
same minimum and verification to an HTTPS proxy. Proxy credentials require an
https:// URL; authenticated http:// proxy configuration is rejected before
network access, while an anonymous HTTP proxy remains supported.
Do not combine these explicit proxy arguments with a custom
BirHttpSenderInterface; construction fails closed because the package cannot
safely decide which component owns routing and credentials. When the explicit
proxy arguments are null, the native sender leaves libcurl’s ambient proxy
behavior intact, including HTTPS_PROXY and NO_PROXY. A fully custom sender
owns its own proxy, TLS, credential-redaction, and routing policy, including
enforcement of the deployment’s minimum TLS version.
The native sender reuses its cURL handle but limits any connection to five
minutes when the installed libcurl exposes CURLOPT_MAXLIFETIME_CONN. This
forces periodic connection and certificate revalidation. Cloning
NativeSoapTransport also clones its mutable envelope builder, so later SID
changes cannot split the session used in the HTTP header from the one written
to the SOAP body. Custom transports own equivalent pooling and clone-safety
decisions.
The limiter contract is deliberately small:
BirRequestLimiterInterface::acquire(BirOperation $operation, array $parameters = []): void.
It returns normally when the caller may proceed and throws
BirRateLimitException otherwise. A search limiter can inspect the
SearchCriteria stored in $parameters['criteria'] to charge its
identifierCount(); every other native operation costs one. The package
service provider constructs its configured limiter directly, so replacing the
interface in the container alone does not change the native production graph.
Pass a limiter to NativeSoapTransport or replace the transport when a custom
policy is required.
A limiter that needs the native search/report recovery boundary may also
implement BirRateLimitScopeInterface:
interface BirRateLimitScopeInterface
{
public function beginRateLimitScope(): void;
public function endRateLimitScope(): void;
}
NativeBirGateway brackets each logical callForRecords() sequence with these
methods when the transport implements the interface, and NativeSoapTransport
forwards them when its limiter implements it. The protocol is an explicit,
nest-safe begin/end pair, not a closure. A custom transport or limiter that does
not need scoped pacing may implement only its primary interface.
The built-in cache limiter supports only the exact base Laravel
Illuminate\Cache\Repository with ArrayStore, DatabaseStore, FileStore,
MemcachedStore, or RedisStore. It deliberately rejects tagged/decorated or
subclassed repositories and every other store, even if it exposes locks. To use
DynamoDB, FailoverStore, MemoizedStore, NullStore, or a custom backend,
write a BirRequestLimiterInterface implementation with coordination semantics
appropriate to that backend and pass it to NativeSoapTransport.
With the built-in CurlBirHttpSender, each independently constructed native
transport owns one persistent cURL handle. curl_reset() clears callbacks,
request bodies, and SID headers between calls while keeping that handle’s
connection, DNS, and TLS session caches reusable. Production and sandbox graphs
construct separate senders and never share their handles. Cloning a native
transport intentionally shares its sender while cloning the SID-bearing
envelope state; when the sender is native, the clone therefore shares its cURL
handle and must not be used concurrently. A supplied custom sender owns its own
pooling and sharing rules. A custom transport should preserve credential and
environment isolation even if it uses a different connection pool.
Both access styles resolve BirRegonService from the same container scope:
use cieplik206\BirRegon\BirRegonService;
use cieplik206\BirRegon\Facades\BirRegon;
$service = app(BirRegonService::class);
$companies = $service->forNip($nip)->get();
$sameResults = BirRegon::forNip($nip)->get();
Choose dependency injection for explicit domain dependencies and the facade for concise Laravel integration code. Applications upgrading a custom 1.x factory or client should also read the Upgrade guide for 2.0.