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

Identity Number Bundle Laravel Package

jongotlin/identity-number-bundle

Symfony bundle that integrates jongotlin/identity-number into your app, adding validator constraints and service wiring to check national identity numbers across supported countries, including format and checksum validation for forms and domain models.

View on GitHub
Deep Wiki
Context7

Getting Started

First, correct the premise: this is a Symfony bundle, not a Laravel package. There is no service provider, facade, config file, or Rule object that Laravel will auto-discover. The bundle exists to wire the jongotlin/identity-number library into Symfony's DI container, Form component, and Validator component. In Laravel, you skip the bundle entirely and depend on the library underneath it.

composer require jongotlin/identity-number

Where to look first: the jongotlin/identity-number repository, not the bundle. The bundle's value to you is as a usage reference — its classes and test fixtures show the intended call patterns, the valid/invalid edge cases, and the three number types it supports:

  • Personnummer — Swedish personal identity number (10- or 12-digit, Luhn-checked, date-encoded).
  • Samordningsnummer — coordination number for people without a personnummer (day-of-month offset by +60).
  • Organisationsnummer — Swedish company/VAT registration number.

Read the library source end to end before writing any glue. It is small (~100 lines of meaningful logic), MIT-licensed, and you will be maintaining it.

First use case — a single signup form field:

use Jongi\IdentityNumber\IdentityNumber;

$id = IdentityNumber::create('811218-9876');

if (! $id->isValid()) {
    // reject
}

Once that works, register the wrapper as a singleton in a dedicated provider so form requests and actions can inject it:

// app/Providers/IdentityNumberServiceProvider.php
public function register(): void
{
    $this->app->singleton(IdentityNumberValidator::class, fn () => new IdentityNumberValidator());
}

Do not pull in symfony/framework-bundle or symfony/validator just to consume one constraint. That is the wrong trade for a Laravel app and will drag version conflicts with Laravel's own transitive Symfony packages.

Implementation Patterns

Validation rule (the workhorse pattern). Wrap the library in a ValidationRule and use it everywhere a Swedish number is captured:

class SwedishIdentityNumber implements ValidationRule
{
    public function __construct(
        private bool $allowCoordination = false,
        private bool $allowOrganisation = false,
    ) {}

    public function validate(string $attribute, mixed $value, Closure $fail): void
    {
        try {
            $number = IdentityNumber::create((string) $value);
        } catch (\Throwable) {
            $fail('validation.identity_number.malformed');
            return;
        }

        if (! $number->isValid()) {
            $fail('validation.identity_number.invalid');
            return;
        }

        if (! $this->allowCoordination && $number->isCoordinationNumber()) {
            $fail('validation.identity_number.coordination_not_accepted');
        }
    }
}

The try/catch around create() matters — malformed input is the single most common failure mode you will see in production logs, and you want a clean $fail() rather than a 500.

Value object + Cast. Once a number is validated, do not let it re-enter the domain as a loose string. Add a Cast or value object so the only way to construct it in business code is via a validated instance. This prevents "trusted" imports, seeders, and API payloads from bypassing validation.

Normalization before validation. Users paste numbers with spaces, +/- separators, and either 10- or 12-digit forms. Normalize first (strip whitespace, standardize separator, resolve century), then validate. Keep normalization as a separate concern behind its own interface so you can change the contract without touching rules.

Fixture corpus as acceptance tests. Do not invent test numbers. Pull the published Skatteverket test ranges and encode them as a Pest/PHPUnit dataset covering: valid personnummer, valid coordination numbers, valid organisation numbers, invalid Luhn checksums, impossible dates (month 13, day 32), coordination day offset (day 61+), and malformed input. This is where the package earns its keep — its own test suite is a starting point.

Form request wiring.

class SignupRequest extends FormRequest
{
    public function rules(): array
    {
        return [
            'ssn' => ['required', 'string', new SwedishIdentityNumber()],
        ];
    }
}

Organisation number at invoicing/billing capture. Run the same rule with allowOrganisation: true on the billing detail form. This is a different surface than signup — treat it separately so you can enable/disable coordination numbers independently.

Integration tip — parallel run on migration. If you are replacing an existing regex or third-party check, run both for one release behind a flag and log disagreements. The regex was probably wrong; you want to see how wrong before you delete it.

Feature-flag the rollout. Log every rejection with the raw input (hashed/redacted) for the first weeks. Swedish support tooling needs a manual-override path — a legitimate user must be able to bypass a format check.

Gotchas and Tips

The description lies. The package README/description text mentions South African ID numbers. The actual code is Swedish. Do not trust the marketing copy — read the source and trust the tests.

10-digit vs 12-digit forms. 811218-9876 and 198112189876 refer to the same person but encode the century differently. Decide your storage contract (store 12-digit normalized, display 10-digit) and enforce it in the Cast, or you will get duplicate-account merges.

Separators are semantic. - vs + signals whether the person is over 100 in the Swedish convention. Do not silently strip + — handle it explicitly or you will miscalculate age for centenarians and, more importantly, quietly accept the wrong century form.

Coordination numbers are not invalid personnummer. A coordination number uses the same structure but with day-of-month + 60. If you reject them by default, you silently block foreign workers, students, and recent arrivals — a real, growing user segment. Make this an explicit config flag, not an accident of implementation.

Luhn proves structure, not existence. A syntactically valid personnummer can belong to nobody, or to someone else. This library does not verify identity. If your compliance surface needs identity verification, that is BankID or a Skatteverket/Navet integration — a separate decision with its own budget. Do not let the rule's name ("identity number") blur this in code review or in stakeholder comms.

Malformed input may throw, not return false. Check the library's contract for each entry point. Wrap defensively in the rule; never let a user paste crash a request.

Composer constraints will conflict eventually. Verify the library's php and any Symfony component constraints against your target Laravel version before adopting. If it pins an old Symfony major that Laravel's transitive dep tree resolves elsewhere, that alone confirms the "use the library, not the bundle" decision — which you were already going to make.

Zero stars, single maintainer, no dependents. Treat this as a reference implementation to vendor, not a dependency with a support contract. Fork it into app/Domain/IdentityNumber/ (or a private package) with attribution, keep the version pinned, and schedule an annual upstream review. An unmaintained package on a KYC-adjacent path will not survive a vendor-risk review — plan for that now rather than at audit time.

Test with real published test numbers, not made-up ones. Luhn-valid but nonexistent numbers make excellent fixtures and will never accidentally match a real citizen. Encoded fixtures also capture the domain knowledge in CI, so it is not trapped in one engineer's head.

Extension points you will actually use: the library's number-type checks (isCoordinationNumber(), organisation-number range checks) and the date/gender extraction helpers. Everything else — the rule, the cast, the message keys, the normalization contract — you own and should keep in your own namespace.

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