تطوير واجهات REST API

أنماط تصميم واجهات برمجة التطبيقات

16 دقيقة الدرس 32 من 35

أنماط تصميم واجهات برمجة التطبيقات

تتبع واجهات برمجة التطبيقات المصممة جيدًا أنماطًا راسخة تجعل الكود أكثر قابلية للصيانة والاختبار والتوسع. في هذا الدرس، سنستكشف أنماط التصميم الأساسية لبناء واجهات برمجة تطبيقات قوية، بما في ذلك نمط المستودع، وDTOs، وفئات الإجراءات، وطبقات الخدمة.

نمط المستودع

يفصل نمط المستودع منطق الوصول إلى البيانات عن منطق الأعمال، مما يجعل الكود أكثر قابلية للاختبار والصيانة:

app/Repositories/UserRepository.php:
<?php

namespace App\Repositories;

use App\Models\User;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Pagination\LengthAwarePaginator;

class UserRepository
{
    /**
     * العثور على المستخدم بواسطة المعرف
     */
    public function find(int $id): ?User
    {
        return User::find($id);
    }

    /**
     * الحصول على جميع المستخدمين مع الترقيم
     */
    public function paginate(int $perPage = 15): LengthAwarePaginator
    {
        return User::latest()->paginate($perPage);
    }

    /**
     * إنشاء مستخدم جديد
     */
    public function create(array $data): User
    {
        return User::create($data);
    }

    /**
     * تحديث مستخدم موجود
     */
    public function update(User $user, array $data): bool
    {
        return $user->update($data);
    }

    /**
     * حذف مستخدم
     */
    public function delete(User $user): bool
    {
        return $user->delete();
    }

    /**
     * العثور على المستخدم بواسطة البريد الإلكتروني
     */
    public function findByEmail(string $email): ?User
    {
        return User::where('email', $email)->first();
    }

    /**
     * الحصول على المستخدمين النشطين
     */
    public function getActive(): Collection
    {
        return User::where('is_active', true)
            ->orderBy('name')
            ->get();
    }

    /**
     * البحث عن المستخدمين بالاسم أو البريد الإلكتروني
     */
    public function search(string $query): Collection
    {
        return User::where('name', 'LIKE', "%{$query}%")
            ->orWhere('email', 'LIKE', "%{$query}%")
            ->get();
    }

    /**
     * الحصول على المستخدمين المنشأين في نطاق زمني
     */
    public function getCreatedBetween(string $startDate, string $endDate): Collection
    {
        return User::whereBetween('created_at', [$startDate, $endDate])
            ->get();
    }
}
فوائد المستودع:
  • المسؤولية الواحدة - تتعامل المستودعات فقط مع الوصول إلى البيانات
  • قابلية الاختبار - سهلة للمحاكاة في اختبارات الوحدة
  • المرونة - يمكن استبدال مصادر البيانات دون تغيير منطق الأعمال
  • إعادة الاستخدام - الاستعلامات الشائعة محددة مرة واحدة

كائنات نقل البيانات (DTOs)

تغلف DTOs نقل البيانات بين الطبقات، مما يوفر أمان الأنواع والتحقق:

app/DTOs/CreateUserDTO.php:
<?php

namespace App\DTOs;

use Illuminate\Http\Request;

class CreateUserDTO
{
    public function __construct(
        public readonly string $name,
        public readonly string $email,
        public readonly string $password,
        public readonly ?string $phone = null,
        public readonly array $roles = [],
    ) {}

    /**
     * إنشاء DTO من الطلب
     */
    public static function fromRequest(Request $request): self
    {
        return new self(
            name: $request->input('name'),
            email: $request->input('email'),
            password: bcrypt($request->input('password')),
            phone: $request->input('phone'),
            roles: $request->input('roles', []),
        );
    }

    /**
     * تحويل إلى مصفوفة
     */
    public function toArray(): array
    {
        return [
            'name' => $this->name,
            'email' => $this->email,
            'password' => $this->password,
            'phone' => $this->phone,
        ];
    }

    /**
     * الحصول على سمات النموذج فقط (باستثناء الأدوار)
     */
    public function getModelAttributes(): array
    {
        return array_filter([
            'name' => $this->name,
            'email' => $this->email,
            'password' => $this->password,
            'phone' => $this->phone,
        ]);
    }
}
app/DTOs/UpdateUserDTO.php:
<?php

namespace App\DTOs;

use Illuminate\Http\Request;

class UpdateUserDTO
{
    public function __construct(
        public readonly ?string $name = null,
        public readonly ?string $email = null,
        public readonly ?string $password = null,
        public readonly ?string $phone = null,
        public readonly ?bool $isActive = null,
    ) {}

