Laravel المتقدم

منصة API: GraphQL مع Laravel

18 دقيقة الدرس 33 من 40

منصة API: GraphQL مع Laravel

GraphQL هو بديل قوي لواجهات REST API يسمح للعملاء بطلب البيانات التي يحتاجونها بالضبط. يغطي هذا الدرس تنفيذ GraphQL في Laravel باستخدام Lighthouse PHP، وهو إطار عمل لخدمة GraphQL من تطبيقات Laravel.

تثبيت وتكوين Lighthouse

Lighthouse هي مكتبة GraphQL الأكثر شهرة لـ Laravel. توفر طريقة تصريحية لتعريف مخطط GraphQL الخاص بك وتتكامل بسلاسة مع Eloquent.

# تثبيت Lighthouse و GraphQL Playground
composer require nuwave/lighthouse
composer require mll-lab/laravel-graphql-playground

# نشر التكوين
php artisan vendor:publish --tag=lighthouse-config
php artisan vendor:publish --tag=lighthouse-schema

# التكوين في config/lighthouse.php
return [
    'route' => [
        'uri' => '/graphql',
        'middleware' => [
            \Nuwave\Lighthouse\Support\Http\Middleware\AcceptJson::class,
        ],
    ],
    'schema' => [
        'register' => base_path('graphql/schema.graphql'),
    ],
];

# سيكون GraphQL Playground متاحاً على /graphql-playground
نصيحة: GraphQL Playground هو IDE تفاعلي لاختبار استعلامات GraphQL الخاصة بك. يوفر الإكمال التلقائي والتوثيق وسجل الاستعلامات، مما يجعله أساسياً للتطوير.

تعريف مخطط GraphQL الخاص بك

المخطط هو العقد بين العميل والخادم. يحدد الاستعلامات والطفرات المتاحة وأنواع البيانات التي تعيدها.

# graphql/schema.graphql

"A datetime string with format `Y-m-d H:i:s`"
scalar DateTime @scalar(class: "Nuwave\\Lighthouse\\Schema\\Types\\Scalars\\DateTime")

type Query {
    "الحصول على جميع المستخدمين"
    users: [User!]! @all

    "الحصول على مستخدم بواسطة المعرف"
    user(id: ID! @eq): User @find

    "البحث عن المستخدمين بالاسم"
    searchUsers(name: String! @where(operator: "like")): [User!]! @all

    "الحصول على منشورات مقسمة إلى صفحات"
    posts(first: Int!, page: Int): PostPaginator @paginate

    "الحصول على المستخدم المصادق عليه الحالي"
    me: User @auth
}

type User {
    id: ID!
    name: String!
    email: String!
    posts: [Post!]! @hasMany
    created_at: DateTime!
    updated_at: DateTime!
}

type Post {
    id: ID!
    title: String!
    content: String!
    author: User! @belongsTo
    comments: [Comment!]! @hasMany
    created_at: DateTime!
    updated_at: DateTime!
}

type Comment {
    id: ID!
    body: String!
    post: Post! @belongsTo
    user: User! @belongsTo
    created_at: DateTime!
}

type PostPaginator {
    data: [Post!]!
    paginatorInfo: PaginatorInfo!
}

type PaginatorInfo {
    currentPage: Int!
    lastPage: Int!
    total: Int!
    hasMorePages: Boolean!
}
ملاحظة: نظام التوجيهات في Lighthouse (مثل @all و @find و @paginate) ينشئ تلقائياً محللات بناءً على اصطلاحات Eloquent، مما يقلل بشكل كبير من الكود المكرر.

تنفيذ الطفرات

الطفرات هي طريقة GraphQL لتعديل البيانات. إنها مشابهة لـ POST و PUT و DELETE في واجهات REST API.

# graphql/schema.graphql

type Mutation {
    "إنشاء مستخدم جديد"
    createUser(input: CreateUserInput! @spread): User @create

    "تحديث مستخدم موجود"
    updateUser(id: ID!, input: UpdateUserInput! @spread): User @update

    "حذف مستخدم"
    deleteUser(id: ID! @whereKey): User @delete

    "إنشاء منشور"
    createPost(input: CreatePostInput!): Post @field(resolver: "PostMutator@create")

    "تسجيل دخول المستخدم"
    login(email: String!, password: String!): AuthPayload @field(resolver: "AuthMutator@login")

    "تسجيل خروج المستخدم"
    logout: LogoutResponse @field(resolver: "AuthMutator@logout") @guard
}

