آموزش سیمفونی روز پنجم – مسیریابی
اگر روز چهارم را تمام کرده باشید، باید به اصول MVC آشنا شده باشید. پس زمان بیشتری را صرف آن کنید و به گذشته فکر نکنید!
در تمرین دیروز، صفحات jobeet را گسترش دادیم، بنابراین چندین راهکار Symfony را بررسی کردیم. راهکارهایی همچون layout، کمککنندهها و شیارها.
امروز هم با دنیای شگفت انگیز مسیریابی (Routing) در Symfony آشنا میشویم.
URLها
اگر در صفحه اصلی بروی شغلی کلیک کنید، URL آن همانند این است: job/show/if/1 . اگر پیش از این وبسایتی را با PHP توسعه داده باشید، احتمالاْ بیشتر با URLهایی از قبیل job.php?id=1 خو گرفتهاید. Symfony چگونه اینکار را انجام داد؟ چگونه اکشن مبنا برای URL را تعیین میکند؟ امروز پاسخ تمام این سوالات را بررسی میکنیم.
اما ابتدا در رابطه با URLها صحبت میکنیم و اینکه آنها واقعاْ چه هستند. در مفاهیم وب، یک URL معرف یکتا برای وسیلهای در وب میباشد. هنگامی که به URL خاصی میروید، بوسیله همان URL از مرورگر برای واکشی وسیلهای معین سوال میکنید. بنابراین URL رابط میان وبسایت و کاربران است، که باید شامل اطلاعاتی از منبع خود باشد. اما URLهای مرسوم واقعاْ شرح درستی از محتوا نیستند، بلکه ساختار کلی برنامه را نمایش میدهند. کاربران به اینکه وبسایت شما بوسیله PHP توسعه دادهشده و یا … کاری ندارند و از آن گذشته نشان دادن ساختار کاری برنامه به کاربران از لحاظ امنیتی کار اشتباهی میباشد. چرا که کاربران میتوانند URL قسمتهایی که دسترسی محدود دارند را حدس بزنند. البته توسعه دهنده باید این صفحات را با شیوه مناسبی امن کند اما اینگونه شما میتوانید بر امنیت آنها بیفزائید.
URLها در Symfony به اندازهای اهمیت دارند که فریمورکی مختص به مدیریت آنها وجود دارد: فریمورک مسیریابی(routing). مسیر یابی URIهای داخلی و URLهای خارجی را مدیریت میکند. هنگامی که درخواستی وارد میشود، مسیریاب URL را تجزیه کرده و به یک URI داخلی تبدیل میکند.
شما قبلاْ URI داخلی صفحات job را در قالب showSuccess.php دیدهاید:
'job/show?id='.$job->getId()
کمککننده url_for() این URI داخلی را به یک URL صحیح تبدیل میکند:
/job/show/id/1
URIهای داخلی از چندین بخش تشکیل شدهاند: job یک ماژول است، show یک اکشن است و رشته کوئری (query string) پارامترهایی را برای اجرای اکشن اضافه میکند. طرح کلی برای URLها اینگونه است:
MODULE/ACTION?key=value&key_1=value_1&...
از آنجایی که Symfony دو راه برای پردازش دارد، میتوانید URLها را بدون انجام کار تکنیکی تغییر دهید. این یکی از مزایای اصلی الگوی طراحی front-controller است.
پیکرهبندی مسیریابی
مسیریابی بین URIها و URLها در فایل پیکرهبندی routing.yml صورت میگیرد:
# apps/frontend/config/routing.yml homepage: url: / param: { module: default, action: index } default_index: url: /:module param: { action: index } default: url: /:module/:action/*
این فایل مسیر یابی را شرح میدهد. مسیر یک نام (homepage)، یک الگو (/:module/:action/*) و مقداری پارامتر دارد (کلید param). هنگامی که درخواستی وارد میشود، مسیریاب سعی میکند تا URL را با یک الگو تطبیق دهد. اولین مسیر مصابقت دادهشده انتخاب میشود، بنابراین ترتیب در routing.yml مهم است. به مثالی توجه کنید تا درک بهتری از مسیر یابی داشته باشید.
هنگامی که شما صفحهاصلی jobeet را درخواست میکنید، که URL آن /job است، اولین مسیری که مطابقت دارد همان default_index است. در یک الگو، کلماتی که با پیشوند (:) میآیند متغیر هستند. بنابراین الگوی /:module به این معنی است که / را با هرچیزی به دنبال آن مطابقت دهد. در مثال ما، متغیر module مقدار job را دارد. این مقدار با کد زیر میتواند در اکشن بازیابی شود:
$request->getParameter('module')
همچنین این مسیریابی مقداری پیشفرض برای متغیر اکشن تعیین میکند. پس برای تمام URLهایی که با این مسیریاب مطابقت دارند، درخواست میتواند یک پارامتر action با مقدار index داشته باشد.
اگر شما درخواست صفحه job/show/id/1 را بدهید، Symfony آن را با الگوی بعدی مطابقت میدهد: :module/:action/* . در یک الگو (*) با مجموعهای از متغیر/مقدار که بوسیله / از هم جدا شدهاند مطابقت دارد.
| Request parameter | Value |
|---|---|
| module | job |
| action | show |
| id | 1 |
یادداشت: متغیرهای module و action خاص هستند جون بوسیله Symfony برای تصمیم در رابطه با اجرای اکشن استفاده میشوند.
URL (آدرس) job/show/id/1 میتواند با استفاده از فراخوانی کمککننده url_for() برای یک قالب ساخته شود:
url_for('job/show?id='.$job->getId())
همچنین شما میتوانید نام مسیرتان را با پیشوند @ مشخص کنید:
url_for('@default?module=job&action=show&id='.$job->getId())
هر دو فراخوانی با هم مطابقت میکنند. اما دومی سریعتر است، زیرا مجبور به آزمایش تمامی مسیریابها برای مطابقت بهتر نیست و برای پیادهسازی کمتر تلاش میکند. (نام ماژول و اکشن در URI داخلی حاضر نیستند).
سازش مسیر – Route Customazation
در حالحاضر، هنگامی که درخواست URL اسلش (/) را میدهید با صفحه خوشآمد سیمفونی مواجه میشوید. دلیل آن مطابقت URL با مسیر homepage است. اما میشود آن را به نفع خود تغییر داد. برای اینکار، متغیر module مسیر homepage را به job تغییر میدهیم.
# apps/frontend/config/routing.yml homepage: url: / param: { module: job, action: index }
حال میتوانیم پیوند موجود در لوگوی jobeet را در layout با استفاده از مسیر homepage تغییر دهیم.
<!-- apps/frontend/templates/layout.php --> <h1> <a href="<?php echo url_for('@homepage') ?>"> <img src="/images/jobeet.gif" alt="Jobeet Job Board" /> </a> </h1>
خیلی راحت بود. به عنوان یک کار پیچیدهتر، تغییراتی را برای job ایجاد کنید تا URL پر معنیتری داشته باشد:
/job/rayan-pardaz/gorgan-iran/1/web-developer
بدون دانستن هیچ اطلاعی راجع به jobeet و بدون حتی نگاه کردن به صفحه، میتوان فهمید که شرکت رایانپرداز بدنبال یک توسعه دهنده وب برای کار کردن در گرگان-ایران میباشد!
یادداشت: برای رساندن اطلاعات به کاربران URLها بسیار مهم هستند. برای مثال زمانی که URL را در پست الکترونیکی کپی میکنید و یا وبسایت را برای موتورهای جستجو بهینه میکنید.
طرح زیر با اینچنین URLهایی مطابقت دارد:
/job/:company/:location/:id/:position
فایل routing.yml را ویرایش کرده و مسیر job_show_user را در ابتدای آن اضافه کنید:
job_show_user: url: /job/:company/:location/:id/:position param: { module: job, action: show }
اگر صفحه اصلی jobeet را refresh کنید، لینک مضاغل تغییری نکرده است و بدین خاطر است که برای ایجاد مسیر، شما باید تمامی متغیرهای لازم را عبور دهید. پس فراخوانی url_for را در فایل indexSuccess.php تغییر دهید.
url_for('job/show?id='.$job->getId().'&company='.$job->getCompany(). '&location='.$job->getLocation().'&position='.$job->getPosition())
همچنین یک URI داخلی میتواند بیانگر یک آرایه باشد.
url_for(array( 'module' => 'job', 'action' => 'show', 'id' => $job->getId(), 'company' => $job->getCompany(), 'location' => $job->getLocation(), 'position' => $job->getPosition(), ))
احتیاجات
روز اول آموزش درباره معتبرسازی و مدیریت خطاها برای دلایل درست بحث کردیم. سیستم مسیریابی در ویژگیهای معتبر سازی ایجاد شده است. هر متغیر الگو میتواند بوسیله یک عبارت تعیین شده که بوسیله requirements تعریف شده است، اعتبار سنجی شود:
job_show_user: url: /job/:company/:location/:id/:position param: { module: job, action: show } requirements: id: \d+
requirements ذکر شده در بالا، عددی بودن id را ضروری میکند و در غیر این صورت مسیر تطبیق داده نمیشود.
کلاس مسیر
در حقیقت هر یک از تعاریف موجود در فایل routing.yml به یک شیء از کلاس sfroute تبدیل میشود. این کلاسها میتوانند بوسیله تعریف یک مشخصه کلاس در تعاریف مسیر تغییر کنند. اگر شما با پروتوکل http آشنا باشید، میدانید که متدهای زیادی همچون GET، POST، HEA D، DELETE و PUT را تعریف میکند. سهتای اول در تمام مرورگرها پشتیبانی میشوند و دوتای دیگر نه.
برای محدود کردن تطابق مسیر به برخی از متدهای درخواست، میتوانید کلاس مسیر را به sfRequestRoute تغییر داده و الزامی را برای متغیر مجازی sf_method اضافه کنید:
job_show_user: url: /job/:company/:location/:id/:position class: sfRequestRoute param: { module: job, action: show } requirements: id: \d+ sf_method: [get]
یادداشت: ملزوم کردن مسیر برای مطابقت با برخی از متدهای http کاملاْ با استفاده ازsfWebRequest::isMethod() در اکشن برابری نمیکند، به این دلیل که مسیریابی در صورت عدم تطابق به دنبال مسیر دیگری برای تطابق میگردد.
کلاس شیء مسیر – Object Route Class
URI جدید یک شغل واقعاْ طولانی و نوشتن ان سخت است (url_for(’job/show?id=’.$job->getId().’&company=’.$job->getCompany().’&location=’.$job->getLocation().’&position=’.$job->getPosition())) اما در قسمت قبل یاد گرفتیم که کلاس مسیر میتواند تغییر کند. برای مسیر job_show_user بهتر است که از sfPropelRoute استفاده کنیم. چون این کلاس برای مسیرهایی که اشیاء propel یا کلکسیونی از آنها را نمایش میدهند بهینه شده است.
job_show_user: url: /job/:company/:location/:id/:position class: sfPropelRoute options: { model: JobeetJob, type: object } param: { module: job, action: show } requirements: id: \d+ sf_method: [get]
مقدار مشخص شده برای options رفتار مسیر را بهینه میکند. در اینجا خصیصه model کلاس مدل کلاس مدل پروپل (JobeetJob) وابسته به مسیر را مشخص میکند، و خصیصه type هم تعیین میکند که این مسیر باید یک شیء را نشان دهد. (همچنین برای نمایشش کلکسیونی از اشیاء میتوان از list استفاده کرد)
حالا مسیر job_show_user متوجه شده است که با JobeetJob رابطه دارد و بنابراین براحتی با url_for فراخوانی میشود:
url_for(array('sf_route' => 'job_show_user', 'sf_subject' => $job))
و یا تنها:
url_for('job_show_user', $job)
یادداشت: مثال اول برای زمانی که احتیاج به عبور آرگومنتهای زیادی برای یک شیء است میتواند بسیار سودمند باشد.
چون تمام متغیرها در این مسیر یک مقدار نظیر به نظیر قابل دستیابی در کلاس JobeetJob دارند، بخوبی کار میکند. (برای مثال، متغیر company با مقدار getCompany() جایگزین میشود)
اگر به URLهای ساخته شده نگاه کنید، خواهید دید که کاملا به چیزی که میخواهیم تبدیل نشده است.
http://jobeet.localhost/frontend_dev.php/job/Sensio+Labs/Paris%2C+France/1/Web+Developerنیاز داریم تا مقادیر ستونها را بصورت slug در آوریم. فایل JobeetJob را باط کرده و متد زیر را به آن اضافه کنید:
// lib/model/JobeetJob.php public function getCompanySlug() { return Jobeet::slugify($this->getCompany()); } public function getPositionSlug() { return Jobeet::slugify($this->getPosition()); } public function getLocationSlug() { return Jobeet::slugify($this->getLocation()); }
سپس فایل lib/Jobeet.class.php را ایجاد کرده و متد slugify را در آن اضافه کنید:
// lib/Jobeet.class.php class Jobeet { static public function slugify($text) { // replace all non letters or digits by - $text = preg_replace('/\W+/', '-', $text); // trim and lowercase $text = strtolower(trim($text, '-')); return $text; } }
حالا سه تا دسترسی مجازی داریم:
getCompanySlug()، getPositionSlug() و getLocationSlug(). اینها مقادیر این سه ستون را در قالب متد slugify برمیگردانند. حالا میتوانید نام اصلی ستونها را با مشابه slug شده انها در مسیر job_show_user تغییر دهید:
job_show_user: url: /job/:company_slug/:location_slug/:id/:position_slug class: sfPropelRoute options: { model: JobeetJob, type: object } param: { module: job, action: show } requirements: id: \d+ sf_method: [get]
از آنجایی که کلاسی به سیستم اضافه شده است، قبل از refresh کردن صفحه اصلی لازم است تا cach را پاک کنید.
$ php symfony cc
حالا میتوانید URLها را اینگونه مشاهده کنید:
http://jobeet.localhost/frontend_dev.php/job/sensio-labs/paris-france/1/web-developerاما این تنها نصف داستان است. مسیر قابلیت آن را دارد که بر مبنای یک شیء ایجاد شود، اما در عین حال قابلیت پیدا کردن شیء مرتبط به URL معین را هم دارد. شیء مرتبط میتواند با متد getObject() از شیء مسیر بازیافت شود. هنگامی که یک درخواست ورودی تجزیه میشود ذخائر مسیریابی شیء مسیر را برای استفاده شیء در اکشنها تطبیق میدهند. بنابراین متد executeShow() را برای استفاده شیء مسیر برای بازگرداندن شیء jobeet تغییر دهید:
class jobActions extends sfActions { public function executeShow(sfWebRequest $request) { $this->job = $this->getRoute()->getObject(); $this->forward404Unless($this->job); } // ... }
اگر سعی کنید شغلی را با یک id ناشناخته دریافت کنید، صفحه ۴۰۴ را با اخطاری متفاوت میبینید:
این بدین خاطر است که اخطار ۴۰۴ بطور خودکار و بوسیله متد getRoute() پیش آمده. بنابراین میشود متد executeShow را سادهتر کرد:
class jobActions extends sfActions { public function executeShow(sfWebRequest $request) { $this->job = $this->getRoute()->getObject(); } // ... }
نکته: اگر نمیخواهید مسیر به صفحه ۴۰۴ منتقل شود، میتوانید مقدار allow_empty را به true تغییر دهید.
یادداشت: اشیا مرتبط یک مسیر بسیار تنبل هستند. و تنها در صورتی که شما متد getRoute() را فراخوانی کرده باشید، از پایگاه داده بازیافت میشوند.
مسیریابی در اکشنها و قالبها
در قالب، کمک کننده url_for آدرسهای داخلی را به URLهای خارجی تبدیل میکند. برخی از کمککنندههای دیگر سیمفونی URLهای داخلی را در قالب آرگومنت میگیرند، همچون کمککننده link_to که یک تگ a ایجاد میکند:
<?php echo link_to($job->getPosition(), 'job_show_user', $job) ?>
خروجی این کد در قالب HTML اینچنین است:
<a href="/job/sensio-labs/paris-france/1/web-developer">Web Developer</a>
هر دوی این کمککنندهها میتوانند URLهای مطلق ایجاد کنند:
url_for('job_show_user', $job, true); link_to($job->getPosition(), 'job_show_user', $job, true);
اگر میخواهید یک URL از یک اکشن ایجاد کنید، میتوانید از متد generateUrl() استفاده کنید:
$this->redirect($this->generateUrl('job_show_user', $job));
خانواده متدهای redirect
در قسمت قبل این آموزش، در مورد متدهای forward صحبت کردیم. این متدها درخواست جاری را بدون رفت و برگشت مرورگر به یک اکشن دیگر ارسال میکرد.
متدهای redirect کاربر را به یک URL دیگر هدایت میکند. همچون forward میتوانید از متد redirect()و یا متد های میانبر redirectIf() و redirectUnless() استفاده کنید.
مجموعه کلاس های مسیر
قبلاْ برای ماژول job، مسیر اکشن show را بهینه کردیم. اما URL سایر متدها ( همچون index و new و …) بوسیله مسیر پیشفرض مدیریت میشوند:
default: url: /:module/:action/*
مسیر default راه خوبی بای شروع کدنویسی بدون مشخص کردن تعداد زیادی مسیر میباشد. اما چون مسیر به یک catch-all اثر میکند، نمیتواند برای نیازهای خاص ما پیکرهبندی شود.
ار آنجایی که تمام اکشنهای job با کلاس مدل JobeetJob مرتبط هستند، همانطور که قبلاْ برای اکشن show اینکار را انجام دادیم میتوانیم براحتی یک مسیر دستی sfPropelRoute برای هر یک مشخص کنیم. اما همچون ماژول job مشخص کردن هفت اکشن کلاسیک برای یک مدل سخت است، گذشته از این میشود از کلاس sfPropelRouteCollection استفاده کرد. فایل routing.yml را باز کرده و تغییرات زیر را در ان اعمال کنید:
# apps/frontend/config/routing.yml job: class: sfPropelRouteCollection options: { model: JobeetJob } job_show_user: url: /job/:company_slug/:location_slug/:id/:position_slug class: sfPropelRoute options: { model: JobeetJob, type: object } param: { module: job, action: show } requirements: id: \d+ sf_method: [get] # default rules homepage: url: / param: { module: job, action: index } default_index: url: /:module param: { action: index } default: url: /:module/:action/*
مسیر job در بالا میتواند یک میانبر به هفت مسیر زیر را بطور خودکار ایجاد کند:
job: url: /job.:sf_format class: sfPropelRoute options: { model: JobeetJob, type: list } param: { module: job, action: index, sf_format: html } requirements: { sf_method: get } job_new: url: /job/new.:sf_format class: sfPropelRoute options: { model: JobeetJob, type: object } param: { module: job, action: new, sf_format: html } requirements: { sf_method: get } job_create: url: /job.:sf_format class: sfPropelRoute options: { model: JobeetJob, type: object } param: { module: job, action: create, sf_format: html } requirements: { sf_method: post } job_edit: url: /job/:id/edit.:sf_format class: sfPropelRoute options: { model: JobeetJob, type: object } param: { module: job, action: edit, sf_format: html } requirements: { sf_method: get } job_update: url: /job/:id.:sf_format class: sfPropelRoute options: { model: JobeetJob, type: object } param: { module: job, action: update, sf_format: html } requirements: { sf_method: put } job_delete: url: /job/:id.:sf_format class: sfPropelRoute options: { model: JobeetJob, type: object } param: { module: job, action: delete, sf_format: html } requirements: { sf_method: delete } job_show: url: /job/:id.:sf_format class: sfPropelRoute options: { model: JobeetJob, type: object } param: { module: job, action: show, sf_format: html } requirements: { sf_method: get }
یادداشت: برخی از مسیرهایی که بوسیله sfPropelRouteCollection ایجاد شدهاند، URLهای یکسان دارند. مسیریاب باز هم قابلیت استفاده از انها را دارد زیرا هرکدام نیاازمند یک متد خاص http هستند.
مسیرهای job_delete و job_update متدهایی را نیاز دارند که توسط مرورگر پشتیبانی نمیشود، اما به این دلیل که Symfony آن را شبیهسازی میکند بدرستی کار میکنند. قالب _form.php را باز کنید تا یک مثال را مشاهده کنید:
// apps/frontend/modules/job/templates/_form.php <form action="..." ...> <?php if (!$form->getObject()->isNew()): ?> <input type="hidden" name="sf_method" value="PUT" /> <?php endif; ?> <?php echo link_to( 'Delete', 'job/delete?id='.$form->getObject()->getId(), array('method' => 'delete', 'confirm' => 'Are you sure?') ) ?>
تمام کمککنندههای symfony میتوانند شبیهسازی یک هر متد http را که باید به یک پارامتر خاص sf_method عبور دادهشوند را بیان کنند.
یادداشت: سیمفونی پارامترهای خاص دیگری را همچون sf_method را هم دارد که همه با پیشوند sf_ شروع میشوند.
در مسیرهای ساخته شده در بالا میتوانید بقیه را مشاهده کنید، sf_format که هر یک در آینده توضیح داده خواهند شد.
اشکالزدایی مسیر
هنگامی که از مجموعهی مسیر استفاده میکنید، بد نیست تا بعضی اوقات مسیرهای ساخته شده را لیست کنیم. خروجی دستور app:routes تمام مسیرها را برای application دریافتی میدهد:
$ php symfony app:routes frontend
همچنین میشود اطلاعات زیادی را برای اشکالزدایی مسیر بوسیله رد کردن نام آن در قالب یک آرگومنت داشته باشید:
$ php symfony app:routes frontend job_edit
مسیرهای پیشفرض
مشخص کردن مسیرهای مختلف برای تمامی URLها تمرین خوبی بود، همچون مسیر job که تمامی مسیرهای لازم برای شرح دادن jobeet را مشخص میکرد.
مسیرهای پیشفرض در فایل routing.yml را پاک کرده یا کامنت کنید.
# apps/frontend/config/routing.yml #default_index: # url: /:module # param: { action: index } # #default: # url: /:module/:action/*
برنامه همچون قبل بخوبی کار میکند.
فردا میبینمتون
امروز اطلاعات جدید زیادی را دریافت کردیم. یاد گرفتیم که چگونه از فریمورک مسیریابی symfony استفاده کنیم و چگونه بوسیله آن URLها را از ساختار تکنیکی برنامه جدا کنیم.
فردا مفاهیم جدیدی را معرفی میکنیم.
پن – شما لطفاْ در صورت برخوردن با هر مشکلی در زمینه این آموزش شرح کامل رو اینجا بگذارید تا شاید بکار سایر کاربرا هم بیاد. ممنون




فرید در تاریخ 2 ژوئن , 2009 @ 11:39 ق.ظ
ایولا مهدی خان
خوشم میاد پشت کار داری
یه خورده هم به ما قرض بده
به امید ادامه این کاره قشنگت و موفقیت روز افزونت