    public static function fromRequest(Request $request): self
    {
        return new self(
            name: $request->input('name'),
            email: $request->input('email'),
            password: $request->has('password')
                ? bcrypt($request->input('password'))
                : null,
            phone: $request->input('phone'),
            isActive: $request->has('is_active')
                ? (bool) $request->input('is_active')
                : null,
        );
    }

    /**
     * الحصول على السمات غير الفارغة فقط
     */
    public function toArray(): array
    {
        return array_filter([
            'name' => $this->name,
            'email' => $this->email,
            'password' => $this->password,
            'phone' => $this->phone,
            'is_active' => $this->isActive,
        ], fn($value) => $value !== null);
    }
}

فئات الإجراءات (وحدات التحكم ذات الإجراء الواحد)

تتبع فئات الإجراءات نمط وحدة التحكم ذات الإجراء الواحد - فئة واحدة، مسؤولية واحدة:

app/Actions/CreateUserAction.php:
<?php

namespace App\Actions;

use App\DTOs\CreateUserDTO;
use App\Models\User;
use App\Repositories\UserRepository;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;

class CreateUserAction
{
    public function __construct(
        private UserRepository $userRepository
    ) {}

    /**
     * تنفيذ الإجراء
     */
    public function execute(CreateUserDTO $dto): User
    {
        return DB::transaction(function () use ($dto) {
            // إنشاء المستخدم
            $user = $this->userRepository->create($dto->getModelAttributes());

            // تعيين الأدوار إذا تم توفيرها
            if (!empty($dto->roles)) {
                $user->roles()->attach($dto->roles);
            }

            // إرسال بريد إلكتروني ترحيبي (في قائمة الانتظار)
            // SendWelcomeEmail::dispatch($user);

            Log::info('تم إنشاء المستخدم', [
                'user_id' => $user->id,
                'email' => $user->email,
            ]);

            return $user->fresh(['roles']);
        });
    }
}
app/Actions/UpdateUserAction.php:
<?php

namespace App\Actions;

use App\DTOs\UpdateUserDTO;
use App\Models\User;
use App\Repositories\UserRepository;
use Illuminate\Support\Facades\DB;

class UpdateUserAction
{
    public function __construct(
        private UserRepository $userRepository
    ) {}

    public function execute(User $user, UpdateUserDTO $dto): User
    {
        return DB::transaction(function () use ($user, $dto) {
            $attributes = $dto->toArray();

            if (empty($attributes)) {
                return $user;
            }

            $this->userRepository->update($user, $attributes);

            return $user->fresh();
        });
    }
}
app/Actions/DeleteUserAction.php:
<?php

namespace App\Actions;

use App\Models\User;
use App\Repositories\UserRepository;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Storage;

class DeleteUserAction
{
    public function __construct(
        private UserRepository $userRepository
    ) {}

    public function execute(User $user): bool
    {
        return DB::transaction(function () use ($user) {
            // حذف صورة المستخدم إذا كانت موجودة
            if ($user->avatar) {
                Storage::delete($user->avatar);
            }

            // حذف السجلات ذات الصلة
            $user->posts()->delete();
            $user->comments()->delete();

            // حذف المستخدم
            return $this->userRepository->delete($user);
        });
    }
}

استخدام الأنماط في وحدات التحكم

الآن تصبح وحدات التحكم الخاصة بك منسقات رفيعة:

app/Http/Controllers/Api/UserController.php:
<?php

namespace App\Http\Controllers\Api;

use App\Actions\CreateUserAction;
use App\Actions\UpdateUserAction;
use App\Actions\DeleteUserAction;
use App\DTOs\CreateUserDTO;
use App\DTOs\UpdateUserDTO;
use App\Http\Controllers\Controller;
use App\Http\Requests\CreateUserRequest;
use App\Http\Requests\UpdateUserRequest;
use App\Http\Resources\UserResource;
use App\Models\User;
use App\Repositories\UserRepository;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Resources\Json\AnonymousResourceCollection;

class UserController extends Controller
{
    public function __construct(
        private UserRepository $userRepository,
        private CreateUserAction $createUserAction,
        private UpdateUserAction $updateUserAction,
        private DeleteUserAction $deleteUserAction,
    ) {}

    /**
     * عرض قائمة المستخدمين
     */
    public function index(): AnonymousResourceCollection
    {
        $users = $this->userRepository->paginate(15);

        return UserResource::collection($users);
    }

    /**
     * عرض المستخدم المحدد
     */
    public function show(User $user): UserResource
    {
        return new UserResource($user->load(['roles', 'posts']));
    }