input CreateUserInput {
    name: String! @rules(apply: ["required", "string", "max:255"])
    email: String! @rules(apply: ["required", "email", "unique:users,email"])
    password: String! @rules(apply: ["required", "min:8"]) @hash
}

input UpdateUserInput {
    name: String @rules(apply: ["string", "max:255"])
    email: String @rules(apply: ["email", "unique:users,email"])
}

input CreatePostInput {
    title: String! @rules(apply: ["required", "max:255"])
    content: String! @rules(apply: ["required"])
    author_id: ID! @rules(apply: ["required", "exists:users,id"])
}

type AuthPayload {
    access_token: String!
    token_type: String!
    expires_in: Int!
    user: User!
}

type LogoutResponse {
    message: String!
    status: String!
}

إنشاء محللات مخصصة

للمنطق المعقد، ستحتاج إلى محللات مخصصة. هذه فئات PHP تتعامل مع استعلامات أو طفرات محددة.

<?php

// app/GraphQL/Mutations/PostMutator.php
namespace App\GraphQL\Mutations;

use App\Models\Post;
use Illuminate\Support\Facades\Auth;

class PostMutator
{
    public function create($rootValue, array $args)
    {
        $user = Auth::user();

        return Post::create([
            'title' => $args['input']['title'],
            'content' => $args['input']['content'],
            'author_id' => $user->id,
            'published_at' => now(),
        ]);
    }
}

// app/GraphQL/Mutations/AuthMutator.php
namespace App\GraphQL\Mutations;

use Illuminate\Support\Facades\Auth;
use Illuminate\Validation\ValidationException;

class AuthMutator
{
    public function login($rootValue, array $args)
    {
        $credentials = [
            'email' => $args['email'],
            'password' => $args['password'],
        ];

        if (!Auth::attempt($credentials)) {
            throw ValidationException::withMessages([
                'email' => ['بيانات الاعتماد المقدمة غير صحيحة.'],
            ]);
        }

        $user = Auth::user();
        $token = $user->createToken('api-token')->plainTextToken;

        return [
            'access_token' => $token,
            'token_type' => 'Bearer',
            'expires_in' => 3600,
            'user' => $user,
        ];
    }

    public function logout()
    {
        Auth::user()->currentAccessToken()->delete();

        return [
            'message' => 'تم تسجيل الخروج بنجاح',
            'status' => 'success',
        ];
    }
}

// app/GraphQL/Queries/UserQuery.php
namespace App\GraphQL\Queries;

use App\Models\User;

class UserQuery
{
    public function searchAdvanced($rootValue, array $args)
    {
        $query = User::query();

        if (isset($args['name'])) {
            $query->where('name', 'like', "%{$args['name']}%");
        }

        if (isset($args['email'])) {
            $query->where('email', 'like', "%{$args['email']}%");
        }

        if (isset($args['created_after'])) {
            $query->where('created_at', '>=', $args['created_after']);
        }

        return $query->get();
    }
}
تحذير: تحقق دائماً من صحة وتنظيف المدخلات في المحللات الخاصة بك. لا توفر GraphQL حماية مدمجة ضد حقن SQL أو هجمات XSS. استخدم قواعد التحقق في Laravel و Eloquent لعمليات قاعدة البيانات الآمنة.

الاشتراكات للبيانات في الوقت الفعلي

تمكن اشتراكات GraphQL من الاتصال في الوقت الفعلي بين العميل والخادم باستخدام WebSockets.

# تثبيت Laravel WebSockets
composer require beyondcode/laravel-websockets
php artisan vendor:publish --provider="BeyondCode\LaravelWebSockets\WebSocketsServiceProvider"
php artisan migrate

# graphql/schema.graphql
type Subscription {
    "الاستماع للمنشورات الجديدة"
    postCreated: Post

    "الاستماع للتعليقات على منشور محدد"
    commentAdded(postId: ID!): Comment

    "الاستماع لإشعارات المستخدم"
    notificationSent: Notification @guard
}

type Notification {
    id: ID!
    type: String!
    title: String!
    message: String!
    user: User!
    read_at: DateTime
    created_at: DateTime!
}
<?php

// بث حدث اشتراك
use Nuwave\Lighthouse\Execution\Utils\Subscription;

