فصل 7. Zend_Controller

قائمة المحتويات

7.1. نظرة عامة
7.1.1. مقدمة
7.1.2. الـ Request Object
7.1.3. عملية التحويل - routing
7.1.4. عملية الـ Dispatch
7.1.5. الـ Response Object
7.2. البداية
7.2.1. مقدمة
7.2.2. إعدادات السيرفر
7.2.3. ملف الـ Bootstrap
7.2.4. هيكلة المجلدات
7.2.5. الـ Controller الأساسى
7.3. Subclassing
7.3.1. مقدمة
7.3.2. التعريفات
7.3.3. Router Interface
7.3.4. Dispatcher Interface
7.4. Provided Subclasses
7.4.1. Introduction
7.4.2. Zend_Controller_Request_Http
7.4.3. Zend_Controller_Router_Rewrite
7.4.4. Zend_Controller_Response_Http
7.4.5. Zend_Controller_Response_Cli
7.5. Action Controllers
7.5.1. Introduction
7.5.2. Object initialization
7.5.3. Pre- and Post-Dispatch Hooks
7.5.4. Accessors
7.5.5. Utility Methods
7.6. الـ Plugins
7.6.1. مقدمة
7.7. Using a Conventional Modular Directory Structure
7.7.1. Introduction
7.7.2. Specifying Module Controller Directories
7.7.3. Routing to modules
7.7.4. Module or Global Default Controller
7.8. MVC Exceptions
7.8.1. Introduction
7.8.2. How can you handle exceptions?
7.8.3. MVC Exceptions You May Encounter
7.9. Migrating from Previous Versions
7.9.1. Migrating from 0.6.0 to 0.8.0
7.9.2. Migrating from 0.2.0 or before to 0.6.0

7.1. نظرة عامة

7.1.1. مقدمة

يوفر Zend_Controller البنية اللازمة لإنشاء مواقع تعتمد على النمط ( MVC) او Model-View-Controller.

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

