شرح - php mvc tutorial pdf




كيف ينبغي تنظيم نموذج في MVC؟ (4)

أنا أتلقى فهمًا حول إطار MVC ، وغالبًا ما أتساءل عن مقدار الرمز الذي يجب إدخاله في النموذج. أميل إلى الحصول على فصل وصول إلى البيانات يحتوي على طرق مثل هذا:

public function CheckUsername($connection, $username)
{
    try
    {
        $data = array();
        $data['Username'] = $username;

        //// SQL
        $sql = "SELECT Username FROM" . $this->usersTableName . " WHERE Username = :Username";

        //// Execute statement
        return $this->ExecuteObject($connection, $sql, $data);
    }
    catch(Exception $e)
    {
        throw $e;
    }
}

تميل نماذجي إلى أن تكون فئة كيان تم تعيينها إلى جدول قاعدة البيانات.

هل يجب أن يكون لكائن النموذج كل الخصائص المعينة لقاعدة البيانات بالإضافة إلى التعليمة البرمجية الموجودة أعلاه أم أنه من الأفضل فصل تلك التعليمة البرمجية التي تعمل في الواقع قاعدة البيانات؟

هل سأحصل على أربع طبقات؟


إخلاء المسؤولية: فيما يلي وصف لكيفية فهم أنماط تشبه MVC في سياق تطبيقات الويب القائمة على PHP. جميع الروابط الخارجية المستخدمة في المحتوى موجودة لشرح المصطلحات والمفاهيم ، ولا تعني مصداقيتي الخاصة حول هذا الموضوع.

أول شيء يجب أن أوضحه هو: النموذج هو طبقة .

ثانيًا: هناك فرق بين MVC الكلاسيكي وما نستخدمه في تطوير الويب. Here's بعض الإجابات القديمة التي كتبتها ، والتي تصف بإيجاز مدى اختلافها.

ما هو النموذج غير:

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

كما أنه ليس أسلوبًا لتقابل الكائنات ذات الصلة (ORM) ولا تجريدًا لجداول قاعدة البيانات. إن أي شخص يخبرك بخلاف ذلك يحاول على الأرجح "بيع" ORM جديد أو إطار كامل.

ما هو النموذج:

في تكييف MVC المناسب ، يحتوي M على كل منطق عمل المجال ويتم تصنيع طبقة النموذج في الغالب من ثلاثة أنواع من الهياكل:

  • كائنات المجال

    كائن المجال هو حاوية منطقية لمعلومات المجال البحتة ؛ وعادة ما يمثل كيان منطقي في مساحة المجال المشكلة. يشار إليها عادة باسم منطق الأعمال .

    هذا هو المكان الذي تحدد فيه كيفية التحقق من صحة البيانات قبل إرسال فاتورة ، أو لحساب التكلفة الإجمالية لأمر. في نفس الوقت ، تكون كائنات المجال غير مدركة تمامًا للتخزين - لا من حيث (قاعدة بيانات SQL ، واجهة برمجة تطبيقات REST ، ملف نصي ، وما إلى ذلك) ولا حتى إذا تم حفظها أو استردادها.

  • مخططو البيانات

    هذه الكائنات مسؤولة فقط عن التخزين. إذا قمت بتخزين المعلومات في قاعدة بيانات ، فسيكون هذا هو المكان الذي يعيش فيه SQL. أو ربما تستخدم ملف XML لتخزين البيانات ، ويتم توزيع بيانات الخرائط من وإلى ملفات XML.

  • Services

    يمكنك اعتبارها "كائنات نطاق أعلى مستوى" ، ولكن بدلاً من منطق الأعمال ، تكون الخدمات مسؤولة عن التفاعل بين كائنات النطاق ومخطط الخرائط . هذه الهياكل في نهاية المطاف إنشاء واجهة "عامة" للتفاعل مع منطق الأعمال المجال. يمكنك تجنبها ، ولكن في عقوبة تسريب بعض المنطق المجال إلى وحدات تحكم .

    هناك إجابة ذات صلة لهذا الموضوع في سؤال تنفيذ ACL - قد يكون مفيدًا.

