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

Laravel Algerian Provinces Laravel Package

elaborate-code/laravel-algerian-provinces

Laravel package that adds Algeria's 58 provinces (wilayas) to your app, with names, codes, migrations, models, and helpers for seeding, validation, and select dropdowns. Quickly wire official Algerian province data into any Laravel project.

View on GitHub
Deep Wiki
Context7

Getting Started

v2.0.0 is the Sushi release. The province/commune dataset now ships as a SQLite-backed Eloquent source via calebporzio/sushi instead of (or in addition to) whatever v1 did. Practically: there are no migrations to publish or run, no seeders to execute, and the models are read-only lookup tables that work the moment composer require finishes.

  1. Verify the SQLite extension first — this is now a hard runtime dependency:

    php -m | grep -i -E 'pdo_sqlite|sqlite3'
    

    Add pdo_sqlite to your Dockerfile / CI image / php.ini before anything else. Alpine images and slim CI containers frequently skip it, and the failure mode is a confusing "database does not exist" error rather than a missing-extension error.

  2. Install and confirm nothing needs publishing:

    composer require elaborate-code/laravel-algerian-provinces
    php artisan tinker
    >>> \ElaborateCode\LaravelAlgerianProvinces\Models\Province::count();
    

    If that returns the 58 wilayas, you're done wiring. If you're upgrading from v1, skip this step until you've read the "Gotchas" upgrade section — leftover published migrations will conflict.

  3. Where to look first: vendor/elaborate-code/laravel-algerian-provinces/ — the src/Models/ directory (the Sushi base class and the getRows() source file/data path) and the README's model list. The exact model names (Province vs Wilaya, Commune vs Baladiya) and their primary keys are the two things you must confirm before writing relations, because they're referenced in your own migrations.

  4. First use case: a wilaya → commune dependent dropdown on a registration or checkout form. Load provinces as a select, then either render all communes keyed by province (cheap at this scale — a few thousand rows) or fetch them per-province via a small JSON endpoint. Store the chosen province_id / commune_id on your own table as integers.

Implementation Patterns

Store the ID, relate with belongsTo. Keep your own users.province_id / users.commune_id columns and relate back to the package models. belongsTo and eager loading issue separate WHERE id IN (...) queries, which work fine across the MySQL↔SQLite boundary:

// app/Models/User.php
public function province(): BelongsTo
{
    return $this->belongsTo(\ElaborateCode\LaravelAlgerianProvinces\Models\Province::class);
}
User::with('province', 'commune')->paginate();

Validate with Rule::in, not exists:. The exists:provinces,id rule would run against your MySQL connection, where no provinces table exists:

use Illuminate\Validation\Rule;
use ElaborateCode\LaravelAlgerianProvinces\Models\Province;

$request->validate([
    'province_id' => ['required', Rule::in(Province::pluck('id'))],
]);

Resolve IDs in PHP before querying your own models. When you need "all users in the south," fetch the IDs from Sushi first, then use whereIn:

$ids = Province::whereIn('name', ['Adrar', 'Tamanrasset', 'Illizi'])->pluck('id');
$users = User::whereIn('province_id', $ids)->get();

Compose form data in one place. A small view-model or service avoids re-querying per component:

// app/Support/Algeria.php
public static function provinceOptions(): Collection
{
    return Province::orderBy('name')->pluck('name', 'id');
}

public static function communesByProvince(): Collection
{
    return Commune::orderBy('name')->get(['id', 'name', 'province_id'])
        ->groupBy('province_id');
}

Wrap it behind an endpoint if the frontend is JS-driven. Sushi gives you the full query builder, so filtering and searching behave as you'd expect:

Route::get('/api/communes', fn (Request $request) => Commune::query()
    ->when($request->integer('province_id'), fn ($q, $id) => $q->where('province_id', $id))
    ->orderBy('name')
    ->get(['id', 'name', 'province_id']));

Treat it as reference data, not user data. If you need to annotate provinces (delivery pricing, enabled/disabled flags, custom translations), put those in your own table keyed by province_id and join in application code — don't attempt to write to the Sushi models.

Gotchas and Tips

  • No cross-database joins. This is the big one in v2. whereHas('province'), has(), withCount(), join('provinces', ...), and whereRelation() all compile the Sushi table into a subquery/join executed on your MySQL connection, where it doesn't exist. They will throw "table not found." Use belongsTo + with() + PHP-side ID resolution instead. Audit any existing code that used whereHas against these models after upgrading.

  • exists: validation fails silently-ish. Same root cause — it resolves against your default connection. Always prefer Rule::in(...pluck('id')), and add a feature test that posts an invalid wilaya ID so a future refactor doesn't reintroduce exists:.

  • The models are read-only in practice. Sushi builds a throwaway SQLite database; create(), update(), and delete() will either throw or appear to succeed and vanish on the next request. Never write to them, and don't be tempted to "just seed one extra row."

  • Don't hardcode IDs across majors. v2 restructured how data is loaded; ID stability between 1.x and 2.x is not guaranteed. Look up by name/slug where possible, and if you must reference a specific wilaya (e.g. Algiers for a default), assert it in a test: Province::find(16)->name === 'Alger'.

  • Type affinity surprises. SQLite stores what you give it: booleans come back as 0/1, decimals as strings/floats, dates as text. Add explicit $casts on any wrapper model, and don't rely on orderBy sorting numeric-looking string columns numerically — cast or sort in PHP for small sets.

  • Search is ASCII-case-insensitive only. SQLite's LIKE folds case for ASCII but does nothing about accents or Arabic script. where('name', 'like', 'bej%') won't match "Béjaïa." If you need accent-insensitive search, normalise in PHP (Str::ascii()) over a cached collection rather than fighting SQL.

  • Cache invalidation after composer update. Sushi caches the generated SQLite database; if you upgrade the package and see stale wilaya/commune data, clear Sushi's cache (see its README for the current command/Sushi::clearCache() equivalent) or clear your app cache, then re-verify counts.

  • First-request cost. Without caching enabled, Sushi builds the in-memory database per request/process. At province scale this is negligible; if you also query communes heavily in loops, enable Sushi caching rather than calling ::all() repeatedly inside a Blade @foreach.

  • Upgrading from 1.x — clean up first. v2's dependency on Sushi means the old install path is obsolete. Remove any published migrations, seeders, or config from v1, drop the tables/columns they created, and check for vendor:publish instructions in your deploy scripts. Leaving a provinces table behind creates a nasty situation where exists:provinces,id does work locally but the data is stale and diverges from the Sushi source.

  • Deployment scripts must not exclude the data file. If your deploy excludes large vendor paths or does a partial composer install --no-dev with custom excludes, make sure the package's data source ships. A missing data file surfaces as an empty province list, not an error.

  • Tests need pdo_sqlite too. RefreshDatabase won't create these tables (correctly), so tests that touch the models pass in CI only if the extension is installed in the test image. Add one assertion-heavy smoke test — Province::count() matches the 58 wilayas and Commune::where('province_id', ...) returns rows — so an extension regression fails loudly.

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.
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core
nodisrupt/laravel-agent
sierratecnologia/media-manager