Zend_Controller يعتمد على على عدة components اخرى, فى حين انه لا يشترط عليك ان تكن مدرك ما يحدث خلف كواليس هذه الـ components لتتمكن من استخدام النظام, إلا ان بعض المعرفة يعملية تدفق العمليات ستكن مساعدة جدا لك.

  • Zend_Controller_Front يقود و يتحكم فى تدفق العمليات داخل نظام Zend_Controller , و هو تطبيقنا للنمط المسمى FrontController , و يقوم ايضا Zend_Controller_Front بإستلام و معالجة كل الطلبات المستلمة من قبل السيرفر , وهو مسئول بالكامل عن نقل هذه الطلبات إلى الـ ActionControllers اى (Zend_Controller_Action).

  • Zend_Controller_Request_Abstract يمثل بيئة الطلب "request environment" و يوفر methods لتحديد و جلب أسماء الـ controller و الـ action و أى بارامترات request أخرى, إضافة إلى ذلك يقم بتعقب إن ما كان الـ controller الذى يحويه قد تم تنفيذه من قبل Zend_Controller_Dispatcher, و امتدادات للـ abstract request object يمكنك أن تستخدم لكبسلة الـ request environment بالكامل, بما يسمح للـ routers أن تسحب معلومات من الـ request environment لكى تقوم بتحديد أسماء الـ controller و الـ action .

    حسب الإعدادات الأساسية, يتم إستخدام Zend_Controller_Request_Http , و الذى يوفر امكانية الوصول إلى الـ HTTP request environment بالكامل.

  • يتم إستخدام Zend_Controller_Router_Interface لتعريف الـ routers, عملية الـ routing هى عملية تحليل الـ request environment لمعرفة أى controller و أى action تابع للـ controller , يجب أن يستلم الـ request , هذا الـ controller و الـ action و أى بارامترات أخرى سيتم تحديدها فى كائن الـ request و الذى سيتم معالجته بعدها بواسطة Zend_Controller_Dispatcher, تحدث عملية الـ routing مرة واحدة: عند إستلام الطلب الأساسى و قبل أن يتم تنفيذ أول controller .

    الـ router الأساسى , Zend_Controller_Router , يستلم الجزء الأخير من URI كما هو محدد فى Zend_Controller_Request_Http و يقوم بفك محتوياته إلى controller و action و أى باريمترات أخرى تم تمريرها فى الـ url , فعلى سبيل المثال, الـ URL هذا http://localhost/foo/bar/key/value سيتم فك محتوياته ليتم إستخدام الـ controller المسمى foo و الـ action المسمى bar و سيتم تحديد باراميتر بالأسم key سيحمل القيمة value.

  • يُستخدم Zend_Controller_Dispatcher_Interface لتعريف الـ dispatchers, عملية الـ dispatching هى جلب أسم الـ controller و الـ action من كائن الـ request و على أساسهما يتم تحديد ملف/class الـ controller و الـ action method فى الـ controller , و إن لم يتم إيجاد الـ controller أو أن الـ action غير موجود , سيتم القيام بالعمليات الازمة لمعرفة الـ controllers و الـ actions الأساسية الواجب تنفيذها.

    عملية الـ dispatching الفعلية تتكون من إنشاء نسخة من الـ controller class و إستدعاء الـ action method فى هذا الـ class, و عكس الـ routing الذى يحدث مرة واحدة, الـ dispatching يحدث فى حلقة متكررة, و إن تم تغيير حالة الـ dispatch الخاصة بكائن الـ request عند أى نقطة, سيتم تكرار الحلقة من جديد, و إستدعاء أى action يوجد فى كائن الـ request, و فى أول مرة تنتهى الحلقة التكرارية مع أن تكون حالة الـ dispatch الخاصة بكائن الـ request (قيمتها true), سيتم إنهاء العملية.

    الـ dispatcher الأساسى هو Zend_Controller_Dispatcher, و هو يُعرِف الـ controllers على انهم CamelCasedClasses ينتهون بالكلمة Controller , و الـ action methods على انهم camelCasedMethods تنتهى بالكلمة Action : كما فى SomeFooController::barAction, سيتم الأشارة للـ controller على انه somefoo و الـ action هو bar.

    ايضاً, يمكنك تحديد module ليتم إستخدامه عند تحميل الـ controller, الـ module يتم إستخدامه لتحديد مجلد فرعى و/أو class prefix ليتم إستخدامه عند تحميل الـ controller , و بسهولة , يمكنك تحديد الـ module عند تحديد مجلدات الـ controllers .

    $front->setControllerDirectory(array(
        'default' => '/path/to/controllers',
        'user'    => '/path/to/controllers/user',
        'admin'   => '/path/to/controllers/admin'
    ));
    

    فى المثال بالأعلى, إن تم إختيار الـ module المسمى 'user' مع الـ controller المسمى 'news' , سيتم البحث عن NewsController.php فى /path/to/controllers/user أولاً قبل البحث فى المسارات الأخرى. أيضاً, اولاً سيتم أعتبار أن الـ class يسمى User_NewsController و بعدها NewsController. و سيكون الـ request بهذا الشكل مثلاً , http://localhost/news/action/module/user بإستخدام الـ router الأساسى, و http://localhost/module/news/action بإستخدام الـ RewriteRouter.

    تعتبر الـ modules مفيدة فى حالة أنك تريد فصل الكود إلى مجلدات فرعية أو إستخدام أكواد من مصدر أخر "third party" أو إعادة إستخدام نفس الـ controller library فى تطبيقات مختلفة.

  • Zend_Controller_Action هو الجزء الأساسى فى الـ controller component, كل controller عبارة عن class يرث من Zend_Controller_Action, و هذا الـ class لديه action methods.

  • Zend_Controller_Response_Abstract يُعرِف الـ response class الأساسى الذى يُستخدم لتجميع و إرجاع الردود "responses" من الـ action controllers, و هو يقوم بتجميع كل من الـ headers و محتويات جسم الصفحة, و حيث أنه يُطبِق __toString() , فيمكن طباعته مباشرة لإرسال كل الـ headers و المحتويات مرة واحدة.

    الـ response class الأساسى هو Zend_Controller_Response_Http, و الذى يتوافق مع العمل فى بيئة الـ HTTP.