يجب أن يحدث الاتصال بين طبقة النموذج والأجزاء الأخرى من الثالوث MVC فقط من خلال الخدمات . للفصل الواضح فوائد إضافية قليلة:

  • يساعد على تطبيق مبدأ المسؤولية الواحدة (SRP)
  • يوفر "مساحة كبيرة للمناورة" في حالة تغير المنطق
  • يحافظ على وحدة تحكم بسيطة قدر الإمكان
  • يعطي مخططًا واضحًا ، إذا احتجت في أي وقت إلى واجهة برمجة تطبيقات خارجية

كيف تتفاعل مع النموذج؟

المتطلبات الأساسية: مشاهدة محاضرات "Global State و Singletons" و "لا تبحث عن الأشياء!" من محادثات كود النظيفة.

الحصول على الوصول إلى مثيلات الخدمة

لكل من مثيلات العرض والتحكم (ما يمكنك الاتصال به: "طبقة واجهة المستخدم") للوصول إلى هذه الخدمات ، هناك طريقتان عامتان:

  1. يمكنك حقن الخدمات المطلوبة في صناع وجهات النظر والتحكم الخاصة بك مباشرة ، ويفضل استخدام حاوية DI.
  2. استخدام مصنع للخدمات كإعتماد إلزامي لجميع وجهات النظر والتحكم الخاصة بك.

كما قد تشك ، حاوية DI هي حل أكثر أناقة (في حين أنها ليست أسهل للمبتدئين). المكتبات اثنين ، التي أوصي بالنظر في هذه الوظيفة سيكون مكون DependencyInjection مستقل Auryn أو Auryn .

يتيح لك كل من الحلول باستخدام مصنع وحاوية DI مشاركة مثيلات الخوادم المختلفة ليتم مشاركتها بين وحدة التحكم المحددة وعرض دورة طلب-استجابة معينة.

تغيير حالة النموذج

الآن يمكنك الوصول إلى طبقة النموذج في وحدات التحكم ، تحتاج إلى البدء في استخدامها فعليًا:

public function postLogin(Request $request)
{
    $email = $request->get('email');
    $identity = $this->identification->findIdentityByEmailAddress($email);
    $this->identification->loginWithPassword(
        $identity,
        $request->get('password')
    );
}

تتحكم وحدات التحكم الخاصة بك في مهمة واضحة للغاية: قم بإدخال مدخلات المستخدم ، وبناءً على هذه المدخلات ، قم بتغيير الحالة الحالية لمنطق العمل. في هذا المثال ، الحالات التي تم تغييرها هي "مستخدم مجهول" و "تسجيل دخول المستخدم".

وحدة التحكم ليست مسؤولة عن التحقق من صحة إدخالات المستخدم ، لأن ذلك جزء من قواعد العمل ، ومن المؤكد أن المتحكم لا يتصل باستعلامات SQL ، مثل ما تراه here أو here (من فضلك لا تكرههم ، فهم مضللون ، ليسوا شريرين).

عرض المستخدم على تغيير الدولة.

حسنًا ، قام المستخدم بتسجيل الدخول (أو فشل). ماذا الآن؟ المستخدم سعيد لا يزال غير مدرك له. لذا فأنت تحتاج إلى تقديم رد فعلي ، وهذا هو مسؤولية وجهة النظر.

public function postLogin()
{
    $path = '/login';
    if ($this->identification->isUserLoggedIn()) {
        $path = '/dashboard';
    }
    return new RedirectResponse($path); 
}

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

يمكن للطبقة التقديمية أن تحصل على تفاصيل أكثر ، كما هو موضح هنا: فهم آراء MVC في PHP .

لكنني فقط أقوم بعمل REST API!

بالطبع ، هناك حالات ، عندما يكون هذا مبالغة.

MVC هو مجرد حل ملموس لمبدأ فصل المخاوف . يفصل MVC واجهة المستخدم عن منطق العمل ، ويفصلها في واجهة المستخدم عن معالجة مدخلات المستخدم والعرض التقديمي. هذا أمر بالغ الأهمية. في حين أن الناس غالبا ما يصفونه بأنه "ثالوث" ، فإنه لا يتكون في الواقع من ثلاثة أجزاء مستقلة. الهيكل أشبه بهذا:

وهذا يعني أنه عندما يكون منطق طبقة العرض التقريبي غير موجود ، فإن النهج البراغماتي هو الاحتفاظ بها كطبقة واحدة. كما أنه يمكن تبسيط بعض جوانب طبقة النموذج.

باستخدام هذا الأسلوب ، يمكن كتابة مثال تسجيل الدخول (API) على النحو التالي:

public function postLogin(Request $request)
{
    $email = $request->get('email');
    $data = [
        'status' => 'ok',
    ];
    try {
        $identity = $this->identification->findIdentityByEmailAddress($email);
        $token = $this->identification->loginWithPassword(
            $identity,
            $request->get('password')
        );
    } catch (FailedIdentification $exception) {
        $data = [
            'status' => 'error',
            'message' => 'Login failed!',
        ]
    }

    return new JsonResponse($data);
}

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

كيف تبني النموذج؟

نظرًا لعدم وجود فئة "نموذج" واحدة (كما هو موضح أعلاه) ، فأنت لا تقوم "بإنشاء النموذج" حقًا. وبدلاً من ذلك ، يمكنك البدء في إنشاء الخدمات ، القادرة على تنفيذ طرق معينة. ثم تنفيذ كائنات المجال ومخطط الخرائط .

مثال على طريقة الخدمة:

في كلا الأسلوبين أعلاه كان هناك طريقة تسجيل الدخول هذه لخدمة تحديد الهوية. كيف سيكون شكلها بالفعل. أنا أستخدم نسخة معدلة قليلاً من نفس الوظيفة من مكتبة ، كتبتها .. لأنني كسول:

public function loginWithPassword(Identity $identity, string $password): string
{
    if ($identity->matchPassword($password) === false) {
        $this->logWrongPasswordNotice($identity, [
            'email' => $identity->getEmailAddress(),
            'key' => $password, // this is the wrong password
        ]);

        throw new PasswordMismatch;
    }

    $identity->setPassword($password);
    $this->updateIdentityOnUse($identity);
    $cookie = $this->createCookieIdentity($identity);

    $this->logger->info('login successful', [
        'input' => [
            'email' => $identity->getEmailAddress(),
        ],
        'user' => [
            'account' => $identity->getAccountId(),
            'identity' => $identity->getId(),
        ],
    ]);

    return $cookie->getToken();
}

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

private function changeIdentityStatus(Entity\Identity $identity, int $status)
{
    $identity->setStatus($status);
    $identity->setLastUsed(time());
    $mapper = $this->mapperFactory->create(Mapper\Identity::class);
    $mapper->store($identity);
}

طرق إنشاء مصممي الخرائط

لتنفيذ تجريد من المثابرة ، على النهج الأكثر مرونة هو إنشاء مصممي البيانات المخصصة.

من: كتاب PoEAA

من الناحية العملية ، يتم تطبيقها للتفاعل مع فئات أو فئات محددة. دعنا نقول لديك Customer Admin في التعليمات البرمجية الخاصة بك (كلا ترث من الطبقة المتفوقة User ). من المحتمل أن يكون لدى كلاهما مصمّم خرائط منفصل ، نظرًا لأنها تحتوي على حقول مختلفة. ولكن سوف ينتهي بك الأمر أيضًا مع العمليات المشتركة والمستخدمة بشكل شائع. على سبيل المثال: تحديث "آخر مرة شوهدت عبر الإنترنت" . وبدلاً من جعل مصممي الخرائط الحاليين أكثر تعقيدًا ، كلما كان النهج الأكثر واقعية هو الحصول على "أداة تصميم المستخدم" العامة ، التي تعمل على تحديث ذلك الطابع الزمني فقط.

