فصل 7. Zend_Controller
السابق |  | التالي

## فصل 7. Zend_Controller

## 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_RewriteRouter”.

أسم الـ 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();

السابق |  | التالي
6.4. Configuring Zend_Console_Getopt | البداية | 7.2. البداية