Laravel المتقدم

أنماط التصميم: المراقب والمزخرف

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

نمط المراقب (Observer Pattern)

يحدد نمط المراقب تبعية واحد لمتعدد بين الكائنات بحيث عندما يتغير أحد الكائنات حالته، يتم إخطار جميع معتمديه وتحديثهم تلقائيًا. نظام الأحداث في Laravel مبني على هذا النمط.

المكونات الرئيسية:
  • الموضوع (Subject): الكائن الذي يتم مراقبته (يحتفظ بقائمة المراقبين)
  • المراقب (Observer): الكائنات التي تريد الحصول على إشعار بالتغييرات
  • الموضوع الملموس (Concrete Subject): يخزن الحالة ويخطر المراقبين عند تغيير الحالة
  • المراقب الملموس (Concrete Observer): ينفذ طريقة التحديث للتفاعل مع التغييرات

تنفيذ نمط المراقب

// نظام مراقبة أسعار الأسهم
namespace App\Observers;

interface Subject
{
    public function attach(Observer $observer): void;
    public function detach(Observer $observer): void;
    public function notify(): void;
}

interface Observer
{
    public function update(Subject $subject): void;
    public function getName(): string;
}

class Stock implements Subject
{
    private array $observers = [];
    private string $symbol;
    private float $price;

    public function __construct(string $symbol, float $initialPrice)
    {
        $this->symbol = $symbol;
        $this->price = $initialPrice;
    }

    public function attach(Observer $observer): void
    {
        $name = $observer->getName();

        if (!isset($this->observers[$name])) {
            $this->observers[$name] = $observer;
            echo "تم إرفاق المراقب: {$name}\n";
        }
    }

    public function detach(Observer $observer): void
    {
        $name = $observer->getName();

        if (isset($this->observers[$name])) {
            unset($this->observers[$name]);
            echo "تم فصل المراقب: {$name}\n";
        }
    }

    public function notify(): void
    {
        foreach ($this->observers as $observer) {
            $observer->update($this);
        }
    }

    public function setPrice(float $price): void
    {
        echo "السهم {$this->symbol}: تغير السعر من {$this->price} إلى {$price}\n";
        $this->price = $price;
        $this->notify();
    }

    public function getPrice(): float
    {
        return $this->price;
    }

    public function getSymbol(): string
    {
        return $this->symbol;
    }
}

// المراقبين الملموسين
class EmailAlert implements Observer
{
    private string $email;
    private float $threshold;

    public function __construct(string $email, float $threshold)
    {
        $this->email = $email;
        $this->threshold = $threshold;
    }

    public function update(Subject $subject): void
    {
        if ($subject instanceof Stock) {
            if ($subject->getPrice() >= $this->threshold) {
                $this->sendEmail($subject);
            }
        }
    }

    private function sendEmail(Stock $stock): void
    {
        echo "بريد إلكتروني إلى {$this->email}: {$stock->getSymbol()} وصل إلى {$stock->getPrice()}\n";
    }

    public function getName(): string
    {
        return "EmailAlert-{$this->email}";
    }
}

class SmsAlert implements Observer
{
    private string $phoneNumber;
    private float $threshold;

    public function __construct(string $phoneNumber, float $threshold)
    {
        $this->phoneNumber = $phoneNumber;
        $this->threshold = $threshold;
    }

    public function update(Subject $subject): void
    {
        if ($subject instanceof Stock) {
            if ($subject->getPrice() >= $this->threshold) {
                $this->sendSms($subject);
            }
        }
    }

    private function sendSms(Stock $stock): void
    {
        echo "رسالة نصية إلى {$this->phoneNumber}: {$stock->getSymbol()} عند {$stock->getPrice()}\n";
    }

    public function getName(): string
    {
        return "SmsAlert-{$this->phoneNumber}";
    }
}

class DatabaseLogger implements Observer
{
    public function update(Subject $subject): void
    {
        if ($subject instanceof Stock) {
            $this->logToDatabase($subject);
        }
    }

    private function logToDatabase(Stock $stock): void
    {
        echo "سجل قاعدة البيانات: {$stock->getSymbol()} تم تحديث السعر إلى {$stock->getPrice()}\n";
        // StockPrice::create([...])
    }

    public function getName(): string
    {
        return 'DatabaseLogger';
    }
}

// الاستخدام
$tesla = new Stock('TSLA', 200.0);

$emailAlert = new EmailAlert('investor@example.com', 250.0);
$smsAlert = new SmsAlert('+1234567890', 220.0);
$logger = new DatabaseLogger();