// عند إنشاء منشور جديد
$post = Post::create($data);
Subscription::broadcast('postCreated', $post);

// عند إضافة تعليق
$comment = Comment::create($data);
Subscription::broadcast('commentAdded', $comment, [
    'postId' => $comment->post_id,
]);

// تصفية بيانات الاشتراك
// app/GraphQL/Subscriptions/CommentAdded.php
namespace App\GraphQL\Subscriptions;

use App\Models\Comment;
use Illuminate\Support\Facades\Auth;

class CommentAdded
{
    public function filter($subscriber, $value)
    {
        // إرسال فقط للمستخدمين الذين لديهم حق الوصول إلى المنشور
        $comment = $value;
        return $comment->post->isAccessibleBy(Auth::user());
    }

    public function resolve($rootValue, $args, $context)
    {
        return $rootValue;
    }
}

التفويض في GraphQL

تنفيذ التفويض الصحيح أمر بالغ الأهمية لتأمين واجهة GraphQL API الخاصة بك.

# graphql/schema.graphql

type Query {
    "استعلام للمسؤولين فقط"
    allUsers: [User!]! @all @guard @can(ability: "viewAny", model: "App\\Models\\User")

    "الحصول على المنشورات التي يمكن للمستخدم الوصول إليها"
    myPosts: [Post!]! @all @guard @inject(context: "user.id", name: "author_id")
}

type Mutation {
    "تحديث المنشور (المؤلف فقط)"
    updatePost(id: ID!, input: UpdatePostInput!): Post
        @update
        @can(ability: "update", find: "id")
}

# التفويض المخصص في المحلل
<?php

// app/Policies/PostPolicy.php
namespace App\Policies;

use App\Models\Post;
use App\Models\User;

class PostPolicy
{
    public function update(User $user, Post $post)
    {
        return $user->id === $post->author_id;
    }

    public function delete(User $user, Post $post)
    {
        return $user->id === $post->author_id || $user->isAdmin();
    }
}

// app/GraphQL/Mutations/PostMutator.php
public function delete($rootValue, array $args)
{
    $post = Post::findOrFail($args['id']);

    $this->authorize('delete', $post);

    $post->delete();

    return $post;
}

تمرين 1: واجهة GraphQL API للمدونة

أنشئ واجهة GraphQL API كاملة لنظام مدونة مع الميزات التالية:

  • الاستعلامات: قائمة المنشورات مع التقسيم إلى صفحات، البحث في المنشورات حسب العنوان/المحتوى، الحصول على منشور مع التعليقات
  • الطفرات: إنشاء/تحديث/حذف المنشورات، إضافة تعليقات للمنشورات، الإعجاب/إلغاء الإعجاب بالمنشورات
  • تنفيذ المصادقة باستخدام رموز Sanctum
  • إضافة التفويض لضمان أن المؤلفين فقط يمكنهم تحرير منشوراتهم
  • إنشاء اشتراك للتعليقات الجديدة على المنشورات

تمرين 2: واجهة API لمنتجات التجارة الإلكترونية

ابنِ واجهة GraphQL API لمنصة تجارة إلكترونية:

  • حدد الأنواع للمنتجات والفئات والطلبات والمستخدمين
  • نفذ استعلامات للبحث عن المنتجات مع التصفية (نطاق السعر، الفئة، التقييم)
  • أنشئ طفرات لإدارة السلة (إضافة إلى السلة، تحديث الكمية، الدفع)
  • أضف تحديثات المخزون في الوقت الفعلي باستخدام الاشتراكات
  • نفذ التفويض على مستوى الحقل (إخفاء الأسعار للزوار)

تمرين 3: GraphQL للشبكة الاجتماعية

طور واجهة GraphQL API لشبكة اجتماعية مع:

  • ملفات تعريف المستخدمين مع المنشورات والمتابعين والمتابَعين
  • استعلام خلاصة الأخبار الذي يعيد منشورات من المستخدمين المتابعين
  • طفرات للمتابعة/إلغاء المتابعة، إنشاء منشورات، الإعجاب/التعليق
  • اشتراك الإشعارات في الوقت الفعلي
  • تنفيذ تقسيم إلى صفحات يعتمد على المؤشر للتمرير اللانهائي
  • إضافة تحديد المعدل لمنع البريد المزعج