تطوير واجهات REST API
بناء واجهة برمجة تطبيقات RESTful كاملة - الجزء 1
بناء واجهة برمجة تطبيقات RESTful كاملة - الجزء 1
في هذه السلسلة المكونة من ثلاثة أجزاء، سنبني واجهة برمجة تطبيقات تجارة إلكترونية كاملة من الصفر، مطبقين جميع الأنماط وأفضل الممارسات التي تعلمناها. يركز الجزء 1 على التخطيط وتصميم قاعدة البيانات وإعداد الأساس.
نظرة عامة على المشروع
سنبني واجهة برمجة تطبيقات لمنصة تجارة إلكترونية بالميزات التالية:
- إدارة المستخدمين: التسجيل، المصادقة، الملفات الشخصية
- كتالوج المنتجات: المنتجات، الفئات، العلامات، البحث
- سلة التسوق: إضافة/إزالة العناصر، الكميات
- الطلبات: الدفع، سجل الطلبات، تتبع الحالة
- المراجعات: تقييمات ومراجعات المنتجات
- لوحة الإدارة: إدارة المنتجات، الطلبات، المستخدمين
التخطيط: نقاط نهاية واجهة برمجة التطبيقات
مبدأ تصميم واجهة برمجة التطبيقات: صمم نقاط نهاية واجهة برمجة التطبيقات قبل كتابة الكود. فكر في الموارد والعلاقات والعمليات. وثق كل شيء.
لنحدد نقاط نهاية واجهة برمجة التطبيقات:
نقاط نهاية المصادقة:
POST /api/v1/auth/register # تسجيل مستخدم جديد
POST /api/v1/auth/login # تسجيل دخول المستخدم
POST /api/v1/auth/logout # تسجيل خروج المستخدم
POST /api/v1/auth/refresh # تحديث الرمز
GET /api/v1/auth/me # الحصول على المستخدم المصادق عليه
PUT /api/v1/auth/profile # تحديث الملف الشخصي
POST /api/v1/auth/password/forgot # نسيت كلمة المرور
POST /api/v1/auth/password/reset # إعادة تعيين كلمة المرور
نقاط نهاية المنتجات:
GET /api/v1/products # قائمة المنتجات (مع الفلاتر)
GET /api/v1/products/{id} # الحصول على تفاصيل المنتج
POST /api/v1/products # إنشاء منتج (مسؤول)
PUT /api/v1/products/{id} # تحديث منتج (مسؤول)
DELETE /api/v1/products/{id} # حذف منتج (مسؤول)
GET /api/v1/products/{id}/reviews # الحصول على مراجعات المنتج
POST /api/v1/products/{id}/reviews # إضافة مراجعة
نقاط نهاية الفئات:
GET /api/v1/categories # قائمة جميع الفئات
GET /api/v1/categories/{id} # الحصول على تفاصيل الفئة
POST /api/v1/categories # إنشاء فئة (مسؤول)
PUT /api/v1/categories/{id} # تحديث فئة (مسؤول)
DELETE /api/v1/categories/{id} # حذف فئة (مسؤول)
GET /api/v1/categories/{id}/products # المنتجات في الفئة
نقاط نهاية السلة:
GET /api/v1/cart # الحصول على سلة المستخدم
POST /api/v1/cart/items # إضافة عنصر إلى السلة
PUT /api/v1/cart/items/{id} # تحديث عنصر السلة
DELETE /api/v1/cart/items/{id} # إزالة من السلة
DELETE /api/v1/cart # مسح السلة
نقاط نهاية الطلبات:
GET /api/v1/orders # قائمة طلبات المستخدم
GET /api/v1/orders/{id} # الحصول على تفاصيل الطلب
POST /api/v1/orders # إنشاء طلب (الدفع)
PUT /api/v1/orders/{id}/cancel # إلغاء الطلب
GET /api/v1/admin/orders # قائمة جميع الطلبات (مسؤول)
PUT /api/v1/admin/orders/{id} # تحديث حالة الطلب (مسؤول)
تصميم قاعدة البيانات
لنصمم مخطط قاعدة البيانات مع العلاقات:
جدول المستخدمين:
users
├── id (المفتاح الأساسي)
├── name
├── email (فريد)
├── password
├── phone
├── role (enum: user, admin)
├── email_verified_at
├── created_at
└── updated_at
جدول الفئات:
categories
├── id (المفتاح الأساسي)
├── name
├── slug (فريد)
├── description
├── image
├── parent_id (قابل للإلغاء، مرجع ذاتي)
├── is_active
├── created_at
└── updated_at
جدول المنتجات:
products
├── id (المفتاح الأساسي)
├── category_id (مفتاح خارجي)
├── name
├── slug (فريد)
├── description
├── price (عشري)
├── sale_price (عشري، قابل للإلغاء)
├── sku (فريد)
├── stock_quantity
├── is_active
├── is_featured
├── created_at
└── updated_at
جدول صور المنتجات:
product_images
├── id (المفتاح الأساسي)
├── product_id (مفتاح خارجي)
├── image_path
├── is_primary
├── order
├── created_at
└── updated_at
جدول السلات:
carts
├── id (المفتاح الأساسي)
├── user_id (مفتاح خارجي، قابل للإلغاء)
├── session_id (للسلات الضيف)
├── created_at
└── updated_at
cart_items
├── id (المفتاح الأساسي)
├── cart_id (مفتاح خارجي)
├── product_id (مفتاح خارجي)
├── quantity
├── price (لقطة عند وقت الإضافة)
├── created_at
└── updated_at
جدول الطلبات:
orders
├── id (المفتاح الأساسي)
├── user_id (مفتاح خارجي)
├── order_number (فريد)
├── status (enum: pending, processing, completed, cancelled)
├── subtotal
├── tax
├── shipping
├── total
├── payment_method
├── payment_status
├── shipping_address (JSON)
├── billing_address (JSON)
├── notes
├── created_at
└── updated_at
order_items
├── id (المفتاح الأساسي)
├── order_id (مفتاح خارجي)
├── product_id (مفتاح خارجي)
├── product_name (لقطة)
├── quantity
├── price (لقطة)
├── subtotal
├── created_at
└── updated_at
جدول المراجعات:
reviews
├── id (المفتاح الأساسي)
├── product_id (مفتاح خارجي)
├── user_id (مفتاح خارجي)
├── rating (1-5)
├── title
├── comment
├── is_verified_purchase
├── is_approved
├── created_at
└── updated_at
إنشاء الترحيلات
لننشئ ترحيلات قاعدة البيانات:
إنشاء ترحيل المستخدمين:
<?php
# موجود بالفعل في Laravel
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('users', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('email')->unique();
$table->timestamp('email_verified_at')->nullable();
$table->string('password');
$table->string('phone')->nullable();
$table->enum('role', ['user', 'admin'])->default('user');
$table->rememberToken();
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('users');
}
};
database/migrations/xxxx_create_categories_table.php:
<?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');
$table->string('slug')->unique();
$table->text('description')->nullable();
$table->string('image')->nullable();
$table->foreignId('parent_id')
->nullable()
->constrained('categories')
->nullOnDelete();
$table->boolean('is_active')->default(true);
$table->timestamps();
$table->index(['slug', 'is_active']);
});
}
public function down(): void
{
Schema::dropIfExists('categories');
}
};
database/migrations/xxxx_create_products_table.php:
<?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('products', function (Blueprint $table) {
$table->id();
$table->foreignId('category_id')
->constrained()
->cascadeOnDelete();
$table->string('name');
$table->string('slug')->unique();
$table->text('description')->nullable();
$table->decimal('price', 10, 2);
$table->decimal('sale_price', 10, 2)->nullable();
$table->string('sku')->unique();
$table->integer('stock_quantity')->default(0);
$table->boolean('is_active')->default(true);
$table->boolean('is_featured')->default(false);
$table->timestamps();
$table->index(['slug', 'is_active']);
$table->index(['category_id', 'is_active']);
});
Schema::create('product_images', function (Blueprint $table) {
$table->id();
$table->foreignId('product_id')
->constrained()
->cascadeOnDelete();
$table->string('image_path');
$table->boolean('is_primary')->default(false);
$table->integer('order')->default(0);
$table->timestamps();
$table->index(['product_id', 'is_primary']);
});
}
public function down(): void
{
Schema::dropIfExists('product_images');
Schema::dropIfExists('products');
}
};
database/migrations/xxxx_create_carts_table.php:
<?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('carts', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')
->nullable()
->constrained()
->cascadeOnDelete();
$table->string('session_id')->nullable()->index();
$table->timestamps();
});
Schema::create('cart_items', function (Blueprint $table) {
$table->id();
$table->foreignId('cart_id')
->constrained()
->cascadeOnDelete();
$table->foreignId('product_id')
->constrained()
->cascadeOnDelete();
$table->integer('quantity')->default(1);
$table->decimal('price', 10, 2);
$table->timestamps();
$table->unique(['cart_id', 'product_id']);
});
}
public function down(): void
{
Schema::dropIfExists('cart_items');
Schema::dropIfExists('carts');
}
};
database/migrations/xxxx_create_orders_table.php:
<?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('orders', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')
->constrained()
->cascadeOnDelete();
$table->string('order_number')->unique();
$table->enum('status', [
'pending',
'processing',
'completed',
'cancelled'
])->default('pending');
$table->decimal('subtotal', 10, 2);
$table->decimal('tax', 10, 2)->default(0);
$table->decimal('shipping', 10, 2)->default(0);
$table->decimal('total', 10, 2);
$table->string('payment_method');
$table->string('payment_status')->default('pending');
$table->json('shipping_address');
$table->json('billing_address');
$table->text('notes')->nullable();
$table->timestamps();
$table->index(['user_id', 'status']);
$table->index('order_number');
});
Schema::create('order_items', function (Blueprint $table) {
$table->id();
$table->foreignId('order_id')
->constrained()
->cascadeOnDelete();
$table->foreignId('product_id')
->constrained();
$table->string('product_name'); // لقطة
$table->integer('quantity');
$table->decimal('price', 10, 2);
$table->decimal('subtotal', 10, 2);
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('order_items');
Schema::dropIfExists('orders');
}
};
database/migrations/xxxx_create_reviews_table.php:
<?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('reviews', function (Blueprint $table) {
$table->id();
$table->foreignId('product_id')
->constrained()
->cascadeOnDelete();
$table->foreignId('user_id')
->constrained()
->cascadeOnDelete();
$table->tinyInteger('rating')->unsigned();
$table->string('title');
$table->text('comment')->nullable();
$table->boolean('is_verified_purchase')->default(false);
$table->boolean('is_approved')->default(false);
$table->timestamps();
$table->index(['product_id', 'is_approved']);
$table->unique(['product_id', 'user_id']); // مراجعة واحدة لكل مستخدم لكل منتج
});
}
public function down(): void
{
Schema::dropIfExists('reviews');
}
};
إنشاء النماذج
الآن لننشئ نماذج Eloquent مع العلاقات:
app/Models/Category.php:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
class Category extends Model
{
protected $fillable = [
'name',
'slug',
'description',
'image',
'parent_id',
'is_active',
];
protected $casts = [
'is_active' => 'boolean',
];
/**
* الفئة الأم
*/
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 scopeActive($query)
{
return $query->where('is_active', true);
}
/**
* نطاق للحصول على الفئات الجذرية
*/
public function scopeRoot($query)
{
return $query->whereNull('parent_id');
}
}
app/Models/Product.php:
<?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',
'sale_price',
'sku',
'stock_quantity',
'is_active',
'is_featured',
];
protected $casts = [
'price' => 'decimal:2',
'sale_price' => 'decimal:2',
'is_active' => 'boolean',
'is_featured' => 'boolean',
];
protected $appends = ['current_price', 'in_stock'];
/**
* علاقة الفئة
*/
public function category(): BelongsTo
{
return $this->belongsTo(Category::class);
}
/**
* علاقة الصور
*/
public function images(): HasMany
{
return $this->hasMany(ProductImage::class);
}
/**
* علاقة المراجعات
*/
public function reviews(): HasMany
{
return $this->hasMany(Review::class);
}
/**
* الحصول على السعر الحالي (سعر التخفيض إذا كان متاحًا)
*/
public function getCurrentPriceAttribute(): float
{
return $this->sale_price ?? $this->price;
}
/**
* التحقق من توفر المنتج في المخزون
*/
public function getInStockAttribute(): bool
{
return $this->stock_quantity > 0;
}
/**
* نطاق المنتجات النشطة
*/
public function scopeActive($query)
{
return $query->where('is_active', true);
}
/**
* نطاق المنتجات المميزة
*/
public function scopeFeatured($query)
{
return $query->where('is_featured', true);
}
/**
* نطاق المنتجات المتوفرة في المخزون
*/
public function scopeInStock($query)
{
return $query->where('stock_quantity', '>', 0);
}
}
أفضل ممارسات الترحيل: أنشئ دائمًا فهارس على المفاتيح الخارجية والأعمدة التي تستعلم عنها بشكل متكرر. استخدم أنواع الأعمدة المناسبة (عشري للمال، enum لحقول الحالة). أضف قيود الفرادة عند الحاجة.
تمرين تطبيقي:
- قم بتشغيل الترحيلات لإنشاء جميع الجداول
- أنشئ النماذج المتبقية (Cart، Order، Review، إلخ)
- حدد جميع العلاقات بين النماذج
- أضف النطاقات والمحصلات المناسبة للنماذج
- أنشئ seeder لملء بيانات الاختبار
الملخص
في الجزء 1، قمنا بـ:
- التخطيط لنقاط نهاية واجهة برمجة التطبيقات والموارد
- تصميم مخطط قاعدة بيانات منظم
- إنشاء ترحيلات بفهارس وقيود مناسبة
- بناء نماذج Eloquent مع العلاقات
- إضافة نطاقات ومحصلات للاستعلامات الشائعة
في الجزء 2، سننفذ المصادقة، وننشئ المستودعات والإجراءات، ونبني نقاط نهاية CRUD، ونتعامل مع تحميلات الملفات لصور المنتجات.