summaryrefslogtreecommitdiffstats
path: root/lib/cepis/Address.php
diff options
context:
space:
mode:
authorMurilo Ijanc <[email protected]>2026-07-26 16:41:48 -0300
committerMurilo Ijanc <[email protected]>2026-07-26 16:47:14 -0300
commit535dbda6b48171f8115b455e54790de94d97d42a (patch)
tree5c0410011e61a883bdf063ee612875a637fb1d22 /lib/cepis/Address.php
downloadwoocommerce-535dbda6b48171f8115b455e54790de94d97d42a.tar.gz
Initial importv0.1.0
Diffstat (limited to 'lib/cepis/Address.php')
-rw-r--r--lib/cepis/Address.php133
1 files changed, 133 insertions, 0 deletions
diff --git a/lib/cepis/Address.php b/lib/cepis/Address.php
new file mode 100644
index 0000000..8b970aa
--- /dev/null
+++ b/lib/cepis/Address.php
@@ -0,0 +1,133 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Cepis;
+
+/**
+ * A CEP and everything the database knows about it.
+ *
+ * **Property names are the API's own JSON keys, in Portuguese** —
+ * `logradouro`, `bairro`, `localidade`. Everything else in this library is
+ * English; the data fields are the one deliberate exception, so that what you
+ * read in the JSON at https://cep.is/01001000 is exactly what you read on the
+ * object, with no translation layer and no field-by-field mapping to keep in
+ * your head. The values are Portuguese either way — they come from the
+ * Correios database.
+ *
+ * Fields the database does not have for that CEP come back as empty strings,
+ * never `null` — that is what the API returns.
+ *
+ * {@see Address::$geo} is the exception: it is `null` when there is no
+ * coordinate. There the difference is real, because the API **omits** the
+ * whole field in that case (about 43% of CEPs) rather than sending null.
+ *
+ * {@see Address::$raw} holds the response exactly as it arrived,
+ * uninterpreted, so a field the API grows before this library is updated is
+ * not lost.
+ */
+final class Address
+{
+ /**
+ * The JSON keys mapped onto properties, in the API's own order.
+ */
+ public const FIELDS = [
+ 'cep', 'logradouro', 'complemento', 'unidade', 'bairro', 'localidade',
+ 'uf', 'estado', 'regiao', 'ibge', 'gia', 'ddd', 'siafi',
+ ];
+
+ /**
+ * @param array<string, mixed> $raw
+ */
+ public function __construct(
+ public readonly string $cep = '',
+ public readonly string $logradouro = '',
+ public readonly string $complemento = '',
+ public readonly string $unidade = '',
+ public readonly string $bairro = '',
+ public readonly string $localidade = '',
+ public readonly string $uf = '',
+ public readonly string $estado = '',
+ public readonly string $regiao = '',
+ public readonly string $ibge = '',
+ public readonly string $gia = '',
+ public readonly string $ddd = '',
+ public readonly string $siafi = '',
+ public readonly ?Geo $geo = null,
+ public readonly array $raw = [],
+ ) {
+ }
+
+ /**
+ * Build from the decoded response.
+ *
+ * Unknown fields are ignored during construction but preserved in
+ * {@see Address::$raw}. A new field in the API therefore cannot break this
+ * library.
+ *
+ * @param array<string, mixed> $data
+ */
+ public static function fromArray(array $data): self
+ {
+ $values = [];
+ foreach (self::FIELDS as $name) {
+ $value = $data[$name] ?? '';
+ $values[$name] = is_scalar($value) ? (string) $value : '';
+ }
+
+ return new self(
+ cep: $values['cep'],
+ logradouro: $values['logradouro'],
+ complemento: $values['complemento'],
+ unidade: $values['unidade'],
+ bairro: $values['bairro'],
+ localidade: $values['localidade'],
+ uf: $values['uf'],
+ estado: $values['estado'],
+ regiao: $values['regiao'],
+ ibge: $values['ibge'],
+ gia: $values['gia'],
+ ddd: $values['ddd'],
+ siafi: $values['siafi'],
+ geo: Geo::fromArray($data['geo'] ?? null),
+ raw: $data,
+ );
+ }
+
+ /**
+ * Read any field of the raw response, including one the API grew after
+ * this release, or `null` when it is absent.
+ */
+ public function field(string $name): ?string
+ {
+ $value = $this->raw[$name] ?? null;
+
+ return is_scalar($value) ? (string) $value : null;
+ }
+
+ /**
+ * The CEP with its mask: `01001-000`.
+ *
+ * The API always returns it unmasked; formatting is presentation, and it
+ * lives here so it does not become a repeated `sprintf` in every project
+ * that uses this.
+ */
+ public function formattedCep(): string
+ {
+ return substr($this->cep, 0, 5) . '-' . substr($this->cep, 5, 3);
+ }
+
+ /**
+ * @return array<string, mixed>
+ */
+ public function toArray(): array
+ {
+ $out = [];
+ foreach (self::FIELDS as $name) {
+ $out[$name] = $this->{$name};
+ }
+ $out['geo'] = $this->geo?->toArray();
+
+ return $out;
+ }
+}