Caution: You are browsing the legacy symfony 1.x part of this website.
SymfonyWorld Online 2020
100% online
30+ talks + workshops
Live + Replay watch talks later

اليوم6: أكثر مع النموذج

سابقا في 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::Criteria
  • NOT_EQUAL::Criteria
  • GREATER_EQUAL::Criteria , GREATER_THAN::Criteria
  • LESS_EQUAL::Criteria , LESS_THAN::Criteria
  • NOT_LIKE::Criteria , LIKE::Criteria
  • CUSTOM::Criteria
  • NOT_IN::Criteria , IN::Criteria
  • ISNOTNULL::Criteria , ISNULL::Criteria
  • CURRENT_TIMESTAMP::Criteria, CURRENT_TIME::Criteria CURRENT_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, كل المعلومات التي تحتاجها متوفرة ويدخل هذا كله ضمن راحة متصفحك :

SQL statements in the web debug toolbar

تسلسل العناصر

حتى لو كانت تعمل على الرمز, هو أبعد ما يكون عن الكمال لأنها لا تأخذ في الحسبان بعض الاحتياجات من يوم 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:        [email protected]

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

Homepage sorted by category

حركية 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:        [email protected]
 
<?php endfor; ?>

كن حذرا , لن يروق ل YAML أن تعبث بتسننه. نأخذ في الاعتبار النصائح البسيطة التالية عند إضافة رمز PHP لملف YAML:

  • البيانات <?php ?> يجب أن تبدأ دائما بالخط أو أن تكون جزءا لا يتجزأ من قيمتها

  • إذا كان البيان <?php ?> ينتهي بخط , تحتاج ضمنيا إلى انتاج خط جديد ("\n").

    يمكنك الآن إعادة تحميل fixtures بواسطة propel:data-load ونرى ما إذا كانت فقط 10 وظائف معروضة على الصفحة الرئيسية لفئة Programming. في ما يلي لقطة للشاشة ، علينا تغيير الحد الأقصى لعدد الوظائف إلى خمس لجعل الصورة أصغر :

    Pagination

    تأمين صفحة الوظيفة


.حين تنتهي مدة صلاحية الوظيفة, يجب أن لا يكون من الممكن الوصول إليه بعد الآن ، حتى لو كنت تعرف عنوان الموقع
حول عنوان موقع الوظائف المنتهية
( ضع مكان 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

404 for expired job

وصلة إلى صفحة الفئة

والآن ، دعونا نضيف رابط لصفحة الفئة على الصفحة الرئيسية ، و ننشأ صفحة الفئة .

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

نراكم غدا إن شاء الله

قم بالعمل على تنفيذ على مشروع Jobeetمحليا. من فضلك ، أكثر من إستعمال وثائق الإنترنت API وجميع الوثائق المتاحة على موقع symfony لمساعدتك في الخروج بحلول .

حظ سعيد!