كيفية عمل Zend_Controller تعتبر بسيطة نسبياً . حيث يتم إستلام طلب "request" عن طريق Zend_Controller_Front, و الذى بدوره يستدعى Zend_Controller_Router ليعرف أى Controller (و أى action فى هذا الـ controller ) سيتم تنفيذه . ثم يقوم Zend_Controller_Router بتقسيم الـ URI إلى اجزاء صغيرة ليحدد اسم الـ controller و الـ action فى الـ request. بعدها يقوم Zend_Controller_Front بتشغيل حلقة تنفيذ متكررة "dispatch loop". حيث يقوم بإستدعاء Zend_Controller_Dispatcher و تمرير الـ request إليه , ليقوم بتنفيذ الـ controller و الـ action المحددين فى الـ request (أو الأساسيين). و بعد إنتهاء الـ controller من عمله , يعود التحكم إلى Zend_Controller_Front . إذا قام الـ controller بتوضيح انه يجب تنفيذ controller اخر و ذلك عن طر يق تغييره لحالة الـ dispatch الخاصة بالـ request , سيتم إستكمال الحلقة التنفيذية و سيتم تنفيذ الـ controller الجديد , أو تنتهى العملية .

7.1.2. الـ Request Object

الـ request object هو عبارة عن value object بسيط يتم تمريره ما بين Zend_Controller_Front و الـ router و الـ dispatcher "المنفذ" و الـ controller classes , و يحمل هذا الكائن بيانات الـ controller و الـ action و البارامترات التى سيتم تمريرها إلى الـ action , بالأضافة إلى بيانات الـ request environment أى كانت .. سواء HTTP او CLI او PHP-GTK .

  • يمكن الوصول إلى إسم الـ controller بواسطة getControllerName() و setControllerName().

  • يمكن الوصول إلى إسم الـ action الذى سيتم إستدعائه من الـ controller بواسطة getActionName() و setActionName().

  • البارامترات التى سيتم تمريرها إلى الـ action هى عبارة عن associative array تتكون من أزواج من key/value يمكن الوصول إليهم من خلال getParams() و setParams() , أو يمكن الوصول إلى كل براميتر على حدى بواسطة getParam() و setParam() .

إعتماداً على نوع الـ request, من الممكن أن يكن هناك methods اخرى متوفرة , الـ request الأساسى الذى يتم إستخدامه Zend_Controller_Request_Http , على سبيل المثال, يوفر methods لإرجاع الـ request URI و معلومات الـ path و بارامترات $_POST و $_GET .. إلخ.

يتم تمرير الـ request object إلى الـ front controller , أو إذا لم يتم توفير واحد , يتم إنشاء واحد فى بداية عملية التنفيذ "dispatch" و قبل أن يحدث الـ routing , و يتم تمريره إلى كل كائن فى سلسلة التنفيذ "dispatch chain".

ايضاً, يكن الـ request object مفيد إلى حد ما فى الـ testing , حيث أنه من الممكن أن يقوم المطور بالتعديل فى قيم الـ request environment بما فيها أسم الـ controller و الـ action و البارمترات و الـ URI ..إلخ, ثم يمرر الـ request object إلى الـ front controller ليختبر كيف سيتعامل برنامجه مع هذه البيانات, و عند جمعه مع إستخدام الـ response object , يصبح من الممكن عمل unit testing لتطبيقات الـ MVC بشكل موسع و دقيق.

7.1.3.  عملية التحويل - routing

قبل أن تتمكن من إنشاء اول controller بنفسك, يجب ان تفهم كيف تعمل عملية ال توجيه "routing" كما هى مطبقة فى Zend_Controller_Router. تذكر أن كيفية العمل مقسمة الى عمل يه توجيه و التى تحدث مرة واحدة , و عملية تنفيذ "dispatching" و التى تحدث بعدها فى حلقة تكرارية.

