- PHP 100%
composer.json
- PHP requirement: >=5.4.0 -> ^8.2 (matches the LiBilling stack)
- illuminate/support: >=5.0 -> ^11.0 || ^12.0
- illuminate/database: added (we touch Eloquent::class_uses_recursive
+ Model::query() directly)
- guzzlehttp/guzzle dropped (never imported anywhere)
- PSR-0 -> PSR-4 (PSR-0 is deprecated; PSR-4 is the modern standard)
- minimum-stability: dev removed (default stable is correct)
- description rewritten to actually describe what the package does
- keywords expanded for discoverability
src/ layout
- Flattened: src/LithiumHosting/LaravelCompanion/Traits/* moved to
src/Traits/* (and similarly for Utilities/Contracts/Abstracts).
Cuts 3 levels of unused nesting; matches PSR-4 convention.
HasLongIdsTrait
- Added declare(strict_types=1) + return types
- Dropped the commented-out alternative creating() block at the
bottom; was dead code drift
- SoftDeletes detection now uses class_uses_recursive() instead
of class_uses(), so models that compose SoftDeletes through a
base class or trait-of-traits still get the soft-deleted
uniqueness check
- getColumnName / getColumnLength / getCharset / getSeparator
renamed to getLongIdColumnName / etc. to match the static-prop
naming + cut accidental collisions with model-level methods of
the same name. Functional behavior unchanged.
RandomStringGenerator
- strict_types + full type hints on every method
- generate() no longer infinitely recurses on impossible
constraints (length 2 + all-classes required + 3+ classes
enabled); throws LogicException after 50 attempts so the
operator sees the bad config instead of a stack-overflow crash
- call_user_func_array("{$this->model}::where", ...) replaced
with direct $this->model::query()->where() (clearer, statically
analyzable, faster)
- setBannedWords() added so consumers can extend the filter or
disable it for trusted internal codes without forking
- getChars() now throws InvalidArgumentException on empty
charset (used to silently produce empty strings)
- setSeparator() validates the array shape before storing
(previously accepted any 2-element input including non-string
separators)
Observer / ModelObserver
- Added strict_types + Model type hints on every callback
- Observer now `implements ModelObserver` (was unrelated)
- setSlug uses Illuminate\Support\Str::slug() instead of the
long-deprecated global str_slug() helper
README rewritten with real usage docs, customization table, and the
package's actual purpose (the prior README was a stale copy-paste
of the GeoIP package title).
libilling_new sanity check: AccountRecoveryStaffEmailUrlTest still
passes (touches a model with HasLongIdsTrait); composer dump-autoload
clean.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|---|---|---|
| src | ||
| .gitignore | ||
| composer.json | ||
| LICENSE | ||
| LICENSE.md | ||
| README.md | ||
Laravel Companion
A small Laravel utility kit that ships two things you reach for over and over again on every model-heavy project:
HasLongIdsTrait— auto-populates a stable, URL-safe identifier column on every Eloquent model that uses the trait. Use it as the public route key so your URLs read/orders/g4n7-b2kinstead of/orders/123and so consumers can't enumerate your tables by incrementing an integer.RandomStringGenerator— the fluent string builder under the trait. Use it standalone for any other "give me a unique short code" need (support PINs, voucher codes, registrar registration keys, etc.).
Plus a tiny Observer / ModelObserver pair that pre-builds the standard Eloquent lifecycle hooks if you'd rather subclass than copy them every time.
Installation
composer require lithiumhosting/laravel-companion
No service provider registration; the trait is opt-in per model, the generator is new-able anywhere.
HasLongIdsTrait — public IDs without enumeration
Add a long_id column to your table:
Schema::table('orders', function (Blueprint $table) {
$table->string('long_id', 6)->unique()->after('id');
});
Use the trait on your model:
use LithiumHosting\LaravelCompanion\Traits\HasLongIdsTrait;
class Order extends Model
{
use HasLongIdsTrait;
}
On every creating event the trait generates a unique value into the column. The check honors SoftDeletes if the model uses it, so a soft-deleted g4n7-b2k won't be reused while it's still recoverable.
If you want the column to be your public route key:
public function getRouteKeyName(): string
{
return 'long_id';
}
Customization
Set any of these protected static properties on your model to override the defaults:
| Property | Default | Effect |
|---|---|---|
$longIdColumnName |
'long_id' |
Use a different column name (number, slug, reference, etc.) |
$longIdColumnLength |
6 |
Generated string length |
$longIdCharset |
'lower|numbers' |
Pipe-delimited combo: lower, upper, numbers, special |
$longIdSeparator |
[] |
[position, char] — e.g. [3, '-'] produces abc-def |
Example for an order-number-style column:
class Order extends Model
{
use HasLongIdsTrait;
protected static $longIdColumnName = 'number';
protected static $longIdColumnLength = 8;
protected static $longIdCharset = 'upper|numbers';
protected static $longIdSeparator = [4, '-']; // "AB12-CD34"
}
RandomStringGenerator — standalone
Inject it via app(RandomStringGenerator::class) or just new RandomStringGenerator() and chain:
use LithiumHosting\LaravelCompanion\Utilities\RandomStringGenerator;
$pin = (new RandomStringGenerator())
->setLength(6)
->setChars('numbers')
->setModel(UserSupportPin::class)
->setColumn('pin')
->generate();
Available builder methods:
| Method | Purpose |
|---|---|
setLength(int) |
Output length, default 6 |
setChars(string $type, string $special = '') |
Charset combo (lower|upper|numbers|special) + optional explicit special chars |
setSeparator([position, char]) |
Insert a separator every N chars |
setPrefix(string) |
Prepend a literal prefix (e.g. 'SUP-') |
setModel(string) |
Eloquent model class to check for uniqueness against |
setColumn(string) |
Column on that model to dedupe against |
requireAllClasses(bool) |
When true, every charset in setChars() MUST appear in the output at least once |
generate(bool $withTrashed = false) |
Produce the string, dedupe against the model + column. Pass true to also check soft-deleted rows. |
getRandomString() |
Produce a string without DB lookup (handy in seeders / tests) |
The generator also runs every candidate through a banned-words filter so generated codes don't accidentally read as profanity, especially relevant when handing customers a code to read back on a phone call.
Observer + ModelObserver
Optional. If you write Eloquent observers and want a default no-op base class with every lifecycle hook (creating, created, updating, ..., restoring, restored) pre-stubbed, extend Observer or implement ModelObserver. Includes a setSlug($slug, $model, $slugColumn = 'slug') helper that uses Str::slug() and deduplicates within the model.
License
This package, laravel-companion is licensed under The MIT License (MIT). Please see License File for more information.
Is it any good?
Yes.
When people first hear about a new product, they frequently ask if it is any good. A Hacker News user remarked:
Note to self: Starting immediately, all raganwald projects will have a "Is it any good?" section in the readme, and the answer shall be "yes.".