$tesla->attach($emailAlert);
$tesla->attach($smsAlert);
$tesla->attach($logger);

$tesla->setPrice(210.0);  // فقط المسجل يتلقى إشعار
$tesla->setPrice(225.0);  // المسجل والرسائل النصية يتلقيان إشعار
$tesla->setPrice(255.0);  // الثلاثة يتلقون إشعار

نظام أحداث Laravel (مراقب مدمج)

نظام الأحداث في Laravel هو تنفيذ أنيق لنمط المراقب.

// صف الحدث
namespace App\Events;

use App\Models\Order;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;

class OrderShipped
{
    use Dispatchable, SerializesModels;

    public function __construct(public Order $order) {}
}

// مستمعين متعددين (مراقبين)
namespace App\Listeners;

class SendShipmentNotification
{
    public function handle(OrderShipped $event): void
    {
        $order = $event->order;
        Mail::to($order->customer->email)
            ->send(new OrderShippedMail($order));
    }
}

class UpdateInventory
{
    public function handle(OrderShipped $event): void
    {
        foreach ($event->order->items as $item) {
            $item->product->decrement('stock', $item->quantity);
        }
    }
}

class LogShipment
{
    public function handle(OrderShipped $event): void
    {
        Log::info('تم شحن الطلب', [
            'order_id' => $event->order->id,
            'shipped_at' => now(),
        ]);
    }
}

class NotifyWarehouse
{
    public function handle(OrderShipped $event): void
    {
        // إشعار نظام المستودع عبر API
        Http::post('https://warehouse.example.com/api/notify', [
            'order_id' => $event->order->id,
        ]);
    }
}

// التسجيل في EventServiceProvider
protected $listen = [
    OrderShipped::class => [
        SendShipmentNotification::class,
        UpdateInventory::class,
        LogShipment::class,
        NotifyWarehouse::class,
    ],
];

// إرسال الحدث (إخطار جميع المراقبين)
OrderShipped::dispatch($order);

نمط المزخرف (Decorator Pattern)

يرفق نمط المزخرف مسؤوليات إضافية لكائن ديناميكيًا. توفر المزخرفات بديلاً مرنًا للصفوف الفرعية لتوسيع الوظائف.

حالات الاستخدام: إضافة ميزات للكائنات في وقت التشغيل، عندما تنشئ الصفوف الفرعية انفجار من الصفوف، أنظمة middleware، أغلفة التسجيل/التخزين المؤقت.

المشكلة: تنسيق النص

تحتاج لتطبيق خيارات تنسيق مختلفة (غامق، مائل، تحته خط، لون) على النص، ويمكن للمستخدمين دمجها بأي طريقة.

// واجهة المكون
interface TextComponent
{
    public function render(): string;
}

// المكون الملموس
class PlainText implements TextComponent
{
    public function __construct(private string $text) {}

    public function render(): string
    {
        return $this->text;
    }
}

// المزخرف الأساسي
abstract class TextDecorator implements TextComponent
{
    public function __construct(protected TextComponent $component) {}

    public function render(): string
    {
        return $this->component->render();
    }
}

// المزخرفات الملموسة
class BoldDecorator extends TextDecorator
{
    public function render(): string
    {
        return '<strong>' . parent::render() . '</strong>';
    }
}

class ItalicDecorator extends TextDecorator
{
    public function render(): string
    {
        return '<em>' . parent::render() . '</em>';
    }
}

class UnderlineDecorator extends TextDecorator
{
    public function render(): string
    {
        return '<u>' . parent::render() . '</u>';
    }
}

class ColorDecorator extends TextDecorator
{
    public function __construct(TextComponent $component, private string $color) {
        parent::__construct($component);
    }

    public function render(): string
    {
        return '<span style="color: ' . $this->color . '">'
               . parent::render()
               . '</span>';
    }
}

class UppercaseDecorator extends TextDecorator
{
    public function render(): string
    {
        return strtoupper(parent::render());
    }
}

// الاستخدام - ربط المزخرفات ديناميكيًا
$text = new PlainText('مرحبا بالعالم');

// غامق فقط
$bold = new BoldDecorator($text);
echo $bold->render();  // <strong>مرحبا بالعالم</strong>

// غامق + مائل
$boldItalic = new ItalicDecorator(new BoldDecorator($text));
echo $boldItalic->render();  // <em><strong>مرحبا بالعالم</strong></em>

// غامق + مائل + تحته خط + لون أحمر
$formatted = new ColorDecorator(
    new UnderlineDecorator(
        new ItalicDecorator(
            new BoldDecorator($text)
        )
    ),
    'red'
);
echo $formatted->render();

