PHP code example of aliziodev / laravel-product-catalog
1. Go to this page and download the library: Download aliziodev/laravel-product-catalog library. Choose the download type require.
2. Extract the ZIP file and open the index.php.
3. Add this code to the index.php.
<?php
require_once('vendor/autoload.php');
/* Start to develop here. Best regards https://php-download.com/ */
aliziodev / laravel-product-catalog example snippets
use Aliziodev\ProductCatalog\Models\Product;
use Aliziodev\ProductCatalog\Models\ProductVariant;
use Aliziodev\ProductCatalog\Enums\ProductType;
use Aliziodev\ProductCatalog\Enums\InventoryPolicy;
use Aliziodev\ProductCatalog\Facades\ProductCatalog;
// 1. Create product
$product = Product::create(['name' => 'T-Shirt', 'code' => 'TS-001', 'type' => ProductType::Simple]);
// 2. Create variant
$variant = $product->variants()->create(['sku' => 'TS-001-WHT', 'price' => 150000, 'is_default' => true]);
// 3. Set stock
$variant->inventoryItem()->create(['quantity' => 100, 'policy' => InventoryPolicy::Track]);
// 4. Publish
$product->publish();
// 5. Query
Product::published()->inStock()->with('variants')->get();
// config/product-catalog.php
return [
// The Eloquent model used throughout the package (search drivers, API controller).
// Override when extending the base Product model in your application.
// Your model must extend Aliziodev\ProductCatalog\Models\Product.
'model' => \Aliziodev\ProductCatalog\Models\Product::class,
// Prefix for all package tables. Change BEFORE running migrations.
'table_prefix' => env('PRODUCT_CATALOG_TABLE_PREFIX', 'catalog_'),
'inventory' => [
// Built-in: 'database' (tracks stock in DB), 'null' (always in stock).
// Register custom drivers via ProductCatalog::extend().
'driver' => env('PRODUCT_CATALOG_INVENTORY_DRIVER', 'database'),
],
'slug' => [
// Regenerate the slug prefix when the product name changes.
'auto_generate' => true,
'separator' => '-',
// Length of the permanent random suffix (4–32). Recommended: 8.
'route_key_length' => (int) env('PRODUCT_CATALOG_ROUTE_KEY_LENGTH', 8),
],
'search' => [
// Built-in: 'database' (default) or 'scout'.
'driver' => env('PRODUCT_CATALOG_SEARCH_DRIVER', 'database'),
],
'routes' => [
// Set true to register the built-in read-only catalog API routes.
'enabled' => env('PRODUCT_CATALOG_ROUTES_ENABLED', false),
'prefix' => env('PRODUCT_CATALOG_ROUTES_PREFIX', 'catalog'),
'middleware' => ['api'],
],
];
// Status scopes
Product::published()->get();
Product::draft()->get();
// Price range (active variants only)
$product->minPrice(); // float|null
$product->maxPrice(); // float|null
$product->priceRange(); // ['min' => 850000.0, 'max' => 1200000.0] | null
// Stock scope — products with at least one purchasable active variant
// NOTE: variants without an inventoryItem record are excluded from this scope.
// Always create an inventoryItem when creating a variant, even for Allow policy.
Product::inStock()->get();
// Search across name, code, short_description, and variant SKUs
Product::search('RS-AIR')->get();
// Filter
Product::forBrand($brand)->published()->get();
Product::withTag($tag)->inStock()->get();
// Low stock alert
use Aliziodev\ProductCatalog\Models\InventoryItem;
InventoryItem::lowStock()->with('variant.product')->get();
use Aliziodev\ProductCatalog\Search\ProductSearchBuilder;
// Fluent API
ProductSearchBuilder::query('kemeja')
->inCategory('t-shirts') // slug or ID
->withTags(['sale', 'new-arrival']) // AND logic, slug or ID
->forBrand('stylehouse') // slug or ID
->priceBetween(50_000, 500_000)
->onlyInStock()
->sortBy('price')->sortAscending()
->paginate(24);
// Build from HTTP request — maps q, category, brand, tag/tags[],
// min_price, max_price, in_stock, type, sort_by, sort_direction
ProductSearchBuilder::fromRequest($request)->paginate(24);
// Control which relations are eager-loaded
ProductSearchBuilder::query('laptop')
->withRelations(['brand', 'primaryCategory', 'tags', 'defaultVariant'])
->paginate(20);
// config/product-catalog.php
'model' => \App\Models\Product::class, // top-level — used by all subsystems
'search' => [
'driver' => env('PRODUCT_CATALOG_SEARCH_DRIVER', 'database'),
],
// Custom search driver
use Aliziodev\ProductCatalog\Facades\ProductCatalog;
ProductCatalog::extendSearch('typesense', function ($app) {
return new \App\Search\TypesenseSearchDriver;
});
// Find by slug (both old and new slugs resolve)
$product = Product::findBySlug('ergonomic-mouse-a1b2c3d4');
$product = Product::findBySlugOrFail('ergonomic-mouse-a1b2c3d4');
// Scope variant
Product::published()->bySlug($slug)->firstOrFail();
use Aliziodev\ProductCatalog\Http\Resources\ProductResource;
use Aliziodev\ProductCatalog\Http\Resources\ProductVariantResource;
$product = Product::with(['brand', 'primaryCategory', 'tags', 'variants'])->findOrFail($id);
return ProductResource::make($product);
use Aliziodev\ProductCatalog\Events\ProductPublished;
use Aliziodev\ProductCatalog\Events\InventoryReserved;
class SendNewProductNotification
{
public function handle(ProductPublished $event): void
{
// $event->product
}
}
class HandleStockReservation
{
public function handle(InventoryReserved $event): void
{
// $event->variant
// $event->type — MovementType::Reserve or MovementType::Release
// $event->quantity — positive for reserve, negative for release
// $event->reservedBefore
// $event->reservedAfter
// $event->reason
// $event->movement — the InventoryMovement record
// $event->isReserve() — true when type is Reserve
// $event->isRelease() — true when type is Release
}
}
use Aliziodev\ProductCatalog\Enums\InventoryPolicy;
$variant->inventoryItem()->create([
'quantity' => 0,
'policy' => InventoryPolicy::Allow, // never runs out
'low_stock_threshold' => null,
]);
namespace App\Models;
use Aliziodev\ProductCatalog\Models\Product as BaseProduct;
use Spatie\MediaLibrary\HasMedia;
use Spatie\MediaLibrary\InteractsWithMedia;
use Spatie\MediaLibrary\MediaCollections\Models\Media;
class Product extends BaseProduct implements HasMedia
{
use InteractsWithMedia;
public function registerMediaCollections(): void
{
$this->addMediaCollection('featured')
->singleFile()
->acceptsMimeTypes(['image/jpeg', 'image/png', 'image/webp']);
$this->addMediaCollection('gallery');
}
public function registerMediaConversions(?Media $media = null): void
{
$this->addMediaConversion('thumb')
->width(400)
->height(400)
->sharpen(5);
$this->addMediaConversion('webp')
->format('webp')
->quality(80);
}
}
// In your controllers / services — always use your extended model
use App\Models\Product;
$product = Product::with('variants')->findOrFail($id);
$product->getFirstMediaUrl('featured', 'thumb'); // ✓ works
// When coming from a variant relationship, re-query:
$product = App\Models\Product::find($variant->product_id);
$product->getFirstMediaUrl('featured', 'thumb'); // ✓ works
// app/Models/ProductVariant.php
namespace App\Models;
use Aliziodev\ProductCatalog\Models\ProductVariant as BaseVariant;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class ProductVariant extends BaseVariant
{
public function product(): BelongsTo
{
return $this->belongsTo(Product::class); // App\Models\Product
}
}
// Upload featured image
$product->addMediaFromRequest('image')->toMediaCollection('featured');
// Upload gallery
$product->addMediaFromRequest('gallery')->toMediaCollection('gallery');
// Get URLs
$product->getFirstMediaUrl('featured', 'thumb');
$product->getMedia('gallery')->map->getUrl('webp');
namespace App\Inventory;
use Aliziodev\ProductCatalog\Contracts\InventoryProviderInterface;
use Aliziodev\ProductCatalog\Exceptions\InventoryException;
use Aliziodev\ProductCatalog\Models\ProductVariant;
use App\Models\Inventory; // your own inventory model
use Illuminate\Database\Eloquent\Model;
class AppInventoryProvider implements InventoryProviderInterface
{
public function getQuantity(ProductVariant $variant): int
{
return Inventory::where('sku', $variant->sku)->value('quantity') ?? 0;
}
public function isInStock(ProductVariant $variant): bool
{
return $this->getQuantity($variant) > 0;
}
public function canFulfill(ProductVariant $variant, int $quantity): bool
{
return $this->getQuantity($variant) >= $quantity;
}
public function adjust(
ProductVariant $variant,
int $delta,
string $reason = '',
?Model $reference = null,
): void {
$record = Inventory::where('sku', $variant->sku)->firstOrFail();
$newQty = $record->quantity + $delta;
if ($newQty < 0) {
throw InventoryException::insufficientStock(abs($delta), $record->quantity);
}
$record->update(['quantity' => $newQty]);
}
public function set(
ProductVariant $variant,
int $quantity,
string $reason = '',
?Model $reference = null,
): void {
Inventory::updateOrCreate(
['sku' => $variant->sku],
['quantity' => max(0, $quantity)]
);
}
public function reserve(
ProductVariant $variant,
int $quantity,
string $reason = '',
?Model $reference = null,
): void {
// implement reservation against your external system
}
public function release(
ProductVariant $variant,
int $quantity,
string $reason = '',
?Model $reference = null,
): void {
// implement release against your external system
}
public function commit(
ProductVariant $variant,
int $quantity,
string $reason = '',
?Model $reference = null,
): void {
// implement commit (reservation → permanent deduction) against your external system
}
}
use Aliziodev\ProductCatalog\Facades\ProductCatalog;
public function boot(): void
{
ProductCatalog::extend('app', function ($app) {
return new \App\Inventory\AppInventoryProvider;
});
}
// tests/TestCase.php
use Orchestra\Testbench\TestCase as OrchestraTestCase;
use Aliziodev\ProductCatalog\ProductCatalogServiceProvider;
abstract class TestCase extends OrchestraTestCase
{
use \Illuminate\Foundation\Testing\RefreshDatabase;
protected function getPackageProviders($app): array
{
return [ProductCatalogServiceProvider::class];
}
protected function defineDatabaseMigrations(): void
{
$this->loadMigrationsFrom(
base_path('vendor/aliziodev/laravel-product-catalog/database/migrations')
);
}
}
// tests/Feature/CheckoutTest.php
use Aliziodev\ProductCatalog\Models\Product;
use Aliziodev\ProductCatalog\Models\ProductVariant;
use Aliziodev\ProductCatalog\Enums\InventoryPolicy;
it('can add item to cart', function () {
$variant = ProductVariant::factory()->create(['price' => 150000]);
// Use Allow policy — no inventory record needed
$variant->inventoryItem()->create(['quantity' => 0, 'policy' => InventoryPolicy::Allow]);
// ... your test assertions
});