هذا النص عبارة عن تعديل لجزء من دليل إطار عمل Hanami لإطار Laravel. ما سبب الاهتمام بهذه المادة المعينة؟ يوفر وصفًا خطوة بخطوة مع عرض توضيحي للأشياء الشائعة الخاصة بلغات البرمجة والأطر مثل:
- باستخدام نمط التفاعلات.
- مظاهرة TDD \ BDD.
تجدر الإشارة إلى أن هذه ليست مجرد أطر عمل مختلفة مع أيديولوجيات مختلفة (على وجه الخصوص ، فيما يتعلق ORM) ، ولكن أيضًا لغات برمجة مختلفة ، لكل منها ثقافته الخاصة و "أفضل الممارسات" الراسخة لأسباب تاريخية. تميل لغات وأطر البرمجة المختلفة إلى استعارة الحلول الأكثر نجاحًا من بعضها البعض ، لذلك ، على الرغم من الاختلافات في التفاصيل ، لا تختلف الأشياء الأساسية ، ما لم نأخذ بالطبع PL مع نموذج مختلف في البداية. من المثير للاهتمام أن نقارن كيف يتم حل مشكلة واحدة ونفس المشكلة في أنظمة بيئية مختلفة.
لذلك ، في البداية لدينا إطار عمل هانامي (روبي) - إطار جديد إلى حد ما ينجذب أكثر أيديولوجيًا إلى Symfony ، مع ORM "على المستودعات". والهدف الهدف Laravel \ Lumen (php) مع السجل النشط.
في عملية التكيف ، تم قطع الزوايا الأكثر حدة:
- تم حذف الجزء الأول من الدليل بتهيئة المشروع ووصفًا لميزات الإطار وأشياء محددة مماثلة.
- يتم سحب ORM Eloquent على الكرة الأرضية ويعمل أيضًا كمستودع.
- خطوات لإنشاء رمز وقوالب لإرسال البريد الإلكتروني.
تم الحفظ والتأكيد على:
- المتفاعلات - تم إجراء تطبيق مناسب على الواجهة.
- اختبارات ، خطوة بخطوة التنمية من خلال TDD.
الجزء الأول من البرنامج التعليمي Hanami الأصلي ، والذي سيتم الرجوع إليه في النص أدناه.
النص التعليمي التفاعلي الأصلي
ربط إلى مستودع مع رمز php تكييفها في نهاية النص.
التفاعلات
ميزة جديدة: إخطارات البريد الإلكتروني
سيناريو الميزات: كمسؤول ، عندما أضيف كتابًا ، أرغب في تلقي إعلامات بالبريد الإلكتروني.
نظرًا لأن التطبيق لا يحتوي على مصادقة ، يمكن لأي شخص إضافة كتاب جديد. سنحدد عنوان البريد الإلكتروني للمسؤول من خلال متغيرات البيئة.
هذا مجرد مثال يوضح وقت استخدام المتفاعلين ، وخاصة كيفية استخدام المتفاعل Hanami.
يمكن أن يكون هذا المثال بمثابة أساس لوظائف أخرى ، مثل تأكيد المسؤول للكتب الجديدة قبل نشرها. أو منح المستخدمين القدرة على تحديد عنوان بريد إلكتروني لتحرير الكتاب من خلال رابط خاص.
في الممارسة العملية ، يمكنك استخدام المتفاعلين لتنفيذ أي منطق أعمال مستخلص من طبقة الشبكة. هذا مفيد بشكل خاص عندما تريد الجمع بين عدة أشياء من أجل التحكم في تعقيد قاعدة الشفرة.
يتم استخدامها لعزل منطق الأعمال غير التافهة باتباع مبدأ المسؤولية الفردية.
في تطبيقات الويب ، يتم استخدامها عادةً من خلال إجراءات التحكم. وبهذه الطريقة تقوم بفصل المهام ، وكائنات منطق الأعمال ، والمتفاعلين ؛ فهم لا يعرفون شيئًا عن طبقة شبكة التطبيق.
الاسترجاعات؟ نحن لسنا بحاجة لهم!
أسهل طريقة لتنفيذ إشعار بريد إلكتروني هي إضافة رد اتصال.
أي بعد إنشاء إدخال كتاب جديد في قاعدة البيانات ، يتم إرسال بريد إلكتروني.
من الناحية المعمارية ، لا يوفر هانامي مثل هذه الآلية. هذا لأننا نعتبر عمليات الاسترجاعات النموذجية نمطًا مضادًا للنمط. أنها تنتهك مبدأ المسؤولية وحدها. في حالتنا ، يقومون بخلط طبقة الثبات بشكل غير صحيح مع إشعارات البريد الإلكتروني.
أثناء الاختبار (وعلى الأرجح في بعض الحالات الأخرى) ، ستحتاج إلى تخطي رد الاتصال. هذا مربكًا بسرعة ، حيث يمكن تشغيل عمليات رد الاتصال المتعددة لحدث واحد بترتيب معين. بالإضافة إلى ذلك ، يمكنك تخطي بعض عمليات الاسترجاعات. تجعل عمليات الاسترجاعات التعليمات البرمجية هشة ويصعب فهمها.
بدلاً من ذلك ، نوصي صراحة بدلاً من ضمني.
المتفاعل هو كائن يمثل حالة استخدام محددة.
أنها تسمح لكل فئة أن تتحمل مسؤولية واحدة. مسؤولية المتفاعل الوحيد هي الجمع بين كائنات وطريقة دعوات لتحقيق نتيجة محددة.
فكرة
تتمثل الفكرة الرئيسية للمتفاعلين في استخراج الأجزاء المعزولة من الوظيفة إلى فصل جديد.
يجب أن تكتب طريقتين __construct
فقط: __construct
call
.
في تطبيق php __invoke
، __invoke
طريقة الاتصال على المعدل المحمي وتسمى عبر __invoke
.
هذا يعني أن مثل هذه الأشياء سهلة التفسير ، حيث لا يوجد سوى طريقة واحدة متاحة لاستخدامها.
إن تغليف السلوك في كائن واحد يجعل من السهل اختباره. كما أنه يجعل من الأسهل فهم قاعدة الكود الخاصة بك ، وليس فقط ترك التعقيد الخفي ضمنيًا.
التحضير
لنفترض أن لدينا تطبيق Bookshelf " بدء الاستخدام " ، ونريد إضافة ميزة "إشعار البريد الإلكتروني للكتاب المُضاف".
كتابة المتفاعل
لنقم بإنشاء مجلد للمتفاعلين لدينا ومجلد لاختباراتهم:
$ mkdir lib/bookshelf/interactors $ mkdir tests/bookshelf/interactors
نضعها في lib/bookshelf
لأنها ليست ذات صلة بتطبيق الويب. يمكنك إضافة كتب لاحقًا من خلال بوابة المشرف أو واجهة برمجة التطبيقات أو حتى أداة مساعدة لسطر الأوامر.
أضف AddBook
واكتب tests/bookshelf/interactors/AddBookTest.php
اختبار جديدة tests/bookshelf/interactors/AddBookTest.php
:
تشغيل مجموعة اختبار AddBook
الفصل الدراسي Class does not exist
بسبب عدم وجود فئة في AddBook
. لنقم بإنشاء هذه الفئة في ملف lib/bookshelf/interactors/AddBook.php
:
<?php namespace Lib\Bookshelf\Interactors; use Lib\Interactor\Interactor; class AddBook { use Interactor; public function __construct() { } protected function call() { } }
هناك طريقتان فقط يجب أن __construct
هذه الفئة: __construct
لإعداد البيانات call
لتطبيق البرنامج النصي.
يجب أن تستدعي هذه الطرق ، خاصةً call
، الأساليب الخاصة التي تكتبها.
بشكل افتراضي ، تعتبر النتيجة ناجحة ، لأننا لم نوضح صراحة فشل العملية.
دعنا نجري الاختبار:
$ phpunit
يجب أن تمر جميع الاختبارات!
الآن ، دعونا نجعل AddBook
لدينا يفعل شيئًا حقًا!
خلق كتاب
تغيير tests/bookshelf/interactors/AddBookTest.php
:
public function testCreateBook() { $result = $this->subjectCall(); $this->assertEquals("The Fire Next Time", $result->book->title); $this->assertEquals("James Baldwin", $result->book->author); }
إذا phpunit
اختبارات phpunit
، فسترى خطأ:
Exception: Undefined property Lib\Interactor\InteractorResult::$book
لنملأ مفاعلنا ، ثم اشرح ما فعلناه:
<?php namespace Lib\Bookshelf\Interactors; use Lib\Interactor\Interactor; use Lib\Bookshelf\Book; class AddBook { use Interactor; protected static $expose = ["book"]; private $book = null; public function __construct() { } protected function call($bookAttributes) { $this->book = new Book($bookAttributes); } }
شيئان مهمان يجب ملاحظتهما هنا:
String protected static $expose = ["book"];
يضيف خاصية book
إلى كائن النتيجة الذي سيتم إرجاعه عند استدعاء المتفاعل.
تعيّن طريقة call
طراز Book
إلى خاصية book
، والتي ستكون متاحة كنتيجة لذلك.
الآن يجب أن تمر الاختبارات.
لقد قمنا بتهيئة نموذج Book
، لكن لم يتم تخزينه في قاعدة البيانات.
حفظ الكتاب
لدينا كتاب جديد ، تم الحصول عليه من العنوان والمؤلف ، لكنه لم يدخل بعد في قاعدة البيانات.
نحن بحاجة إلى استخدام BookRepository
لدينا لحفظه.
إذا قمت بإجراء الاختبارات ، سترى خطأً جديداً في الرسالة " Failed asserting that null is not null
.
هذا لأن الكتاب الذي أنشأناه ليس له معرف ، لأنه لن يستلمه إلا عندما يتم حفظه.
لكي ينجح الاختبار ، نحتاج إلى إنشاء كتاب محفوظ. هناك طريقة أخرى ليست أقل صحة وهي حفظ الكتاب الذي لدينا بالفعل.
قم بتحرير طريقة call
في lib/bookshelf/interactors/AddBook.php
:
protected function call($bookAttributes) { $this->book = Book::create($bookAttributes); }
بدلاً من استدعاء new Book
، نقوم Book::create
بسمات الكتاب.
لا تزال الطريقة تُرجع الكتاب ، كما تحفظ هذا السجل في قاعدة البيانات.
إذا قمت بإجراء الاختبارات الآن ، سترى أن جميع الاختبارات تمر.
حقن التبعية
دعونا refactor لاستخدام حقن التبعية.
لا تزال الاختبارات تعمل ، لكنها تعتمد على ميزات الحفظ في قاعدة البيانات (يتم تحديد خاصية المعرف بعد الحفظ الناجح). هذه هي تفاصيل التنفيذ لكيفية عمل الحفظ. على سبيل المثال ، إذا كنت ترغب في إنشاء UUID قبل حفظه والإشارة إلى أن الحفظ كان ناجحًا بطريقة أخرى غير ملء عمود المعرف ، فسيتعين عليك تغيير هذا الاختبار.
يمكننا تعديل اختبارنا والمتفاعل لجعله أكثر موثوقية: سيكون أقل عرضة للكسر بسبب التغييرات خارج ملفه.
إليك كيفية استخدام حقن التبعية في التفاعل:
في الأساس ، هذا هو نفسه ، مع رمز أكثر قليلاً ، لإنشاء خاصية repository
.
في الوقت الحالي ، يتحقق الاختبار من سلوك طريقة create
للتأكد من أن معرفه مليء بـ $this->assertNotNull($result->book->id)
.
هذه هي تفاصيل التنفيذ.
بدلاً من ذلك ، يمكننا تعديل الاختبار للتأكد فقط من استدعاء طريقة create
على المستودع ، والثقة في أن المستودع سيحفظ الكائن (لأن هذا هو مسؤوليته).
دعنا نغير اختبار testPersistsBook
:
الآن اختبارنا لا ينتهك حدود منطقته.
كل ما فعلناه هو إضافة التبعية للمتفاعل على المستودع.
إشعار البريد الإلكتروني
دعونا نضيف إشعار البريد الإلكتروني!
يمكنك أيضًا القيام بأي شيء هنا ، على سبيل المثال ، إرسال رسالة نصية قصيرة أو إرسال رسالة دردشة أو تنشيط ربط الويب.
سنترك نص الرسالة فارغًا ، ولكن في حقل الموضوع سنشير إلى "إضافة كتاب!".
إنشاء tests/bookshelf/mail/BookAddedNotificationTest.php
اختبار الإخطار tests/bookshelf/mail/BookAddedNotificationTest.php
:
<?php use Lib\Bookshelf\Mail\BookAddedNotification; use Illuminate\Support\Facades\Mail; class BookAddedNotificationTest extends TestCase { public function setUp() { parent::setUp(); Mail::fake(); $this->mail = new BookAddedNotification(); } public function testCorrectAttributes() { $this->mail->build(); $this->assertEquals('no-reply@example.com', $this->mail->from[0]['address']); $this->assertEquals('admin@example.com', $this->mail->to[0]['address']); $this->assertEquals('Book added!', $this->mail->subject); } }
أضف فئة الإشعارات lib/Bookshelf/Mail/BookAddedNotification.php
:
<?php namespace Lib\Bookshelf\Mail; use Illuminate\Mail\Mailable; use Illuminate\Queue\SerializesModels; class BookAddedNotification extends Mailable { use SerializesModels; public function build() { $this->from('no-reply@example.com') ->to('admin@example.com') ->subject('Book added!'); return $this->view('emails.book_added_notification'); } }
الآن كل اختباراتنا تمر!
لكن الإخطار لم يتم إرساله بعد. نحن بحاجة إلى الاتصال بإرسال من التفاعل AddBook
لدينا.
AddBook
اختبار AddBook
للتأكد من أنه سيتم استدعاء AddBook
البريد:
public function testSendMail() { Mail::fake(); $this->subjectCall(); Mail::assertSent(BookAddedNotification::class, 1); }
إذا أجرينا الاختبارات ، فقد حصلنا على الخطأ: The expected [Lib\Bookshelf\Mail\BookAddedNotification] mailable was sent 0 times instead of 1 times.
.
نحن الآن دمج الإخطار إرسال في التفاعل.
public function __construct(Book $repository, BookAddedNotification $mail) { $this->repository = $repository; $this->mail = $mail; } protected function call($bookAttributes) { $this->book = $this->repository->create($bookAttributes); Mail::send($this->mail); }
نتيجة لذلك ، سيرسل المتفاعل إشعارًا حول إضافة الكتاب إلى البريد الإلكتروني.
التكامل تحكم
أخيرًا ، نحتاج إلى استدعاء المتفاعل من الإجراء.
تحرير app/Http/Controllers/BooksCreateController.php
ملف الإجراء app/Http/Controllers/BooksCreateController.php
:
<?php namespace App\Http\Controllers; use Lib\Bookshelf\Interactors\AddBook; use Illuminate\Http\Request; use Illuminate\Http\Response; class BooksCreateController extends Controller { public function __construct(AddBook $addBook) { $this->addBook = $addBook; } public function call(Request $request) { $input = $request->all(); ($this->addBook)($input); return (new Response(null, 201)); } }
اختباراتنا تمر ، ولكن هناك مشكلة صغيرة.
نحن اختبار رمز إنشاء الكتاب مرتين.
عادة ما تكون هذه ممارسة سيئة ، ويمكننا إصلاحها من خلال توضيح ميزة أخرى للمتفاعلين.
سنقوم بإزالة الإشارة على BookRepository
في الاختبارات ونستخدم AddBook
المتفاعل مع AddBook
الخاص بنا:
<?php use Lib\Bookshelf\Interactors\AddBook; class BooksCreateControllerTest extends TestCase { public function testCallsInteractor() { $attributes = ['title' => '1984', 'author' => 'George Orwell']; $addBook = Mockery::mock(AddBook::class); $this->app->instance(AddBook::class, $addBook); $addBook->expects()->__invoke($attributes); $response = $this->call('POST', '/books', $attributes); } }
الآن اختباراتنا تمر وأنها أكثر موثوقية!
يأخذ الإجراء مدخلات (من معلمات طلب http) ويستدعي المتفاعل للقيام بمهمته. المسؤولية الوحيدة للعمل هي العمل مع الشبكة. ويعمل المتفاعل مع منطق عملنا الحقيقي.
هذا يبسط الإجراءات واختباراتها إلى حد كبير.
الإجراءات معفاة عمليا من منطق الأعمال.
عندما نقوم بتعديل التفاعل ، لم نعد بحاجة إلى تغيير الإجراء أو اختباره.
لاحظ أنه في تطبيق حقيقي ، ربما ترغب في القيام بأكثر من المنطق أعلاه ، على سبيل المثال للتأكد من نجاح النتيجة. وإذا حدث فشل ، فأنت تريد إرجاع الأخطاء من التفاعل.
مستودع مع رمز