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

Idna Laravel Package

rowbot/idna

rowbot/idna provides IDNA 2008 and UTS #46 processing for PHP: convert internationalized domain names to ASCII (Punycode) and back, with validation and normalization for safe, spec-compliant handling of Unicode domains.

View on GitHub
Deep Wiki
Context7

Technical Evaluation

Architecture fit

rowbot/idna is a framework-agnostic, pure-PHP implementation of UTS#46 (Unicode IDNA Compatibility Processing) — the algorithm that converts Unicode domain names into their ASCII A-label (punycode) form and back into human-readable U-labels. It has no Laravel service provider, no facade, no config surface, and no global state; it is a low-level primitive that fits into the application as a wrapped service, a validation rule, and a value object rather than as a "package integration."

For a Laravel codebase the realistic consumers are:

  • Identity / registration flows — normalizing and validating the domain portion of email addresses and custom/vanity domains, multi-tenant subdomain assignment.
  • Outbound integrations — webhook URLs, SSRF-guard inputs, third-party API base URLs, sitemap/feed ingestion. Anything that stores or dereferences a hostname supplied by a user.
  • Display layer — rendering münchen.example instead of xn--mnchen-3ya.example in UI, emails, and PDFs while keeping the ASCII form as the canonical stored value.
  • URL parsing — if we ever adopt WHATWG URL semantics (crawler, link shortener, redirect handling), rowbot/url depends on this package, so choosing it now consolidates a future dependency rather than adding one.

The API surface is small: ASCII↔Unicode conversion with a UTS#46 options object controlling strictness, hyphen/bidi/joiner checks, STD3 ASCII rules, transitional vs. non-transitional processing, and DNS length verification. That smallness is a feature — it is trivially wrappable and testable — and a limitation, in that it does not provide confusable/homograph detection (UTS#39), public-suffix awareness, or any policy layer. Those remain ours to build.

Integration feasibility

Straightforward mechanically: composer require rowbot/idna, wrap in one class, done. The friction is not code, it is overlap. PHP's ext-intl already exposes idn_to_ascii($d, IDNA_DEFAULT, INTL_IDNA_VARIANT_UTS46, $info), which is also UTS#46 — backed by ICU. So the honest feasibility question is not "can we integrate this?" but "why this instead of the function we already have?" The defensible answers are narrow but real:

  1. Deterministic table versioning. ICU's Unicode version is tied to the host's libicu build, which differs between a developer's Mac, the CI image, and the production base image. That produces environment-dependent validation — a domain that passes in staging and fails in prod. A package that ships its own generated UTS#46 data tables eliminates that class of bug. This is the single strongest argument.
  2. Per-label, structured error reporting rather than a boolean plus an error code.
  3. No hard ext-intl dependency for the conversion path (it still requires mbstring, and I would confirm whether Normalizer is used internally before assuming intl is optional — verify against composer.json rather than the README).
  4. Consistency with rowbot/url if WHATWG parsing is on the roadmap.