بعض التعليقات الإضافية:

  1. جداول قاعدة البيانات والطراز

    في بعض الأحيان ، هناك علاقة مباشرة 1: 1: 1 بين جدول قاعدة البيانات ، كائن المجال ، ومخطط ، في المشاريع الكبيرة قد يكون أقل شيوعا مما تتوقع:

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

      مثال: إذا كنت تقوم بإنشاء تقرير شهري. هذا من شأنه أن يجمع معلومات من جداول مختلفة ، ولكن لا يوجد جدول MonthlyReport السحري في قاعدة البيانات.

    • يمكن أن يؤثر مخطط واحد على جداول متعددة.

      مثال: عندما تقوم بتخزين البيانات من كائن User ، يمكن أن يحتوي كائن المجال هذا على مجموعة من كائنات المجال الأخرى - مثيلات Group . إذا قمت بتغييرها وتخزينها User ، فسيتعين على مخطط البيانات تحديث و / أو إدراج مدخلات في جداول متعددة.

    • يتم تخزين البيانات من كائن مفرد في أكثر من جدول.

      مثال: في الأنظمة الكبيرة (أعتقد: شبكة اجتماعية متوسطة الحجم) ، قد يكون عمليًا تخزين بيانات مصادقة المستخدم والبيانات التي يتم الوصول إليها غالبًا بشكل منفصل عن أجزاء كبيرة من المحتوى ، نادرًا ما يكون مطلوبًا. في هذه الحالة ، قد لا يزال لديك فصل User واحد ، لكن المعلومات التي يحتوي عليها ستعتمد على ما إذا كانت التفاصيل الكاملة قد تم جلبها.

    • لكل كائن نطاق يمكن أن يكون هناك أكثر من مخطط واحد

      على سبيل المثال: لديك موقع إخباري يحتوي على برامج تشفير مشتركة لكلٍّ من البرامج العامة والإدارية. ولكن ، بينما تستخدم كلتا الواجهتين نفس فئة Article ، تحتاج الإدارة إلى المزيد من المعلومات المملوءة بها. في هذه الحالة ، سيكون لديك مخططان منفصلان: "internal" و "external". كل استعلامات مختلفة أداء ، أو حتى استخدام قواعد بيانات مختلفة (كما هو الحال في الرئيسي أو الرقيق).

  2. طريقة العرض ليست قالبًا

    عرض الحالات في MVC (إذا كنت لا تستخدم صيغة MVP للنمط) هي المسؤولة عن المنطق التقديمي. وهذا يعني أن كل عرض سيعمل عادةً على توفير بعض القوالب على الأقل. وهي تحصل على بيانات من طبقة النموذج ثم تقوم ، بناءً على المعلومات المستلمة ، باختيار قالب وتعيين القيم.

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

    يمكنك استخدام إما قوالب PHP الأصلية أو استخدام بعض محرك templating طرف ثالث. قد يكون هناك أيضًا بعض مكتبات الجهات الخارجية ، والتي يمكنها استبدال مثيلي العرض تمامًا.

  3. ماذا عن النسخة القديمة من الجواب؟

    التغيير الرئيسي الوحيد هو أن ما يسمى بالنموذج في النسخة القديمة ، هو في الواقع خدمة . تبقى بقية "analogy library" بشكل جيد.

    العيب الوحيد الذي أراه هو أن هذه المكتبة ستكون غريبة حقًا ، لأنها ستعيد لك معلومات من الكتاب ، لكن لن تسمح لك بلمس الكتاب نفسه ، وإلا فإن التجريد سيبدأ في "التسريب". قد يتعين علي التفكير في تشبيه أكثر ملاءمة.

  4. ما هي العلاقة بين مثيلات View و Controller ؟

    يتكون هيكل MVC من طبقتين: ui و model. الهياكل الرئيسية في طبقة واجهة المستخدم هي وجهات النظر والتحكم.

    عندما تتعامل مع مواقع الويب التي تستخدم نمط تصميم MVC ، فإن أفضل طريقة هي أن يكون لديك علاقة 1: 1 بين طرق العرض ووحدات التحكم. يمثل كل عرض صفحة كاملة في موقع الويب الخاص بك ولديه وحدة تحكم مخصصة لمعالجة جميع الطلبات الواردة لهذا العرض المعين.

    على سبيل المثال ، لتمثيل المقالة المفتوحة ، سيكون لديك \Application\Controller\Document و \Application\View\Document . هذا سيحتوي على جميع الوظائف الرئيسية لطبقة واجهة المستخدم ، عندما يتعلق الأمر بالتعامل مع المقالات (بالطبع قد يكون لديك بعض مكونات XHR التي لا ترتبط مباشرة بالمقالات) .


