PHPackages                             robertogallea/laravel-codicefiscale - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Validation &amp; Sanitization](/categories/validation)
4. /
5. robertogallea/laravel-codicefiscale

ActiveLibrary[Validation &amp; Sanitization](/categories/validation)

robertogallea/laravel-codicefiscale
===================================

Codice fiscale validation for php/laravel

2.3.1(5mo ago)65218.5k↓38.3%191PHPPHP ^8.2CI passing

Since Mar 22Pushed 6d ago6 watchersCompare

[ Source](https://github.com/robertogallea/laravel-codicefiscale)[ Packagist](https://packagist.org/packages/robertogallea/laravel-codicefiscale)[ GitHub Sponsors](https://github.com/robertogallea)[ RSS](/packages/robertogallea-laravel-codicefiscale/feed)WikiDiscussions main Synced 2w ago

READMEChangelog (10)Dependencies (8)Versions (47)Used By (1)

[![Laravel Codice Fiscale](https://camo.githubusercontent.com/01a36fd69e63fba0edb5038d3e16e66f9e8911a836397118a39839f8aced61db/68747470733a2f2f62616e6e6572732e6265796f6e64636f2e64652f4c61726176656c253230436f6469636525323046697363616c652e706e673f7468656d653d6c69676874267061636b6167654d616e616765723d636f6d706f7365722b72657175697265267061636b6167654e616d653d726f626572746f67616c6c65612532466c61726176656c2d636f6469636566697363616c65267061747465726e3d636861726c696542726f776e267374796c653d7374796c655f31266465736372697074696f6e3d436f646963652b66697363616c652b76616c69646174696f6e2b616e642b70617273696e672b69732b612b627265657a65266d643d312673686f7757617465726d61726b3d3026666f6e7453697a653d313030707826696d616765733d6964656e74696669636174696f6e267769647468733d32303026686569676874733d6175746f)](https://camo.githubusercontent.com/01a36fd69e63fba0edb5038d3e16e66f9e8911a836397118a39839f8aced61db/68747470733a2f2f62616e6e6572732e6265796f6e64636f2e64652f4c61726176656c253230436f6469636525323046697363616c652e706e673f7468656d653d6c69676874267061636b6167654d616e616765723d636f6d706f7365722b72657175697265267061636b6167654e616d653d726f626572746f67616c6c65612532466c61726176656c2d636f6469636566697363616c65267061747465726e3d636861726c696542726f776e267374796c653d7374796c655f31266465736372697074696f6e3d436f646963652b66697363616c652b76616c69646174696f6e2b616e642b70617273696e672b69732b612b627265657a65266d643d312673686f7757617465726d61726b3d3026666f6e7453697a653d313030707826696d616765733d6964656e74696669636174696f6e267769647468733d32303026686569676874733d6175746f)

laravel-codicefiscale
=====================

[](#laravel-codicefiscale)

[![Author](https://camo.githubusercontent.com/bd84f242372028b300dda5beb4de8d4730d4df44685c8554a873e716659feb1c/68747470733a2f2f696d672e736869656c64732e696f2f7374617469632f76313f6c6162656c3d617574686f72266d6573736167653d726f6267616c6c656126636f6c6f723d353041424631266c6f676f3d74776974746572267374796c653d666c61742d737175617265)](https://twitter.com/robgallea)[![Latest Version on Packagist](https://camo.githubusercontent.com/3aee563f404a627ba3d355462bb564d3d25efc58beb9473216d077b8b14c6cd7/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f726f626572746f67616c6c65612f6c61726176656c2d636f6469636566697363616c652e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/robertogallea/laravel-codicefiscale)[![Software License](https://camo.githubusercontent.com/55c0218c8f8009f06ad4ddae837ddd05301481fcf0dff8e0ed9dadda8780713e/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c6963656e73652d4d49542d627269676874677265656e2e7376673f7374796c653d666c61742d737175617265)](LICENSE.md)[![Sponsor me!](https://camo.githubusercontent.com/78d8f37e45135156aa667c76aa28893949f0d59ea84c527e854792166f2a06eb/68747470733a2f2f696d672e736869656c64732e696f2f7374617469632f76313f6c6162656c3d53706f6e736f72266d6573736167653d254532253944254134266c6f676f3d476974487562266c696e6b3d68747470733a2f2f6769746875622e636f6d2f73706f6e736f72732f726f626572746f67616c6c6561)](https://github.com/sponsors/robertogallea)[![Packagist Downloads](https://camo.githubusercontent.com/f571faede5d7b4e8f63efed2223e3e08bb2110e0d310756ae73bba0bdf1e8d1e/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f64742f726f626572746f67616c6c65612f6c61726176656c2d636f6469636566697363616c65)](https://packagist.org/packages/robertogallea/laravel-codicefiscale)

laravel-codicefiscale is a package for parsing, generating, and validating the Italian `CodiceFiscale` (tax code). 3.0 is a ground-up rewrite: a small, framework-agnostic domain core (`Robertogallea\CodiceFiscale`) with immutable value objects and single-purpose services, plus a Laravel integration layer (`Robertogallea\CodiceFiscale\Laravel`) - a validation rule, an Eloquent cast, a Faker provider, and an artisan command backed by real Italian government data.

> **Upgrading from 2.x?** 3.0 is an intentional, clean break with no compatibility shims. See [UPGRADE.md](UPGRADE.md) for a complete call-by-call migration table.

Requirements
------------

[](#requirements)

- PHP ^8.2
- Laravel (`illuminate/database`, `illuminate/support`) ^12.0 or ^13.0

Table of contents
-----------------

[](#table-of-contents)

- [Setup](#setup)
- [Quick start](#quick-start)
- [Core domain](#core-domain)
    - [`CodiceFiscale`](#codicefiscale)
    - [Generation](#generation)
    - [Customizing generation and parsing](#customizing-generation-and-parsing)
    - [Parsing](#parsing)
    - [Validation](#validation)
    - [Matching against a person](#matching-against-a-person)
    - [Omocodia](#omocodia)
- [Birthplace domain](#birthplace-domain)
- [Laravel integration](#laravel-integration)
    - [Validation rule](#validation-rule)
    - [Eloquent cast](#eloquent-cast)
    - [Faker provider](#faker-provider)
    - [`codice-fiscale:update-places`](#codice-fiscaleupdate-places)

Setup
-----

[](#setup)

```
composer require robertogallea/laravel-codicefiscale:^3.0
```

The service provider is auto-discovered - no manual registration needed.

Publish the config file if you want to customize it:

```
php artisan vendor:publish --provider="Robertogallea\CodiceFiscale\Laravel\CodiceFiscaleServiceProvider" --tag="config"
```

```
// config/codicefiscale.php

return [
    'database' => [
        // Path to the dedicated SQLite database backing the
        // birthplace repository. Never your application's own
        // database - installing/using this package doesn't touch it.
        'path' => storage_path('app/codicefiscale/places.sqlite'),
    ],

    // Sources for `codice-fiscale:update-places`. Only ever fetched
    // when that command runs, never during normal validation.
    'sources' => [
        'municipalities' => 'https://www.anagrafenazionale.interno.it/wp-content/uploads/ANPR_archivio_comuni.csv',
        'countries' => 'https://www.anagrafenazionale.interno.it/wp-content/uploads/tabella_2_statiesteri.xlsx',
    ],
];
```

Birthplace reference data (Italian municipalities and foreign countries) is never bundled with the package - nobody has verified the redistribution terms of the government datasets it comes from, so bundling a converted copy would mean redistributing it under this package's own unverified authority. Instead, each installation downloads its own copy:

```
php artisan codice-fiscale:update-places
```

This populates the dedicated SQLite database configured above via a service-provider-managed migration and connection - it never touches your application's own database or migration bookkeeping. Run it once after installing, and again whenever you want fresher data. **Semantic validation and birthplace lookups return "unknown" until this has been run at least once.**

Update just one dataset at a time if you don't need both refreshed:

```
php artisan codice-fiscale:update-places --municipalities-only
php artisan codice-fiscale:update-places --countries-only
```

Quick start
-----------

[](#quick-start)

The essentials - validate a codice fiscale, parse one apart, and generate one. Each is covered in full below; this is just enough to see it working.

**Validate**

```
public function rules(): array
{
    return [
        'fiscal_code' => 'codice_fiscale',
    ];
}
```

Need to also cross-check against other request fields (name, birth date, birthplace, gender)? See [Validation rule](#validation-rule) for `CodiceFiscaleRule::make()->matching(...)`.

**Parse**

```
use Robertogallea\CodiceFiscale\Contracts\BirthPlaceRepository;
use Robertogallea\CodiceFiscale\Parsing\Parser;

$parser = new Parser(app(BirthPlaceRepository::class));
$parsed = $parser->parse($cf);

$parsed->surnameCode();    // 'RSS' - a 3-character encoded fragment, NOT the real surname
$parsed->nameCode();       // 'MRA' - same caveat
$parsed->gender();         // Gender::Male
$parsed->birthDate();      // DateTimeImmutable('1985-04-15'), or null if no candidate is plausible
$parsed->birthYear();      // 1985, or null under the same condition as birthDate()
$parsed->birthMonth();     // 4
$parsed->birthDay();       // 15
$parsed->birthPlaceCode(); // BirthPlaceCode('H501')
$parsed->birthPlace();     // ?BirthPlace - null if the code isn't recognized, or update-places hasn't run
$parsed->isOmocodia();     // false
```

See [Parsing](#parsing) for the full field reference, and [Reference-date resolution](#reference-date-resolution) for how the two-digit birth year gets resolved.

**Generate**

```
use Robertogallea\CodiceFiscale\Data\BirthPlaceCode;
use Robertogallea\CodiceFiscale\Data\Person;
use Robertogallea\CodiceFiscale\Enums\Gender;
use Robertogallea\CodiceFiscale\Generation\Generator;

$person = new Person(
    firstName: 'Mario',
    lastName: 'Rossi',
    birthDate: new DateTimeImmutable('1985-04-15'),
    birthPlace: BirthPlaceCode::from('H501'), // Roma
    gender: Gender::Male,
);

$cf = (new Generator())->generate($person);

$cf->value(); // 'RSSMRA85D15H501T'
```

See [Generation](#generation) for name normalization and swapping out the encoders.

Core domain
-----------

[](#core-domain)

Everything in this section lives under `Robertogallea\CodiceFiscale` and has no Laravel dependency - it works identically outside a Laravel application.

### `CodiceFiscale`

[](#codicefiscale)

An immutable value object. It can only ever be constructed already structurally valid - there's no way to hold a malformed one:

```
use Robertogallea\CodiceFiscale\CodiceFiscale;
use Robertogallea\CodiceFiscale\Exceptions\InvalidCodiceFiscaleException;

$cf = CodiceFiscale::from('RSSMRA85D15H501T'); // throws InvalidCodiceFiscaleException if malformed
$cf = CodiceFiscale::tryFrom('RSSMRA85D15H501T'); // returns null instead of throwing

$cf->value(); // 'RSSMRA85D15H501T'
(string) $cf; // same - CodiceFiscale implements Stringable
```

`from()`/`tryFrom()` only check *structure* (length, character classes, valid-shaped fields) - not checksum or semantic correctness. See [Validation](#validation) for the rest.

### Generation

[](#generation)

`Generator` takes a `Person` and produces a `CodiceFiscale`:

```
use Robertogallea\CodiceFiscale\Data\BirthPlaceCode;
use Robertogallea\CodiceFiscale\Data\Person;
use Robertogallea\CodiceFiscale\Enums\Gender;
use Robertogallea\CodiceFiscale\Generation\Generator;

$person = new Person(
    firstName: 'Mario',
    lastName: 'Rossi',
    birthDate: new DateTimeImmutable('1985-04-15'),
    birthPlace: BirthPlaceCode::from('H501'), // Roma
    gender: Gender::Male,
);

$cf = (new Generator())->generate($person);

$cf->value(); // 'RSSMRA85D15H501T'
```

Name normalization (accents, apostrophes, mixed case, extra whitespace) happens automatically inside `Generator` - pass names exactly as the person typed them.

### Customizing generation and parsing

[](#customizing-generation-and-parsing)

`Generator` composes four internal, independently-tested services - `NameEncoder`, `DateEncoder`, `BirthPlaceEncoder`, `Checksum` - plus a swappable `Contracts\NameNormalizer` (default `ItalianNameNormalizer`, handling Latin-script accents/apostrophes/whitespace), all as constructor parameters with defaults. Supply your own `NameNormalizer` if you need different text-cleanup behavior (e.g. broader multi-script transliteration):

```
use Robertogallea\CodiceFiscale\Contracts\NameNormalizer;

final class MyNormalizer implements NameNormalizer
{
    public function normalize(string $name): string { /* ... */ }
}

$cf = (new Generator(nameNormalizer: new MyNormalizer()))->generate($person);
```

Similarly, `Parser` resolves a codice fiscale's ambiguous two-digit birth year via a swappable `Contracts\BirthDateResolver` (default `DefaultBirthDateResolver(maxAge: 120)`) - see [Reference-date resolution](#reference-date-resolution) below. Supply your own when you have domain-specific knowledge the default can't have:

```
use Robertogallea\CodiceFiscale\Contracts\BirthDateResolver;
use Robertogallea\CodiceFiscale\Data\BirthDateResolutionContext;

final class Post1970Resolver implements BirthDateResolver
{
    // e.g. "this system only ever has customers born after 1970"
    public function resolve(BirthDateResolutionContext $context): ?DateTimeImmutable { /* ... */ }
}

$parser = new Parser(app(BirthPlaceRepository::class), birthDateResolver: new Post1970Resolver());
```

### Parsing

[](#parsing)

`Parser` decodes a `CodiceFiscale` back into its constituent parts. It needs a `Contracts\BirthPlaceRepository` to resolve birthplace codes to real records - in a Laravel app, resolve the one the service provider already binds:

```
use Robertogallea\CodiceFiscale\Contracts\BirthPlaceRepository;
use Robertogallea\CodiceFiscale\Parsing\Parser;

$parser = new Parser(app(BirthPlaceRepository::class));
$parsed = $parser->parse($cf);

$parsed->surnameCode();    // 'RSS' - a 3-character encoded fragment, NOT the real surname
$parsed->nameCode();       // 'MRA' - same caveat
$parsed->gender();         // Gender::Male
$parsed->birthDate();      // DateTimeImmutable('1985-04-15'), or null if no candidate is plausible
$parsed->birthYear();      // 1985, or null under the same condition as birthDate()
$parsed->birthMonth();     // 4
$parsed->birthDay();       // 15
$parsed->birthPlaceCode(); // BirthPlaceCode('H501')
$parsed->birthPlace();     // ?BirthPlace - null if the code isn't recognized, or update-places hasn't run
$parsed->isOmocodia();     // false
```

There is no way to recover a person's real first/last name from a codice fiscale - `surnameCode()`/`nameCode()` are the same 3-character encoded fragments the algorithm itself works with, just honestly named (2.x's `getFirstName()`/`getLastName()` implied otherwise).

#### Reference-date resolution

[](#reference-date-resolution)

Two-digit birth years are inherently ambiguous (`85` could mean 1885 or 1985). `Parser` resolves this automatically via **reference-date resolution**, the default `BirthDateResolver` strategy:

1. Discard whichever century candidate isn't a real calendar date (e.g. a Feb 29 that only one century's calendar has).
2. Discard a candidate after the reference date (today, unless overridden - see below).
3. Discard a candidate whose exact age as of the reference date exceeds `maxAge` (120 by default).
4. If no candidate remains, `birthDate()` and `birthYear()` are both `null` - the codice fiscale alone doesn't support a plausible reading.
5. If one candidate remains, that's the answer.
6. If two remain, a `BirthPlaceCode` valid (per the same `BirthPlaceRepository` `birthPlace()` uses) at exactly one candidate date selects that date - historical municipality/province changes can carry real evidence.
7. Otherwise (birthplace history is valid for both dates, or neither), the younger candidate is preferred.

`$parsed->possibleBirthYears(): array{int, int}` still exposes the raw, unfiltered two-digit-year ambiguity for callers who need to see or control it themselves. Supply an explicit reference date to `DefaultBirthDateResolver` for deterministic historical imports and tests:

```
use Robertogallea\CodiceFiscale\Parsing\BirthDate\DefaultBirthDateResolver;

$resolver = new DefaultBirthDateResolver(maxAge: 120, referenceDate: new DateTimeImmutable('2010-01-01'));
$parser = new Parser(app(BirthPlaceRepository::class), birthDateResolver: $resolver);
```

The codice fiscale itself remains inherently ambiguous - this is a documented default interpretation, not an authoritative identity check. Callers with an actual birth date, or other domain knowledge, can use `possibleBirthYears()` directly or supply a custom `BirthDateResolver`.

### Validation

[](#validation)

`Validator` checks a raw string in independently-callable tiers, and never accepts a `Person` - "is this a valid codice fiscale" and "does this codice fiscale belong to this person" are deliberately separate concerns (see [Matching](#matching-against-a-person)):

```
use Robertogallea\CodiceFiscale\Validation\Validator;

$validator = new Validator(app(BirthPlaceRepository::class));

$result = $validator->validate('RSSMRA85D15H501T');
$result->valid();  // true
$result->errors(); // []

$result = $validator->validate('not-a-real-code');
$result->valid();  // false
$result->errors(); // [ValidationError::InvalidFormat]
```

`validate()` runs format as a gate (a malformed string can't safely be sliced further); once format passes, checksum and semantics run independently and both contribute to the same result, so a caller sees everything wrong with the input in one pass:

```
$validator->validateFormat('RSSMRA85D15H501T');   // structural only
$validator->validateChecksum($cf);                // needs a real CodiceFiscale, not a raw string
$validator->validateSemantics($cf);                // valid calendar date + recognized birthplace + valid on that date
```

`ValidationError` is a backed enum: `InvalidFormat`, `InvalidChecksum`, `InvalidDate`, `UnknownBirthPlace`, `BirthPlaceNotValidOnDate` - not exceptions. Exceptions are reserved for genuine API misuse (e.g. `CodiceFiscale::from()` on malformed input), not expected validation failures.

### Matching against a person

[](#matching-against-a-person)

`Matcher` cross-checks a `CodiceFiscale` against a `Person` (all fields required) or a `PartialPerson` (any subset):

```
use Robertogallea\CodiceFiscale\Data\PartialPerson;
use Robertogallea\CodiceFiscale\Matching\Matcher;

$matcher = new Matcher(new Parser(app(BirthPlaceRepository::class)));

$result = $matcher->match($cf, $person); // the same Person the code was generated from
$result->matches();  // true
$result->skipped();  // [] - every field was checked

$result = $matcher->match($cf, new PartialPerson(firstName: 'Mario', gender: Gender::Female));
$result->matches();    // false
$result->matched();    // [PersonField::FirstName]
$result->mismatched(); // [PersonField::Gender]
$result->skipped();    // [PersonField::LastName, PersonField::BirthDate, PersonField::BirthPlace]
```

`MatchResult` distinguishes three explicit states, important when a match result feeds a compliance decision: **matched**, **mismatched**, and **skipped** (a field the `PartialPerson` simply didn't provide - never silently treated as a pass).

### Omocodia

[](#omocodia)

When a computed codice fiscale collides with one already assigned, the Agenzia delle Entrate resolves it by substituting a subset of 7 fixed digit positions with letters. Any of the 2⁷ = 128 combinations may apply independently:

```
use Robertogallea\CodiceFiscale\Omocodia\Omocodia;

$omocodia = new Omocodia();

$omocodia->canonical($cf);   // reverses all substitutions back to digits - pure, no repository needed
$omocodia->level($cf);       // 0 - a count of substituted positions (0-7), not an ordinal/unique identifier
$omocodia->variants($cf);    // iterable - all 128 combinations sharing $cf's underlying data

$variant = CodiceFiscale::from('RSSMRA85D15H50ML'); // one substituted position vs. the canonical form
$variant->isEquivalentTo($cf); // true - same canonical form, so the same person-derived data
```

Birthplace domain
-----------------

[](#birthplace-domain)

`Contracts\BirthPlace` is a single time-bounded record: `code()`, `name()`, `validFrom()`, `validTo()` (null if still current), `wasValidOn(DateTimeImmutable)`. `DomesticBirthPlace` (Italian municipality) adds `province()`/`istatCode()`; `ForeignBirthPlace` (country) adds `country(): CountryCode`. A municipality that renamed or changed province produces multiple `BirthPlace` records sharing the same `BirthPlaceCode`, one per era - so a birth date tied to an old municipality identity still resolves correctly.

```
use Robertogallea\CodiceFiscale\Data\BirthPlaceCode;

$repository = app(BirthPlaceRepository::class);

$place = $repository->find(BirthPlaceCode::from('H501')); // valid today, or null
$place = $repository->find(BirthPlaceCode::from('H501'), new DateTimeImmutable('1900-01-01')); // valid on that date

$repository->existedEver(BirthPlaceCode::from('A999')); // false - distinguishes "never valid" from "valid, wrong date"

$repository->search('roma'); // list - case/accent-insensitive substring match, both domestic and foreign
$repository->search('abbadia', new DateTimeImmutable('1950-01-01')); // only era-records valid on that date
$repository->search('san', limit: 10); // most-recent-era-first, capped at 10
```

`search()` resolves a name a person actually typed to the `BirthPlaceCode`(s) it could mean - useful for building a "pick your birthplace" UI without requiring the code up front. It's unfiltered by validity unless `$on` is given, since a renamed municipality's old name should still be found; results across every matching era are returned, most-recently-valid first. It never feeds back into generation - `Person::$birthPlace` still takes a `BirthPlaceCode`, not a name, so callers resolve ambiguity themselves before generating.

`BirthPlaceCode::isForeign(): bool` tells domestic (Italian) codes apart from `Z`-prefixed foreign ones; `BirthPlaceCode::equals(BirthPlaceCode $other): bool` compares two codes by value. `CountryCode` (an ISO 3166-1 alpha-3 string, e.g. `'USA'`) works the same way - `CountryCode::from()`/`tryFrom()` construct it, `equals()` compares it; it's a value object rather than a PHP enum since ~200 countries would make an enum unmaintainable.

The default `BirthPlaceRepository` binding is `Laravel\BirthPlaces\CompositeBirthPlaceRepository`, which routes to an Eloquent-backed repository per kind - populated by [`codice-fiscale:update-places`](#codice-fiscaleupdate-places).

Laravel integration
-------------------

[](#laravel-integration)

Everything in this section lives under `Robertogallea\CodiceFiscale\Laravel`.

### Validation rule

[](#validation-rule)

The `codice_fiscale` string rule checks format/checksum/semantics only:

```
public function rules(): array
{
    return [
        'fiscal_code' => 'codice_fiscale',
    ];
}
```

For cross-checking against other request fields, use the fluent `CodiceFiscaleRule`, naming the *other fields* to check against - not the values themselves:

```
use Robertogallea\CodiceFiscale\Laravel\Rules\CodiceFiscaleRule;

public function rules(): array
{
    return [
        'fiscal_code' => [CodiceFiscaleRule::make()->matching(
            firstName: 'first_name',
            lastName: 'last_name',
            birthDate: 'birth_date',
            birthPlace: 'birth_place_code',
            gender: 'gender',
        )],
    ];
}
```

Any argument can be omitted - an omitted field, or one absent from the request data, is skipped rather than forced into a mismatch. Validation fails with one message per mismatched field, not just the first.

Both the `codice_fiscale` string rule and `CodiceFiscaleRule` report translated, failure-specific messages - a distinct message per failure reason (bad format, bad checksum, a nonexistent date, an unrecognized birthplace, a birthplace not yet/no longer valid on the encoded date) rather than one generic "invalid" message, plus one `:field`-naming message per mismatched field for `->matching()`. `en` and `it` are bundled under the `codicefiscale` translation namespace; publish and customize them with:

```
php artisan vendor:publish --provider="Robertogallea\CodiceFiscale\Laravel\CodiceFiscaleServiceProvider" --tag="lang"
```

### Eloquent cast

[](#eloquent-cast)

`CodiceFiscaleCast` rounds a `fiscal_code`-style attribute to a `CodiceFiscale` value object instead of a raw string:

```
use Robertogallea\CodiceFiscale\Laravel\Casts\CodiceFiscaleCast;

class Person extends Model
{
    protected function casts(): array
    {
        return [
            'fiscal_code' => CodiceFiscaleCast::class,
        ];
    }
}

$person->fiscal_code; // CodiceFiscale|null

$person->fiscal_code = 'RSSMRA85D15H501T'; // or a CodiceFiscale instance
$person->fiscal_code = 'not-a-real-code'; // throws InvalidCodiceFiscaleException immediately
```

Setting a structurally-invalid value throws immediately (fail-fast at the ORM boundary) - bad data can't silently enter your database through the model layer. Reading a row whose stored value is invalid (legacy data, a seeder, a direct write outside the cast) returns `null` instead of throwing, so pre-existing bad data never makes the model unusable for inspection or cleanup.

### Faker provider

[](#faker-provider)

Auto-registered onto Laravel's `Faker\Generator` - no setup needed beyond installing the package:

```
class PersonFactory extends Factory
{
    public function definition(): array
    {
        return [
            'fiscal_code' => fake()->codiceFiscale(),
        ];
    }
}
```

`codiceFiscale()` takes no parameters - it always generates a fully random, valid `CodiceFiscale` via the real `Person`/`Generator` API, drawing its birthplace from a small, fixed set of ten well-known Italian municipalities bundled directly with the provider (not the full ANPR/MAECI dataset - see `docs/adr/0007-faker-provider-bundles-a-small-fixed-fact-list-not-birthplace-data.md`). It works in a fresh application that has never run `codice-fiscale:update-places`, since generation never touches a database at all.

If you need a code for a *specific* person rather than a random one, don't use the Faker provider - call `Generator` directly, as shown in [Generation](#generation).

### `codice-fiscale:update-places`

[](#codice-fiscaleupdate-places)

Covered in [Setup](#setup). Downloads the ANPR comuni archive and MAECI stati-esteri table via Laravel's `Http` facade and upserts them into the dedicated SQLite database - safe to re-run at any time; existing rows are updated in place rather than duplicated.

###  Health Score

66

—

FairBetter than 99% of packages

Maintenance87

Actively maintained with recent releases

Popularity50

Moderate usage in the ecosystem

Community26

Small or concentrated contributor base

Maturity86

Battle-tested with a long release history

 Bus Factor1

Top contributor holds 74.8% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~57 days

Recently: every ~92 days

Total

45

Last Release

169d ago

Major Versions

1.14.0 → 2.0.02024-06-14

PHP version history (3 changes)1.0.0PHP &gt;=7.1

1.12.0PHP &gt;=7.4

2.0.0PHP ^8.2

### Community

Maintainers

![](https://www.gravatar.com/avatar/818f547bcf73a82393d9014c85c90c83d760102a8d4dfe806353afb83848a901?d=identicon)[robertogallea](/maintainers/robertogallea)

---

Top Contributors

[![robertogallea](https://avatars.githubusercontent.com/u/19411470?v=4)](https://github.com/robertogallea "robertogallea (157 commits)")[![makroxyz](https://avatars.githubusercontent.com/u/2069949?v=4)](https://github.com/makroxyz "makroxyz (26 commits)")[![michelepizzi](https://avatars.githubusercontent.com/u/20208916?v=4)](https://github.com/michelepizzi "michelepizzi (10 commits)")[![trippo](https://avatars.githubusercontent.com/u/497169?v=4)](https://github.com/trippo "trippo (3 commits)")[![JBou](https://avatars.githubusercontent.com/u/4021928?v=4)](https://github.com/JBou "JBou (3 commits)")[![MoroAlberto](https://avatars.githubusercontent.com/u/21240710?v=4)](https://github.com/MoroAlberto "MoroAlberto (2 commits)")[![caiojhonny](https://avatars.githubusercontent.com/u/2778743?v=4)](https://github.com/caiojhonny "caiojhonny (2 commits)")[![wit3](https://avatars.githubusercontent.com/u/5450259?v=4)](https://github.com/wit3 "wit3 (1 commits)")[![carloeusebi](https://avatars.githubusercontent.com/u/129429172?v=4)](https://github.com/carloeusebi "carloeusebi (1 commits)")[![DavideDelNista](https://avatars.githubusercontent.com/u/59122941?v=4)](https://github.com/DavideDelNista "DavideDelNista (1 commits)")[![eleftrik](https://avatars.githubusercontent.com/u/6959298?v=4)](https://github.com/eleftrik "eleftrik (1 commits)")[![fabio-ivona](https://avatars.githubusercontent.com/u/8792274?v=4)](https://github.com/fabio-ivona "fabio-ivona (1 commits)")[![StyleCIBot](https://avatars.githubusercontent.com/u/11048387?v=4)](https://github.com/StyleCIBot "StyleCIBot (1 commits)")[![BitTheCat](https://avatars.githubusercontent.com/u/12282238?v=4)](https://github.com/BitTheCat "BitTheCat (1 commits)")

---

Tags

hacktoberfestlaravelphp-library

###  Code Quality

TestsPHPUnit

Code StylePHP CS Fixer

### Embed Badge

![Health badge](/badges/robertogallea-laravel-codicefiscale/health.svg)

```
[![Health](https://phpackages.com/badges/robertogallea-laravel-codicefiscale/health.svg)](https://phpackages.com/packages/robertogallea-laravel-codicefiscale)
```

###  Alternatives

[aws/aws-sdk-php

AWS SDK for PHP - Use Amazon Web Services in your PHP project

6.2k555.0M2.8k](/packages/aws-aws-sdk-php)[neuron-core/neuron-ai

The PHP Agentic Framework.

2.0k832.6k54](/packages/neuron-core-neuron-ai)[tencentcloud/tencentcloud-sdk-php

TencentCloudApi php sdk

3661.3M49](/packages/tencentcloud-tencentcloud-sdk-php)[eslazarev/wildberries-sdk

Wildberries OpenAPI clients (generated).

353.6k](/packages/eslazarev-wildberries-sdk)[tempest/framework

The PHP framework that gets out of your way.

2.3k37.6k21](/packages/tempest-framework)[files.com/files-php-sdk

Files.com PHP SDK

2482.9k](/packages/filescom-files-php-sdk)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