يقوم Zend_Controller_Front بإستدعاء Zend_Controller_Router (أو أى router أخر متوافر) ليقوم بترجمة محتوى الـ URI و يستخرج اسم الـ controller و اسم الـ action فى هذا الـ controller . يقوم Zend_Controller_Router بجلب الـ URI من الـ request object ثم يقوم بتفكيكه ليعرف أسم كل من الـ controller و الـ action و أى بارمترات URL ممررة فى المسار ثم يقوم بوضع كل هذه البيانات فى الـ request object.

يقوم Zend_Controller_Router بعملية بسيطة ليتعرف على اسم الـ controller و اسم الـ action التابع لهذا الـ controller :

http://framework.zend.com/controller/action/
        

لاحظ بالأعلى ان اول قسم دائما هو اسم الـ controller و أن الق سم التانى دائما يحمل أسم الـ action.

اختيارياً, يمكنك تمرير قيم فى الـ URI و التى سيتم تمريرها بعدها الى الـ controller , و هذا يكون على شكل زوج من key/value :

http://framework.zend.com/controller/action/key1/value1/
        

إذا لم يتم إيجاد إى من الـ controller أو الـ action فى مسار الـ URI, فسيقوم Zend_Controller_Dispatcher بمحاولة جلب هذه القيم من بارامترات الـ request object , و إن لم يتم إيجادهم , فسيستخدم القيم الأفتراضية , و فى كلا الحالتين , القيم الأفتراضية هى "index". و المثال التالى يوضح ذلك:

http://framework.zend.com/roadmap/future/
Controller: roadmap
Action    : future

http://framework.zend.com/roadmap/
Controller: roadmap
Action    : index

http://framework.zend.com/
Controller: index
Action    : index
        

ايضاً, يمكنك تحديد controllers موجودة فى مجلدات فرعية أو modules بعدة طرق :

  • أسماء الـ controllers التى تحتوى شرطة (-) أو شرطة منخفضة (_) يمكن إستخدامها, على سبيل المثال, http://framework.zend.com/admin-roadmap/future من المفترض أن تشير إلى الـ controller المسمى Admin_RoadmapController .

  • عن طريق تفعيل الباراميتر useModules فى الـ front controller , ستتمكن من إستخدام مجلدات فرعية عن طريق URIs افضل, حينها الـ URL الخاص بالمثال السابق سيبدو مثل http://framework.zend.com/admin/roadmap/future. , لعمل ذلك, قم بتفعيل الباراميتر useModules فى الـ front controller أو الـ router :

    $front->setParam('useModules', true);
    // or
    $router->setParam('useModules', true);
    

    هذا سيعمل مع إستخدام سواء أى من الـ router الأساسى أو الـ RewriteRouter.

[ملاحظة] المرونة

إن كنت تريد المزيد من الأمكانيات المرنة, فربما ستود إلقاء نظرة على قسم 7.4.3, “Zend_Controller_Router_Rewrite”.

أسم الـ controller و أسم الـ action فى هذا الـ controller و أى بارامترات اختيارية أخرى (تشمل الـ module), توجد فى الـ request object , و عندما يدخل Zend_Controller_Front دورة التنفيذ "dispatch loop" , سيتم تمرير الـ request object إلى Zend_controller_Dispatcher.

7.1.4. عملية الـ Dispatch

عملية الـ dispatching هى أخذ الـ request object , (الذى يرث Zend_Controller_Request_Abstract), و إستخراج أسم الـ controller و الـ action و أى بارامترات اختيارية موجودة به, ثم إنشاء نسخة من هذا الـ Controller و إستدعاء الـ Action من هذا الـ controller , و فى حالة أنه لم يتم إيجاد قيم لأسماء الـ controller أو الـ action, سيتم إستخدام القيم الأفتراضية الخاصة بهم, و القيمة الأفتراضية التى يحددها Zend_Controller_Dispatcher لهما هى index, لكن يمكن للمطور أن يقوم بتغيير هذا بأستخدام setDefaultController() و setDefaultAction() .

