class Client extends AbstractClient implements RoleCredentialsInterface (View source)

CNR API Client

Home of the two capabilities the CNR platform has and the flat IBS/Moniker platform does not: API sessions — the accessors {\CNIC\CNR\getSession()}/{@see \CNIC\CNR\setSession()} plus the lifecycle {\CNIC\CNR\login()}/{@see \CNIC\CNR\logout()}/{\CNIC\CNR\saveSession()}/{@see \CNIC\CNR\reuseSession()} — and role credentials ({\CNIC\RoleCredentialsInterface}). Both read state that only {\CNIC\CNR\SocketConfig} carries, which is why they live here rather than on {\CNIC\AbstractClient} — see the note there.

The lifecycle methods were a SessionCapable trait used by a SessionClient subclass until RSRMID-2969. The trait had one host, the subclass added nothing else, and nothing in the SDK ever produced a session-less CNR client — so the split bought a distinction no code made, at the price of three file opens to find login(). It also carried a @psalm-require-extends Client, which is the trait admitting it was only ever a part of this class. Do not reintroduce either: if a genuinely session-less CNR client ever becomes a real use case, that is a new type with a narrower contract, not a trait extracted back out.

Constants

private VERSION

Current module version.

Kept in sync automatically by semantic-release — see .releaserc.json.

Properties

protected array<string, mixed> $context

context data for the client

from  AbstractClient
protected AbstractSocketConfig $socketConfig

Object covering API connection data — the one home for connection configuration; see {getSocketConfig()}.

from  AbstractClient
protected bool $debugMode

activity flag for debug mode

from  AbstractClient
protected string $userAgent

User agent sent with every request. Empty until {setUserAgent()} is called; while it is empty {getUserAgent()} derives the SDK default.

from  AbstractClient
protected LoggerInterface $logger

logger instance for debug mode

from  AbstractClient
protected TransportInterface $transport

HTTP transport layer

from  AbstractClient

Methods

__construct(AbstractSocketConfig|null $socketConfig = null)

Narrowed from {AbstractClient::__construct()}'s ?AbstractSocketConfig, mirroring the covariant {newSocketConfig()} factory below.

getSocketConfig()

The CNR SocketConfig, narrowed from the shared {AbstractSocketConfig} type of {AbstractClient::$socketConfig}.

request(array $cmd = [], string $path = "api/call.cgi")

Perform API request using the given command

performRequest(array $cmd, string $path = "")

Shared request lifecycle (template method). Never reimplement it in a brand; vary it through exactly two hooks — {buildCommand()} (command flattening) and {newResponse()} (covariant Response factory) — plus the {newSocketConfig()} subtype, which is where a brand-mandatory cURL option would go. No brand declares one.

array
buildCommand(array $cmd)

Flatten the given command into wire form (CNR uppercase key/value pairs) and convert its IDN parameters to punycode.

newResponse(string $raw, array $cmd, array $cfg, string|null $error = null)

Instantiate a CNR Response for the given raw payload.

newSocketConfig()

Instantiate CNR SocketConfig

newTransport()

Instantiate the HTTP transport for this client. Mirrors {newSocketConfig()} — the default is the production cURL transport; override or {setTransport()} to inject a test double so the request() lifecycle can run offline.

setTransport(TransportInterface $transport)

Inject a custom HTTP transport (e.g. a record/replay cassette transport for offline tests) in place of the default {HttpTransport}.

getTransport()

The transport this client posts through — the read half of {setTransport()}, mirroring {getSocketConfig()}.

newLogger(LogSinkInterface $sink)

Instantiate the CNR logger writing to the given sink

setLogSink(LogSinkInterface $sink)

Route debug output somewhere other than standard output, keeping this brand's format.

setCustomLogger(LoggerInterface $customLogger)

Set custom logger to use instead of the default one — use this to replace the format as well as the destination. Create your own class extending \CNIC\AbstractLogger (format only) or implementing \CNIC\LoggerInterface (format and destination).

