pchapl/ids
pchapl/ids is a lightweight Laravel package for generating and managing unique identifiers for Eloquent models. It makes it easy to add custom ID behavior to your models with minimal configuration, reducing boilerplate while keeping identifiers consistent and application-specific.
Install and pin
composer require pchapl/ids
Pin to the concrete release rather than a floating constraint, since the package shipped a single 1.0.0 in September 2021 and has had no activity since:
"pchapl/ids": "^1.0"
That constraint is fine for patch safety, but treat it as effectively frozen — there is no upstream to lean on if something breaks.
Where to look first (in this order)
vendor/pchapl/ids/composer.json — confirms the PHP and illuminate/* version bounds. This is the fastest way to know whether your Laravel version (8/9/10/11) is actually supported or merely tolerated.vendor/pchapl/ids/src/ — find the service provider. Look for register() / boot() to see what gets bound into the container, and whether there is a publishes() call for a config file.config/ids.php in the package — publish it with php artisan vendor:publish --tag=ids (verify the tag in the provider source; the README does not document it).First use case
The realistic first use is generating a collision-resistant, human-friendly identifier for a model — an order reference, invoice number, or short public slug — without hand-rolling a Str::random() wrapper. Start with a single throwaway integration:
use Pchapl\Ids\Facades\Ids; // confirm actual namespace from src/
$id = Ids::generate(); // verify method name in source
Write one test asserting uniqueness over a loop of 10k generations and one asserting the format matches your expectation. If those two pass, you understand the API better than the description does.
Before you commit
Because the description is generic ("configurable ID handling", "extensible foundation") and the release notes were empty, spend 20 minutes reading src/. You are adopting an undocumented, dormant, WTFPL-licensed package. That is acceptable for a low-risk utility; it is not acceptable for anything where identifier collision or predictability has a business consequence.
Wrap it behind your own interface
This is the single most important pattern given the package's stagnation. Do not let Pchapl\Ids types leak across your codebase:
namespace App\Support;
interface IdGenerator
{
public function generate(?string $prefix = null): string;
}
final class PackageIdGenerator implements IdGenerator
{
public function generate(?string $prefix = null): string
{
$id = Ids::generate();
return $prefix ? "{$prefix}-{$id}" : $id;
}
}
Bind it in a SupportServiceProvider, and inject IdGenerator everywhere. Replacing the package later — with Str::ulid(), a UUIDv7 implementation, or an in-house scheme — becomes a one-file change.
Model integration via a trait or booted() hook
Keep the generation out of controllers:
protected static function booted(): void
{
static::creating(function (self $model) {
$model->reference ??= app(IdGenerator::class)->generate('ORD');
});
}
Use ??= so explicit values (seeders, imports, test fixtures) are not overwritten. If the identifier must be unique at the database level, back it with a unique index regardless of what the package promises — never rely on application-level uniqueness alone.
Column type and indexing
Store generated identifiers in a string column sized to your actual format, not bigint. Add a unique index. For MySQL/InnoDB, random-looking string primary keys fragment the clustered index; if this is a primary key on a high-write table, keep the auto-increment id as the PK and put the generated identifier in a secondary unique column.
Environment-appropriate generation
If the package is configurable (check the config file), use short/readable formats in local and dev, and the full-strength format in production. If it is not configurable to that degree, do the branching in your adapter rather than in call sites.
Idempotency and retries
When generating an identifier inside a queued job or an HTTP retry, generate it once at the start and carry it through. Do not generate inside a loop that may re-execute — a retried job should reuse the same identifier so downstream consumers see one entity, not two.
Testing
IdGenerator in the container rather than trying to seed the package's randomness.The documentation is effectively absent. The description lists capabilities, not an API. Do not guess method names from the description — Ids::generate(), Ids::make(), Ids::create() are all plausible. Read src/ before writing a single call site, and note what you find in a comment above your adapter so the next developer does not repeat the work.
Abandonment risk is the headline issue. Last release was 2021-09-25, zero stars, single release. There is no maintainer roadmap, no security policy, and no upgrade path. Pin the version, vendored-fork it into your repo if it becomes load-bearing, and never let it be the only thing standing between you and a unique key on a revenue-critical table.
WTFPL requires legal sign-off in most organizations. It is permissive, but it is not OSI-approved and many dependency scanners flag it or refuse to classify it. Get explicit approval before it ships in a commercial product; if approval is slow, the adapter interface means you can ship with Str::ulid() and swap later.
Check the illuminate/* bounds before anything else. A 2021 package likely declares ^8.0 and may not install cleanly on Laravel 11. If Composer resolves it, it may still pull a transitive version you did not intend. Run composer why pchapl/ids after install to see what actually got resolved.
Config caching will bite you. If you publish a config file, php artisan config:cache must be part of your deploy script, and any environment-dependent behaviour must read through config(), not env(), at runtime. env() calls outside config files return null once config is cached — a classic source of "works locally, wrong in production" identifier bugs.
Predictability vs. opacity. Short, human-readable identifiers are guessable and enumerable. If these IDs appear in URLs, in APIs, or anywhere a user could probe, add authorization checks per-object rather than relying on unguessability. If you need non-enumerable public IDs, that is a different design decision — do not assume this package gives it to you because the description says "unique."
Collision handling must be explicit. Whatever the package does internally, your write path should handle a duplicate-key exception. A retry loop of 3 attempts with a fresh generation is cheap insurance:
retry(3, fn () => Order::create(['reference' => $ids->generate('ORD')]), 0);
Extension points are unverified. The description advertises an "extensible foundation," but with no docs you cannot know whether that means a contract to bind, a strategy class, or nothing at all. Before designing around extensibility, grep the source for interface, abstract, and container bindings. If there is no bind/singleton in the provider, "extensible" likely means "fork it."
Debugging tip. If generation appears to return duplicates, first check that you are not resolving a fresh instance per call in a way that resets an internal counter or seed. Second, check that the package is not falling back to a degraded mode (e.g. uniqid()-style time-based generation) where two calls in the same microsecond collide. A quick ray()/dd() inside src/ is faster than reasoning about it from the outside — and because the package is tiny, temporarily patching vendor/ to log is a legitimate debugging move.
How can I help you explore Laravel packages today?