The three sequence generators — SKU, order number, and ticket number — and how to configure, scope, and replace them. See the Documentation index.
Each generator is enabled by adding its trait to an Eloquent model; the value is generated automatically on save via a model observer:
| Generator | Trait | Default destination column |
|---|---|---|
| SKU | Simtabi\Lacommerce\Traits\HasSku |
sku |
| Order number | Simtabi\Lacommerce\Traits\HasOrderNumber |
order_number |
| Ticket number | Simtabi\Lacommerce\Traits\HasTicketNumber |
ticket_number |
use Simtabi\Lacommerce\Traits\HasSku;
use Illuminate\Database\Eloquent\Model;
class Product extends Model
{
use HasSku;
}
$product = new Product();
$product->name = 'Laravel is Awesome';
$product->save();
echo $product->sku; // "LAR-8056449213"The trait registers model-event listeners for the destination field, generated every time you save the model. If you
plan to overwrite values manually, add the destination column to the model's $fillable.
Simtabi\Lacommerce\Supports\Identifiers generates a value without a model. It is a final class of three
static methods, and it goes through no registry, so nothing another package or your application registers
can change what it returns:
use Simtabi\Lacommerce\Supports\Identifiers;
Identifiers::sku('Laravel is Awesome'); // "LAR-8056449213"
Identifiers::sku('Laravel is Awesome', '_'); // "LAR_8056449213"
Identifiers::sku('Laravel is Awesome', '-', 'acme'); // "ACME-LAR-8056449213"
Identifiers::orderNumber(); // "ORD-3920571846"
Identifiers::orderNumber('INV', '/'); // "INV/3920571846"
Identifiers::ticketNumber('Support request'); // "SUP-1749302865"| Method | Returns |
|---|---|
sku(string $source, string $separator = '-', ?string $prefix = null) |
The prefix when given, then the first three characters of the studly-cased source, then ten random digits, joined by the separator and upper-cased. |
orderNumber(?string $prefix = null, string $separator = '-') |
The prefix (ORD when empty), the separator, ten random digits, upper-cased. |
ticketNumber(string $source, string $separator = '-', ?string $prefix = null) |
As sku(). |
The separator is used as given. The traits pass the configured generator.default.separator, and the
prefix from the generator's config block (see Prefixes). A value from
Identifiers is not checked against your table; uniqueness is enforced by the traits' generators.
The same three are registered on Illuminate\Support\Str under vendor-and-package names. They read the
configured separator when you pass none:
| Macro | Forwards to |
|---|---|
Str::simtabiLacommerceSku(string $source, ?string $separator = null, ?string $prefix = null) |
Identifiers::sku() |
Str::simtabiLacommerceOrderNumber(?string $source, ?string $separator = null, ?string $prefix = null) |
Identifiers::orderNumber($prefix, …); $source is ignored |
Str::simtabiLacommerceTicketNumber(string $source, ?string $separator = null, ?string $prefix = null) |
Identifiers::ticketNumber() |
Each takes the same arguments as the bare macro it replaces, so migrating is a rename. The sku and
ticketNumber macros pass their third $prefix argument through, so Str::simtabiLacommerceSku('laravel', '-', 'acme') returns e.g. ACME-LAR-8056449213. The 0.1.0 macros accepted it and dropped it.
The bare
Str::sku(),Str::orderNumber()andStr::ticketNumber()from 0.1.0 are deprecated.Str's macros are one flat map keyed by name, so another package or your application registeringStr::sku()silently replaces this one, or this one replaces theirs. They still work, forward to the scoped macros with the same arguments, and raise oneE_USER_DEPRECATEDper name per boot. Setregister_legacy_macrostofalseto stop registering them and leave the names free. The earliest release that could remove them is 0.2.0. Since this release, the traits no longer call anyStrmacro for the three shipped generators, so overridingStr::sku()does not change the SKUs your models get.
Overload the config method (skuConfigs() / orderNumberConfigs() / ticketNumberConfigs()) to change
settings for a specific model:
use Simtabi\Lacommerce\Generators\Concerns\Sku\SkuConfigs;
use Simtabi\Lacommerce\Traits\HasSku;
class Product extends Model
{
use HasSku;
public function skuConfigs(): SkuConfigs
{
return SkuConfigs::make()
->setSourceColumn(['id', 'user_id'])
->setDestinationColumn('sku')
->setPrefix('ACME')
->setSeparator('-')
->forceUnique(true)
->generateOnCreate(true)
->refreshOnUpdate(false);
}
}Each generator's config block takes a prefix, null by default. When set, it leads the value:
| Block | prefix |
Value |
|---|---|---|
sku |
'acme' |
ACME-BLU-8056449213 |
ticket_number |
'HD' |
HD-PRI-1749302865 |
order_number |
'INV' |
INV-3920571846; the prefix replaces the default ORD |
setPrefix() on a model's configs sets it per model, as in the example above. getPrefix() returns null
when none is set.
The shipped SkuGenerator, OrderNumberGenerator and TicketNumberGenerator are final, so they are not the
thing to extend. The provider builds whichever class a config block's generator key names as
new $class($model), so a custom generator needs two things:
- a constructor that takes the model as its only argument, and
Simtabi\Lacommerce\Generators\Services\Contracts\GeneratorInterface, whose one method,render(), returns the value. Implement the per-type interface beside it inGenerators\Contracts(SkuGeneratorInterface,OrderNumberGeneratorInterface,TicketNumberGeneratorInterface), which extends it, so the class is what the container says it resolves.
A class naming anything else fails on the first save with an InvalidOptionException that names the config
key.
Simtabi\Lacommerce\Generators\Services\Generator is the base the shipped generators extend, and it is
built to be extended. Its parent constructor takes the model, the trait's config method (skuConfigs,
orderNumberConfigs or ticketNumberConfigs) and the identifier kind (sku, orderNumber or
ticketNumber). render() builds the source with getSourceString(), makes a candidate with makeValue(),
and, when unique is on, retries while exists() finds the candidate in the destination column. Override
whichever of those protected methods you need, and the rest, uniqueness included, keeps working.
This one falls back to a fixed source when the model's source columns are empty:
namespace App\Generators;
use Illuminate\Database\Eloquent\Model;
use Simtabi\Lacommerce\Generators\Contracts\SkuGeneratorInterface;
use Simtabi\Lacommerce\Generators\Services\Generator;
final class FallbackSkuGenerator extends Generator implements SkuGeneratorInterface
{
public function __construct(Model $model)
{
parent::__construct($model, 'skuConfigs', 'sku');
}
protected function getSourceString(): string
{
$fields = array_filter($this->model->only($this->modelConfig->getSourceColumn()));
if ($fields === []) {
return 'item';
}
return implode($this->modelConfig->getSeparator(), $fields);
}
}A product named Blue shirt still gets BLU-8056449213; one with an empty name gets ITE-8056449213.
When the value has nothing to do with the shipped format, implement the interface directly. You then own uniqueness:
namespace App\Generators;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Str;
use Simtabi\Lacommerce\Generators\Contracts\TicketNumberGeneratorInterface;
final class SlugTicketNumberGenerator implements TicketNumberGeneratorInterface
{
public function __construct(private readonly Model $model)
{
}
public function render(): string
{
return 'TKT-' . Str::upper(Str::slug((string) $this->model->getAttribute('name')));
}
}Name the class in the published config/simtabi/lacommerce.php:
'generator' => [
// ...
'sku' => [
'generator' => \App\Generators\FallbackSkuGenerator::class,
'source_column' => 'name',
'destination_column' => 'sku',
'prefix' => null,
],
],Both examples are the package's own test fixtures, run against a model on every build, and a test fails if the first one here stops matching its fixture.
A Stock Keeping Unit is a unique identifier/code referring to a particular stock-keeping unit.