getLogger()

The logger this client writes debug records through — the read half of {setCustomLogger()}/{@see setLogSink()}, mirroring {getSocketConfig()}.

enableDebugMode()

Enable debug output to STDOUT

disableDebugMode()

Disable debug output

string
getPOSTData(array $cmd, bool $maskSecrets = false)

Serialize given command for POST request including connection configuration data

string
getURL()

Get the API connection url that is currently set

setSocketTimeout(int $timeoutSeconds)

Set the request timeout in seconds (default 300).

int
getSocketTimeout()

Get the request timeout in seconds currently configured.

setUserAgent(string $label, string $revision, array $modules = [])

Set a custom user agent (for platforms that use this SDK)

string
getUserAgent()

Get the user agent string — the one set via {setUserAgent()}, or the SDK default when none was.

setExtraCurlOptions(array $opts)

Merge additional cURL options into the bag, overriding existing values on key collision. Forwards to {AbstractSocketConfig::setExtraCurlOptions()}, whose docblock carries the detail: what reaches the wire, and the two sets of keys that are refused — the SDK-managed settings ({AbstractSocketConfig::MANAGED_OPTIONS}, rejected here and now) and the transport's own ({HttpTransport::PROTECTED_OPTIONS}, rejected on the next request).

resetCurlOptions()

Restore the cURL option bag to the brand defaults, discarding anything previously handed to {setExtraCurlOptions()}. Options only — the proxy and referer are separate state and survive; see {AbstractSocketConfig::resetCurlOptions()}.

setProxy(string $proxy = "")

Set proxy to use for API communication

string|null
getProxy()

Get proxy configuration for API communication

setReferer(string $referer = "")

Set Referer to use for API communication

string|null
getReferer()

Get Referer configuration for API communication

string
getVersion()

Get the current module version

setURL(string $url)

Set another connection url to be used for API communication

setCredentials(string $login = "", string $password = "")

Set Credentials to be used for API communication.

useHighPerformanceConnectionSetup()

Activate High Performance Setup — route requests through the co-located proxy on loopback.

array
IDNConvert(array $domains)

Convert domain names to idn + punycode.

array
executeCurl(string $postData, array $cfg)

Delegate cURL execution to the transport layer.

void
close()

Close all cURL connections

string
getLiveUrl()

Get LIVE system URL.

System|null
getSystem()

Get the API system in use, or null when the configured URL is neither of the brand's two known endpoints.

bool
isOTE()

Check whether the OT&E system is in use

useOTESystem()

Set OT&E System for API communication

useLIVESystem()

Set LIVE System for API communication (this is the default setting)

setContext(array $context)

Set context data for the client

string|null
getSession()

Get the API Session ID that is currently set, or null when there is none.

setSession(string $session = "")

Set an API session id to be used for API communication.

login()

Perform API login to start session-based communication.

logout()

Perform API logout to close the API session in use.

saveSession(array $session)

Apply session data to a PHP session object

reuseSession(array $session)

Rebuild connection settings from a PHP session object.

setRoleCredentials(string $accountId = "", string $roleId = "", string $password = "")

Set Role Credentials to be used for API communication.

Response|null
requestNextResponsePage(Response $currentPage)

Request the next page of list entries for the current list query

array
requestAllResponsePages(array $cmd)

Request all pages/entries for the given query command

Details

__construct(AbstractSocketConfig|null $socketConfig = null)

Narrowed from {AbstractClient::__construct()}'s ?AbstractSocketConfig, mirroring the covariant {newSocketConfig()} factory below.

The narrowing is the point: new Client(new \CNIC\IBS\SocketConfig()) has to be an analysis error at the call site, not an {\CNIC\Exception\UnsupportedFeatureException} thrown later from {\CNIC\CNR\getSocketConfig()}. PHP exempts constructors from LSP under class inheritance, so a subclass may narrow a parameter here where it could not on any other method; PHPStan and Psalm both accept the declaration and enforce it against callers.