Technical risk

  • Adoption risk (high). Two stars, zero dependents, MIT, effectively a single maintainer. This is a bus-factor-one dependency. It must be treated as forkable vendored code, not as an ecosystem pillar. Pin an exact version; forbid ^ ranges in composer.json.
  • Duplicate sources of truth (medium). If both ext-intl and this package perform normalization in different code paths (e.g., Laravel's email rule, filter_var, and our wrapper), we get two Unicode versions disagreeing. Mitigation: a single IdnNormalizer boundary and a lint rule against calling idn_to_ascii directly.
  • Behavioral semantics (medium). Transitional vs. non-transitional processing changes real outcomes: faß.example is fass.example under transitional and xn--fa-hia.example under non-transitional. Flipping the flag post-launch silently orphans stored identifiers. This is a configuration decision that must be made once, documented, and covered by tests — not left to a default.
  • Scope confusion (medium). UTS#46 answers "is this a well-formed domain," not "is this a safe domain." Mixed-script and homograph attacks (аpple.com with Cyrillic а) pass validation cleanly. If the driver is anti-spoofing for user-facing domains, this package is necessary but insufficient and the assessment must say so up front.
  • Data provenance (low, but flagged). The release date metadata (2026-10-09) is not consistent with a present-day evaluation, and the score (18.86 with a 64.44 opportunity component) indicates a package with strong category relevance and near-zero social proof. Treat the scoring inputs as directional only and verify release history, issue responsiveness, and CI status directly on the repository.
  • Release cadence (low). Updates track Unicode versions and are inherently infrequent and large. There will be long quiet periods punctuated by table regenerations — this looks like abandonment but is normal for this category. Do not mistake it for a maintenance signal in either direction.

Key questions

  • Does it hard-require ext-intl, and does it require mbstring? What is the PHP floor, and does that floor survive our next Laravel major upgrade?
  • What is the memory and autoload cost of the bundled Unicode tables under a 128M memory_limit FPM pool? Measure with opcache.preload, not by reading the source.
  • What is the error contract — exceptions, or an errors collection returned alongside the result? This determines whether invalid input becomes a 500 or a 422.
  • Which Unicode version are the tables generated from, and what is the documented regeneration process? Can we rebuild them ourselves if upstream goes quiet?
  • Is there any UTS#39 (confusables) intent upstream, or do we own that permanently?
  • For our specific inputs: what is the authoritative behavior for trailing dots, mixed-case ASCII, empty labels, labels >63 octets, and total >253 octets — and does it match our database constraints?

Integration Approach

Stack fit

Laravel 10/11+, PHP 8.1+. No provider required. Recommended shape:

  • App\Support\Idn\IdnNormalizer — the single application boundary. Methods toAscii(string): AsciiDomain, toUnicode(string): string, validate(string): ValidationResult. Inject the package's converter; never let the raw library escape this class.
  • App\Casts\AsciiDomainCast — Eloquent cast that normalizes on set and guarantees the ASCII form is what hits the column.
  • App\Rules\IdnDomain / IdnEmail — custom validation rules. Note that Laravel's email rule and filter_var(..., FILTER_VALIDATE_EMAIL) both reject U-labels outright, so IDN email support requires a rule that splits at the last @, normalizes the domain, and validates the local part separately.
  • App\Http\Middleware\NormalizeIdnInput — optional, applied to registration/tenant-creation routes only. Avoid global input rewriting; it invites subtle bugs in request logging and signed-URL verification.

Data modeling

Store both forms, with the A-label canonical:

  • domain_ascii — varchar(253), ascii_bin collation, UNIQUE. Uniqueness belongs on the ASCII form, otherwise two accounts can claim visually identical names via different Unicode encodings.
  • domain_unicode — varchar(255), display only, never used in WHERE.

Enforce the 63-octet-per-label and 253-octet-total limits in application code via the library's DNS-length verification, and mirror them as column lengths so the database is a backstop rather than a surprise.

Migration path

  1. Corpus first. Build a golden-file test corpus before writing integration code: ASCII, uppercase, trailing-dot, Cyrillic, Arabic RTL (bidi rules), CJK, German ß, Greek final sigma, ZWJ emoji sequences, mixed-script, over-length labels, and known-invalid inputs. This corpus is the asset — it survives package swaps and Unicode table bumps and is the artifact that makes a future fork or replacement decision cheap.
  2. Shadow mode. If idn_to_ascii or a regex is currently in use, run the new normalizer in parallel on real inputs for a sprint and log divergences. Divergences are the whole point; do not skip to cutover.
  3. Read-only enablement. Use the normalizer to validate and display while still storing whatever you stored before.
  4. Backfill and reconcile. Batch-normalize existing rows, surface collisions (two rows whose ASCII forms match), and resolve them as a product decision — not a script.
  5. Enforce on write. Add the cast and the validation rule, make the column UNIQUE, and remove the shadow logging.

Compatibility

Watch three seams. Laravel's url/email rules and Request::validate assume ASCII hostnames — custom rules are mandatory, not optional. Guzzle and Laravel's HTTP client pass hostnames through as bytes and will happily attempt DNS on a U-label and fail; normalize before any outbound call, and pair with an SSRF guard since normalization can change the resolved host. Database collation is the third: utf8mb4_unicode_ci will case-fold and accent-fold the ASCII column in ways that break the uniqueness guarantee, so the binary collation is a requirement, not a preference.

Sequencing

Corpus and wrapper with unit tests (2–3 days) → validation rule and cast behind a feature flag (2 days) → shadow comparison on production traffic (1 sprint, near-zero effort) → backfill and collision review (scoped after shadow data) → enable writes → optionally surface Unicode on display. Total engineering effort to a safe production state is roughly one to two weeks; the flag and shadow phases are what keep it safe and are the phases most likely to be cut under schedule pressure.

Operational Impact

Maintenance

Pin to an exact version. Add the repository to the dependency-watch list with an explicit owner. Budget roughly 1–2 days per quarter for: reviewing upstream releases, re-running the golden corpus, and verifying the Unicode table version. On Unicode majors, treat the upgrade as a behavioral change requiring a regression pass, not a patch bump. If upstream stalls for more than 18 months, fork to an internal namespace — the license is MIT, the code is self-contained, and the fork cost is low precisely because the corpus already exists.

Support

No commercial support, no SLA, no security response process. Escalation path is: read the source (small), then fork. Do not route production incidents to a GitHub issue queue with two stars. Internal documentation should state plainly that this is a forkable primitive, and one engineer should be designated as the owner with enough context to patch it.

Scaling

Favorable. Pure CPU, no I/O, no network, no external service, no cache to warm. Costs are per-request table loading and per-call conversion — microseconds to low milliseconds for typical inputs, negligible against any database round-trip. Two things to actually measure rather than assume: memory footprint of the Unicode tables under constrained FPM pools (consider opcache.preload and a container singleton), and throughput on bulk paths — a million-row backfill should be chunked and timed, and if it exceeds the batch window, a memoization cache keyed on sha1($input) in Redis collapses the cost, since domain inputs are highly repetitive.

Failure modes

  • Uncaught validation exceptions → 500s on signup. The most likely incident. The wrapper must convert all library errors into a validation result; the library's error contract should never reach a controller.
  • Normalization drift between write and read paths → duplicate tenants or login failures. Mitigated by the single boundary class and the unique index on the ASCII column; the index is the safety net, the class is the fix.
  • Transitional-mode flip → stored identifiers silently stop resolving. Mitigated by making the flag an explicit, tested, documented constant with corpus coverage on both sides.
  • ext-intl or mbstring absent in a rebuilt container → hard failure at first use. Add an extension assertion to the health check and the Dockerfile, not to runtime code.
  • Memory exhaustion in a small FPM pool under concurrent first-loads. Mitigated by measurement and preloading.
  • Homograph phishing — the package will validate аpple.com with a Cyrillic а as perfectly well-formed. This is not a bug and will not be fixed upstream. If user-facing domain registration is in scope, a UTS#39 confusables check and a mixed-script policy are separate, additional work — scope them or explicitly accept the risk in writing.
  • Over-length or empty-label inputs surfacing raw library error strings to end users, which are unhelpful and occasionally include raw codepoints. Map to human-readable messages in the wrapper.

Ramp-up

Low, and pleasantly so. The API is two methods and an options object; a mid-level engineer can integrate, test, and ship in a day or two. The real cost is conceptual, not mechanical: IDNA2008 vs. UTS#46, transitional processing, bidi and joiner rules, and the A-label/U-label distinction are genuinely unfamiliar territory for most PHP developers. Budget half a day of reading plus a walkthrough of the golden corpus, and write the transitional-mode decision and the "UTS#46 is not an anti-spoofing control" caveat into the code comments where the next engineer will actually find them. The overall proposition is a small, focused, low-risk dependency whose main risks — bus factor and scope misreading — are both cheap to mitigate and expensive to ignore.

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.
ignibyte/aic-framework
glavweb/data-schema-bundle
glavweb/datagrid-bundle
tacticmedia/scim-server
uni-method/frame
neika-ai/sdk
adachsoft/php-code-style
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
frankyso/ipaymu
3mdikram/worldmoneywords
snowsoft/config
itmosoft/laravel-subscriptions
larva/laravel-ai-qwen
adachsoft/api-version-bundle