    /**
     * تخزين مستخدم جديد
     */
    public function store(CreateUserRequest $request): JsonResponse
    {
        $dto = CreateUserDTO::fromRequest($request);
        $user = $this->createUserAction->execute($dto);

        return (new UserResource($user))
            ->response()
            ->setStatusCode(201);
    }

    /**
     * تحديث المستخدم المحدد
     */
    public function update(UpdateUserRequest $request, User $user): UserResource
    {
        $dto = UpdateUserDTO::fromRequest($request);
        $updatedUser = $this->updateUserAction->execute($user, $dto);

        return new UserResource($updatedUser);
    }

    /**
     * إزالة المستخدم المحدد
     */
    public function destroy(User $user): JsonResponse
    {
        $this->deleteUserAction->execute($user);

        return response()->json([
            'message' => 'تم حذف المستخدم بنجاح',
        ], 200);
    }
}
مسؤوليات وحدة التحكم: يجب أن تتعامل وحدات التحكم فقط مع مخاوف HTTP - التحقق من الطلب، استدعاء الإجراءات، إرجاع الردود. يعيش كل منطق الأعمال في الإجراءات، وكل الوصول إلى البيانات في المستودعات.

نمط طبقة الخدمة

بالنسبة لمنطق الأعمال المعقد، استخدم فئات الخدمة:

app/Services/UserService.php:
<?php

namespace App\Services;

use App\Models\User;
use App\Repositories\UserRepository;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Hash;

class UserService
{
    public function __construct(
        private UserRepository $userRepository
    ) {}

    /**
     * التحقق من بيانات اعتماد المستخدم
     */
    public function verifyCredentials(string $email, string $password): ?User
    {
        $user = $this->userRepository->findByEmail($email);

        if (!$user || !Hash::check($password, $user->password)) {
            return null;
        }

        return $user;
    }

    /**
     * الحصول على إحصائيات المستخدم
     */
    public function getUserStats(User $user): array
    {
        return Cache::remember("user.{$user->id}.stats", 3600, function () use ($user) {
            return [
                'posts_count' => $user->posts()->count(),
                'comments_count' => $user->comments()->count(),
                'followers_count' => $user->followers()->count(),
                'following_count' => $user->following()->count(),
                'total_views' => $user->posts()->sum('views'),
            ];
        });
    }

    /**
     * حظر المستخدم والتنظيف ذي الصلة
     */
    public function banUser(User $user, string $reason): void
    {
        $user->update([
            'is_active' => false,
            'banned_at' => now(),
            'ban_reason' => $reason,
        ]);

        // إلغاء جميع الرموز
        $user->tokens()->delete();

        // إخفاء جميع منشورات المستخدم
        $user->posts()->update(['is_visible' => false]);

        // مسح ذاكرة التخزين المؤقت
        Cache::forget("user.{$user->id}.stats");
    }

    /**
     * تفعيل حساب المستخدم
     */
    public function activateUser(User $user): void
    {
        $user->update([
            'is_active' => true,
            'email_verified_at' => now(),
        ]);
    }
}

نمط منشئ الاستعلام

أنشئ منشئات استعلام قابلة لإعادة الاستخدام وقابلة للتسلسل للاستعلامات المعقدة:

app/QueryBuilders/UserQueryBuilder.php:
<?php

namespace App\QueryBuilders;

use Illuminate\Database\Eloquent\Builder;

class UserQueryBuilder extends Builder
{
    /**
     * تصفية المستخدمين النشطين
     */
    public function active(): self
    {
        return $this->where('is_active', true);
    }

    /**
     * تصفية المستخدمين المتحققين
     */
    public function verified(): self
    {
        return $this->whereNotNull('email_verified_at');
    }

    /**
     * تصفية حسب الدور
     */
    public function withRole(string $role): self
    {
        return $this->whereHas('roles', function ($query) use ($role) {
            $query->where('name', $role);
        });
    }

    /**
     * البحث بالاسم أو البريد الإلكتروني
     */
    public function search(string $query): self
    {
        return $this->where(function ($q) use ($query) {
            $q->where('name', 'LIKE', "%{$query}%")
              ->orWhere('email', 'LIKE', "%{$query}%");
        });
    }

    /**
     * الترتيب حسب تاريخ الإنشاء
     */
    public function latest(): self
    {
        return $this->orderBy('created_at', 'desc');
    }

    /**
     * مع العلاقات الشائعة
     */
    public function withRelations(): self
    {
        return $this->with(['roles', 'profile']);
    }

