Jobeet FR

Le tutoriel pour Symfony2 en français

Vue normale

Dans ce chapitre, nous allons faire la page de catégorie, comme il est décrit dans les scénarios du deuxième chapitre: "L'utilisateur voit une liste de toutes les offres d'emploi de la catégorie triées par date et paginée avec 20 emplois par page".


La route Category

Tout d'abord, nous devons ajouter une route pour utiliser des URL propres pour la page de catégorie. Ajoutez ce code au début du fichier de configuration:

# src/Ens/JobeetBundle/Resources/config/routing.yml
 
EnsJobeetBundle_category:
    pattern:  /category/{slug}
    defaults: { _controller: EnsJobeetBundle:Category:show }

Pour obtenir le jeton (slug) d'une catégorie, nous devons ajouter la méthode getSlug() à notre classe Category:

// src/Ens/JobeetBundle/Entity/Category.php
// ...
 
use Ens\JobeetBundle\Utils\Jobeet;
 
class Category
{
  // ...
 
  public function getSlug()
  {
    return Jobeet::slugify($this->getName());
  }
 
  // ...
}

Le lien de la catégorie

Maintenant, modifiez le template index.html.twig du contrôleur Job et ajoutez le lien vers la page catégorie:

<!-- src/Ens/JobeetBundle/Resources/views/Job/index.html.twig -->
<!-- some HTML code -->
 
            <h1><a href="{{ path('EnsJobeetBundle_category', { 'slug': category.slug }) }}">{{ category.name }}</a></h1>
 
<!-- some HTML code -->
 
          </table>
 
          {% if category.morejobs %}
            <div class="more_jobs">
              and <a href="{{ path('EnsJobeetBundle_category', { 'slug': category.slug }) }}">{{ category.morejobs }}</a>
              more...
            </div>
          {% endif %}
        </div>
      {% endfor %}
    </div>
{% endblock %}

Dans le template ci-dessus, nous avons utilisé category.morejobs, nous allons donc le définir:

// src/Ens/JobeetBunlde/Entity/Category.php
// ...
 
private $more_jobs;
 
// ...
 
public function setMoreJobs($jobs)
{
  $this->more_jobs = $jobs >=  0 ? $jobs : 0;
}
 
public function getMoreJobs()
{
  return $this->more_jobs;
}
 
// ...

La propriété more_jobs prendra comme valeur le nombre d'offres actives pour la catégorie moins le nombre d'offres figurant sur la page d'accueil. Maintenant, dans JobController, nous avons besoin de définir la valeur more_jobs pour chaque catégorie:

// src/Ens/JobeetBundle/Controller/JobController.php
// ...
 
public function indexAction()
{
  $em = $this->getDoctrine()->getEntityManager();
 
  $categories = $em->getRepository('EnsJobeetBundle:Category')->getWithJobs();
 
  foreach($categories as $category)
  {
    $category->setActiveJobs($em->getRepository('EnsJobeetBundle:Job')->getActiveJobs($category->getId(), $this->container->getParameter('max_jobs_on_homepage')));
    $category->setMoreJobs($em->getRepository('EnsJobeetBundle:Job')->countActiveJobs($category->getId()) - $this->container->getParameter('max_jobs_on_homepage'));
  }
 
  return $this->render('EnsJobeetBundle:Job:index.html.twig', array(
    'categories' => $categories
  ));
}
// ...

La fonction countActiveJobs doit être ajoutée dans JobRepository:

// src/Ens/JobeetBundle/Repository/JobRepository.php
// ...
 
public function countActiveJobs($category_id = null)
{
  $qb = $this->createQueryBuilder('j')
    ->select('count(j.id)')
    ->where('j.expires_at > :date')
    ->setParameter('date', date('Y-m-d H:i:s', time()));
 
  if($category_id)
  {
    $qb->andWhere('j.category = :category_id')
    ->setParameter('category_id', $category_id);
  }
 
  $query = $qb->getQuery();
 
  return $query->getSingleScalarResult();
}
 
// ...

Maintenant, vous devriez voir le résultat dans votre navigateur:


Création du contrôleur Category

Il est maintenant temps de créer le contrôleur Category. Créez un nouveau fichier CategoryController.php dans votre répertoire Controller:

// src/Ens/JobeetBundle/Controller/CategoryController.php
 
namespace Ens\JobeetBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Ens\JobeetBundle\Entity\Category;
 
/**
* Category controller.
*
*/
class CategoryController extends Controller
{
 
}

Nous pourrions utiliser la commande doctrine:generate:crud comme nous l'avons fait pour JobController, mais nous n'aurons pas besoin de 90% du code généré, donc nous pouvons juste créer un nouveau contrôleur à partir de zéro.


Mettre à jour la Base De Données

Nous avons besoin d'ajouter une colonne slug dans la table category et une entrée lifecycleCallbacks pour définir la valeur de cette colonne:

# src/Ens/JobeetBundle/Resources/config/doctrine/Category.orm.yml
 
Ens\JobeetBundle\Entity\Category:
  type: entity
  repositoryClass: Ens\JobeetBundle\Repository\CategoryRepository
  table: category
  id:
    id:
      type: integer
      generator: { strategy: AUTO }
  fields:
    name:
      type: string
      length: 255
      unique: true
    slug:
      type: string
      length: 255
      unique: true
  oneToMany:
    jobs:
      targetEntity: Job
      mappedBy: category
    category_affiliates:
      targetEntity: CategoryAffiliate
      mappedBy: category
  lifecycleCallbacks:
    prePersist: [ setSlugValue ]
    preUpdate: [ setSlugValue ]

Retirez de l'entité Category (src/Ens/JobeetBundle/Entity/category.php) la méthode getSlug que nous avons créé précédemment et exécutez la commande Doctrine pour mettre à jour la classe d'entité Category:

php app/console doctrine:generate:entities EnsJobeetBundle

Maintenant vous devriez avoir ce qui suit ajouté à Category.php:

// srr/Ens/JobeetBundle/Entity/Category.php
// ...
 
/**
* @var string $slug
*/
private $slug;
 
/**
* Set slug
*
* @param string $slug
*/
public function setSlug($slug)
{
    $this->slug = $slug;
}
/**
* Get slug
*
* @return string
*/
public function getSlug()
{
    return $this->slug;
}
/**
* @ORM\prePersist
*/
public function setSlugValue()
{
    // Add your code here
}

Changez la fonction setSlugValue avec ce qui suit:

public function setSlugValue()
{
    $this->slug = Jobeet::slugify($this->getName());
}

Maintenant, nous devons supprimer la BDD et la recréer avec la nouvelle colonne de la table category et charger les fixtures:

php app/console doctrine:database:drop --force
php app/console doctrine:database:create
php app/console doctrine:schema:update --force
php app/console doctrine:fixtures:load

La page catégorie

Tout est maintenant en place pour créer la méthode showAction(). Ajoutez le code suivant au fichier CategoryController.php:

// src/Ens/JobeetBundle/Controller/CategoryController.php
// ...
 
public function showAction($slug)
{
    $em = $this->getDoctrine()->getEntityManager();
 
    $category = $em->getRepository('EnsJobeetBundle:Category')->findOneBySlug($slug);
 
    if (!$category) {
        throw $this->createNotFoundException('Unable to find Category entity.');
    }
 
    $category->setActiveJobs($em->getRepository('EnsJobeetBundle:Job')->getActiveJobs($category->getId()));
 
    return $this->render('EnsJobeetBundle:Category:show.html.twig', array(
        'category' => $category,
    ));
}
 
// ...

La dernière étape consiste à créer le template show.html.twig:

<!-- src/Ens/JobeetBundle/Resources/view/Category/show.html.twig -->

{% extends 'EnsJobeetBundle::layout.html.twig' %}
 
{% block title %}
  Jobs in the {{ category.name }} category
{% endblock %}
 
{% block stylesheets %}
  {{ parent() }}
  <link rel="stylesheet" href="{{ asset('bundles/ensjobeet/css/jobs.css') }}" type="text/css" media="all" />
{% endblock %}
 
