Caution: You are browsing the legacy symfony 1.x part of this website.

اليوم 7 :صفحة Category

سابقا في Jobeet

بالأمس قمتم بتوسيع معرفتكم ب symfony في الكثير من المجالات المختلفة: للاستعلام ب Propel, التجهيزات الثابتة، التوجيه، تصحيح الأخطاء وتعريف التكوين. و انتهينا بتحد صغير لهذا اليوم Jobeet .

أرجو أن تعملوا على صفحة الفئة لأن هذا اليوم التعليمي من جوبيت سيكون أكثر قيمة بالنسبة لكم .

مستعدين؟ فلنتحدث عن الطريقة التنفيذية المحتملة.

مسار Category

أولا ، نحن بحاجة إلى إضافة مسار لتحديد عنوان جيد لصفحة الفئة Category. أضفه في بداية ملف التوجيه:

# apps/frontend/config/routing.yml
category:
  url:      /category/:slug
  class:    sfPropelRoute
  param:    { module: category, action: show }
  options:  { model: JobeetCategory, type: object }

tip

كلما بدأت بتنفيذ ميزة جديدة، من المستحسن التفكير في العنوان وخلق الطريق الذي يربط به. ومن الواجب إزالة قواعد التوجيه الافتراضي.

يمكن للمسار استخدام أي عمود من العناصر المرتبطة به على شكل متغير ويمكنه أيضا استخدام أي قيمة أخرى إذا كان هناك اتصال ب Accessor محدد في عنصر الصنف . لكى يعمل المسار, نحن بحاجة إلى إضافة accessor افتراضى في JobeetCategory لأن المتغير فى slug ليس له أي عمود مقابل له في الجدولcategory.

// lib/model/JobeetCategory.php
public function getSlug()
{
  return Jobeet::slugify($this->getName());
}

رابط Category

الآن لنغير الدالة indexSuccess.php فى template و المتواجدة فى الوحدة job و ذلك من أجل إضافة رابط لصفحة Category

<!-- some HTML code -->
    <h1><?php echo link_to($category, 'category', $category) ?></h1>
<!-- some HTML code -->
      </table>
      <?php if (($count = $category->countActiveJobs() - sfConfig::get('app_max_jobs_on_homepage')) > 0): ?>
        <div class="more_jobs">
          and <?php echo link_to($count, 'category', $category) ?>
          more...
        </div>
      <?php endif; ?>
    </div>
  <?php endforeach; ?>
</div>

نضيف هذا الرابط فقط إذا كان لدينا أكثر من 10 وظائف للعرض فى كل فئة category . يحتوى الرابط على الوظائف غير المعروضة. لكى يعمل template نحتاج إلى إضافة الدالة ()countActiveJobs إلى JobeetCategory .

// lib/model/JobeetCategory.php
public function countActiveJobs()
{
  $criteria = new Criteria();
  $criteria->add(JobeetJobPeer::CATEGORY_ID, $this->getId());
 
  return JobeetJobPeer::countActiveJobs($criteria);
}

تستعمل الدالة ()countActiveJobs دالة لا وجود لها حتى الآن في JobeetJobPeer هى الدالة ()countActiveJobs . نقوم بتعويض محتوى الملف JobeetJobPeer.php بالترميز البرمجي التالى :

// lib/model/JobeetJobPeer.php
class JobeetJobPeer extends BaseJobeetJobPeer
{
  static public function doSelectActive(Criteria $criteria)
  {
    return self::doSelectOne(self::addActiveJobsCriteria($criteria));
  }
 
  static public function getActiveJobs(Criteria $criteria = null)
  {
    return self::doSelect(self::addActiveJobsCriteria($criteria));
  }
 
  static public function countActiveJobs(Criteria $criteria = null)
  {
    return self::doCount(self::addActiveJobsCriteria($criteria));
  }
 
  static public function addActiveJobsCriteria(Criteria $criteria = null)
  {
    if (is_null($criteria))
    {
      $criteria = new Criteria();
    }
 
    $criteria->add(self::EXPIRES_AT, time(), Criteria::GREATER_THAN);
    $criteria->addDescendingOrderByColumn(self::CREATED_AT);
 
    return $criteria;
  }
}

