تكامل xAPI في مشاريع Laravel — الدليل الشامل
تكامل xAPI في مشاريع Laravel — الدليل الشامل
المؤلف: فريق Bzzix التقني
آخر تحديث: 2026
المستوى: متقدم
المتطلبات: PHP 8.0+، Laravel 9+، Composer
الوقت المقدر للقراءة: 35 دقيقة
مقدمة
إذا كنت تبني منصة تعليمية مخصصة باستخدام Laravel، فأنت في المكان الصحيح. هذا الدليل يأخذك من الصفر حتى تكامل xAPI كامل مع NELC، بما في ذلك إرسال الـ Statements، معالجة الأخطاء، الإرسال المجمّع، والاختبار الكامل.
1. المكتبات المطلوبة
1.1 PHP xAPI Libraries المتاحة
| المكتبة | الوصف | التوصية |
|---|---|---|
xapiphp/xapi |
مكتبة PHP رسمية لـ xAPI | ✅ موصى بها |
rusticisoftware/tincan |
مكتبة Tin Can/xAPI | ✅ جيدة |
adlnet/xAPI-Spec |
المواصفات الرسمية | 📖 مرجع |
| بناء مخصص | Laravel Service + HTTP Client | ✅ الأفضل للتحكم الكامل |
1.1.1 الحزمة الرسمية من المركز الوطني (NELC Laravel Package)
يوفر المركز الوطني للتعليم الإلكتروني (NELC) حزمة رسمية مفتوحة المصدر لتسهيل دمج وتكامل معايير xAPI وإرسال التقارير البرمجية من تطبيقات Laravel مباشرة:
- حزمة Laravel LRS Package: laravel-lrs-package
تحتوي هذه الحزمة على النماذج الأساسية والأكواد الجاهزة لبناء إفادات xAPI المتوافقة تماماً مع شروط الاعتماد والربط.
1.2 تثبيت المكتبة الأساسية
composer require xapiphp/xapi
أو استخدام HTTP Client المدمج في Laravel:
# لا حاجة لإضافة حزمة — Guzzle مدمج في Laravel
composer require guzzlehttp/guzzle
2. إعداد مشروع Laravel
2.1 متغيرات البيئة
أضف هذه المتغيرات إلى ملف .env:
# NELC LRS Configuration
XAPI_ENDPOINT=https://lrs.nelc.gov.sa/xapi/
XAPI_USERNAME=your-nelc-username
XAPI_PASSWORD=your-nelc-password
XAPI_VERSION=1.0.3
# Platform Settings
XAPI_PLATFORM=Bzzix LMS
XAPI_HOMEPAGE=https://your-platform.com
# Performance
XAPI_BATCH_SIZE=50
XAPI_RETRY_ATTEMPTS=3
XAPI_RETRY_DELAY=5
2.2 ملف config/xapi.php
<?php
return [
/*
|--------------------------------------------------------------------------
| xAPI LRS Configuration
|--------------------------------------------------------------------------
*/
'endpoint' => env('XAPI_ENDPOINT', 'https://lrs.nelc.gov.sa/xapi/'),
'username' => env('XAPI_USERNAME', ''),
'password' => env('XAPI_PASSWORD', ''),
'version' => env('XAPI_VERSION', '1.0.3'),
/*
|--------------------------------------------------------------------------
| Platform Configuration
|--------------------------------------------------------------------------
*/
'platform' => env('XAPI_PLATFORM', 'Bzzix LMS'),
'homepage' => env('XAPI_HOMEPAGE', config('app.url')),
/*
|--------------------------------------------------------------------------
| Performance Settings
|--------------------------------------------------------------------------
*/
'batch_size' => env('XAPI_BATCH_SIZE', 50),
'retry_attempts' => env('XAPI_RETRY_ATTEMPTS', 3),
'retry_delay' => env('XAPI_RETRY_DELAY', 5), // seconds
/*
|--------------------------------------------------------------------------
| Standard Verb URIs
|--------------------------------------------------------------------------
*/
'verbs' => [
'completed' => 'http://adlnet.gov/expapi/verbs/completed',
'passed' => 'http://adlnet.gov/expapi/verbs/passed',
'failed' => 'http://adlnet.gov/expapi/verbs/failed',
'answered' => 'http://adlnet.gov/expapi/verbs/answered',
'launched' => 'http://adlnet.gov/expapi/verbs/launched',
'experienced' => 'http://adlnet.gov/expapi/verbs/experienced',
'attempted' => 'http://adlnet.gov/expapi/verbs/attempted',
'interacted' => 'http://adlnet.gov/expapi/verbs/interacted',
'imported' => 'http://adlnet.gov/expapi/verbs/imported',
'created' => 'http://adlnet.gov/expapi/verbs/created',
'rated' => 'http://adlnet.gov/expapi/verbs/rated',
'commented' => 'http://adlnet.gov/expapi/verbs/commented',
],
/*
|--------------------------------------------------------------------------
| Activity Types
|--------------------------------------------------------------------------
*/
'activity_types' => [
'course' => 'http://adlnet.gov/expapi/activities/course',
'module' => 'http://adlnet.gov/expapi/activities/module',
'lesson' => 'http://adlnet.gov/expapi/activities/lesson',
'assessment' => 'http://adlnet.gov/expapi/activities/assessment',
'quiz' => 'http://adlnet.gov/expapi/activities/assessment',
'question' => 'http://adlnet.gov/expapi/activities/question',
'video' => 'https://w3id.org/xapi/video/activity-type/video',
'simulation' => 'http://adlnet.gov/expapi/activities/simulation',
'meeting' => 'http://adlnet.gov/expapi/activities/meeting',
],
];
3. بناء خدمة xAPI في Laravel
3.1 XApiStatement Builder Class
<?php
namespace App\Services\XApi;
use Ramsey\Uuid\Uuid;
use Carbon\Carbon;
/**
* XApiStatement — بانٍ لجمل xAPI
*
* المؤلف: فريق Bzzix التقني
* الإصدار: 2.0
*/
class XApiStatement
{
protected array $statement = [];
public function __construct()
{
$this->statement['id'] = Uuid::uuid4()->toString();
$this->statement['timestamp'] = Carbon::now()->toIso8601String();
}
/**
* تعريف الفاعل (المتعلم)
*/
public function actor(string $name, string $email, ?string $accountId = null): static
{
if ($accountId) {
$this->statement['actor'] = [
'objectType' => 'Agent',
'name' => $name,
'account' => [
'homePage' => config('xapi.homepage'),
'name' => $accountId,
],
];
} else {
$this->statement['actor'] = [
'objectType' => 'Agent',
'name' => $name,
'mbox' => 'mailto:' . $email,
];
}
return $this;
}
/**
* تعريف الفعل
*/
public function verb(string $verbKey, string $displayAr, string $displayEn = ''): static
{
$verbUri = config("xapi.verbs.{$verbKey}", $verbKey);
$this->statement['verb'] = [
'id' => $verbUri,
'display' => array_filter([
'ar' => $displayAr,
'en-US' => $displayEn ?: $verbKey,
]),
];
return $this;
}
/**
* تعريف الموضوع (النشاط)
*/
public function object(
string $activityId,
string $nameAr,
string $nameEn = '',
string $type = 'course',
string $descriptionAr = ''
): static {
$typeUri = config("xapi.activity_types.{$type}", $type);
$definition = [
'name' => array_filter([
'ar' => $nameAr,
'en-US' => $nameEn ?: $nameAr,
]),
'type' => $typeUri,
];
if ($descriptionAr) {
$definition['description'] = ['ar' => $descriptionAr];
}
$this->statement['object'] = [
'objectType' => 'Activity',
'id' => $activityId,
'definition' => $definition,
];
return $this;
}
/**
* إضافة النتيجة
*/
public function result(
?bool $success = null,
?bool $completion = null,
?float $score = null,
?string $duration = null,
?string $response = null
): static {
$result = [];
if ($success !== null) $result['success'] = $success;
if ($completion !== null) $result['completion'] = $completion;
if ($duration !== null) $result['duration'] = $duration;
if ($response !== null) $result['response'] = $response;
if ($score !== null) {
$result['score'] = [
'scaled' => round($score / 100, 4),
'raw' => $score,
'min' => 0,
'max' => 100,
];
}
if (!empty($result)) {
$this->statement['result'] = $result;
}
return $this;
}
/**
* إضافة السياق
*/
public function context(array $extra = []): static
{
$this->statement['context'] = array_merge([
'platform' => config('xapi.platform'),
'language' => 'ar',
], $extra);
return $this;
}
/**
* بناء الـ Statement النهائي
*/
public function build(): array
{
return $this->statement;
}
/**
* تحويل إلى JSON
*/
public function toJson(): string
{
return json_encode($this->statement, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
}
}
3.2 XApiService — خدمة الإرسال إلى LRS
<?php
namespace App\Services\XApi;
use Illuminate\Http\Client\Response;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
use Exception;
/**
* XApiService — خدمة إرسال جمل xAPI إلى LRS
*/
class XApiService
{
protected string $endpoint;
protected string $username;
protected string $password;
protected string $version;
public function __construct()
{
$this->endpoint = config('xapi.endpoint');
$this->username = config('xapi.username');
$this->password = config('xapi.password');
$this->version = config('xapi.version');
}
/**
* إرسال Statement واحد
*/
public function send(array $statement): array
{
return $this->sendBatch([$statement]);
}
/**
* إرسال مجموعة من الـ Statements (Batch)
*/
public function sendBatch(array $statements): array
{
$attempts = config('xapi.retry_attempts', 3);
$delay = config('xapi.retry_delay', 5);
for ($attempt = 1; $attempt <= $attempts; $attempt++) {
try {
$response = Http::withBasicAuth($this->username, $this->password)
->withHeaders([
'X-Experience-API-Version' => $this->version,
'Content-Type' => 'application/json; charset=utf-8',
'Accept' => 'application/json',
])
->timeout(30)
->post($this->endpoint . 'statements', $statements);
if ($response->successful()) {
Log::info('xAPI Statements sent successfully', [
'count' => count($statements),
'response' => $response->json(),
]);
return [
'success' => true,
'ids' => $response->json(),
'count' => count($statements),
'attempt' => $attempt,
];
}
Log::warning("xAPI Send failed (attempt {$attempt})", [
'status' => $response->status(),
'body' => $response->body(),
'attempt' => $attempt,
]);
if ($attempt < $attempts) {
sleep($delay * $attempt); // تأخير تصاعدي
}
} catch (Exception $e) {
Log::error("xAPI Exception (attempt {$attempt}): " . $e->getMessage());
if ($attempt < $attempts) {
sleep($delay * $attempt);
}
}
}
return [
'success' => false,
'error' => 'فشل إرسال الـ Statement بعد ' . $attempts . ' محاولات',
];
}
/**
* استرجاع Statements من LRS
*/
public function getStatements(array $filters = []): array
{
try {
$response = Http::withBasicAuth($this->username, $this->password)
->withHeaders(['X-Experience-API-Version' => $this->version])
->get($this->endpoint . 'statements', $filters);
if ($response->successful()) {
return $response->json();
}
return ['error' => $response->body()];
} catch (Exception $e) {
Log::error('xAPI Get Statements Error: ' . $e->getMessage());
return ['error' => $e->getMessage()];
}
}
/**
* اختبار الاتصال بـ LRS
*/
public function testConnection(): bool
{
try {
$response = Http::withBasicAuth($this->username, $this->password)
->withHeaders(['X-Experience-API-Version' => $this->version])
->get($this->endpoint . 'about');
return $response->successful();
} catch (Exception $e) {
Log::error('xAPI Connection Test Failed: ' . $e->getMessage());
return false;
}
}
}
4. تسجيل الخدمة في Laravel
4.1 XApiServiceProvider
<?php
namespace App\Providers;
use App\Services\XApi\XApiService;
use Illuminate\Support\ServiceProvider;
class XApiServiceProvider extends ServiceProvider
{
public function register(): void
{
$this->app->singleton(XApiService::class, function ($app) {
return new XApiService();
});
$this->app->alias(XApiService::class, 'xapi');
}
public function boot(): void
{
$this->publishes([
__DIR__ . '/../../config/xapi.php' => config_path('xapi.php'),
], 'xapi-config');
}
}
أضفه في config/app.php:
'providers' => [
// ...
App\Providers\XApiServiceProvider::class,
],
5. أمثلة عملية كاملة
5.1 عند إكمال الدورة
use App\Services\XApi\XApiStatement;
use App\Services\XApi\XApiService;
// في CourseController أو Observer
public function markComplete(Course $course, User $user): void
{
$statement = (new XApiStatement())
->actor(
name: $user->name,
email: $user->email,
accountId: (string) $user->id
)
->verb('completed', 'أكمل', 'completed')
->object(
activityId: url("/courses/{$course->slug}"),
nameAr: $course->title_ar,
nameEn: $course->title_en,
type: 'course',
descriptionAr: $course->description_ar ?? ''
)
->result(
success: true,
completion: true,
score: $user->getCourseScore($course->id),
duration: $user->getCourseDuration($course->id)
)
->context([
'extensions' => [
'https://bzzix.com/xapi/extensions/course-id' => $course->id,
]
])
->build();
app(XApiService::class)->send($statement);
}
5.2 عند اجتياز الاختبار
public function quizPassed(Quiz $quiz, User $user, float $score): void
{
$verb = $score >= $quiz->pass_score ? 'passed' : 'failed';
$verbAr = $score >= $quiz->pass_score ? 'اجتاز' : 'رسب في';
$statement = (new XApiStatement())
->actor($user->name, $user->email)
->verb($verb, $verbAr)
->object(
activityId: url("/quizzes/{$quiz->id}"),
nameAr: $quiz->title_ar,
type: 'assessment'
)
->result(
success: $score >= $quiz->pass_score,
completion: true,
score: $score
)
->build();
app(XApiService::class)->send($statement);
}
5.3 إرسال مجمّع (Batch Sending)
use App\Jobs\SendXApiStatements;
// جمع الـ Statements وإرسالها في Batch
public function syncPendingStatements(): void
{
$pending = XApiLog::where('sent', false)
->limit(config('xapi.batch_size'))
->get();
if ($pending->isEmpty()) return;
$statements = $pending->pluck('statement')->toArray();
$result = app(XApiService::class)->sendBatch($statements);
if ($result['success']) {
$pending->each->markAsSent();
}
}
6. نموذج قاعدة بيانات للـ Queue المحلية
// Migration
Schema::create('xapi_logs', function (Blueprint $table) {
$table->id();
$table->string('statement_id')->unique();
$table->json('statement');
$table->enum('status', ['pending', 'sent', 'failed'])->default('pending');
$table->integer('attempts')->default(0);
$table->text('error_message')->nullable();
$table->timestamp('sent_at')->nullable();
$table->timestamps();
});
7. اختبار التكامل مع Postman
7.1 إعداد Postman Collection
{
"info": {
"name": "Bzzix xAPI Tests",
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
},
"item": [
{
"name": "Test Connection (About)",
"request": {
"method": "GET",
"url": "{{xapi_endpoint}}about",
"header": [
{ "key": "Authorization", "value": "Basic {{xapi_auth}}" },
{ "key": "X-Experience-API-Version", "value": "1.0.3" }
]
}
},
{
"name": "Send Test Statement",
"request": {
"method": "POST",
"url": "{{xapi_endpoint}}statements",
"header": [
{ "key": "Authorization", "value": "Basic {{xapi_auth}}" },
{ "key": "Content-Type", "value": "application/json" },
{ "key": "X-Experience-API-Version", "value": "1.0.3" }
],
"body": {
"mode": "raw",
"raw": "{\n \"actor\": {\n \"name\": \"اختبار Bzzix\",\n \"mbox\": \"mailto:test@bzzix.com\"\n },\n \"verb\": {\n \"id\": \"http://adlnet.gov/expapi/verbs/completed\",\n \"display\": { \"ar\": \"أكمل\" }\n },\n \"object\": {\n \"id\": \"https://bzzix.com/test-activity\",\n \"definition\": {\n \"name\": { \"ar\": \"اختبار تكامل xAPI\" }\n }\n }\n}"
}
}
}
]
}
8. Laravel Artisan Command للاختبار
<?php
namespace App\Console\Commands;
use App\Services\XApi\XApiService;
use App\Services\XApi\XApiStatement;
use Illuminate\Console\Command;
class TestXApiCommand extends Command
{
protected $signature = 'xapi:test {--email=test@bzzix.com}';
protected $description = 'اختبار تكامل xAPI مع LRS';
public function handle(XApiService $xapi): int
{
$this->info('🔍 اختبار الاتصال بـ LRS...');
if (!$xapi->testConnection()) {
$this->error('❌ فشل الاتصال بـ LRS. تحقق من بيانات الاعتماد.');
return 1;
}
$this->info('✅ الاتصال بـ LRS ناجح!');
$this->info('📤 إرسال Statement تجريبي...');
$statement = (new XApiStatement())
->actor('مستخدم الاختبار', $this->option('email'))
->verb('completed', 'أكمل', 'completed')
->object(
config('app.url') . '/test',
'اختبار تكامل xAPI من Laravel',
'xAPI Integration Test from Laravel'
)
->result(success: true, completion: true, score: 100)
->build();
$result = $xapi->send($statement);
if ($result['success']) {
$this->info('✅ تم إرسال Statement بنجاح!');
$this->table(['المفتاح', 'القيمة'], [
['Statement ID', $statement['id']],
['LRS Response', implode(', ', $result['ids'] ?? [])],
['عدد المحاولات', $result['attempt']],
]);
return 0;
}
$this->error('❌ فشل الإرسال: ' . ($result['error'] ?? 'خطأ غير معروف'));
return 1;
}
}
تشغيل الاختبار:
php artisan xapi:test
php artisan xapi:test --email=custom@test.com
9. [خدمة Bzzix] — طلب تطوير تكامل مخصص
🚀 خدمة تطوير تكامل xAPI من Bzzix
هل تحتاج إلى فريق متخصص يبني لك تكامل xAPI مخصص في مشروع Laravel؟
ما يشمله التطوير:
- ✅ تحليل متطلبات مشروعك
- ✅ تطوير XApiService مخصص
- ✅ تكامل مع NELC LRS
- ✅ Queue وRetry Logic
- ✅ لوحة تحكم لمراقبة الـ Statements
- ✅ اختبار Postman Collection كامل
- ✅ توثيق API للفريق التقني
- ✅ تدريب الفريق
تواصل مع Bzzix: bzzix.com/contact
الخطوة التالية
لدليل NELC الكامل والمتطلبات الرسمية، انتقل إلى:
07_nelc_integration.md
© 2026 Bzzix — جميع الحقوق محفوظة. هذه الوثيقة جزء من مجموعة وثائق xAPI الرسمية لمنصة Bzzix.