{% block content %}
  <div class="category">
    <div class="feed">
      <a href="">Feed</a>
    </div>
    <h1>{{ category.name }}</h1>
  </div>
 
  <table class="jobs">
    {% for entity in category.activejobs %}
      <tr class="{{ cycle(['even', 'odd'], loop.index) }}">
        <td class="location">{{ entity.location }}</td>
        <td class="position">
          <a href="{{ path('ens_job_show', { 'id': entity.id, 'company': entity.companyslug, 'location': entity.locationslug, 'position': entity.positionslug }) }}">
            {{ entity.position }}
          </a>
        </td>
        <td class="company">{{ entity.company }}</td>
      </tr>
    {% endfor %}
  </table>
{% endblock %}

Inclure d'autres templates Twig

Notez que nous avons copié/collé la balise <table> qui crée une liste d'offres à partir du template des offres index.html.twig. C'est une mauvaise solution. Lorsque vous avez besoin de réutiliser une partie d'un template, vous devez créer un nouveau template Twig avec ce code et l'inclure où vous en avez besoin.

Créez le fichier list.html.twig:

<!-- src/Ens/JobeetBundle/Resources/views/Job/list.html.twig -->
 
<table class="jobs">
  {% for entity in jobs %}
    <tr class="{{ cycle(['even', 'odd'], loop.index) }}">
      <td class="location">{{ entity.location }}</td>
      <td class="position">
        <a href="{{ path('ens_job_show', { 'id': entity.id, 'company': entity.companyslug, 'location': entity.locationslug, 'position': entity.positionslug }) }}">
          {{ entity.position }}
        </a>
      </td>
      <td class="company">{{ entity.company }}</td>
    </tr>
  {% endfor %}
</table>

Vous pouvez inclure un template à l'aide de la balise {% include%}. Remplacez le code HTML <table> dans les deux templates avec la balise include:

<!-- in src/Ens/JobeetBundle/Resources/view/Job/index.html.twig -->
{% include 'EnsJobeetBundle:Job:list.html.twig' with {'jobs': category.activejobs} %}
 
<!-- in src/Ens/JobeetBundle/Resources/view/Category/show.html.twig -->
{% include 'EnsJobeetBundle:Job:list.html.twig' with {'jobs': category.activejobs} %}

Pagination de listes

Au moment où ce tutoriel est écrit, Symfony2 ne fournit pas de réels bons outils de pagination "out of the box". Afin de résoudre ce problème, nous allons utiliser l'ancienne méthode classique.

Tout d'abord, nous allons ajouter un paramètre page à la route EnsJobeetBundle_category. Le paramètre page aura une valeur par défaut de 1, donc il ne sera pas obligatoire:

# src/Ens/JobeetBundle/Resources/config/routing.yml
 
EnsJobeetBundle_category:
    pattern: /category/{slug}/{page}
    defaults: { _controller: EnsJobeetBundle:Category:show, page: 1 }
 
# ...

Le nombre d'offres sur chaque page sera défini comme un paramètre personnalisé dans le fichier app/config/config.yml:

# app/config/config.yml
# ...
 
parameters:
    max_jobs_on_homepage: 10
    max_jobs_on_category: 20

Modifiez la méthode getActiveJobs de JobRepository afin d'inclure un paramètre $offset pour être utilisé par Doctrine lors de la récupération des offres:

// src/Ens/JobeetBundle/Repository/JobRepository.php
// ...
 
public function getActiveJobs($category_id = null, $max = null, $offset = null)
{
  $qb = $this->createQueryBuilder('j')
    ->where('j.expires_at > :date')
    ->setParameter('date', date('Y-m-d H:i:s', time()))
    ->orderBy('j.expires_at', 'DESC');
 
  if($max)
  {
    $qb->setMaxResults($max);
  }
 
  if($offset)
  {
    $qb->setFirstResult($offset);
  }
 
  if($category_id)
  {
    $qb->andWhere('j.category = :category_id')
       ->setParameter('category_id', $category_id);
  }
 
  $query = $qb->getQuery();
 
  return $query->getResult();
}
 