Parameters

AbstractSocketConfig|null $socketConfig

connection configuration to adopt; null has the brand build its default via {\CNIC\newSocketConfig()}

AbstractSocketConfig getSocketConfig()

The CNR SocketConfig, narrowed from the shared {AbstractSocketConfig} type of {AbstractClient::$socketConfig}.

The one narrowing point for CNR's platform-specific config state (session, persistent, role separator). A typed property cannot be re-declared with a narrower type in PHP, so the covariant {\CNIC\CNR\newSocketConfig()} factory cannot inform the property's type — this accessor carries that knowledge instead, in exactly one place, rather than each caller asserting.

It is the covariant override of {\CNIC\AbstractClient::getSocketConfig()}, and deliberately the only one: two methods narrowing the same property would be two places to keep in step. Consumers holding CNR\Client therefore reach getSession()/setPersistent() with no narrowing of their own.

The guard is unreachable for correctly-typed callers. There are two writers of the property — the covariant newSocketConfig() above and the constructor parameter (RSRMID-2966) — and both are narrowed to SocketConfig, so neither can seat a foreign config without an analysis error at the call site first. It throws rather than assert()ing because assert() is compiled out when zend.assertions is disabled, which would turn a subclass that returned the wrong config into an undefined-method fatal instead of a named SDK exception.

ResponseInterface request(array $cmd = [], string $path = "api/call.cgi")

Perform API request using the given command

Parameters

array $cmd

API command

string $path

path segment appended to the base URL to select the endpoint

Return Value

ResponseInterface

protected ResponseInterface performRequest(array $cmd, string $path = "")

Shared request lifecycle (template method). Never reimplement it in a brand; vary it through exactly two hooks — {buildCommand()} (command flattening) and {newResponse()} (covariant Response factory) — plus the {newSocketConfig()} subtype, which is where a brand-mandatory cURL option would go. No brand declares one.

Brand-specific command rewriting belongs behind buildCommand(), not here: CNR's IDN conversion lives in {\CNIC\CNR\IDNCommandRewriter}, and a shared step gated by a flag only one brand sets is the shape that replaced.

Parameters

array $cmd

API command

string $path

path segment appended to the base URL to select the endpoint

Return Value

ResponseInterface

protected array buildCommand(array $cmd)

Flatten the given command into wire form (CNR uppercase key/value pairs) and convert its IDN parameters to punycode.

The IDN rewrite is CNR's alone — IBS/Moniker convert server-side — so it runs here, in the brand hook, and not on {\CNIC\AbstractClient} behind a flag; see {\CNIC\CNR\IDNCommandRewriter}. It must run after the flattening: the rules match wire keys (NAMESERVER0, OBJECTID), not the caller's nested, arbitrarily-cased input.

Parameters

array $cmd

API command

Return Value

array

protected ResponseInterface newResponse(string $raw, array $cmd, array $cfg, string|null $error = null)

Instantiate a CNR Response for the given raw payload.

Parameters

string $raw
array $cmd

flattened command that produced the response

array $cfg

connection config used for the request

string|null $error

transport error, if any; non-null means $raw is unusable and the brand's "httperror" template is substituted instead

Return Value

ResponseInterface

protected AbstractSocketConfig newSocketConfig()

Instantiate CNR SocketConfig

Return Value

AbstractSocketConfig

protected TransportInterface newTransport()

Instantiate the HTTP transport for this client. Mirrors {newSocketConfig()} — the default is the production cURL transport; override or {setTransport()} to inject a test double so the request() lifecycle can run offline.

Return Value

TransportInterface

AbstractClient setTransport(TransportInterface $transport)

Inject a custom HTTP transport (e.g. a record/replay cassette transport for offline tests) in place of the default {HttpTransport}.

