Installation:
composer require poing/earmark
Add the service provider to config/app.php under providers (automatically included if using Laravel 5.5+).
Publish Config:
php artisan earmark:config
Review config/earmark.php for defaults (e.g., lock_timeout, recycle_after_minutes).
Run Migrations:
php artisan migrate
Creates the earmarks table with key, value, locked_at, and recycled_at columns.
First Use Case: Reserve a phone extension for a user:
use Poing\Earmark\Facades\Earmark;
$extension = Earmark::get('phone_extension');
// Returns next available value (e.g., "1001") and locks it for the session.
Reserving Values:
Earmark::get($key) to fetch the next available value (e.g., sequential IDs, phone numbers).lock_timeout).$userId = Earmark::get('user_id'); // Returns "1001" and locks it
Recycling Values:
Earmark::unset('phone_extension', $extension);
recycled_at and reused in future requests.Batch Reservations:
$extensions = Earmark::getMultiple(['phone_extension', 'fax_extension'], 2);
// Returns array of 2 values for each key.
Custom Series:
series config (e.g., alphanumeric, date-based):
'series' => [
'invoice' => function ($current) {
return 'INV-' . str_pad($current + 1, 6, '0', STR_PAD_LEFT);
},
],
Database Transactions: Wrap reservations in transactions to ensure atomicity:
DB::transaction(function () {
$extension = Earmark::get('phone_extension');
// Use $extension in your logic...
});
Session Binding: Bind reserved values to the user session for persistence:
session()->put('phone_extension', $extension);
Fallback Logic: Handle edge cases (e.g., no values available) with callbacks:
$value = Earmark::get('key', function () {
return 'DEFAULT_VALUE';
});
Testing:
Use Earmark::flush() to reset the table for tests:
Earmark::flush('phone_extension'); // Clear all values for a key
Lock Timeouts:
lock_timeout is 5 minutes. Adjust in config/earmark.php if needed.locked_at timestamps.Race Conditions:
try {
$value = Earmark::get('key', 10); // 10-second timeout
} catch (\Poing\Earmark\Exceptions\LockException $e) {
// Retry or fallback
}
Recycling Delays:
unset() are not immediately reused. Wait for recycle_after_minutes (default: 1 minute) or force a refresh:
Earmark::refresh('phone_extension');
Key Collisions:
user_id and invoice_id) may cause unexpected behavior.Check Locks:
Query the earmarks table for locked values:
SELECT * FROM earmarks WHERE locked_at IS NOT NULL;
Manually update locked_at to NULL to release stuck locks.
Log Warnings:
Enable debug mode in config/earmark.php to log lock failures:
'debug' => env('APP_DEBUG', false),
Custom Storage: Override the default Eloquent model by binding your own:
Earmark::extend(function ($app) {
$app->singleton(\Poing\Earmark\Contracts\EarmarkRepository::class, function () {
return new CustomEarmarkRepository();
});
});
Event Listeners:
Listen for earmark.locked and earmark.unlocked events to trigger side effects:
Earmark::locked(function ($key, $value) {
// Log or notify when a value is reserved
});
Series Customization: Extend the series logic for complex patterns (e.g., hierarchical IDs):
'series' => [
'ticket' => function ($current, $key) {
$prefix = explode('_', $key)[0];
return $prefix . '-' . strtoupper(dechex($current));
},
],
Rate Limiting: Combine with Laravel’s rate limiting to prevent abuse:
RateLimiter::for('earmark')->by(function (Request $request) {
return $request->user()->id;
})->allow(10)->perMinute();
How can I help you explore Laravel packages today?