2025-11-21 10:49:41 +00:00
|
|
|
# Product Management
|
|
|
|
|
|
2025-11-25 16:25:20 +00:00
|
|
|
## Overview
|
|
|
|
|
|
|
|
|
|
The Laravel Shop package provides a complete product management system with support for:
|
|
|
|
|
- Multi-language content through `HasMetaTranslation` trait
|
|
|
|
|
- Flexible pricing with `ProductPrice` model
|
|
|
|
|
- Stock management and reservations
|
|
|
|
|
- Product variants (parent/child relationships)
|
|
|
|
|
- Categories, attributes, and actions
|
|
|
|
|
- Product relations (related, upsell, cross-sell)
|
|
|
|
|
|
2025-11-21 10:49:41 +00:00
|
|
|
## Creating Products
|
|
|
|
|
|
|
|
|
|
### Minimal Product Creation
|
|
|
|
|
|
|
|
|
|
The absolute minimum to create a product:
|
|
|
|
|
|
|
|
|
|
```php
|
|
|
|
|
use Blax\Shop\Models\Product;
|
|
|
|
|
|
|
|
|
|
$product = Product::create([
|
|
|
|
|
'slug' => 'my-product',
|
|
|
|
|
]);
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
This will automatically:
|
2025-11-25 16:25:20 +00:00
|
|
|
- Generate a random slug if not provided (e.g., 'new-product-abc12345')
|
|
|
|
|
- Initialize meta field as empty JSON object
|
2025-11-21 10:49:41 +00:00
|
|
|
- Set status to 'draft'
|
|
|
|
|
- Set type to 'simple'
|
|
|
|
|
|
|
|
|
|
### Basic Product Creation
|
|
|
|
|
|
|
|
|
|
```php
|
|
|
|
|
$product = Product::create([
|
|
|
|
|
'slug' => 'blue-hoodie',
|
|
|
|
|
'sku' => 'HOOD-BLU-001',
|
|
|
|
|
'type' => 'simple',
|
|
|
|
|
'status' => 'published',
|
2025-11-22 08:55:58 +00:00
|
|
|
'is_visible' => true,
|
2025-11-21 10:49:41 +00:00
|
|
|
'featured' => false,
|
|
|
|
|
]);
|
|
|
|
|
|
2025-11-25 16:25:20 +00:00
|
|
|
// Add translated content (stored in meta column)
|
2025-11-21 10:49:41 +00:00
|
|
|
$product->setLocalized('name', 'Blue Hoodie', 'en');
|
|
|
|
|
$product->setLocalized('description', 'Comfortable cotton hoodie', 'en');
|
|
|
|
|
$product->setLocalized('short_description', 'Cotton hoodie', 'en');
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### Advanced Product Creation
|
|
|
|
|
|
|
|
|
|
```php
|
|
|
|
|
$product = Product::create([
|
|
|
|
|
// Basic Info
|
|
|
|
|
'slug' => 'premium-headphones',
|
|
|
|
|
'sku' => 'HEAD-PREM-001',
|
|
|
|
|
'type' => 'simple',
|
|
|
|
|
'status' => 'published',
|
2025-11-22 08:55:58 +00:00
|
|
|
'is_visible' => true,
|
2025-11-21 10:49:41 +00:00
|
|
|
'featured' => true,
|
|
|
|
|
'published_at' => now(),
|
|
|
|
|
'sort_order' => 10,
|
|
|
|
|
|
2025-11-25 16:25:20 +00:00
|
|
|
// Sale Period
|
2025-11-21 10:49:41 +00:00
|
|
|
'sale_start' => now(),
|
|
|
|
|
'sale_end' => now()->addDays(7),
|
|
|
|
|
|
|
|
|
|
// Stock Management
|
|
|
|
|
'manage_stock' => true,
|
|
|
|
|
'low_stock_threshold' => 10,
|
|
|
|
|
|
|
|
|
|
// Physical Properties
|
|
|
|
|
'weight' => 0.5, // kg
|
|
|
|
|
'length' => 20, // cm
|
|
|
|
|
'width' => 15, // cm
|
|
|
|
|
'height' => 10, // cm
|
|
|
|
|
'virtual' => false,
|
|
|
|
|
'downloadable' => false,
|
|
|
|
|
|
|
|
|
|
// Tax
|
|
|
|
|
'tax_class' => 'standard',
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
// Add translations
|
|
|
|
|
$product->setLocalized('name', 'Premium Wireless Headphones', 'en');
|
|
|
|
|
$product->setLocalized('name', 'Auriculares Premium Inalámbricos', 'es');
|
|
|
|
|
|
|
|
|
|
$product->setLocalized('description', 'High-quality wireless headphones with noise cancellation', 'en');
|
|
|
|
|
$product->setLocalized('short_description', 'Premium wireless headphones', 'en');
|
2025-11-25 16:25:20 +00:00
|
|
|
|
|
|
|
|
// Add custom meta data
|
|
|
|
|
$product->meta = (object)[
|
|
|
|
|
'brand' => 'AudioPro',
|
|
|
|
|
'color' => 'black',
|
|
|
|
|
'warranty' => '2 years',
|
|
|
|
|
];
|
|
|
|
|
$product->save();
|
2025-11-21 10:49:41 +00:00
|
|
|
```
|
|
|
|
|
|
2025-11-25 16:25:20 +00:00
|
|
|
## Product Pricing
|
2025-11-21 10:49:41 +00:00
|
|
|
|
2025-11-25 16:25:20 +00:00
|
|
|
Products use the `ProductPrice` model for flexible pricing. Each product must have at least one price to be purchasable.
|
|
|
|
|
|
|
|
|
|
### Creating Product Prices
|
2025-11-21 10:49:41 +00:00
|
|
|
|
|
|
|
|
```php
|
2025-11-25 16:25:20 +00:00
|
|
|
use Blax\Shop\Models\ProductPrice;
|
|
|
|
|
|
|
|
|
|
// Create a default price
|
|
|
|
|
$price = ProductPrice::create([
|
|
|
|
|
'purchasable_type' => Product::class,
|
|
|
|
|
'purchasable_id' => $product->id,
|
|
|
|
|
'currency' => 'USD',
|
|
|
|
|
'unit_amount' => 4999, // $49.99 in cents
|
|
|
|
|
'is_default' => true,
|
|
|
|
|
'active' => true,
|
|
|
|
|
'type' => 'one_time',
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
// Add sale price
|
|
|
|
|
$price->update([
|
|
|
|
|
'sale_unit_amount' => 3999, // $39.99
|
|
|
|
|
]);
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### Multi-Currency Pricing
|
|
|
|
|
|
|
|
|
|
```php
|
|
|
|
|
// USD price (default)
|
|
|
|
|
ProductPrice::create([
|
|
|
|
|
'purchasable_type' => Product::class,
|
|
|
|
|
'purchasable_id' => $product->id,
|
|
|
|
|
'currency' => 'USD',
|
|
|
|
|
'unit_amount' => 4999,
|
|
|
|
|
'is_default' => true,
|
|
|
|
|
'active' => true,
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
// EUR price
|
|
|
|
|
ProductPrice::create([
|
|
|
|
|
'purchasable_type' => Product::class,
|
|
|
|
|
'purchasable_id' => $product->id,
|
|
|
|
|
'currency' => 'EUR',
|
|
|
|
|
'unit_amount' => 4499,
|
|
|
|
|
'is_default' => false,
|
|
|
|
|
'active' => true,
|
|
|
|
|
]);
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### Recurring Prices (Subscriptions)
|
|
|
|
|
|
|
|
|
|
```php
|
|
|
|
|
ProductPrice::create([
|
|
|
|
|
'purchasable_type' => Product::class,
|
|
|
|
|
'purchasable_id' => $product->id,
|
|
|
|
|
'currency' => 'USD',
|
|
|
|
|
'unit_amount' => 999, // $9.99/month
|
|
|
|
|
'type' => 'recurring',
|
|
|
|
|
'interval' => 'month',
|
|
|
|
|
'interval_count' => 1,
|
|
|
|
|
'trial_period_days' => 7,
|
|
|
|
|
'is_default' => true,
|
|
|
|
|
'active' => true,
|
|
|
|
|
]);
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### Get Current Price
|
|
|
|
|
|
|
|
|
|
```php
|
|
|
|
|
// Get the current price (considers sale prices and dates)
|
|
|
|
|
$currentPrice = $product->getCurrentPrice(); // Returns float
|
|
|
|
|
|
|
|
|
|
// Check if product is on sale
|
|
|
|
|
if ($product->isOnSale()) {
|
|
|
|
|
echo "On sale!";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Get default price
|
|
|
|
|
$defaultPrice = $product->defaultPrice()->first();
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## Stock Management
|
|
|
|
|
|
|
|
|
|
### Enable Stock Management
|
|
|
|
|
|
|
|
|
|
```php
|
|
|
|
|
$product->update([
|
|
|
|
|
'manage_stock' => true,
|
|
|
|
|
'low_stock_threshold' => 10,
|
2025-11-21 10:49:41 +00:00
|
|
|
]);
|
|
|
|
|
```
|
|
|
|
|
|
2025-11-25 16:25:20 +00:00
|
|
|
### Increase/Decrease Stock
|
|
|
|
|
|
|
|
|
|
```php
|
|
|
|
|
// Increase stock
|
|
|
|
|
$product->increaseStock(50);
|
|
|
|
|
|
|
|
|
|
// Decrease stock
|
|
|
|
|
$product->decreaseStock(1);
|
|
|
|
|
|
|
|
|
|
// Get available stock
|
|
|
|
|
$available = $product->getAvailableStock();
|
|
|
|
|
|
|
|
|
|
// Check if in stock
|
|
|
|
|
if ($product->isInStock()) {
|
|
|
|
|
echo "In stock!";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check if low stock
|
|
|
|
|
if ($product->isLowStock()) {
|
|
|
|
|
echo "Low stock warning!";
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### Stock Reservations
|
|
|
|
|
|
|
|
|
|
```php
|
|
|
|
|
use Blax\Shop\Models\ProductStock;
|
|
|
|
|
|
|
|
|
|
// Reserve stock temporarily
|
|
|
|
|
$reservation = $product->reserveStock(
|
|
|
|
|
quantity: 2,
|
|
|
|
|
reference: $cart,
|
|
|
|
|
until: now()->addMinutes(15),
|
|
|
|
|
note: 'Cart reservation'
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// Release reservation
|
|
|
|
|
$reservation->update(['status' => 'completed']);
|
|
|
|
|
|
|
|
|
|
// Get active reservations
|
|
|
|
|
$reservations = $product->reservations()->get();
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### Stock History
|
|
|
|
|
|
|
|
|
|
```php
|
|
|
|
|
// Get all stock records
|
|
|
|
|
$stockRecords = $product->stocks()->get();
|
|
|
|
|
|
|
|
|
|
// Filter by type
|
|
|
|
|
$increases = $product->stocks()->where('type', 'increase')->get();
|
|
|
|
|
$decreases = $product->stocks()->where('type', 'decrease')->get();
|
|
|
|
|
$reservations = $product->stocks()->where('type', 'reservation')->get();
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## Product Variants
|
|
|
|
|
|
|
|
|
|
### Create Parent Product
|
2025-11-21 10:49:41 +00:00
|
|
|
|
|
|
|
|
```php
|
|
|
|
|
$parent = Product::create([
|
|
|
|
|
'type' => 'variable',
|
|
|
|
|
'slug' => 'hoodie',
|
2025-11-25 16:25:20 +00:00
|
|
|
'status' => 'published',
|
2025-11-21 10:49:41 +00:00
|
|
|
]);
|
|
|
|
|
|
2025-11-25 16:25:20 +00:00
|
|
|
$parent->setLocalized('name', 'Hoodie', 'en');
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### Create Variants
|
|
|
|
|
|
|
|
|
|
```php
|
|
|
|
|
// Small variant
|
2025-11-21 10:49:41 +00:00
|
|
|
$small = Product::create([
|
|
|
|
|
'type' => 'simple',
|
|
|
|
|
'slug' => 'hoodie-small',
|
|
|
|
|
'sku' => 'HOOD-S',
|
|
|
|
|
'parent_id' => $parent->id,
|
2025-11-25 16:25:20 +00:00
|
|
|
'status' => 'published',
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
ProductPrice::create([
|
|
|
|
|
'purchasable_type' => Product::class,
|
|
|
|
|
'purchasable_id' => $small->id,
|
|
|
|
|
'currency' => 'USD',
|
|
|
|
|
'unit_amount' => 4999,
|
|
|
|
|
'is_default' => true,
|
|
|
|
|
'active' => true,
|
2025-11-21 10:49:41 +00:00
|
|
|
]);
|
|
|
|
|
|
2025-11-25 16:25:20 +00:00
|
|
|
// Medium variant
|
2025-11-21 10:49:41 +00:00
|
|
|
$medium = Product::create([
|
|
|
|
|
'type' => 'simple',
|
|
|
|
|
'slug' => 'hoodie-medium',
|
|
|
|
|
'sku' => 'HOOD-M',
|
|
|
|
|
'parent_id' => $parent->id,
|
2025-11-25 16:25:20 +00:00
|
|
|
'status' => 'published',
|
2025-11-21 10:49:41 +00:00
|
|
|
]);
|
|
|
|
|
|
2025-11-25 16:25:20 +00:00
|
|
|
ProductPrice::create([
|
|
|
|
|
'purchasable_type' => Product::class,
|
|
|
|
|
'purchasable_id' => $medium->id,
|
|
|
|
|
'currency' => 'USD',
|
|
|
|
|
'unit_amount' => 4999,
|
|
|
|
|
'is_default' => true,
|
|
|
|
|
'active' => true,
|
2025-11-21 10:49:41 +00:00
|
|
|
]);
|
2025-11-25 16:25:20 +00:00
|
|
|
|
|
|
|
|
// Get all variants
|
|
|
|
|
$variants = $parent->children()->get();
|
2025-11-21 10:49:41 +00:00
|
|
|
```
|
|
|
|
|
|
2025-11-25 16:25:20 +00:00
|
|
|
## Categories
|
|
|
|
|
|
|
|
|
|
### Create Categories
|
2025-11-21 10:49:41 +00:00
|
|
|
|
|
|
|
|
```php
|
2025-11-25 16:25:20 +00:00
|
|
|
use Blax\Shop\Models\ProductCategory;
|
|
|
|
|
|
|
|
|
|
$category = ProductCategory::create([
|
|
|
|
|
'name' => 'Electronics',
|
|
|
|
|
'slug' => 'electronics',
|
|
|
|
|
'description' => 'Electronic products',
|
|
|
|
|
'is_visible' => true,
|
|
|
|
|
'sort_order' => 1,
|
2025-11-21 10:49:41 +00:00
|
|
|
]);
|
|
|
|
|
|
2025-11-25 16:25:20 +00:00
|
|
|
// Create child category
|
|
|
|
|
$subCategory = ProductCategory::create([
|
|
|
|
|
'name' => 'Headphones',
|
|
|
|
|
'slug' => 'headphones',
|
|
|
|
|
'parent_id' => $category->id,
|
|
|
|
|
'is_visible' => true,
|
|
|
|
|
'sort_order' => 1,
|
|
|
|
|
]);
|
2025-11-21 10:49:41 +00:00
|
|
|
```
|
|
|
|
|
|
2025-11-25 16:25:20 +00:00
|
|
|
### Attach Products to Categories
|
2025-11-21 10:49:41 +00:00
|
|
|
|
|
|
|
|
```php
|
2025-11-25 16:25:20 +00:00
|
|
|
// Attach single category
|
|
|
|
|
$product->categories()->attach($category->id);
|
|
|
|
|
|
|
|
|
|
// Attach multiple categories
|
|
|
|
|
$product->categories()->attach([$category1->id, $category2->id]);
|
|
|
|
|
|
|
|
|
|
// Sync categories (removes others)
|
|
|
|
|
$product->categories()->sync([$category1->id, $category2->id]);
|
|
|
|
|
|
|
|
|
|
// Get product categories
|
|
|
|
|
$categories = $product->categories()->get();
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### Query Products by Category
|
|
|
|
|
|
|
|
|
|
```php
|
|
|
|
|
// Get products in category
|
|
|
|
|
$products = Product::byCategory($category->id)->get();
|
|
|
|
|
|
|
|
|
|
// Get category tree
|
|
|
|
|
$tree = ProductCategory::getTree();
|
|
|
|
|
|
|
|
|
|
// Get visible categories
|
|
|
|
|
$visible = ProductCategory::visible()->get();
|
|
|
|
|
|
|
|
|
|
// Get root categories
|
|
|
|
|
$roots = ProductCategory::roots()->get();
|
2025-11-21 10:49:41 +00:00
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## Product Attributes
|
|
|
|
|
|
2025-11-25 16:25:20 +00:00
|
|
|
### Add Attributes
|
2025-11-21 10:49:41 +00:00
|
|
|
|
|
|
|
|
```php
|
|
|
|
|
use Blax\Shop\Models\ProductAttribute;
|
|
|
|
|
|
2025-11-25 16:25:20 +00:00
|
|
|
// Add color attribute
|
2025-11-21 10:49:41 +00:00
|
|
|
ProductAttribute::create([
|
|
|
|
|
'product_id' => $product->id,
|
2025-11-25 16:25:20 +00:00
|
|
|
'key' => 'Color',
|
|
|
|
|
'value' => 'Blue',
|
2025-11-21 10:49:41 +00:00
|
|
|
'sort_order' => 1,
|
|
|
|
|
]);
|
|
|
|
|
|
2025-11-25 16:25:20 +00:00
|
|
|
// Add size attribute
|
2025-11-21 10:49:41 +00:00
|
|
|
ProductAttribute::create([
|
|
|
|
|
'product_id' => $product->id,
|
2025-11-25 16:25:20 +00:00
|
|
|
'key' => 'Size',
|
|
|
|
|
'value' => 'Large',
|
2025-11-21 10:49:41 +00:00
|
|
|
'sort_order' => 2,
|
|
|
|
|
]);
|
|
|
|
|
|
2025-11-25 16:25:20 +00:00
|
|
|
// Get product attributes
|
|
|
|
|
$attributes = $product->attributes()->get();
|
2025-11-21 10:49:41 +00:00
|
|
|
```
|
|
|
|
|
|
2025-11-25 16:25:20 +00:00
|
|
|
## Product Actions
|
2025-11-21 10:49:41 +00:00
|
|
|
|
2025-11-25 16:25:20 +00:00
|
|
|
Product actions allow you to trigger events when certain things happen (e.g., on purchase).
|
2025-11-21 10:49:41 +00:00
|
|
|
|
2025-11-25 16:25:20 +00:00
|
|
|
### Create Product Actions
|
2025-11-21 10:49:41 +00:00
|
|
|
|
|
|
|
|
```php
|
2025-11-25 16:25:20 +00:00
|
|
|
use Blax\Shop\Models\ProductAction;
|
2025-11-21 10:49:41 +00:00
|
|
|
|
2025-11-25 16:25:20 +00:00
|
|
|
// Send email on purchase
|
|
|
|
|
ProductAction::create([
|
2025-11-21 10:49:41 +00:00
|
|
|
'product_id' => $product->id,
|
2025-11-25 16:25:20 +00:00
|
|
|
'action_type' => 'SendWelcomeEmail',
|
|
|
|
|
'event' => 'purchased',
|
|
|
|
|
'parameters' => [
|
|
|
|
|
'template' => 'welcome',
|
|
|
|
|
'delay' => 0,
|
|
|
|
|
],
|
|
|
|
|
'active' => true,
|
|
|
|
|
'sort_order' => 1,
|
2025-11-21 10:49:41 +00:00
|
|
|
]);
|
|
|
|
|
|
2025-11-25 16:25:20 +00:00
|
|
|
// Grant access on purchase
|
|
|
|
|
ProductAction::create([
|
2025-11-21 10:49:41 +00:00
|
|
|
'product_id' => $product->id,
|
2025-11-25 16:25:20 +00:00
|
|
|
'action_type' => 'GrantCourseAccess',
|
|
|
|
|
'event' => 'purchased',
|
|
|
|
|
'parameters' => [
|
|
|
|
|
'course_id' => 123,
|
|
|
|
|
],
|
|
|
|
|
'active' => true,
|
|
|
|
|
'sort_order' => 2,
|
2025-11-21 10:49:41 +00:00
|
|
|
]);
|
2025-11-25 16:25:20 +00:00
|
|
|
```
|
2025-11-21 10:49:41 +00:00
|
|
|
|
2025-11-25 16:25:20 +00:00
|
|
|
### Trigger Actions
|
|
|
|
|
|
|
|
|
|
```php
|
|
|
|
|
// Actions are automatically triggered on events
|
|
|
|
|
// You can also manually trigger them:
|
|
|
|
|
$product->callActions('purchased', $productPurchase);
|
2025-11-21 10:49:41 +00:00
|
|
|
|
2025-11-25 16:25:20 +00:00
|
|
|
// On refund
|
|
|
|
|
$product->callActions('refunded', $productPurchase);
|
2025-11-21 10:49:41 +00:00
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## Product Relations
|
|
|
|
|
|
|
|
|
|
### Related Products
|
|
|
|
|
|
|
|
|
|
```php
|
2025-11-25 16:25:20 +00:00
|
|
|
// Add related products
|
2025-11-21 10:49:41 +00:00
|
|
|
$product->relatedProducts()->attach($relatedProduct->id, [
|
|
|
|
|
'type' => 'related',
|
|
|
|
|
]);
|
|
|
|
|
|
2025-11-25 16:25:20 +00:00
|
|
|
// Get related products
|
|
|
|
|
$related = $product->relatedProducts()->wherePivot('type', 'related')->get();
|
2025-11-21 10:49:41 +00:00
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### Upsells
|
|
|
|
|
|
|
|
|
|
```php
|
2025-11-25 16:25:20 +00:00
|
|
|
// Add upsell product
|
|
|
|
|
$product->relatedProducts()->attach($upsellProduct->id, [
|
2025-11-21 10:49:41 +00:00
|
|
|
'type' => 'upsell',
|
|
|
|
|
]);
|
|
|
|
|
|
2025-11-25 16:25:20 +00:00
|
|
|
// Get upsell products
|
|
|
|
|
$upsells = $product->upsells()->get();
|
2025-11-21 10:49:41 +00:00
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### Cross-sells
|
|
|
|
|
|
|
|
|
|
```php
|
2025-11-25 16:25:20 +00:00
|
|
|
// Add cross-sell product
|
|
|
|
|
$product->relatedProducts()->attach($crossSellProduct->id, [
|
2025-11-21 10:49:41 +00:00
|
|
|
'type' => 'cross-sell',
|
|
|
|
|
]);
|
|
|
|
|
|
2025-11-25 16:25:20 +00:00
|
|
|
// Get cross-sell products
|
|
|
|
|
$crossSells = $product->crossSells()->get();
|
2025-11-21 10:49:41 +00:00
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## Querying Products
|
|
|
|
|
|
2025-11-25 16:25:20 +00:00
|
|
|
### Scopes
|
2025-11-21 10:49:41 +00:00
|
|
|
|
|
|
|
|
```php
|
|
|
|
|
// Published products
|
2025-11-25 16:25:20 +00:00
|
|
|
$published = Product::published()->get();
|
2025-11-21 10:49:41 +00:00
|
|
|
|
2025-11-25 16:25:20 +00:00
|
|
|
// Visible products (published + visible + published_at check)
|
|
|
|
|
$visible = Product::visible()->get();
|
2025-11-21 10:49:41 +00:00
|
|
|
|
|
|
|
|
// Featured products
|
2025-11-25 16:25:20 +00:00
|
|
|
$featured = Product::featured()->get();
|
|
|
|
|
|
|
|
|
|
// In stock products
|
|
|
|
|
$inStock = Product::inStock()->get();
|
2025-11-21 10:49:41 +00:00
|
|
|
|
2025-11-25 16:25:20 +00:00
|
|
|
// Low stock products
|
|
|
|
|
$lowStock = Product::lowStock()->get();
|
|
|
|
|
|
|
|
|
|
// By type
|
|
|
|
|
$simple = Product::where('type', 'simple')->get();
|
|
|
|
|
$virtual = Product::where('virtual', true)->get();
|
|
|
|
|
$downloadable = Product::where('downloadable', true)->get();
|
2025-11-21 10:49:41 +00:00
|
|
|
```
|
|
|
|
|
|
2025-11-25 16:25:20 +00:00
|
|
|
### Search
|
2025-11-21 10:49:41 +00:00
|
|
|
|
|
|
|
|
```php
|
2025-11-25 16:25:20 +00:00
|
|
|
// Search by slug, SKU, or name
|
|
|
|
|
$results = Product::search('headphones')->get();
|
2025-11-21 10:49:41 +00:00
|
|
|
|
|
|
|
|
// Price range
|
2025-11-25 16:25:20 +00:00
|
|
|
$results = Product::priceRange(min: 10.00, max: 100.00)->get();
|
2025-11-21 10:49:41 +00:00
|
|
|
|
|
|
|
|
// Order by price
|
2025-11-25 16:25:20 +00:00
|
|
|
$cheapest = Product::orderByPrice('asc')->get();
|
|
|
|
|
$expensive = Product::orderByPrice('desc')->get();
|
|
|
|
|
```
|
2025-11-21 10:49:41 +00:00
|
|
|
|
2025-11-25 16:25:20 +00:00
|
|
|
### Combining Scopes
|
2025-11-21 10:49:41 +00:00
|
|
|
|
2025-11-25 16:25:20 +00:00
|
|
|
```php
|
2025-11-21 10:49:41 +00:00
|
|
|
$products = Product::visible()
|
|
|
|
|
->inStock()
|
|
|
|
|
->byCategory($categoryId)
|
2025-11-25 16:25:20 +00:00
|
|
|
->priceRange(min: 20, max: 50)
|
2025-11-21 10:49:41 +00:00
|
|
|
->orderByPrice('asc')
|
2025-11-25 16:25:20 +00:00
|
|
|
->get();
|
2025-11-21 10:49:41 +00:00
|
|
|
```
|
|
|
|
|
|
2025-11-25 16:25:20 +00:00
|
|
|
## Product Visibility
|
2025-11-21 10:49:41 +00:00
|
|
|
|
|
|
|
|
```php
|
2025-11-25 16:25:20 +00:00
|
|
|
// Check if product is visible
|
|
|
|
|
if ($product->isVisible()) {
|
|
|
|
|
// Product is published, visible, and published_at is in past
|
2025-11-21 10:49:41 +00:00
|
|
|
}
|
|
|
|
|
|
2025-11-25 16:25:20 +00:00
|
|
|
// Set visibility
|
|
|
|
|
$product->update([
|
|
|
|
|
'status' => 'published',
|
|
|
|
|
'is_visible' => true,
|
|
|
|
|
'published_at' => now(),
|
|
|
|
|
]);
|
2025-11-21 10:49:41 +00:00
|
|
|
```
|
|
|
|
|
|
2025-11-25 16:25:20 +00:00
|
|
|
## Virtual & Downloadable Products
|
2025-11-21 10:49:41 +00:00
|
|
|
|
|
|
|
|
```php
|
2025-11-25 16:25:20 +00:00
|
|
|
// Virtual product (no shipping)
|
|
|
|
|
$product->update([
|
|
|
|
|
'virtual' => true,
|
|
|
|
|
]);
|
2025-11-21 10:49:41 +00:00
|
|
|
|
2025-11-25 16:25:20 +00:00
|
|
|
// Downloadable product
|
|
|
|
|
$product->update([
|
|
|
|
|
'downloadable' => true,
|
|
|
|
|
]);
|
2025-11-21 10:49:41 +00:00
|
|
|
|
2025-11-25 16:25:20 +00:00
|
|
|
// Both
|
|
|
|
|
$product->update([
|
|
|
|
|
'virtual' => true,
|
|
|
|
|
'downloadable' => true,
|
|
|
|
|
]);
|
2025-11-21 10:49:41 +00:00
|
|
|
```
|
|
|
|
|
|
2025-11-25 16:25:20 +00:00
|
|
|
## API Export
|
2025-11-21 10:49:41 +00:00
|
|
|
|
|
|
|
|
```php
|
2025-11-25 16:25:20 +00:00
|
|
|
// Get product as API array
|
2025-11-21 10:49:41 +00:00
|
|
|
$data = $product->toApiArray();
|
|
|
|
|
|
|
|
|
|
// Returns:
|
|
|
|
|
// [
|
|
|
|
|
// 'id' => '...',
|
|
|
|
|
// 'slug' => '...',
|
2025-11-25 16:25:20 +00:00
|
|
|
// 'sku' => '...',
|
|
|
|
|
// 'name' => '...', // localized
|
|
|
|
|
// 'description' => '...', // localized
|
|
|
|
|
// 'short_description' => '...', // localized
|
|
|
|
|
// 'type' => '...',
|
2025-11-21 10:49:41 +00:00
|
|
|
// 'price' => 49.99,
|
2025-11-25 16:25:20 +00:00
|
|
|
// 'sale_price' => null,
|
|
|
|
|
// 'is_on_sale' => false,
|
|
|
|
|
// 'low_stock' => false,
|
|
|
|
|
// 'featured' => false,
|
|
|
|
|
// 'virtual' => false,
|
|
|
|
|
// 'downloadable' => false,
|
|
|
|
|
// 'weight' => 0.5,
|
|
|
|
|
// 'dimensions' => [...],
|
2025-11-21 10:49:41 +00:00
|
|
|
// 'categories' => [...],
|
|
|
|
|
// 'attributes' => [...],
|
|
|
|
|
// 'variants' => [...],
|
2025-11-25 16:25:20 +00:00
|
|
|
// 'parent' => null,
|
|
|
|
|
// 'created_at' => '...',
|
|
|
|
|
// 'updated_at' => '...',
|
2025-11-21 10:49:41 +00:00
|
|
|
// ]
|
|
|
|
|
```
|