نمط المزخرف: استجابة API

// نظام مزخرف استجابة API
namespace App\Http\Decorators;

interface ApiResponse
{
    public function toArray(): array;
}

class BaseResponse implements ApiResponse
{
    public function __construct(private array $data) {}

    public function toArray(): array
    {
        return $this->data;
    }
}

abstract class ResponseDecorator implements ApiResponse
{
    public function __construct(protected ApiResponse $response) {}

    public function toArray(): array
    {
        return $this->response->toArray();
    }
}

class TimestampDecorator extends ResponseDecorator
{
    public function toArray(): array
    {
        $data = parent::toArray();
        $data['timestamp'] = now()->toIso8601String();
        return $data;
    }
}

class PaginationDecorator extends ResponseDecorator
{
    public function __construct(
        ApiResponse $response,
        private int $currentPage,
        private int $perPage,
        private int $total
    ) {
        parent::__construct($response);
    }

    public function toArray(): array
    {
        $data = parent::toArray();
        $data['pagination'] = [
            'current_page' => $this->currentPage,
            'per_page' => $this->perPage,
            'total' => $this->total,
            'last_page' => ceil($this->total / $this->perPage),
        ];
        return $data;
    }
}

class MetadataDecorator extends ResponseDecorator
{
    public function __construct(
        ApiResponse $response,
        private array $metadata
    ) {
        parent::__construct($response);
    }

    public function toArray(): array
    {
        $data = parent::toArray();
        $data['meta'] = $this->metadata;
        return $data;
    }
}

class EncryptionDecorator extends ResponseDecorator
{
    public function toArray(): array
    {
        $data = parent::toArray();
        $data['encrypted'] = encrypt(json_encode($data['data']));
        unset($data['data']);
        return $data;
    }
}

class CompressionDecorator extends ResponseDecorator
{
    public function toArray(): array
    {
        $data = parent::toArray();
        $data['compressed'] = base64_encode(gzcompress(json_encode($data['data'])));
        $data['compression'] = 'gzip';
        unset($data['data']);
        return $data;
    }
}

// استخدام المتحكم
class UserController extends Controller
{
    public function index(Request $request)
    {
        $users = User::paginate(15);

        // الاستجابة الأساسية
        $response = new BaseResponse([
            'data' => $users->items(),
        ]);

        // إضافة المزخرفات بناءً على المتطلبات
        $response = new TimestampDecorator($response);

        if ($request->has('page')) {
            $response = new PaginationDecorator(
                $response,
                $users->currentPage(),
                $users->perPage(),
                $users->total()
            );
        }

        if ($request->has('include_meta')) {
            $response = new MetadataDecorator($response, [
                'version' => '1.0',
                'endpoint' => $request->path(),
            ]);
        }

        return response()->json($response->toArray());
    }
}

نمط سلسلة المسؤولية (Chain of Responsibility)

تمرر سلسلة المسؤولية طلبًا على طول سلسلة من المعالجات. تقرر كل معالجة إما معالجة الطلب أو تمريره إلى المعالجة التالية.

Laravel Middleware: نظام middleware في Laravel هو مثال مثالي على نمط سلسلة المسؤولية.
// سلسلة التحقق من الدفع
namespace App\Payment\Validators;

abstract class PaymentValidator
{
    private ?PaymentValidator $nextValidator = null;

    public function setNext(PaymentValidator $validator): PaymentValidator
    {
        $this->nextValidator = $validator;
        return $validator;
    }

    public function validate(array $paymentData): bool
    {
        if (!$this->check($paymentData)) {
            return false;
        }

        if ($this->nextValidator) {
            return $this->nextValidator->validate($paymentData);
        }

        return true;
    }

    abstract protected function check(array $paymentData): bool;
}

class AmountValidator extends PaymentValidator
{
    protected function check(array $paymentData): bool
    {
        if (!isset($paymentData['amount']) || $paymentData['amount'] <= 0) {
            throw new \InvalidArgumentException('مبلغ غير صالح');
        }
        return true;
    }
}

class CardValidator extends PaymentValidator
{
    protected function check(array $paymentData): bool
    {
        if (!isset($paymentData['card_number'])) {
            throw new \InvalidArgumentException('رقم البطاقة مطلوب');
        }

        // فحص خوارزمية Luhn
        $cardNumber = str_replace(' ', '', $paymentData['card_number']);
        if (strlen($cardNumber) < 13 || strlen($cardNumber) > 19) {
            throw new \InvalidArgumentException('طول رقم البطاقة غير صالح');
        }

        return true;
    }
}