Parameters

TransportInterface $transport

Return Value

AbstractClient

TransportInterface getTransport()

The transport this client posts through — the read half of {setTransport()}, mirroring {getSocketConfig()}.

Injecting a canned {\CNIC\TransportInterface} is how an embedding application drives the request() lifecycle offline; this accessor is how it reads the double back afterwards to assert what the client handed over, without reaching past the client for a protected property.

Return Value

TransportInterface

protected LoggerInterface newLogger(LogSinkInterface $sink)

Instantiate the CNR logger writing to the given sink

Parameters

LogSinkInterface $sink

Return Value

LoggerInterface

AbstractClient setLogSink(LogSinkInterface $sink)

Route debug output somewhere other than standard output, keeping this brand's format.

This is the seam integrators want: the brand formatter is the part with the logic, the destination is the part that varies per host application. Passing a fresh {\CNIC\EchoSink} restores the shipped default, discarding any logger set via {\CNIC\setCustomLogger()}.

Parameters

LogSinkInterface $sink

Return Value

AbstractClient

AbstractClient setCustomLogger(LoggerInterface $customLogger)

Set custom logger to use instead of the default one — use this to replace the format as well as the destination. Create your own class extending \CNIC\AbstractLogger (format only) or implementing \CNIC\LoggerInterface (format and destination).

Parameters

LoggerInterface $customLogger

Return Value

AbstractClient

LoggerInterface getLogger()

The logger this client writes debug records through — the read half of {setCustomLogger()}/{@see setLogSink()}, mirroring {getSocketConfig()}.

Which of the two setters last ran decides what comes back: a brand logger built around the injected sink, or the custom logger supplied verbatim. That composition rule is order-dependent, so an embedding application that installs its own logger needs a way to confirm the one it supplied is the one in place.

Return Value

LoggerInterface

AbstractClient enableDebugMode()

Enable debug output to STDOUT

Return Value

AbstractClient

AbstractClient disableDebugMode()

Disable debug output

Return Value

AbstractClient

string getPOSTData(array $cmd, bool $maskSecrets = false)

Serialize given command for POST request including connection configuration data

Parameters

array $cmd

API command to encode

bool $maskSecrets

Return Value

string

string getURL()

Get the API connection url that is currently set

Return Value

string

AbstractClient setSocketTimeout(int $timeoutSeconds)

Set the request timeout in seconds (default 300).

The only way to change the timeout: CURLOPT_TIMEOUT in the option bag is rejected ({\CNIC\setExtraCurlOptions()}) rather than quietly overriding what {\CNIC\getSocketTimeout()} reports.

Parameters

int $timeoutSeconds

0 carries cURL's meaning — no timeout

Return Value

AbstractClient

Exceptions

InvalidConfigurationException

int getSocketTimeout()

Get the request timeout in seconds currently configured.

Return Value

int

AbstractClient setUserAgent(string $label, string $revision, array $modules = [])

Set a custom user agent (for platforms that use this SDK)

Parameters

string $label
string $revision
array $modules

further modules to add to user agent string

Return Value

AbstractClient

string getUserAgent()

Get the user agent string — the one set via {setUserAgent()}, or the SDK default when none was.

A pure read — keep it that way. Memoising the default into {$userAgent} would make a getter write during a request, and there is nothing worth memoising: the value is a handful of constants and one php_uname() call.

Return Value

string

AbstractClient setExtraCurlOptions(array $opts)

Merge additional cURL options into the bag, overriding existing values on key collision. Forwards to {AbstractSocketConfig::setExtraCurlOptions()}, whose docblock carries the detail: what reaches the wire, and the two sets of keys that are refused — the SDK-managed settings ({AbstractSocketConfig::MANAGED_OPTIONS}, rejected here and now) and the transport's own ({HttpTransport::PROTECTED_OPTIONS}, rejected on the next request).

Parameters

array $opts

