novo-connect/somali-phone
A Laravel/PHP package for Somali phone numbers. Validate, normalize, and format local and +252 mobile numbers with carrier-aware parsing for clean, consistent user data.
Install the package and check compatibility before anything else:
composer require novo-connect/somali-phone
config/somali-phone.php (if published — prefix/operator tables, default behaviour), and lang/en + lang/so for message keys.php artisan vendor:publish --tag=somali-phone-config
php artisan vendor:publish --tag=somali-phone-lang
First use case: a registration or checkout form with a Somali mobile number. You want to accept the messy real-world input (+252 61 xxx xxxx, 00252..., 06..., Arabic-Indic digits), reject invalid numbers, then store a single canonical form (E.164, e.g. +2526...) so downstream lookups, SMS routing and reporting are consistent.
Minimal flow:
// FormRequest
public function rules(): array
{
return [
'phone' => ['required', 'string', new SomaliPhone()], // confirm exact class name in README
];
}
Then normalize and persist the canonical value rather than the raw input. Keep the raw string in a separate column if you need an audit trail of what the user typed.
Validate, then normalize, then store. Treat these as three distinct steps. Validation answers "is this a real Somali mobile number?"; normalization answers "what is the one canonical representation?"; storage should only ever see the canonical form. Doing normalization inside the validator and relying on that side effect is fragile — call the normalizer explicitly before saving.
Use the right rule for the right layer.
FormRequest / controller validation → the Laravel validation rule.protected function casts(): array
{
return [
'phone' => SomaliPhoneCast::class, // confirm class name
];
}
Verify the cast is idempotent: setting an already-normalized +252... value must not double-prefix it. This matters for seeders, factories, imports and updateOrCreate.
Layer operator/wallet metadata on top. After a number is valid and normalized, the operator, virtual network and wallet lookups are cheap enrichment. Typical uses:
$result = $normalizer->parse($request->input('phone'));
// $result->e164, operator, virtual network, wallet, waafiSupported()
Expose this in an API resource as a nested object (phone.e164, phone.operator, phone.wallet) so the frontend never re-parses.
Handle virtual networks explicitly. Somnet, Soltelco and Durdur mean the prefix alone does not always equal the operating MNO. If your business logic keys off "operator" for pricing or routing, decide deliberately whether you mean the host network or the virtual network, and use the corresponding field. Do not assume one implies the other.
Bulk imports. Normalizing a CSV of contacts should collect per-row failures rather than aborting the whole import. Wrap the parse call, catch the exception, and write rejected rows to an error report with the original value and reason. This is the single highest-value pattern for day-to-day use, since imports are where dirty data actually shows up.
Localization. The package ships English and Somali messages. Set the app locale per request (or per user preference) and let the package resolve messages. If you need custom wording, publish the lang files rather than overriding validation messages globally — targeted overrides avoid collateral damage to other rules.
Testing. Build a fixture set from the documented NCA prefixes plus the awkward formats: leading 0, 00252, bare 252, spaces/dashes, Arabic-Indic digits. Assert both the accept/reject outcome and the normalized output. Don't use real subscriber numbers in tests.
+252..., 00252... and 0... in the same column will make operator lookups, deduplication and SMS delivery inconsistent. Backfill existing rows before adding a unique constraint.toArray(), API serialization and queued jobs will blow up on old rows. Either backfill first or make the cast tolerant (return the raw value when parsing fails) and enforce validity at the write path.nullable. The rule will reject null/empty string unless you add nullable (or sometimes) explicitly. This is the most common "why is my optional field failing" issue.so translation files are present and published — missing keys fall back to the key name or the default locale depending on config.PhoneService::normalize() / ::lookup()) so a future breaking rename is a one-file change rather than a grep across the codebase.How can I help you explore Laravel packages today?