في الويب "MVC" يمكنك القيام بكل ما تريد.

وصف المفهوم الأصلي (1) النموذج بأنه منطق الأعمال. يجب أن يمثل حالة التطبيق وفرض بعض تناسق البيانات. وكثيرا ما يوصف هذا النهج بأنه "نموذج الدهون".

تتبع معظم أطر عمل PHP أسلوبًا أكثر ضحلة ، حيث يكون النموذج مجرد واجهة قاعدة بيانات. ولكن على الأقل يجب على هذه النماذج لا يزال التحقق من صحة البيانات والعلاقات الواردة.

في كلتا الحالتين ، أنت لست بعيدًا جدًا إذا قمت بفصل استدعاءات قاعدة بيانات SQL أو قاعدة البيانات إلى طبقة أخرى. بهذه الطريقة تحتاج فقط إلى الاهتمام بالبيانات / السلوك الحقيقي ، وليس باستخدام واجهة برمجة التطبيقات الفعلية للتخزين. (على أية حال ، من غير المعقول أن تطرفه. على سبيل المثال ، لن تتمكن مطلقًا من استبدال قاعدة البيانات الخلفية بمصاحبة فيلم إذا لم يتم تصميم ذلك مسبقًا).


وفي أغلب الأحيان ، يكون لمعظم التطبيقات بيانات وعرض وجزء معالجة ، ونضع كل تلك الرسائل في الأحرف M و V و C

النموذج ( M ) -> له الصفات التي تحمل حالة التطبيق ولا يعرف أي شيء عن V و C

عرض ( V ) -> يحتوي على عرض التنسيق للتطبيق ولا يعرف إلا عن نموذج كيفية هضم عليه ولا يزعج حول C

المراقب المالي ( C ) ----> وقد تجهيز جزء من التطبيق ويعمل بمثابة الأسلاك بين M و V ويعتمد على كل من M ، V على عكس M و V

تماما هناك فصل القلق بين كل منهما. في المستقبل يمكن إضافة أي تغيير أو تحسينات بسهولة بالغة.


ينتمي كل شيء هو منطق الأعمال في نموذج ، سواء كان استعلام قاعدة بيانات ، حسابات ، مكالمة REST ، إلخ.

يمكنك الحصول على الوصول إلى البيانات في النموذج نفسه ، ولا يمنعك نمط MVC من القيام بذلك. يمكنك تناول السكر مع الخدمات ، مصممي الخرائط وغير ذلك ، ولكن التعريف الفعلي للنموذج هو طبقة تتعامل مع منطق الأعمال ، لا شيء أكثر من ذلك ، لا شيء أقل من ذلك. يمكن أن يكون فئة أو وظيفة أو وحدة كاملة مع كائنات غازيليون إذا كان هذا هو ما تريده.

من الأسهل دائمًا وجود كائن منفصل يقوم بالفعل بتنفيذ استعلامات قاعدة البيانات بدلاً من تنفيذها في النموذج مباشرةً: سيكون هذا مفيدًا بشكل خاص عند اختبار الوحدة (بسبب سهولة حقن تبعية قاعدة بيانات وهمية في نموذجك):

class Database {
   protected $_conn;

   public function __construct($connection) {
       $this->_conn = $connection;
   }

   public function ExecuteObject($sql, $data) {
       // stuff
   }
}

abstract class Model {
   protected $_db;

   public function __construct(Database $db) {
       $this->_db = $db;
   }
}

class User extends Model {
   public function CheckUsername($username) {
       // ...
       $sql = "SELECT Username FROM" . $this->usersTableName . " WHERE ...";
       return $this->_db->ExecuteObject($sql, $data);
   }
}

$db = new Database($conn);
$model = new User($db);
$model->CheckUsername('foo');

أيضا ، في PHP ، نادرًا ما تحتاج إلى التقاط / إعادة الاستثناءات نظرًا لأنه يتم الحفاظ على backtrace ، خاصة في حالة مثل المثال الخاص بك. فقط دع الاستثناء يتم طرحه والقبض عليه في جهاز التحكم بدلاً من ذلك.





model