// ...

Modifiez l'action showAction de CategoryController avec ce qui suit:

// src/Ens/JobeetBundle/Controller/CategoryController.php
// ...
 
public function showAction($slug, $page)
{
  $em = $this->getDoctrine()->getEntityManager();
 
  $category = $em->getRepository('EnsJobeetBundle:Category')->findOneBySlug($slug);
 
  if (!$category) {
    throw $this->createNotFoundException('Unable to find Category entity.');
  }
 
  $total_jobs = $em->getRepository('EnsJobeetBundle:Job')->countActiveJobs($category->getId());
  $jobs_per_page = $this->container->getParameter('max_jobs_on_category');
  $last_page = ceil($total_jobs / $jobs_per_page);
  $previous_page = $page > 1 ? $page - 1 : 1;
  $next_page = $page < $last_page ? $page + 1 : $last_page;
 
  $category->setActiveJobs($em->getRepository('EnsJobeetBundle:Job')->getActiveJobs($category->getId(), $jobs_per_page, ($page - 1) * $jobs_per_page));
 
  return $this->render('EnsJobeetBundle:Category:show.html.twig', array(
    'category' => $category,
    'last_page' => $last_page,
    'previous_page' => $previous_page,
    'current_page' => $page,
    'next_page' => $next_page,
    'total_jobs' => $total_jobs
  ));
}

Enfin, nous allons mettre à jour le template:

<!-- src/Ens/JobeetBundle/Resources/views/Category/show.html.twig -->
 
{% extends 'EnsJobeetBundle::layout.html.twig' %}
 
{% block title %}
  Jobs in the {{ category.name }} category
{% endblock %}
 
{% block stylesheets %}
  {{ parent() }}
  <link rel="stylesheet" href="{{ asset('bundles/ensjobeet/css/jobs.css') }}" type="text/css" media="all" />
{% endblock %}
 
{% block content %}
  <div class="category">
    <div class="feed">
      <a href="">Feed</a>
    </div>
    <h1>{{ category.name }}</h1>
  </div>
 
  {% include 'EnsJobeetBundle:Job:list.html.twig' with {'jobs': category.activejobs} %}
 
  {% if last_page > 1 %}
    <div class="pagination">
      <a href="{{ path('EnsJobeetBundle_category', { 'slug': category.slug, 'page': 1 }) }}">
        <img src="{{ asset('bundles/ensjobeet/images/first.png') }}" alt="First page" title="First page" />
      </a>
 
      <a href="{{ path('EnsJobeetBundle_category', { 'slug': category.slug, 'page': previous_page }) }}">
        <img src="{{ asset('bundles/ensjobeet/images/previous.png') }}" alt="Previous page" title="Previous page" />
      </a>
 
      {% for page in 1..last_page %}
        {% if page == current_page %}
          {{ page }}
        {% else %}
          <a href="{{ path('EnsJobeetBundle_category', { 'slug': category.slug, 'page': page }) }}">{{ page }}</a>
        {% endif %}
      {% endfor %}
 
      <a href="{{ path('EnsJobeetBundle_category', { 'slug': category.slug, 'page': next_page }) }}">
        <img src="{{ asset('bundles/ensjobeet/images/next.png') }}" alt="Next page" title="Next page" />
      </a>
 
      <a href="{{ path('EnsJobeetBundle_category', { 'slug': category.slug, 'page': last_page }) }}">
        <img src="{{ asset('bundles/ensjobeet/images/last.png') }}" alt="Last page" title="Last page" />
      </a>
    </div>
  {% endif %}
 
  <div class="pagination_desc">
    <strong>{{ total_jobs }}</strong> jobs in this category
 
    {% if last_page > 1 %}
      - page <strong>{{ current_page }}/{{ last_page }}</strong>
    {% endif %}
  </div>
{% endblock %}

Le résultat:


Chapitre précédent Chapitre suivant


Une question ? Une réaction ?

comments powered by Disqus