كما ترى لتفادى التكرار, لقد قمنا بتعميل الترميز البرمجى باكمله فى JobeetJobPeer لادخال دالة جديدة و مشتركة هى الدالة ()addActiveJobsCriteria.

DRY (Don't Repeat Yourself). (DRY (Don't Repeat Yourself)(http://en.wikipedia.org/wiki/Don%27t_repeat_yourself.

tip

اول مرة نعيد فيها استعمال الترميز البرمجي قد يكون كافيا, لكن إذا اضطررنا إلى إعادة استعماله مرات عديدة ِعندها يستحسن تعميل جميع الاستعمالات في دالة كما فعلنا بالأعلى .

في الدالة ()countActiveJobs عوض استعمال ()doSelect ثم حساب عدد النتائج, استعملنا الدالة الاسرع ()doCount .

لقد قمنا بتغيير الكثير من الملفات بالنسبة لهذه الميزة فقط ٠لكن في كل مرة كنا نضيف فيها بعض الترميز البرمجى كنا نحاول وضعه في المكان الصحيح من البرنامج لكي نستطيع إعادة استعماله .
في هذه العملية قمنا بتعميل بعض الترميز المهم . فهذا نموذج جيد عن كيفية العمل في مشروع symfony. Homepage

إنشاء الوحدة Job Category

حان الوقت لإنشاء الوحدة category.

$ php symfony generate:module frontend category

إذا كان قد سبق لكم وأنشأتم وحدة فلابد أنكم قد إستعملتم propel:generate-module . هذا جيد لكننا لا نحتاج إلى 90% من الترميز البرمجي المولد لقد استعملت generate:module وذلك لإنشاء وحدة فارغة.

tip

لمذا لم نقتصر على إضافة الاجراء category إلى الوحدة job ؟ كان من الممكن فعل ذلك لكن بما أن الموضوع الرئيسي في صفحة category
هو category كان من الطبيعي إنشاء الوحدة category.

عند الوصول إلى صفحة category يتوجب على مسار category العثور على الفئة category المرتبطة بالمتغير المطلوب slug, لكن بما أن slug غير مسجل في قاعدة البيانات و لأننا لا نستطيع إستنتاج إسم الفئة category عن طريق slug, فإنه لا توجد أي وسيلة للعثور على الفئة category المرتبطة ب slug.

تحديث قاعدة البيانات

نحن بحاجة إلى إضافة عمود slug إلى الجدول category:

# config/schema.yml
propel:
  jobeet_category:
    id:           ~
    name:         { type: varchar(255), required: true }
    slug:         { type: varchar(255), required: true, index: unique }

الآن بما أن slug هو عمود حقيقي يجب حذف الدالة ()JobeetCategoryمنgetSlug.

في كل مرة يتغير إسم category علينا تغيير slug أيضا. لنغير الدالة ()setName :

// lib/model/JobeetCategory.php
public function setName($name)
{
  parent::setName($name);
 
  $this->setSlug(Jobeet::slugify($name));
}

لتحديث جداول قاعدة البيانات إستعمل propel:build-all-load ثم إملأ قاعدة البيانات بتركيباتنا fixtures .

$ php symfony propel:build-all-load

لدينا هنا كل شيء لإنشاء الدالة ()executeShow قم بتغيير محتوى ملف الإجراءت category بالترميز البرمجي التالي:

// apps/frontend/modules/category/actions/actions.class.php
class categoryActions extends sfActions
{
  public function executeShow(sfWebRequest $request)
  {
    $this->category = $this->getRoute()->getObject();
    $this->jobs = $this->category->getActiveJobs();
  }
}

note

بما أننا حذفنا الدالة المولدة ()executeIndex نستطيع أيضا حذف القالب المولد تلقائياً indexSuccess.php. (apps/frontend/modules/category/templates/indexSuccess.php).

الخطوة الأخيرة هي إنشاء القالب showSuccess.php.

// apps/frontend/modules/category/template/showSuccess.php
<?php use_stylesheet('jobs.css') ?>
 
<?php slot('title', sprintf('Jobs in the %s category', $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>

الجزئيات

لاحظ أننا قمنا بنسخ و إلصاق <table> من القالب indexSuccess.php للوحدة (job) والتي تعمل على إنشاء قائمة الوظائف . هذا ليس جيدا, لنتعلم طريقة جديدة. عندما تكون بحاجة إلى إعادة استعمال بعض الأجزاء من القالب template عليك إذن إنشاء الجزء partial.

الجزء هو قطعة من الترميز البرمجي الموجود في القالب template والذي يمكن أن تتشاركه مجموعة من القوالب . الجزء هو مجرد قالب يبدأ ب '_'.

أنشئ الملف list.php_:

// apps/frontend/modules/job/templates/_list.php
<table class="jobs">
  <?php foreach ($jobs 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>

يمكن إضافة الجزء و ذلك باستعمال المساعد ()include_partial helper:

<?php include_partial('job/list', array('jobs' => $jobs)) ?>

المتغير الأول في الدالة ()include_partial هو إسم الجزء ( مكون من إسم الوحدة, و / و إسم الجزء مع حذف '_').

المتغير الثاني في الدالة هو جدول من المتغيرات التي يجب تمريرها إلى الجزء .

note

لماذا لا نستعمل الدالة البانية ()include بدلا من استعمال المساعد helper()include_partial ؟ الفرق الرئيسي بينهما هو أن المساعد ()include_partial يتحمل built-in cache

قم بتعويض الترميز البرمجي الموجود في table>HTML> لكلا القالبين templates وذلك باستدعاء : ()include_partial.

// in apps/frontend/modules/job/templates/indexSuccess.php
<?php include_partial('job/list', array('jobs' => $category->getActiveJobs(sfConfig::get('app_max_jobs_on_homepage')))) ?>
 
// in apps/frontend/modules/category/templates/showSuccess.php
<?php include_partial('job/list', array('jobs' => $category->getActiveJobs())) ?>

قائمة تقسيم الصفحات

حسب متطلبات اليوم 2:

"تقسم القائمة إلى صفحات في كل صفحة 20 وظيفة".

من أجل تقسيم قائمة من العناصر Propel, يقدم لنا symfony الصنف : sfPropelPager. عوض تمرير العنصر job في القالب templateshowSuccess فإننا نمرر صفحة.

// apps/frontend/modules/category/actions/actions.class.php
public function executeShow(sfWebRequest $request)
{
  $this->category = $this->getRoute()->getObject();
 
  $this->pager = new sfPropelPager(
    'JobeetJob',
    sfConfig::get('app_max_jobs_on_category')
  );
  $this->pager->setCriteria($this->category->getActiveJobsCriteria());
  $this->pager->setPage($request->getParameter('page', 1));
  $this->pager->init();
}

tip

تأخذ الدالة قيمة افتراضية على شكل متغير ثاني ()getParameter . في الترميز البرمجي للإجراء أعلاه, إذا لم يوجد المتغير المطلوب للصفحة فإن الدالة ()getParameter ستعطي كنتيجة 1

يأخذ sfPropelPager constructor صنف النموذج و العدد الأقصى من items للعرض في كل صفحة . أضف القيمة الاخيرة في ملف وحدة التحكم:

# apps/frontend/config/app.yml
all:
  active_days:          30
  max_jobs_on_homepage: 10
  max_jobs_on_category: 20

عندما نختار items من قاعدة البيانات فإن الدالة ()sfPropelPager::setCriteria تستعمل العنصر Criteria

أضف الدالة ()getActiveJobsCriteria

// lib/model/JobeetCategory.php
public function getActiveJobsCriteria()
{
  $criteria = new Criteria();
  $criteria->add(JobeetJobPeer::CATEGORY_ID, $this->getId());
 
  return JobeetJobPeer::addActiveJobsCriteria($criteria);
}

بما أننا الآن حددنا الدالة ()getActiveJobsCriteria نستطيع تعميل الدوال الاخرى ل JobeetCategory كي نستعملها .

// lib/model/JobeetCategory.php
public function getActiveJobs($max = 10)
{
  $criteria = $this->getActiveJobsCriteria();
  $criteria->setLimit($max);
 
  return JobeetJobPeer::doSelect($criteria);
}
 
public function countActiveJobs()
{
  $criteria = $this->getActiveJobsCriteria();
 
  return JobeetJobPeer::doCount($criteria);
}

أخيرا لنقم بتحديث القالب template:

<!-- apps/frontend/modules/category/templates/showSuccess.php -->
<?php use_stylesheet('jobs.css') ?>
 
<div class="category">
  <div class="feed">
    <a href="">Feed</a>
  </div>
  <h1><?php echo $category ?></h1>
</div>
 
<?php include_partial('job/list', array('jobs' => $pager->getResults())) ?>
 
<?php if ($pager->haveToPaginate()): ?>
  <div class="pagination">
    <a href="<?php echo url_for('category', $category) ?>?page=1">
      <img src="/legacy/images/first.png" alt="First page" />
    </a>
 
    <a href="<?php echo url_for('category', $category) ?>?page=<?php echo $pager->getPreviousPage() ?>">
      <img src="/legacy/images/previous.png" alt="Previous page" title="Previous page" />
    </a>
 
    <?php foreach ($pager->getLinks() as $page): ?>
      <?php if ($page == $pager->getPage()): ?>
        <?php echo $page ?>
      <?php else: ?>
        <a href="<?php echo url_for('category', $category) ?>?page=<?php echo $page ?>"><?php echo $page ?></a>
      <?php endif; ?>
    <?php endforeach; ?>
 
    <a href="<?php echo url_for('category', $category) ?>?page=<?php echo $pager->getNextPage() ?>">
      <img src="/legacy/images/next.png" alt="Next page" title="Next page" />
    </a>
 
    <a href="<?php echo url_for('category', $category) ?>?page=<?php echo $pager->getLastPage() ?>">
      <img src="/legacy/images/last.png" alt="Last page" title="Last page" />
    </a>
  </div>
<?php endif; ?>
 
<div class="pagination_desc">
  <strong><?php echo $pager->getNbResults() ?></strong> jobs in this category
 
  <?php if ($pager->haveToPaginate()): ?>
    - page <strong><?php echo $pager->getPage() ?>/<?php echo $pager->getLastPage() ?></strong>
  <?php endif; ?>
</div>

معظم هذا الترميز البرمجي يتعلق بروابط صفحات أخرى . لديكم هنا قائمة الأساليب المستخدمة في هذا template :

  • ()getResults: تعطي كنتيجة جدول من العناصر Propel للصفحة الحالية .
  • ()getNbResults: تعطي كنتيجة العدد الإجمالي للنتائج .
  • ()haveToPaginate: تعطي كنتيجة 'صحيح' 'true' إذا كان هناك أكثر من صفحة واحدة .
  • ()getLinks: تعطي كنتيجة قائمة روابط الصفحة المعروضة .
  • ()getPage: تعطي كنتيجة رقم الصفحة الحالية .
  • ()getPreviousPage: تعطي كنتيجة رقم الصفحة السابقة .
  • ()getNextPage: تعطي كنتيجة رقم الصفحة الموالية .
  • ()getLastPage: تعطي كنتيجة رقم الصفحة الأخيرة .

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

إذا عملتم البارحة على تنفيذكم الخاص و أحسستم أنكم لم تتعلموا الكثير هذا اليوم فهذا يعني أنكم بدأتم تعتادون على فلسفة symfony .

عملية إضافة مميزات جديدة لموقع إلكتروني مصمم ب symfony تكون دائما نفسها: يجب التفكير في الروابط, إنشاء بعض الإجراءات ِ, تحديث النموذج وكتابة بعض templates وإذااستطعت تطبيق بعض الممارسات الجيدة في التطوير لهذا المزيج ، ستصبح محترفا في symfony سريعا جدا .

غدا سيكون بداية أسبوع جديد. للإحتفال سنتحدث عن موضوع جديد :آليات الإختبار

يحتوي The release_day_07 subversion tag على الترميز البرمجي لهذا اليوم.

http://svn.jobeet.org/tags/release_day_07/