Symfony2 - Jobeet - Jour 07 - Jouons avec la page Catégorie

1 1 1 1 1 1 1 1 1 1 Rating 0.00 (0 Votes)
Submit to DeliciousSubmit to DiggSubmit to FacebookSubmit to Google PlusSubmit to StumbleuponSubmit to TechnoratiSubmit to TwitterSubmit to LinkedIn

Aujourd'hui, nous allons faire la page de catégorie, comme il est décrit dans la story du 2ème jour (Symfony2 - Jobeet - Jour 02 - Le projet) : "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/Erlem/JobeetBundle/Resources/config/routing.yml
# ...
ErlemJobeetBundle_category:
    pattern:  /category/{slug}
    defaults: { _controller: ErlemJobeetBundle:Category:show }

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

  • src/Erlem/JobeetBundle/Entity/Category.php
use Erlem\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/Erlem/JobeetBundle/Resources/views/Job/index.html.twig
<!-- some HTML code -->

                    <h1><a href={{ path('ErlemJobeetBundle_category', { 'slug': category.slug }) }}>{{ category.name }}</a></h1>

<!-- some HTML code -->

                </table>

                {% if category.morejobs %}
                    <div class="more_jobs">
                        and <a href={{ path('ErlemJobeetBundle_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/Erlem/JobeetBunlde/Entity/Category.php
class Category
{
    // ...

    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/Erlem/JobeetBundle/Controller/JobController.php
public function indexAction()
{
    $em = $this->getDoctrine()->getManager();

    $categories = $em->getRepository('ErlemJobeetBundle:Category')->getWithJobs();

    foreach($categories as $category)
    {
        $category->setActiveJobs($em->getRepository('ErlemJobeetBundle:Job')->getActiveJobs($category->getId(), $this->container->getParameter('max_jobs_on_homepage')));
        $category->setMoreJobs($em->getRepository('ErlemJobeetBundle:Job')->countActiveJobs($category->getId()) - $this->container->getParameter('max_jobs_on_homepage'));
    }

    return $this->render('ErlemJobeetBundle:Job:index.html.twig', array(
        'categories' => $categories
    ));
}

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

  • src/Erlem/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 : http://jobeet.local/app_dev.php/

01-106-symfony2-jobeet-jour-07-jouons-avec-la-page-categorie

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/Erlem/JobeetBundle/Controller/CategoryController.php
<?php

namespace Erlem\JobeetBundle\Controller;
 
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Erlem\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/Erlem/JobeetBundle/Resources/config/doctrine/Category.orm.yml
Erlem\JobeetBundle\Entity\Category:
    type: entity
    repositoryClass: Erlem\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
    manyToMany:
        affiliates:
            targetEntity: Affiliate
            mappedBy: categories
    lifecycleCallbacks:
        prePersist: [ setSlugValue ]
        preUpdate: [ setSlugValue ]

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

php app/console doctrine:generate:entities ErlemJobeetBundle

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

  •  src/Erlem/JobeetBundle/Entity/Category.php
// ...

    /**
     * Set slug
     *
     * @param string $slug
     * @return Category
     */
    public function setSlug($slug)
    {
        $this->slug = $slug;

        return $this;
    }

    /**
     * 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 :

  •  src/Erlem/JobeetBundle/Entity/Category.php
// ...

class Category
{
    // ...

    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/Erlem/JobeetBundle/Controller/CategoryController.php
// ...

public function showAction($slug)
{
    $em = $this->getDoctrine()->getManager();

    $category = $em->getRepository('ErlemJobeetBundle:Category')->findOneBySlug($slug);

    if (!$category) {
        throw $this->createNotFoundException('Unable to find Category entity.');
    }

    $category->setActiveJobs($em->getRepository('ErlemJobeetBundle:Job')->getActiveJobs($category->getId()));

    return $this->render('ErlemJobeetBundle:Category:show.html.twig', array(
        'category' => $category,
    ));
}

// ...

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

  • src/Erlem/JobeetBundle/Resources/view/Category/show.html.twig
{% extends 'ErlemJobeetBundle::layout.html.twig' %}

{% block title %}
    Jobs in the {{ category.name }} category
{% endblock %}

{% block stylesheets %}
    {{ parent() }}
    <link rel="stylesheet" href={{ asset('bundles/erlemjobeet/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('erlem_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/Erlem/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('erlem_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 :

  • src/Erlem/JobeetBundle/Resources/view/Job/index.html.twig
{{ include ('ErlemJobeetBundle:Job:list.html.twig', {'jobs': category.activejobs}) }}
  • src/Erlem/JobeetBundle/Resources/view/Category/show.html.twig
{{ include ('ErlemJobeetBundle:Job:list.html.twig', {'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 ErlemJobeetBundle_category. Le paramètre page aura une valeur par défaut de 1, donc il ne sera pas obligatoire :

  •  src/Erlem/JobeetBundle/Resources/config/routing.yml
ErlemJobeetBundle_category:
    pattern: /category/{slug}/{page}
    defaults: { _controller: ErlemJobeetBundle: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/Erlem/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/Erlem/JobeetBundle/Controller/CategoryController.php
public function showAction($slug, $page)
{
    $em = $this->getDoctrine()->getManager();

    $category = $em->getRepository('ErlemJobeetBundle:Category')->findOneBySlug($slug);

    if (!$category) {
        throw $this->createNotFoundException('Unable to find Category entity.');
    }

    $total_jobs = $em->getRepository('ErlemJobeetBundle: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('ErlemJobeetBundle:Job')->getActiveJobs($category->getId(), $jobs_per_page, ($page - 1) * $jobs_per_page));

    return $this->render('ErlemJobeetBundle: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/Erlem/JobeetBundle/Resources/views/Category/show.html.twig
{% extends 'ErlemJobeetBundle::layout.html.twig' %}

{% block title %}
    Jobs in the {{ category.name }} category
{% endblock %}

{% block stylesheets %}
    {{ parent() }}
    <link rel="stylesheet" href={{ asset('bundles/erlemjobeet/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 ('ErlemJobeetBundle:Job:list.html.twig', {'jobs': category.activejobs}) }}

    {% if last_page > 1 %}
        <div class="pagination">
            <a href={{ path('ErlemJobeetBundle_category', { 'slug': category.slug, 'page': 1 }) }}>
                <img src={{ asset('bundles/erlemjobeet/images/first.png') }} alt="First page" title="First page" />
            </a>

            <a href={{ path('ErlemJobeetBundle_category', { 'slug': category.slug, 'page': previous_page }) }}>
                <img src={{ asset('bundles/erlemjobeet/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('ErlemJobeetBundle_category', { 'slug': category.slug, 'page': page }) }}>{{ page }}</a>
                {% endif %}
            {% endfor %}

            <a href={{ path('ErlemJobeetBundle_category', { 'slug': category.slug, 'page': next_page }) }}>
                <img src={{ asset('bundles/erlemjobeet/images/next.png') }} alt="Next page" title="Next page" />
            </a>

            <a href={{ path('ErlemJobeetBundle_category', { 'slug': category.slug, 'page': last_page }) }}>
                <img src={{ asset('bundles/erlemjobeet/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

Allez à la page : http://jobeet.local/app_dev.php/category/programming

02-106-symfony2-jobeet-jour-07-jouons-avec-la-page-categorie

Code source

Le code source est sur GitHub. Vous pouvez le télécharger en exécutant les commandes ci-dessous (prérequis Symfony2 - Jobeet - Jour 01 - Démarrage du projet) :

su
cd /var/www/
git clone https://github.com/erlem/jobeet.git
cd jobeet/
git checkout tags/v0.7.0
 
composer update
 
php app/console doctrine:database:create
php app/console doctrine:schema:update --force
php app/console doctrine:fixtures:load
 
php app/console assets:install web

chmod 777 -R app/cache/
chmod 777 -R app/logs/

Vous pouvez maintenant tester l'application dans un navigateur: http://jobeet.local/ ou dans l'environnement de développement, http://jobeet.local/app_dev.php.


Symfony2 - Jour 06 - Aller plus loin avec le Modèle
[Sommaire] Symfony2 - Jour 08 - Les tests unitaires >


Pour ce tutoriel, je me suis inspiré du tutoriel Symfony2 Jobeet du site IntelligentBee

Submit to DeliciousSubmit to DiggSubmit to FacebookSubmit to Google PlusSubmit to StumbleuponSubmit to TechnoratiSubmit to TwitterSubmit to LinkedIn