عملية الـ dispatching تحدث فى حلقة تكرارية داخل الـ front controller , و قبل حدوث الـ dispatching , يقوم الـ front controller بعمل route للـ request ليجلب أى قيم حددها المستخدم للـ controller و الـ action و أى بارمترات أخرى, ثم يدخل بعد ذلك فى الـ dispatch loop , لينفذ الـ request.

عند بداية كل دورة , يتم وضع علامة "flag" فى الـ request object توضح أن الـ action قد تم تنفيذه , و إن قام action أو plugin ماقبل/بعد التنفيذ بتعديل قيمة الـ flag, الـ dispatch loop ستستمر و ستحاول تنفيذ الـ request مرة أخرى, و عن طريق تغيير الـ controller و/أو الـ action فى الـ request و إعادة تعديل قيمة الـ dispatch flag , يمكن للمطور أن يُعرِف سلسلة من الـ requests ليتم تنفيذها.

الـ action controller method الذى يتحكم فى التنفيذ هو _forward(); إستدعى هذا الـ method من أى pre/postDispatch() أو action methods, مع توفير أسم controller و action و اختيارياً أى باراميترات إضافية تريد أن يتم إرسالها للـ action الجديد:

public function myAction()
{
    // do some processing...
    // forward to another action, FooController::barAction():
    $this->_forward('foo', 'bar', array('baz' => 'bogus'));
}

7.1.5. الـ Response Object

الـ response object هو الزوج المنطقى للـ request object, و ظيفته هى جمع المحتوى و/أو الـ headers ليتم إخراجهم ككلتة واحدة, و ايضاً, سيقوم الـ front controller بتمرير أى exceptions تم التقاطها إلى الـ response object , و بهذا يسمح للمطور بأن يعالج الـ exceptions بشكل أفضل, و يمكن إلغاء هذا السلوك بإستخدام Zend_Controller_Front::throwExceptions(true):

$front->throwExceptions(true);

حيث أن الـ response object يطبق __toString(), فيمكن أن يتم طباعته مباشرة بدون مشاكل , و هذا يسمح بالأستخدامات التالية :

echo $controller->getResponse();

// or
$response = $controller->getResponse();
echo $response;
        

يجب على المطورين إستخدام الـ response object فى الـ action controllers خاصتهم , فبدلا من طباعة الخرج مباشرة و إرسال الـ headers , قم بتمريرهم إلى الـ response object :

// Within an action controller action:
// Set a header
$this->getResponse()
    ->setHeader('Content-Type', 'text/html')
    ->appendBody($content);

عن طريق عمل هذا, كل الـ headers سيتم إرسالها مرة واحدة , و قبل عرض المحتوى .

يجب أن يحدث exception فى أى تطبيق, لذلك تحقق من الـ exception flag الخاصة بالـ response object بأستخدام isException(), و يمكنك جلب الـ exception بأستخدام getException, ايضاً, من الممكن أن تقوم بإنشاء response objects تقوم بالتحويل إلى error pages أو تقوم بعمل log لرسائل الـ exception أو أن تقوم بعمل formating جميل لرسائل الـ exception (لبيئات التطوير) .. إلخ.

يمكنك جلب الـ response object بعد عملية الـ dispatch() للـ front controller , أو تطلب من الـ front controller أن يرجع الـ response object بدلاً من إرسال الخرج.

// retrieve post-dispatch:
$front->dispatch();
$response = $front->getResponse();
if ($response->isException()) {
    // log, mail, etc...
}

// Or, have the front controller dispatch() process return it
$front->returnResponse(true);
$response = $front->dispatch();

// do some processing...

// finally, echo the response
echo $response;

حسب الأعدادات الأساسية, رسائل الـ exception لا يتم عرضها, هذا السلوك يمكن تغييره بإستدعاء renderException, أو السماح للـ front controller ان يلقى Exceptions كما موضح بالمثال بالأعلى بإستخدام throwExceptions().

$response->renderExceptions(true);
$front->dispatch($request, $response);

// or:
$front->returnResponse(true);
$response = $front->dispatch();
$response->renderExceptions();
echo $response;

// or:
$front->throwExceptions(true);
$front->dispatch();