cURL options keyed by CURLOPT_* constant

Return Value

AbstractClient

Exceptions

UnsupportedFeatureException

AbstractClient resetCurlOptions()

Restore the cURL option bag to the brand defaults, discarding anything previously handed to {setExtraCurlOptions()}. Options only — the proxy and referer are separate state and survive; see {AbstractSocketConfig::resetCurlOptions()}.

Return Value

AbstractClient

AbstractClient setProxy(string $proxy = "")

Set proxy to use for API communication

Parameters

string $proxy

empty string resets it, restoring a direct connection

Return Value

AbstractClient

string|null getProxy()

Get proxy configuration for API communication

Return Value

string|null

AbstractClient setReferer(string $referer = "")

Set Referer to use for API communication

Parameters

string $referer

empty string resets it, so no Referer is sent

Return Value

AbstractClient

string|null getReferer()

Get Referer configuration for API communication

Return Value

string|null

string getVersion()

Get the current module version

Return Value

string

AbstractClient setURL(string $url)

Set another connection url to be used for API communication

Parameters

string $url

Return Value

AbstractClient

AbstractClient setCredentials(string $login = "", string $password = "")

Set Credentials to be used for API communication.

On CNR this discards any active API session: CNR\SocketConfig::setLogin()/ setPassword() clear the session id, because a session and a password are alternative credentials on the wire and the newer one is authoritative. The invariant is deliberate and pinned by a test — CNR\Client::reuseSession() depends on it, restoring the login first and the session second. Set the session after the credentials, never before.

Parameters

string $login

empty string resets the stored login

string $password

empty string resets the stored password

Return Value

AbstractClient

AbstractClient useHighPerformanceConnectionSetup()

Activate High Performance Setup — route requests through the co-located proxy on loopback.

Brand-agnostic and therefore shared — the caller supplies the local proxy, so IBS/Moniker may opt in too. It records a flag on the config rather than rewriting the URL, so the selected system survives it; see {\CNIC\AbstractSocketConfig::useHighPerformanceConnectionSetup()}.

Return Value

AbstractClient

array IDNConvert(array $domains)

Convert domain names to idn + punycode.

Brand-agnostic and therefore shared: a thin pass-through to the vendor converter for callers who want to normalise a name explicitly. The automatic rewrite of an outbound command is a different thing and is deliberately not here — which parameters carry a domain name is CNR knowledge, and it lives in {\CNIC\CNR\IDNCommandRewriter}.

Parameters

array $domains

list of domain names (or tlds)

Return Value

array

protected array executeCurl(string $postData, array $cfg)

Delegate cURL execution to the transport layer.

The configured options are handed over as they are, and beat the transport's own defaults in {\CNIC\HttpTransport::post()} (PHP's + keeps the left operand on a duplicate key).

Do not re-add a per-request options argument here: it would be a route into the option set that skips {\CNIC\AbstractSocketConfig::MANAGED_OPTIONS}, and so a way for a subclass to put a second answer behind getProxy(). A per-request option belongs on the config before the request, or on a transport the caller drives themselves.

Parameters

string $postData
array $cfg

connection config

Return Value

array

[rawResponse, errorMessage|null]

Exceptions

UnsupportedFeatureException

void close()

Close all cURL connections

Return Value

void

string getLiveUrl()

Get LIVE system URL.

There is deliberately no matching getOTEUrl() here: a configuration value no longer needs a hand-written forwarder, so read the OT&E endpoint from {\CNIC\getSocketConfig()}.

Return Value

string

System|null getSystem()

Get the API system in use, or null when the configured URL is neither of the brand's two known endpoints.

Derived from the URL rather than stored beside it, which is what makes it impossible for the two to disagree — and why the return type is nullable: after a setURL() to some other host there is no honest OT&E-or-LIVE answer to give. See {\CNIC\AbstractSocketConfig::getSystem()}.

Return Value

System|null

bool isOTE()