class CvvValidator extends PaymentValidator
{
    protected function check(array $paymentData): bool
    {
        if (!isset($paymentData['cvv'])) {
            throw new \InvalidArgumentException('CVV مطلوب');
        }

        if (!preg_match('/^[0-9]{3,4}$/', $paymentData['cvv'])) {
            throw new \InvalidArgumentException('تنسيق CVV غير صالح');
        }

        return true;
    }
}

class ExpiryValidator extends PaymentValidator
{
    protected function check(array $paymentData): bool
    {
        if (!isset($paymentData['expiry'])) {
            throw new \InvalidArgumentException('تاريخ الانتهاء مطلوب');
        }

        [$month, $year] = explode('/', $paymentData['expiry']);
        $expiryDate = \Carbon\Carbon::createFromDate($year, $month, 1)->endOfMonth();

        if ($expiryDate->isPast()) {
            throw new \InvalidArgumentException('انتهت صلاحية البطاقة');
        }

        return true;
    }
}

class FraudCheckValidator extends PaymentValidator
{
    protected function check(array $paymentData): bool
    {
        // التحقق من خدمة كشف الاحتيال
        $isFraudulent = false; // منطق كشف الاحتيال

        if ($isFraudulent) {
            throw new \RuntimeException('تم وضع علامة على الدفع كاحتيالي');
        }

        return true;
    }
}

// بناء سلسلة التحقق
class PaymentValidationService
{
    public function validate(array $paymentData): bool
    {
        $amountValidator = new AmountValidator();
        $cardValidator = new CardValidator();
        $cvvValidator = new CvvValidator();
        $expiryValidator = new ExpiryValidator();
        $fraudValidator = new FraudCheckValidator();

        // ربط المتحققين
        $amountValidator
            ->setNext($cardValidator)
            ->setNext($cvvValidator)
            ->setNext($expiryValidator)
            ->setNext($fraudValidator);

        // بدء سلسلة التحقق
        return $amountValidator->validate($paymentData);
    }
}

نمط Pipeline في Laravel

Pipeline في Laravel هو تنفيذ أنيق لسلسلة المسؤولية، يستخدم بشكل شائع للـ middleware.

use Illuminate\Pipeline\Pipeline;

class ImageProcessor
{
    public function process(string $imagePath, array $operations): string
    {
        return app(Pipeline::class)
            ->send($imagePath)
            ->through($operations)
            ->then(fn($image) => $image);
    }
}

// العمليات (مراحل الأنابيب)
class ResizeImage
{
    public function handle(string $image, \Closure $next)
    {
        // منطق تغيير الحجم
        $resized = $this->resize($image, 800, 600);
        return $next($resized);
    }
}

class AddWatermark
{
    public function handle(string $image, \Closure $next)
    {
        // منطق العلامة المائية
        $watermarked = $this->addWatermark($image);
        return $next($watermarked);
    }
}

class CompressImage
{
    public function handle(string $image, \Closure $next)
    {
        // منطق الضغط
        $compressed = $this->compress($image, 85);
        return $next($compressed);
    }
}

// الاستخدام
$processor = new ImageProcessor();
$result = $processor->process('image.jpg', [
    ResizeImage::class,
    AddWatermark::class,
    CompressImage::class,
]);
تمرين 1: أنشئ نمط مراقب لنظام منشورات المدونة:
  • الموضوع: BlogPost مع طريقة ()publish
  • المراقبين: EmailSubscribers، SocialMediaPublisher، SearchIndexer، CacheClearer
  • إخطار جميع المراقبين عند نشر المنشور
تمرين 2: ابن نظام مزخرف لاستعلامات قاعدة البيانات:
  • المكون الأساسي: Query يعيد النتائج
  • المزخرفات: CacheDecorator، LoggingDecorator، ProfilingDecorator، RetryDecorator
  • اسمح بربط مزخرفات متعددة
تمرين 3: نفذ سلسلة المسؤولية لتصفية الطلبات:
  • المتحققين: AuthCheck، RateLimitCheck، InputSanitization، PermissionCheck
  • كل متحقق إما يمرر أو يطرح استثناء
  • ابن سلسلة تحقق قابلة لإعادة الاستخدام
علاقات الأنماط: المراقب يفصل الموضوعات عن المراقبين. المزخرف يضيف مسؤوليات بدون وراثة. سلسلة المسؤولية تنشئ خط معالجة. جميع الأنماط الثلاثة تزيد من المرونة وقابلية الصيانة.