سابقا في Jobeet
أمس كان يوما عظيما . هل تعلم كيفية إنشاء عناوين جميلة ، وكيفية استخدام الإطار symfony في جعل الكثير من الامور بالنسبة لك تعمل أتوماتيكيا .
اليوم, نحن سوف نحسن موقع Jobeet من خلال التغيير والتبديل في الرمز هنا وهناك . عمليا , ستتعلم المزيد حول كافة الميزات لدينا أكثر مما عرض خلال الأيام الخمسة الأولى من هذا البرنامج التعليمي .
The Propel Criteria Object
من احتياجات يوم 2 :
"عندما يدخل المستخدم على موقع Jobeet, يرى قائمة الوظائف النشطة"
ولكن من الآن, يتم عرض جميع الوظائف , سواء كانت نشطة أم لا:
class jobActions extends sfActions { public function executeIndex(sfWebRequest $request) { $this->jobs = JobeetJobPeer::doSelect(new Criteria()); } // ... }
الوظيفة النشيطة هي التي نشرت أقل من 30 يوم . الدالة ()doSelect تستخدم عنصر Criteria لوصف قاعدة البيانات لتنفيذ الطلب .
في أعلى هذا الرمز , تمر Criteria إذا كانت فارغة, يعني أن كل السجلات تم استرجاعها من قاعدة البيانات.
دعونا نغير و نقم باختيار الوظائف النشيطة فقط :
public function executeIndex(sfWebRequest $request) { $criteria = new Criteria(); $criteria->add(JobeetJobPeer::CREATED_AT, time() - 86400 * 30, Criteria::GREATER_THAN); $this->jobs = JobeetJobPeer::doSelect($criteria); }
إضافة الدالة ()Criteria::add ل WHERE شرط لتوليد SQL. هنا, نحن نقيد criteria على إختيار فقط الوظائف التي لم يتجاوز عمرها 30 يوما .
الدالة ()add تقبل الكثير من المقارنة بين مختلف المعايير ; هنا الأكثر شيوعا :
EQUAL::CriteriaNOT_EQUAL::CriteriaGREATER_EQUAL::Criteria,GREATER_THAN::CriteriaLESS_EQUAL::Criteria,LESS_THAN::CriteriaNOT_LIKE::Criteria,LIKE::CriteriaCUSTOM::CriteriaNOT_IN::Criteria,IN::CriteriaISNOTNULL::Criteria,ISNULL::CriteriaCURRENT_TIMESTAMP::Criteria,CURRENT_TIME::CriteriaCURRENT_DATE::Criteria
تصحيح أخطاء Propel وليدت SQL
بما أنك لا تكتب باليد بيانات SQL, سيتولى Propel الاختلافات بين محركات قواعد البيانات و اختيار توليد بيانات SQL الأمثل لمحرك قاعدة البيانات خلال 3 أيام .
ولكن في بعض الأحيان , يمثل عونا كبيرا لرؤية SQL التي تولدها Propel; على سبيل المثال , لتصحيح إستعلام إذا كان لا يعمل كما هو متوقع .
في المحيط dev, يسجل symfony الاستعلامات (مع الكثير ) في المجلد /log. هناك ملف الدخول واحد لكل مزيج من التطبيق و المحيط . الملف الذي نتطلع اليه هو بإسم frontend_dev.log:
# log/frontend_dev.log
Dec 6 15:47:12 symfony [debug] {sfPropelLogger} exec: SET NAMES 'utf8'
Dec 6 15:47:12 symfony [debug] {sfPropelLogger} prepare: SELECT jobeet_job.ID, jobeet_job.CATEGORY_ID, jobeet_job.TYPE, jobeet_job.COMPANY, jobeet_job.LOGO, jobeet_job.URL, jobeet_job.POSITION, jobeet_job.LOCATION, jobeet_job.DESCRIPTION, jobeet_job.HOW_TO_APPLY, jobeet_job.TOKEN, jobeet_job.IS_PUBLIC, jobeet_job.CREATED_AT, jobeet_job.UPDATED_AT FROM `jobeet_job` WHERE jobeet_job.CREATED_AT>:p1
Dec 6 15:47:12 symfony [debug] {sfPropelLogger} Binding '2008-11-06 15:47:12' at position :p1 w/ PDO type PDO::PARAM_STR
يمكنك أن ترى بنفسك أن Propel ولد شرطا على العمود created_at
(WHERE jobeet_job.CREATED_AT > :p1).
note
السلسلة :p1 في الاستعلام تشير إلى أن Propel يولد إعدادات البيانات .
القيمة الفعلية ل :p1
('2008-11-06 15:47:12' في المثال أعلاه )
تنتقل أثناء تنفيذ الاستعلام بشكل صحيح إنطلاقا من محرك البحث. استخدام البيانات المعدة يقلل بشكل كبير من تعرض
SQL injection الخاص بك للهجمات .
وهذا جيد , لكن الامر مزعج بعض الشيء للتبديل بين المتصفح ,the IDE , و ملف التسجيل في كل مرة كنت بحاجة لاختبار تغيير ما .
يرجع الفضل في ذلك إلى شريط أدوات تصحيح الويب symfony, كل المعلومات التي تحتاجها متوفرة ويدخل هذا كله ضمن راحة متصفحك :

تسلسل العناصر
حتى لو كانت تعمل على الرمز, هو أبعد ما يكون عن الكمال لأنها لا تأخذ في الحسبان بعض الاحتياجات من يوم 2.
"يمكن للمستخدم إعادة تنشيط أو تمديد صلاحية الاعلان عن الوظائف ل 30 يوما إضافيا ..."
ولكن كما ذكرنا أعلاه يعتمد الرمز على قيمة created_at, و بما أن هذا العمود يخزن تاريخ الإنشاء فإننا لا نستطيع تلبية الاحتياجات المذكورة أعلاه .
ولكن إذا كنت تتذكر مخطط قاعدة البيانات الذي قمنا بوصفه خلال الأيام الثلاثة السابقة , علينا أيضا أن نحدد العمود expires_at. حاليا هذه القيمة فارغة دائما على اعتبار أنها ليست لها مهمة أساسية في بيانات fixture. ولكن عندما يتم إنشاء وظيفة, يأخذ تلقائيا 30 يوم بعد التاريخ الحالي .
عندما تحتاج لعمل شيء تلقائيا قبل أن يتسلسل عنصر Propel مع قاعدة البيانات , يمكنك تفادي الدالة ()save لنموذج العنصر :
// lib/model/JobeetJob.php class JobeetJob extends BaseJobeetJob { public function save(PropelPDO $con = null) { if ($this->isNew() && !$this->getExpiresAt()) { $now = $this->getCreatedAt() ? $this->getCreatedAt('U') : time(); $this->setExpiresAt($now + 86400 * 30); } return parent::save($con); } // ... }
الدالة ()isNew تعطي النتيجة true عندما يكون العنصر متسلسل مع قاعدة لبيانات و false غير ذلك .
والآن , دعونا نغير في action استخدام العمود expires_at بدلا من العمود created_at لاختيار الوظائف النشطة :
public function executeIndex(sfWebRequest $request) { $criteria = new Criteria(); $criteria->add(JobeetJobPeer::EXPIRES_AT, time(), Criteria::GREATER_THAN); $this->jobeet_job_list = JobeetJobPeer::doSelect($criteria); }
نحن نقيد الاستعلامات في إختيار فقط الوظائف مع تاريخ expires_at في المستقبل .
المزيد مع Fixtures
تحديث الصفحة Jobeet في متصفحك لن يغير شيئا لأن الوظيفة في قاعدة البيانات التي تم نشرها قبل بضعة أيام فقط . دعونا نغيير fixtures لاضافة وظيفة منتهية بالفعل :
# data/fixtures/020_jobs.yml
JobeetJob:
# other jobs
expired_job:
category_id: programming
company: Sensio Labs
position: Web Developer
location: Paris, France
description: Lorem ipsum dolor sit amet, consectetur adipisicing elit.
how_to_apply: Send your resume to lorem.ipsum [at] dolor.sit
is_public: true
is_activated: true
created_at: 2005-12-01
token: job_expired
email: job@example.com
note
كن حذرا عند نسخ ولصق الرمز في ملف fixture لعدم كسر تسنن . يجب أن تترك مساحتين قبل expired_job.
كما ترون في الوظيفة أضفناها لدينا في ملف fixture, يمكن تعريف قيمة العمود created_at حتى لو كان تشغيلها تلقائيا من طرف Propel. وقد حددت القيمة القصوى لواحد تلقائيا.
أعد تحميل fixture أعد تحديث متصفحك للتأكد من أن الوظائف القديمة لا تظهر :
$ php symfony propel:data-load
يمكنك أيضا تنفيذ الاستعلام الاتي للتأكد من أن العمود expires_at يعمل تلقائيا بواسطة الدالة ()save, معتمدا على قيمة العمود created_at:
SELECT `position`, `created_at`, `expires_at` FROM `jobeet_job`;
عوامل التهيئة
في الدالة ()JobeetJob::save, من الصعب علينا تشفير عدد أيام الوظيفة التي إنتهت مدة صلاحيتها . كان من الأفضل أن نستطيع التحكم في 30 يوما
يوفر الإطار symfony يوفر ملف تهيئة مدمج في التطبيق بإعدادات محددة , ملف app.yml. هذا الملف YAML يمكن أن يحتوي على جميع الإعدادات التي تريدها :
# apps/frontend/config/app.yml all: active_days: 30
في التطبيق , نحصل على هذه الإعدادات من خلال النموذج الجامع sfConfig:
sfConfig::get('app_active_days')
نبدأ اسم الإعداد ب app_ لأن النموذج sfConfig يمكنكم إيضا من الوصول إلى إعدادات symfony كما سنرى لاحقا .
دعونا نقوم بتحديث الرمز بالأخذ بعين الاعتبار هذا الإعداد الجديد:
public function save(PropelPDO $con = null) { if ($this->isNew() && !$this->getExpiresAt()) { $now = $this->getCreatedAt() ? $this->getCreatedAt('U') : time(); $this->setExpiresAt($now + 86400 * sfConfig::get('app_active_days')); } return parent::save($con); }
ملف التهيئة app.yml طريقة متميزة لتجميع جميع إعدادات تطبيقك.
Refactoring
بالرغم من أننا نتوفر على رمز مكتوب بشكل جيد , هو ليس صحيحا بعد إلى حدّ بعيد . هل يمكنك حل المشكلة ؟
رمز Criteria لا ينتمي إلى action ( طبقة المراقبة ), انه ينتمي الى طبقة النموذج .
في النموذج MVC يعرف النموذج منطق جميع الأعمال , و المراقب هو الوحيد الذي ينادي النموذج لاسترجاع البيانات منه . نحصل من الرمز كنتيجة على مجموعة من الوظائف , دعنا ننتقل من رمز الصنف JobeetJobPeer و أنشأ الدالة ()getActiveJobs:
// lib/model/JobeetJobPeer.php class JobeetJobPeer extends BaseJobeetJobPeer { static public function getActiveJobs() { $criteria = new Criteria(); $criteria->add(self::EXPIRES_AT, time(), Criteria::GREATER_THAN); return self::doSelect($criteria); } }
الآن يمكن استخدام هذه الدالة في رمز action لاستعادة الوظائف النشطة.
public function executeIndex(sfWebRequest $request) { $this->jobeet_job_list = JobeetJobPeer::getActiveJobs(); }
بعض فوائد refactoring على الرمز السابق :
- المنطق أن نحصل على الوظائف النشطة الآن من النموذج حيث تنتسب .
- يكون الرمز في المراقب مقروءا أكثر .
- الدالة
()getActiveJobsصالحة لإعادة الاستعمال ( على سبيل المثال في مكان آخر ). - الرمز النموذجيي الآن وحدة قابلة للاختبار .
لنقم بتصنيف الوظائف حسب قيمة العمود expires_at:
static public function getActiveJobs() { $criteria = new Criteria(); $criteria->add(self::EXPIRES_AT, time(), Criteria::GREATER_THAN); $criteria->addDescendingOrderByColumn(self::EXPIRES_AT); return self::doSelect($criteria); }
الدالة ()addDescendingOrderByColumn تضيف الشرط ORDER BY لل SQL المولد
( وتوجد أيضا ()addAscendingOrderByColumn )
الفئات على الصفحة الرئيسية
المتطلبات من اليوم2 :
" تصنف الوظائف بالفئة ثم تاريخ الإعلان عنها ( الوظائف الأحدث في المقدمة ) "
حتى الآن, لم نأخذ بعين الإعتبار مهمة الفئة . من الاحتياجات , يجب على الصفحة الرئيسية عرض الوظائف حسب الفئة . أولا , نحن بحاجة للحصول على كل الفئات على الأقل مع وظيفة مفعلة .
إفتح الصنف JobeetCategoryPeer و أضف الدالة ()getWithJobs:
[php]
// lib/model/JobeetCategoryPeer.php
class JobeetCategoryPeer extends BaseJobeetCategoryPeer
{
static public function getWithJobs()
{
$criteria = new Criteria();
$criteria->addJoin(self::ID, JobeetJobPeer::CATEGORY_ID);
$criteria->add(JobeetJobPeer::EXPIRES_AT, time(), Criteria::GREATER_THAN);
$criteria->setDistinct();
return self::doSelect($criteria); } }
الدالة ()Criteria::addJoin تضيف الشرط JOIN لل SQL المولد.
افتراضيا, الانضمام شرط يضاف إلى الشرط WHERE. يمكنك أيضا تغيير عامل الانضمام بإضافة الحجج الثالثة
(Criteria::LEFT_JOIN,Criteria::RIGHT_JOIN, and Criteria::INNER_JOIN).
نغير action index وفقا لذلك:
// apps/frontend/modules/job/actions/actions.class.php public function executeIndex(sfWebRequest $request) { $this->categories = JobeetCategoryPeer::getWithJobs(); }
في القالب , نحن بحاجة إلى تكرار جميع الفئات خلاله وعرض الوظائف النشطة:
// apps/frontend/modules/job/indexSuccess.php <?php use_stylesheet('jobs.css') ?> <div id="jobs"> <?php foreach ($categories as $category): ?> <div class="category_<?php echo Jobeet::slugify($category->getName()) ?>"> <div class="category"> <div class="feed"> <a href="">Feed</a> </div> <h1><?php echo $category ?></h1> </div> <table class="jobs"> <?php foreach ($category->getActiveJobs() as $i => $job): ?> <tr class="<?php echo fmod($i, 2) ? 'even' : 'odd' ?>"> <td class="location"><?php echo $job->getLocation() ?></td> <td class="position"><?php echo link_to($job->getPosition(), 'job_show_user', $job) ?></td> <td class="company"><?php echo $job->getCompany() ?></td> </tr> <?php endforeach; ?> </table> </div> <?php endforeach; ?> </div>
note
لعرض اسم الفئة في القالب قد استخدمنا echo $category.
هل هذا الصوت غريب؟ category$هو عنصر, كيف يمكن ل echo السحرية عرض اسم الفئة؟
كان الرد خلال اليوم 3 عندما عرفنا سحر الدالة ()toString__ لجميع نماذج الأصناف .
لهذا العمل ، نحن بحاجة إلى إضافة الدالة ()getActiveJobs إلى الصنف JobeetCategory و هذا يعطي كنتيجة الوظائف النشطة لعنصر هذه الفئة:
// lib/model/JobeetCategory.php public function getActiveJobs() { $criteria = new Criteria(); $criteria->add(JobeetJobPeer::CATEGORY_ID, $this->getId()); return JobeetJobPeer::getActiveJobs($criteria); }
في مناداة ()add, أغفلنا العوامل الثلاث حسب القيمة الإفتراضية ل Criteria::EQUAL.
الدالة ()JobeetCategory::getActiveJobs تستخدم الدالة ()JobeetJobPeer::getActiveJobs لاستعادة الوظائف النشطة لفئة معينة .
عندما ننادي ()JobeetJobPeer::getActiveJobs, نريد أن نتقيد بشرط أكثر حتى من الفئة المقدمة . بدلا من المرور على العنصر الفئة قررنا المرور بالعنصر Criteria و هذه هي أفضل طريقة لنضمن الشرط العام.
تحتاج ()getActiveJobs لدمج عوامل Criteria مع معاييره الخاصة. كما أن Criteria هو عنصر , وهذا بسيط جدا :
// lib/model/JobeetJobPeer.php static public function getActiveJobs(Criteria $criteria = null) { if (is_null($criteria)) { $criteria = new Criteria(); } $criteria->add(JobeetJobPeer::EXPIRES_AT, time(), Criteria::GREATER_THAN); $criteria->addDescendingOrderByColumn(self::EXPIRES_AT); return self::doSelect($criteria); }
تحديد النتائج
لا يزال هناك شرط واحد لتنفيذ في الصفحة الرئيسية لائحة الوظائف :
" بالنسبة لكل فئة ، لا يظهر في القائمة سوى أول 10 وظيفة و وصلة تسمح ببيان جميع الوظائف لفئة معينة "
هذا بسيط جدا نضيف إلى الدالة ()getActiveJobs:
// lib/model/JobeetCategory.php public function getActiveJobs($max = 10) { $criteria = new Criteria(); $criteria->add(JobeetJobPeer::CATEGORY_ID, $this->getId()); $criteria->setLimit($max); return JobeetJobPeer::getActiveJobs($criteria); }
مناسبة الشرط LIMIT الآن صعبة التشفير إلى نموذج ولكن من الافضل أن نستطيع التحكم فيها. غير القالب لتمرير الحد الأقصى لعدد الوظائف المحدد في app.yml:
<!-- apps/frontend/modules/job/indexSuccess.php --> <?php foreach ($category->getActiveJobs(sfConfig::get('app_max_jobs_on_homepage')) as $i => $job): ?>
و أضف إعدادا جديدا في app.yml:
all: active_days: 30 max_jobs_on_homepage: 10

حركية Fixtures
لن ترى أي فرق إلا إذا كنت قد قمت بتخفيض قيمة max_jobs_on_homepage و وضعت واحدا. نحن بحاجة إلى إضافة مجموعة من الوظائف في fixture.
إذا يمكنك نسخ ولصق الوظيفة الموجودة عشرة أو عشرين مرة من جهة... ولكن هناك طريقة أفضل .
الازدواجية سيئة حتى في ملفات fixture.
symfony المنقذ! ملفات YAML في symfony يمكن أن تحتوي على شفرة PHP التي سيتم تقييمها قبل تحليل الملف. حرر
ملف fixtures 020_jobs.yml و أضف ما يلي في نهاية الرمز :
JobeetJob: # Starts at the beginning of the line (no whitespace before) <?php for ($i = 100; $i <= 130; $i++): ?> job_<?php echo $i ?>: category_id: programming company: Company <?php echo $i."\n" ?> position: Web Developer location: Paris, France description: Lorem ipsum dolor sit amet, consectetur adipisicing elit. how_to_apply: | Send your resume to lorem.ipsum [at] company_<?php echo $i ?>.sit is_public: true is_activated: true token: job_<?php echo $i."\n" ?> email: job@example.com <?php endfor; ?>
كن حذرا , لن يروق ل YAML أن تعبث بتسننه. نأخذ في الاعتبار النصائح البسيطة التالية عند إضافة رمز PHP لملف YAML:
البيانات
<?php ?>يجب أن تبدأ دائما بالخط أو أن تكون جزءا لا يتجزأ من قيمتهاإذا كان البيان
<?php ?>ينتهي بخط , تحتاج ضمنيا إلى انتاج خط جديد ("\n").يمكنك الآن إعادة تحميل fixtures بواسطة
propel:data-loadونرى ما إذا كانت فقط 10 وظائف معروضة على الصفحة الرئيسية لفئةProgramming. في ما يلي لقطة للشاشة ، علينا تغيير الحد الأقصى لعدد الوظائف إلى خمس لجعل الصورة أصغر :
تأمين صفحة الوظيفة
.حين تنتهي مدة صلاحية الوظيفة, يجب أن لا يكون من الممكن الوصول إليه بعد الآن ، حتى لو كنت تعرف عنوان الموقع
حول عنوان موقع الوظائف المنتهية
( ضع مكان id, id الحالي في قاعدة بياناتك - ()SELECT id, token FROM jobeet_job WHERE expires_at < NOW ) :
/frontend_dev.php/job/sensio-labs/paris-france/ID/web-developer-expired
بدلا من عرض هذه المهمة ، نحن بحاجة إلى إعادة توجيه المستخدم إلى 404 صفحة . ولكن كيف لنا ان نفعل ذلك لأن إسترجاع الوظيفة يتم تلقائيا من طرف التوجيه ؟
افتراضيا, يستخدم sfPropelRoute الدالة المعيار ()doSelectOne لاستعادة العنصر , ولكن يمكنك تغييره عن طريق توفير الخيار method_for_criteria في التحكم في التوجيه :
# apps/frontend/config/routing.yml
job_show_user:
url: /job/:company_slug/:location_slug/:id/:position_slug
class: sfPropelRoute
options:
model: JobeetJob
type: object
method_for_criteria: doSelectActive
param: { module: job, action: show }
requirements:
id: \d+
sf_method: [GET]
الدالة doSelectActive() ستتلقى العنصر Criteria بناء على التوجيه :
// lib/model/JobeetJobPeer.php class JobeetJobPeer extends BaseJobeetJobPeer { static public function doSelectActive(Criteria $criteria) { $criteria->add(JobeetJobPeer::EXPIRES_AT, time(), Criteria::GREATER_THAN); return self::doSelectOne($criteria); } // ... }
الآن ، إذا أنت حاولت الحصول على وظيفة إنتهت صلاحيتها ، فسوف تحال إلى صفحة 404

وصلة إلى صفحة الفئة
والآن ، دعونا نضيف رابط لصفحة الفئة على الصفحة الرئيسية ، و ننشأ صفحة الفئة .
.لكن إنتظر دقيقة. الساعة لم تنته بعد ونحن لم نعمل كثيرا . وهكذا ، لديك الكثير من وقت الفراغ و المعرفة ما يكفي لتنفيذ ذلك بنفسك! و يمكنك إعتباره كتمرين لك . و سنتحقق جدا مما قمت بتنفيذه
نراكم غدا إن شاء الله
قم بالعمل على تنفيذ على مشروع Jobeetمحليا. من فضلك ، أكثر من إستعمال وثائق الإنترنت API وجميع الوثائق المتاحة على موقع symfony لمساعدتك في الخروج بحلول .
حظ سعيد!
This work is licensed under the Creative Commons Attribution-Share Alike 3.0 Unported License license.