← Назад к вопросам
Каталог товаров интернет-магазина
2.8 Senior🔥 131 комментариев
#API и веб-протоколы#Архитектура и паттерны#Фреймворки
Условие
Спроектировать и реализовать JSON API для каталога товаров интернет-магазина.
Структура
- Дерево категорий (максимальная вложенность - 3 уровня)
- Товары с характеристиками
- Корзина для авторизованных и неавторизованных пользователей
- Заказы
API эндпоинты
- GET /api/categories - дерево категорий
- GET /api/products?category_id=X - товары категории
- GET /api/products/{id} - товар с характеристиками
- POST /api/cart/add - добавить в корзину
- GET /api/cart - содержимое корзины
- POST /api/orders - создать заказ
Требования
- Авторизация через Laravel Sanctum
- Пагинация
- Фильтрация товаров
Технологии
Laravel 10+, MySQL, Laravel Sanctum
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Решение
1. Миграции
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
// Категории (иерархия)
return new class extends Migration {
public function up(): void {
Schema::create("categories", function (Blueprint $table) {
$table->id();
$table->string("name", 255);
$table->string("slug", 255)->unique();
$table->text("description")->nullable();
$table->unsignedBigInteger("parent_id")->nullable();
$table->integer("level")->default(1);
$table->timestamps();
$table->foreign("parent_id")->references("id")->on("categories")->nullOnDelete();
$table->index("parent_id");
});
// Товары
Schema::create("products", function (Blueprint $table) {
$table->id();
$table->foreignId("category_id")->constrained("categories");
$table->string("name", 255);
$table->string("slug", 255)->unique();
$table->text("description")->nullable();
$table->decimal("price", 10, 2);
$table->integer("stock")->default(0);
$table->boolean("is_active")->default(true);
$table->timestamps();
$table->index("category_id");
$table->index("is_active");
});
// Характеристики товаров
Schema::create("product_attributes", function (Blueprint $table) {
$table->id();
$table->foreignId("product_id")->constrained("products")->cascadeOnDelete();
$table->string("name", 255);
$table->string("value", 255);
$table->timestamps();
$table->index("product_id");
});
// Корзина
Schema::create("carts", function (Blueprint $table) {
$table->id();
$table->foreignId("user_id")->nullable()->constrained("users")->cascadeOnDelete();
$table->string("session_id", 255)->nullable();
$table->timestamps();
$table->index("user_id");
$table->index("session_id");
});
// Товары в корзине
Schema::create("cart_items", function (Blueprint $table) {
$table->id();
$table->foreignId("cart_id")->constrained("carts")->cascadeOnDelete();
$table->foreignId("product_id")->constrained("products");
$table->integer("quantity");
$table->decimal("price", 10, 2);
$table->timestamps();
$table->unique(["cart_id", "product_id"]);
});
// Заказы
Schema::create("orders", function (Blueprint $table) {
$table->id();
$table->foreignId("user_id")->constrained("users");
$table->decimal("total", 10, 2);
$table->enum("status", ["pending", "paid", "shipped", "delivered", "cancelled"])->default("pending");
$table->string("customer_email", 255);
$table->string("customer_phone", 20);
$table->text("delivery_address");
$table->timestamps();
$table->index("user_id");
$table->index("status");
});
// Товары в заказе
Schema::create("order_items", function (Blueprint $table) {
$table->id();
$table->foreignId("order_id")->constrained("orders")->cascadeOnDelete();
$table->foreignId("product_id")->constrained("products");
$table->integer("quantity");
$table->decimal("price", 10, 2);
$table->timestamps();
});
}
public function down(): void {
Schema::dropIfExists("order_items");
Schema::dropIfExists("orders");
Schema::dropIfExists("cart_items");
Schema::dropIfExists("carts");
Schema::dropIfExists("product_attributes");
Schema::dropIfExists("products");
Schema::dropIfExists("categories");
}
};
2. Модели
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class Category extends Model {
protected $fillable = ["name", "slug", "description", "parent_id", "level"];
public function parent(): BelongsTo {
return $this->belongsTo(Category::class, "parent_id");
}
public function children(): HasMany {
return $this->hasMany(Category::class, "parent_id");
}
public function products(): HasMany {
return $this->hasMany(Product::class);
}
public function allProducts(): HasMany {
return $this->hasMany(Product::class);
}
}
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
class Product extends Model {
protected $fillable = ["category_id", "name", "slug", "description", "price", "stock", "is_active"];
protected $casts = ["price" => "decimal:2", "is_active" => "boolean"];
public function category(): BelongsTo {
return $this->belongsTo(Category::class);
}
public function attributes(): HasMany {
return $this->hasMany(ProductAttribute::class);
}
}
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class ProductAttribute extends Model {
protected $fillable = ["product_id", "name", "value"];
public function product(): BelongsTo {
return $this->belongsTo(Product::class);
}
}
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
class Cart extends Model {
protected $fillable = ["user_id", "session_id"];
public function user(): BelongsTo {
return $this->belongsTo(User::class);
}
public function items(): HasMany {
return $this->hasMany(CartItem::class);
}
}
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class CartItem extends Model {
protected $fillable = ["cart_id", "product_id", "quantity", "price"];
public function cart(): BelongsTo {
return $this->belongsTo(Cart::class);
}
public function product(): BelongsTo {
return $this->belongsTo(Product::class);
}
}
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
class Order extends Model {
protected $fillable = ["user_id", "total", "status", "customer_email", "customer_phone", "delivery_address"];
protected $casts = ["total" => "decimal:2"];
public function user(): BelongsTo {
return $this->belongsTo(User::class);
}
public function items(): HasMany {
return $this->hasMany(OrderItem::class);
}
}
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class OrderItem extends Model {
protected $fillable = ["order_id", "product_id", "quantity", "price"];
public function order(): BelongsTo {
return $this->belongsTo(Order::class);
}
public function product(): BelongsTo {
return $this->belongsTo(Product::class);
}
}
3. Service для корзины
<?php
namespace App\Services;
use App\Models\Cart;
use App\Models\Product;
use Auth;
use Illuminate\Validation\ValidationException;
use Session;
class CartService {
public function getCart(): Cart {
if (Auth::check()) {
return Cart::firstOrCreate(["user_id" => Auth::id()]);
} else {
$sessionId = Session::getId();
return Cart::firstOrCreate(["session_id" => $sessionId]);
}
}
public function addToCart(int $productId, int $quantity): void {
$product = Product::findOrFail($productId);
if ($product->stock < $quantity) {
throw ValidationException::withMessages([
"quantity" => "Недостаточно товара на складе"
]);
}
$cart = $this->getCart();
$cartItem = $cart->items()->where("product_id", $productId)->first();
if ($cartItem) {
$cartItem->quantity += $quantity;
$cartItem->save();
} else {
$cart->items()->create([
"product_id" => $productId,
"quantity" => $quantity,
"price" => $product->price,
]);
}
}
public function removeFromCart(int $cartItemId): void {
$cart = $this->getCart();
$cart->items()->where("id", $cartItemId)->delete();
}
public function clearCart(): void {
$this->getCart()->items()->delete();
}
public function getCartTotal(): float {
return $this->getCart()->items()->sum("price" * "quantity");
}
}
4. API контроллеры
<?php
namespace App\Http\Controllers\Api;
use App\Models\Category;
use Illuminate\Http\JsonResponse;
class CategoryController extends Controller {
public function index(): JsonResponse {
$categories = Category::where("level", 1)
->with("children.children")
->get();
return response()->json($categories);
}
}
<?php
namespace App\Http\Controllers\Api;
use App\Models\Product;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
class ProductController extends Controller {
public function index(Request $request): JsonResponse {
$query = Product::where("is_active", true);
if ($request->has("category_id")) {
$query->where("category_id", $request->get("category_id"));
}
if ($request->has("search")) {
$query->where("name", "like", "%" . $request->get("search") . "%");
}
$products = $query->paginate(20);
return response()->json($products);
}
public function show(Product $product): JsonResponse {
$product->load("attributes");
return response()->json($product);
}
}
<?php
namespace App\Http\Controllers\Api;
use App\Services\CartService;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
class CartController extends Controller {
protected $cartService;
public function __construct(CartService $cartService) {
$this->cartService = $cartService;
}
public function show(): JsonResponse {
$cart = $this->cartService->getCart();
return response()->json([
"items" => $cart->items()->with("product")->get(),
"total" => $this->cartService->getCartTotal(),
]);
}
public function add(Request $request): JsonResponse {
$validated = $request->validate([
"product_id" => "required|exists:products,id",
"quantity" => "required|integer|min:1",
]);
$this->cartService->addToCart($validated["product_id"], $validated["quantity"]);
return response()->json(["message" => "Item added to cart"], 201);
}
public function remove(Request $request): JsonResponse {
$validated = $request->validate(["item_id" => "required|exists:cart_items,id"]);
$this->cartService->removeFromCart($validated["item_id"]);
return response()->json(["message" => "Item removed"]);
}
}
<?php
namespace App\Http\Controllers\Api;
use App\Models\Order;
use App\Services\CartService;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
class OrderController extends Controller {
protected $cartService;
public function __construct(CartService $cartService) {
$this->cartService = $cartService;
}
public function store(Request $request): JsonResponse {
$validated = $request->validate([
"customer_email" => "required|email",
"customer_phone" => "required|string",
"delivery_address" => "required|string",
]);
$cart = $this->cartService->getCart();
if ($cart->items()->count() === 0) {
return response()->json(["message" => "Cart is empty"], 400);
}
return DB::transaction(function () use ($validated, $cart) {
$total = $this->cartService->getCartTotal();
$order = Order::create([
"user_id" => Auth::id(),
"total" => $total,
"customer_email" => $validated["customer_email"],
"customer_phone" => $validated["customer_phone"],
"delivery_address" => $validated["delivery_address"],
]);
foreach ($cart->items as $item) {
$order->items()->create([
"product_id" => $item->product_id,
"quantity" => $item->quantity,
"price" => $item->price,
]);
}
$this->cartService->clearCart();
return response()->json($order, 201);
});
}
}
5. Маршруты API
<?php
use App\Http\Controllers\Api\CategoryController;
use App\Http\Controllers\Api\ProductController;
use App\Http\Controllers\Api\CartController;
use App\Http\Controllers\Api\OrderController;
use Illuminate\Support\Facades\Route;
Route::get("/categories", [CategoryController::class, "index"]);
Route::get("/products", [ProductController::class, "index"]);
Route::get("/products/{product}", [ProductController::class, "show"]);
Route::middleware("auth:sanctum")->group(function () {
Route::get("/cart", [CartController::class, "show"]);
Route::post("/cart/add", [CartController::class, "add"]);
Route::post("/cart/remove", [CartController::class, "remove"]);
Route::post("/orders", [OrderController::class, "store"]);
});
Это полное решение интернет-магазина с иерархией категорий, товарами, корзиной и заказами.