حالات الاستخدام وخدمات التطبيق
عبّر عن إجراءات الأعمال في صورة حالات استخدام مستقلة عن الأطر.
حالات الاستخدام وخدمات التطبيق درس مجاني في PHP Academy على CoddyKit. هذا هو الدرس 3 من أصل 4. يمكنك قراءة الدرس كاملاً أدناه مجاناً — ثم تمرن عليه مباشرة في المتصفح باستخدام محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7. هذا الدرس جزء من مسار التعلم في PHP Academy، وتقدمك يتزامن عبر الويب وتطبيق CoddyKit. تتضمن دورة PHP Academy 4 دروس في المجموع.
ماهيّة حالة الاستخدام فعلاً
تلتقط حالة الاستخدام (وتُسمّى أيضاً خدمة التطبيق أو المتفاعل) عملية واحدة خاصة بالتطبيق تحديداً، مثل: تسجيل مستخدم أو إجراء طلب أو إلغاء اشتراك. وهي تنسّق بين الكيانات والمنافذ لتنفيذ غرض واحد. والأهم أنها مستقلة عن أطر العمل: فلا تحتوي على Request أو Response أو دوال مساعدة عامة، بل على PHP عادية يمكن استدعاؤها من أي مكان.
كائنات DTO للأمر والنتيجة
تستقبل حالة الاستخدام كائن DTO للأمر غير قابل للتغيير، وتُرجع كائن DTO للنتيجة. كائنات DTO حوامل بيانات بسيطة، فلا تحتوي على سلوك أو منطق للتحقق يتجاوز التحقق من البنية. وتجعل الخصائص للقراءة فقط (في PHP 8.1 والإصدارات الأحدث) هذه الكائنات محصّنة ضد العبث.
<?php
final class RegisterUserCommand
{
public function __construct(
public readonly string $email,
public readonly string $plainPassword,
) {}
}
final class RegisterUserResult
{
public function __construct(public readonly string $userId) {}
}جسم خدمة التطبيق
تترجم الخدمة الأمر إلى عمليات على المجال. وتتولى تنسيق مستوى التطبيق، مثل التحقق من التفرد والحفظ وإرجاع المعرّفات، بينما تفوّض القواعد إلى الكيانات.
<?php
final class RegisterUser
{
public function __construct(
private Users $users,
private PasswordHasher $hasher,
) {}
public function __invoke(RegisterUserCommand $c): RegisterUserResult {
if ($this->users->existsByEmail($c->email)) {
throw new EmailAlreadyRegistered($c->email);
}
$user = User::register(
UserId::generate(),
new Email($c->email),
$this->hasher->hash($c->plainPassword),
);
$this->users->add($user);
return new RegisterUserResult((string) $user->id());
}
}أبقوا المنطق داخل الكيانات
احذروا نموذج المجال الفقير: أي اختزال الكيانات إلى دوال جلب وتعيين، ووضع كل المنطق داخل الخدمات. يجب أن تنتمي الثوابت إلى الكيان. وينبغي أن تُقرأ حالة الاستخدام كسيناريو قصير للنوايا، لا ككتلة طويلة من قواعد الأعمال.
<?php
final class User
{
private function __construct(
private UserId $id,
private Email $email,
private string $passwordHash,
private bool $active = false,
) {}
public static function register(UserId $id, Email $e, string $hash): self {
return new self($id, $e, $hash); // invariants enforced here
}
public function activate(): void {
if ($this->active) throw new AlreadyActive();
$this->active = true;
}
public function id(): UserId { return $this->id; }
}حدود المعاملات
حالة الاستخدام هي الحد الطبيعي للمعاملة: حالة استخدام واحدة = وحدة عمل واحدة متسقة. وبدلاً من نثر beginTransaction() داخل الخدمات، غلّفوا هذه الخدمات بمزيّن للمعاملات، لكي تظل النواة مستقلة عن طبقة الحفظ.
<?php
interface TransactionManager {
public function transactional(callable $work): mixed;
}
final class TransactionalRegisterUser
{
public function __construct(
private RegisterUser $inner,
private TransactionManager $tx,
) {}
public function __invoke(RegisterUserCommand $c): RegisterUserResult {
return $this->tx->transactional(fn() => ($this->inner)($c));
}
}التحقق: أين ينبغي أن يكون
قسّموا التحقق إلى مستويين:
- التحقق من الإدخال (التنسيق والحقول المطلوبة) يحدث في المحوّل القائد أو في أداة تحقق مخصصة قبل تشغيل حالة الاستخدام.
- التحقق من المجال (الثوابت وقواعد الأعمال) يوجد في كائنات القيم والكيانات، ويطلق استثناءات المجال.
تفترض حالة الاستخدام أن الإدخال سليم البنية، وتفرض معناه.
<?php
final class Email
{
public function __construct(public readonly string $value) {
if (!filter_var($value, FILTER_VALIDATE_EMAIL)) {
throw new InvalidArgumentException("Invalid email: $value");
}
}
}
try { new Email('nope'); } catch (Throwable $e) { echo $e->getMessage(), PHP_EOL; }
echo (new Email('a@b.com'))->value, PHP_EOL;إرجاع المخرجات من دون HTTP
هناك نمطان لإرجاع البيانات مع الحفاظ على الاستقلال عن أطر العمل:
- إرجاع كائن DTO للنتيجة (بسيط ومتزامن).
- منفذ مخرجات / عارض — تدفع حالة الاستخدام النتيجة إلى حدّ مخرجات محقون، وتترك للمحوّل قرار التنسيق (JSON أو HTML أو CLI). ويحافظ ذلك على بقاء شكل الاستجابة خارج النواة أيضاً.
<?php
interface RegisterUserOutput {
public function present(RegisterUserResult $r): void;
}
final class RegisterUserWithPresenter {
public function __construct(private Users $users, private PasswordHasher $h) {}
public function __invoke(RegisterUserCommand $c, RegisterUserOutput $out): void {
$user = User::register(UserId::generate(), new Email($c->email), $this->h->hash($c->plainPassword));
$this->users->add($user);
$out->present(new RegisterUserResult((string) $user->id()));
}
}أحداث المجال من حالات الاستخدام
غالباً ما تسجّل حالات الاستخدام أحداث المجال التي تطلقها الكيانات، ثم توزّعها بعد إتمام المعاملة. ويفصل ذلك الآثار الجانبية، مثل إرسال رسالة ترحيب بالبريد الإلكتروني أو تحديث نموذج القراءة، عن سير العمل الأساسي.
<?php
trait RecordsEvents {
private array $events = [];
protected function record(object $e): void { $this->events[] = $e; }
public function releaseEvents(): array {
$e = $this->events; $this->events = []; return $e;
}
}
final class UserRegistered {
public function __construct(public readonly string $userId) {}
}
// Use case calls $user->releaseEvents() and hands them to a dispatcher
echo 'event recorded pattern', PHP_EOL;صنف واحد لكل حالة استخدام
فضّلوا صنفاً ذا إجراء واحد (طريقة عامة واحدة، غالباً __invoke) على خدمة ضخمة تضم عشر طرق. ومن فوائده:
- وضوح المسؤولية الواحدة والتسمية (
CancelSubscriptionبدلاً منSubscriptionService::cancel). - لا يحقن المُنشئ إلا ما تحتاجه هذه العملية.
- سهولة تغليفه بمزيّنات مثل المعاملات والتسجيل والتفويض.
الربط عند جذر التركيب
لا تنشئ حالة الاستخدام تبعياتها باستخدام new؛ بل يتولى جذر التركيب ذلك. إليكم ربطاً يدوياً يمكن وضعه في تعريف حاوية DI.
<?php
$pdo = new PDO('sqlite::memory:');
$users = new PdoUsers($pdo);
$hasher = new BcryptHasher();
$register = new RegisterUser($users, $hasher);
// Decorate with a transaction boundary
$register = new TransactionalRegisterUser($register, new PdoTransactionManager($pdo));
// Driving adapter calls it
$result = $register(new RegisterUserCommand('dev@coddykit.com', 's3cret!'));
echo $result->userId, PHP_EOL;المخاوف المشتركة بين القطاعات عبر المزيّنات
يُعد التسجيل والمقاييس والتفويض مخاوف مشتركة بين القطاعات، لذا أبقوها خارج جسم حالة الاستخدام. غلّفوا الخدمة بمزيّنات تشترك في واجهتها، لتظل النواة مركّزة على سير العمل، بينما تلتف حولها مخاوف البنية التحتية.
<?php
interface RegisterUserHandler {
public function __invoke(RegisterUserCommand $c): RegisterUserResult;
}
final class LoggingRegisterUser implements RegisterUserHandler {
public function __construct(
private RegisterUserHandler $inner,
private LoggerInterface $log,
) {}
public function __invoke(RegisterUserCommand $c): RegisterUserResult {
$this->log->info('register.start', ['email' => $c->email]);
$r = ($this->inner)($c);
$this->log->info('register.ok', ['id' => $r->userId]);
return $r;
}
}تحقّق سريع
أين ينبغي أن توجد القاعدة "يجب أن يكون البريد الإلكتروني فريداً وسليم البنية"؟
مراجعة
تمنحكم حالات الاستخدام المستقلة عن أطر العمل طبقة تطبيق نظيفة:
- صنف ذو إجراء واحد لكل عملية، يستقبل كائن DTO للأمر ويُرجع كائن DTO للنتيجة (أو يدفعها إلى منفذ مخرجات).
- تملك الكيانات وكائنات القيم الثوابت، ولا تفعل الخدمة سوى التنسيق؛ فتجنبوا النماذج الفقيرة.
- حالات الاستخدام هي حدود المعاملات، وتُغلّف بالمزيّنات بدلاً من تضمين
beginTransactionداخلها. - افصلوا التحقق من الإدخال (المحوّل/كائن القيمة) عن التحقق من المجال (الكيانات).
- تفصل أحداث المجال الآثار الجانبية، بينما يربط جذر التركيب التبعيات.
الأسئلة الشائعة
هل درس «حالات الاستخدام وخدمات التطبيق» مجاني؟
نعم — نص درس «حالات الاستخدام وخدمات التطبيق» كامل متاح مجاناً هنا على الويب. لتمرينه بشكل تفاعلي (محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7) وفتح باقي دورة PHP Academy، انتقل إلى CoddyKit PRO. تتضمن دورة PHP Academy 4 دروس في المجموع.
ماذا ستتعلم في «حالات الاستخدام وخدمات التطبيق»؟
عبّر عن إجراءات الأعمال في صورة حالات استخدام مستقلة عن الأطر. تتمرن على PHP Academy مع أكواد عملية تشغلها مباشرة في المتصفح، ومدرس ذكاء اصطناعي متاح 24/7 يجيب على أسئلتك أثناء عملك.
هل أحتاج إلى خبرة سابقة لأبدأ PHP Academy؟
لا تُشترط خبرة سابقة. PHP Academy على CoddyKit منظم للمبتدئين حتى المتقدمين، لذا يمكنك البدء من هنا أو من البداية والتقدم بسرعتك الخاصة. هذا هو الدرس 3 من أصل 4.
كم من الوقت يستغرق درس «حالات الاستخدام وخدمات التطبيق»؟
معظم دروس CoddyKit تستغرق حوالي 5–10 دقائق. كل منها موجز وتفاعلي، لذا تحرز تقدماً مستمراً وتستأنف من حيث توقفت عبر الويب والتطبيق.
هل يمكنني كتابة وتشغيل أكواد في درس PHP Academy هذا؟
نعم. كل درس في PHP Academy يتضمن محرر أكواد مدمج، لذا تكتب وتشغل أكواداً حقيقية مباشرة في متصفحك وتحصل على تعليقات فورية من الذكاء الاصطناعي — بدون إعداد محلي.
جميع الدروس في هذه الدورة
- من البنية متعددة الطبقات إلى البنية النظيفة
- شرح المنافذ والمحوّلات
- حالات الاستخدام وخدمات التطبيق
- عكس الاعتماديات في التطبيق