    /**
     * تم إنشاؤه في نطاق زمني
     */
    public function createdBetween(string $from, string $to): self
    {
        return $this->whereBetween('created_at', [$from, $to]);
    }
}
استخدام منشئ الاستعلام المخصص في النموذج:
<?php

namespace App\Models;

use App\QueryBuilders\UserQueryBuilder;
use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    /**
     * إنشاء منشئ استعلام Eloquent جديد
     */
    public function newEloquentBuilder($query): UserQueryBuilder
    {
        return new UserQueryBuilder($query);
    }
}

// الاستخدام:
$users = User::query()
    ->active()
    ->verified()
    ->withRole('admin')
    ->search('john')
    ->latest()
    ->withRelations()
    ->paginate(15);

محولات موارد API

استخدم موارد API مع الشروط والعلاقات:

app/Http/Resources/UserResource.php:
<?php

namespace App\Http\Resources;

use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;

class UserResource extends JsonResource
{
    /**
     * تحويل المورد إلى مصفوفة
     */
    public function toArray(Request $request): array
    {
        return [
            'id' => $this->id,
            'name' => $this->name,
            'email' => $this->email,
            'avatar' => $this->avatar_url,
            'is_active' => $this->is_active,

            // حقول شرطية
            'phone' => $this->when(
                $request->user()?->can('view-private-info', $this->resource),
                $this->phone
            ),

            // العلاقات
            'roles' => RoleResource::collection($this->whenLoaded('roles')),
            'posts' => PostResource::collection($this->whenLoaded('posts')),

            // حقول محسوبة
            'posts_count' => $this->whenCounted('posts'),
            'is_premium' => $this->subscriptions()->where('status', 'active')->exists(),

            // التواريخ
            'created_at' => $this->created_at->toIso8601String(),
            'updated_at' => $this->updated_at->toIso8601String(),
        ];
    }

    /**
     * الحصول على بيانات إضافية
     */
    public function with(Request $request): array
    {
        return [
            'meta' => [
                'version' => '1.0',
            ],
        ];
    }
}

التفويض القائم على السياسات

app/Policies/UserPolicy.php:
<?php

namespace App\Policies;

use App\Models\User;

class UserPolicy
{
    /**
     * تحديد ما إذا كان يمكن للمستخدم عرض أي مستخدمين
     */
    public function viewAny(User $user): bool
    {
        return $user->hasPermission('users.view');
    }

    /**
     * تحديد ما إذا كان يمكن للمستخدم عرض النموذج
     */
    public function view(User $user, User $model): bool
    {
        return $user->id === $model->id
            || $user->hasPermission('users.view');
    }

    /**
     * تحديد ما إذا كان يمكن للمستخدم إنشاء مستخدمين
     */
    public function create(User $user): bool
    {
        return $user->hasRole('admin');
    }

    /**
     * تحديد ما إذا كان يمكن للمستخدم تحديث النموذج
     */
    public function update(User $user, User $model): bool
    {
        return $user->id === $model->id
            || $user->hasPermission('users.update');
    }

    /**
     * تحديد ما إذا كان يمكن للمستخدم حذف النموذج
     */
    public function delete(User $user, User $model): bool
    {
        return $user->hasRole('admin') && $user->id !== $model->id;
    }
}
تمرين تطبيقي:

أعد هيكلة وحدة تحكم API موجودة لاستخدام هذه الأنماط:

  1. أنشئ مستودعًا لنموذجك
  2. أنشئ DTOs لعمليات الإنشاء والتحديث
  3. استخرج منطق الأعمال إلى فئات الإجراءات
  4. حدّث وحدة التحكم لاستخدام المستودع والإجراءات
  5. أضف منشئ استعلام مخصصًا للاستعلامات المعقدة
  6. أنشئ موارد API لتحويل الردود

الملخص

توفر أنماط تصميم واجهات برمجة التطبيقات بنية وقابلية للصيانة:

  • نمط المستودع: يفصل الوصول إلى البيانات عن منطق الأعمال
  • DTOs: نقل بيانات آمن من حيث الأنواع بين الطبقات
  • فئات الإجراءات: فئات ذات مسؤولية واحدة لعمليات الأعمال
  • طبقة الخدمة: تغليف منطق الأعمال المعقد
  • منشئات الاستعلام: طرق استعلام قابلة لإعادة الاستخدام وقابلة للتسلسل
  • موارد API: تحويل رد متسق
  • السياسات: فصل منطق التفويض

تجعل هذه الأنماط كود واجهة برمجة التطبيقات الخاص بك أكثر قابلية للاختبار والصيانة وسهولة الفهم. في الدرس التالي، سنبني واجهة برمجة تطبيقات RESTful كاملة من الصفر باستخدام كل هذه الأنماط.