Check whether the OT&E system is in use

Return Value

bool

AbstractClient useOTESystem()

Set OT&E System for API communication

Return Value

AbstractClient

AbstractClient useLIVESystem()

Set LIVE System for API communication (this is the default setting)

Return Value

AbstractClient

AbstractClient setContext(array $context)

Set context data for the client

Parameters

array $context

Return Value

AbstractClient

string|null getSession()

Get the API Session ID that is currently set, or null when there is none.

CNR-only: IBS/Moniker have no session concept, so the method is absent there rather than present and answering null.

Return Value

string|null

Client setSession(string $session = "")

Set an API session id to be used for API communication.

Setting a session clears the stored password: the two are alternative credentials on the wire, and CNR's SocketConfig treats the newer one as authoritative. That holds on the reset path too — setSession("") leaves neither, so re-set the credentials to go back to password authentication (see {\CNIC\CNR\SocketConfig::setSession()}).

Parameters

string $session

empty string resets it

Return Value

Client

Response login()

Perform API login to start session-based communication.

The connection is made persistent for the duration of the login request only, then put back: the session id, not the socket, is what the following requests reuse.

"For the duration" includes a duration that ends in a throw, which is why the reset sits in a finally rather than on the line after the call (RSRMID-2980). request() is not throw-free — a transport-owned cURL option or a restated transport-owned header raises {\CNIC\Exception\UnsupportedFeatureException} out of {\CNIC\HttpTransport::post()}, and setExtraCurlOptions() deliberately does not pre-empt that check. An unconditional reset on the following line was therefore skippable, and a skipped reset leaves persistent stuck true, so every later request on this client silently asks the API for a session.

Only the reset is unconditional. The session write stays inside the success branch: a login that threw established nothing.

Return Value

Response

Response logout()

Perform API logout to close the API session in use.

The transport is closed whether or not the command succeeded — a failed StopSession still leaves a connection this client will not reuse.

"Whether or not it succeeded" includes "whether or not it returned at all", which is why the close sits in a finally (RSRMID-2980). A throw out of request() — see {\CNIC\CNR\login()} for the reachable path — used to skip the close on the following line and leak the transport's connection handle for the rest of this client's lifetime. Clearing the session stays on the success branch: an unconfirmed StopSession is no reason to forget a session id that may still be live server-side.

Return Value

Response

Client saveSession(array $session)

Apply session data to a PHP session object

Parameters

array $session

php session instance ($_SESSION)

Return Value

Client

Client reuseSession(array $session)

Rebuild connection settings from a PHP session object.

The two calls are ordered, not interchangeable: setCredentials() clears the session id (a session and a password are alternative credentials, and CNR's SocketConfig treats the newer one as authoritative), so restoring the session second is what makes this work.

Parameters

array $session

php session object ($_SESSION)

Return Value

Client

RoleCredentialsInterface setRoleCredentials(string $accountId = "", string $roleId = "", string $password = "")

Set Role Credentials to be used for API communication.

CNR-only capability (see {\CNIC\RoleCredentialsInterface}): a role login is the account id, the ":" role separator and the role user id, authenticated with that role user's own password.

Parameters

string $accountId

empty string resets it

string $roleId

empty string logs in as the account itself, without a role

string $password

the role user's own password; empty string resets it

Return Value

RoleCredentialsInterface

Response|null requestNextResponsePage(Response $currentPage)

Request the next page of list entries for the current list query

The continuation is assembled from two sources, deliberately: the command that produced $currentPage ({\CNIC\CNR\continuationCommand()}) and $currentPage's own pagination state. Response data — LIMIT, LAST — is not masked and is read straight off the response; command parameters are not.

Parameters

Response $currentPage

Return Value

Response|null

Exceptions

PaginationException

array requestAllResponsePages(array $cmd)

Request all pages/entries for the given query command

Parameters

array $cmd

API list command to use

Return Value

array