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

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

سابقا في Jobeet

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

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

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

مسار Category

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

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

tip

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

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

// lib/model/doctrine/JobeetCategory.class.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/doctrine/JobeetCategory.class.php
public function countActiveJobs()
{
  $q = Doctrine_Query::create()
    ->from('JobeetJob j')
    ->where('j.category_id = ?', $this->getId());
 
  return Doctrine::getTable('JobeetJob')->countActiveJobs($q);
}

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

// lib/model/doctrine/JobeetJobTable.class.php
class JobeetJobTable extends Doctrine_Table
{
  public function retrieveActiveJob(Doctrine_Query $q)
  {
    return $this->addActiveJobsQuery($q)->fetchOne();
  }
 
  public function getActiveJobs(Doctrine_Query $q = null)
  {
    return $this->addActiveJobsQuery($q)->execute();
  }
 
  public function countActiveJobs(Doctrine_Query $q = null)
  {
    return $this->addActiveJobsQuery($q)->count();
  }
 
  public function addActiveJobsQuery(Doctrine_Query $q = null)
  {
    if (is_null($q))
    {
      $q = Doctrine_Query::create()
        ->from('JobeetJob j');
    }
 
    $alias = $q->getRootAlias();
 
    $q->andWhere($alias . '.expires_at > ?', date('Y-m-d h:i:s', time()))
      ->addOrderBy($alias . '.expires_at DESC');
 
    return $q;
  }
}

كما ترى لتفادى التكرار, لقد قمنا بتعميل الترميز البرمجى بأكمله فى JobeetJobTable لإدخال دالة جديدة و مشتركة هى الدالة ()addActiveJobsQuery
DRY (Don't Repeat Yourself).

tip

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

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

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

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

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

$ php symfony generate:module frontend category

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

tip

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

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

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

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

يتكلف سلوك Doctrine المسمى ب Sluggable بالإعتناء بعمود slug مانحتاج إليه ببساطة هو تشغيل سلوك النموذج JobeetCategory وسيتكلف هو بالباقي .

# config/doctrine/schema.yml
JobeetCategory:
  actAs:
    Timestampable: ~
    Sluggable:
      fields: [name]
  columns:
    name:
      type: string(255)
      notnull:  true

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

note

يتم تحديد setting لعمود slug تلقائيا عند تسجيل قياس . يتم تكوين slug باستعمال قيمة name و إعطائها للعنصر object .

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

$ php symfony doctrine:build-all-reload

لدينا هنا كل شيء لإنشاء الدالة ()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 وظيفة".

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

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

tip

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

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

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

عندما نختار items من قاعدة البيانات فإن الدالة ()sfDoctrinePager::setQuery تستعمل العنصر Doctrine_Query

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

// lib/model/doctrine/JobeetCategory.class.php
public function getActiveJobsQuery()
{
  $q = Doctrine_Query::create()
    ->from('JobeetJob j')
    ->where('j.category_id = ?', $this->getId());
 
  return Doctrine::getTable('JobeetJob')->addActiveJobsQuery($q);
}

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

// lib/model/doctrine/JobeetCategory.class.php
public function getActiveJobs($max = 10)
{
  $q = $this->getActiveJobsQuery()
    ->limit($max);
 
  return $q->execute();
}
 
public function countActiveJobs()
{
  return $this->getActiveJobsQuery()->count();
}

أخيرا لنقم بتحديث القالب 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: تعطي كنتيجة جدول من العناصر Doctrine للصفحة الحالية .
  • ()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/