Jobeet FR

Le tutoriel pour Symfony2 en français

Vue normale

Dans ce chapitre, nous allons personnaliser le contrôleur Job que nous avons créé. Il possède déjà la plupart du code nous avons besoin pour Jobeet:

  • - Une page pour lister toutes les offres
  • - Une page pour créer une nouvelle offre
  • - Une page pour mettre à jour une offre existante
  • - Une page pour supprimer une offre

Bien que le code soit prêt à être utilisé tel quel, nous devons modifier les modèles pour correspondre à notre maquette Jobeet.


L'architecture MVC (Modèle Vue Contrôleur)

Pour le développement web, la solution la plus courante pour organiser le code de nos jours est le modèle de conception MVC. En bref, le modèle de conception MVC définit un moyen d'organiser votre code en fonction de sa nature. Ce modèle sépare le code en trois couches:

  • - La couche Modèle (Model) définit la logique métier (la BDD appartenant à cette couche). Vous savez déjà que Symfony2 stocke toutes les classes et les fichiers relatifs au modèle dans le répertoire de vos paquets Entity/.
  • - La Vue (View) est ce avec quoi l'utilisateur interagit (un moteur de template fait partie de cette couche). Dans Symfony2, la couche Vue est principalement faite de templates Twig. Ils sont stockés dans plusieurs répertoires Resources/views/ comme nous le verrons plus loin.
  • - Le Contrôleur (Controller) est un morceau de code qui appelle le modèle pour obtenir certaines données qu'il passe à la Vue pour le rendre au Client. Lorsque nous avons installé Symfony au début de ce tutoriel, nous avons vu que toutes les demandes sont gérées par des contrôleurs frontaux (app.php et app_dev.php). Ces contrôleurs frontaux délèguent le réel travail à des actions.

La mise en page

Si vous regardez de plus près la maquette, vous remarquerez que beaucoup de pages se ressemblent. Vous savez déjà que la duplication de code est mauvais, si nous parlons de code HTML ou PHP, nous avons donc besoin de trouver un moyen d'empêcher ces éléments communs de se dupliquer.

Une façon de résoudre le problème est de définir une en-tête et un pied de page et de les inclure dans chaque modèle. Une meilleure solution est d'utiliser un autre modèle pour résoudre ce problème: le modèle décorateur. Le modèle décorateur résout le problème dans l'autre sens: le template est décoré après que le contenu soit rendu par un template global, appelé layout.

Contrairement à Symfony 1.x, Symfony2 n'a pas de layout par défaut, mais nous allons en créer un et l'utiliser pour décorer nos pages d'application.

Créez un nouveau fichier layout.html.twig dans le répertoire src/Ens/JobeetBundle/Resources/views/ et ajoutez le code suivant:

<!DOCTYPE html>
<html>
  <head>
    <title>
      {% block title %}
        Jobeet - Your best job board
      {% endblock %}
    </title>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    {% block stylesheets %}
      <link rel="stylesheet" href="{{ asset('bundles/ensjobeet/css/main.css') }}" type="text/css" media="all" />
    {% endblock %}
    {% block javascripts %}
    {% endblock %}
    <link rel="shortcut icon" href="{{ asset('bundles/ensjobeet/images/favicon.ico') }}" />
  </head>
  <body>
    <div id="container">
      <div id="header">
        <div class="content">
          <h1><a href="{{ path('ens_job') }}">
            <img src="{{ asset('bundles/ensjobeet/images/logo.jpg') }}" alt="Jobeet Job Board" />
          </a></h1>
 
          <div id="sub_header">
            <div class="post">
              <h2>Ask for people</h2>
              <div>
                <a href="{{ path('ens_job') }}">Post a Job</a>
              </div>
            </div>
 
            <div class="search">
              <h2>Ask for a job</h2>
              <form action="" method="get">
                <input type="text" name="keywords" id="search_keywords" />
                <input type="submit" value="search" />
                <div class="help">
                  Enter some keywords (city, country, position, ...)
                </div>
              </form>
            </div>
          </div>
        </div>
      </div>
 
      <div id="content">
        {% if app.session.hasFlash('notice') %}
          <div class="flash_notice">
            {{ app.session.flash('notice') }}
          </div>
        {% endif %}
 
        {% if app.session.hasFlash('error') %}
          <div class="flash_error">
            {{ app.session.flash('error') }}
          </div>
        {% endif %}
 
        <div class="content">
            {% block content %}
            {% endblock %}
        </div>
      </div>
 
      <div id="footer">
        <div class="content">
          <span class="symfony">
            <img src="{{ asset('bundles/ensjobeet/images/jobeet-mini.png') }}" />
            powered by <a href="http://www.symfony.com/">
              <img src="{{ asset('bundles/ensjobeet/images/symfony.gif') }}" alt="symfony framework" />
            </a>
          </span>
          <ul>
            <li><a href="">About Jobeet</a></li>
            <li class="feed"><a href="">Full feed</a></li>
            <li><a href="">Jobeet API</a></li>
            <li class="last"><a href="">Affiliates</a></li>
          </ul>
        </div>
      </div>
    </div>
  </body>
</html>

Blocs Twig

Avec Twig, le moteur de template par défaut de Symfony2, vous pouvez définir des blocs comme nous l'avons fait ci-dessus. Un bloc Twig peut avoir un contenu par défaut (voir le bloc de titre par exemple) qui peut être remplacé ou étendu dans le template enfant comme vous le verrez dans un instant.

Maintenant, pour faire usage du nouveau layout que nous avons créé, nous avons besoin de modifier tous les modèles d'offres (edit, index, new et show à partir de src/Ens/JobeetBundle/Resources/views/job/) afin d'étendre le template parent (layout) et pour remplacer le bloc de contenu, nous avons défini:

{% extends 'EnsJobeetBundle::layout.html.twig' %}
 
{% block content %}
  <!-- original template code goes here -->
{% endblock %}

Les feuilles de style, images, et javascripts

Comme ce tutoriel n'est pas sur le webdesign, nous avons déjà préparé toutes les ressources que nous utiliserons pour Jobeet: téléchargez l'archive des images et mettez-les dans le répertoire src/Ens/JobeetBundle/Resources/public/images/ ; téléchargez l'archive des feuilles de style et mettez-les dans le répertoire src/Ens/JobeetBundle/Resources/public/css/.

Maintenant, exécutez la commande php app/console assets:install web pour dire à Symfony de les rendre accessibles au public.

Si vous regardez dans le dossier css, vous remarquerez que nous avons 4 fichiers css: admin.css, job.css, jobs.css et main.css. Le main.css est nécessaire dans toutes les pages Jobeet que nous avons inclus dans le layout, dans le bloc Twig des feuilles de style. Les autres fichiers css sont plus spécifiques et nous n'en n'avons besoin que dans certaines pages.

Pour ajouter un nouveau fichier CSS dans un template nous allons remplacer le bloc de feuilles de style, mais appeler le parent avant d'ajouter le nouveau fichier CSS (afin que nous ayons le fichier main.css et les fichiers CSS supplémentaires dont nous avons besoin).

<!-- src/Ens/JobeetBundle/Resources/views/Job/index.html.twig -->
{% extends 'EnsJobeetBundle::layout.html.twig' %}
 
{% block stylesheets %}
  {{ parent() }}
  <link rel="stylesheet" href="{{ asset('bundles/ensjobeet/css/jobs.css') }}" type="text/css" media="all" />
{% endblock %}
 
<!-- the rest of the code -->
<!-- src/Ens/JobeetBundle/Resources/views/Job/show.html.twig -->
{% extends 'EnsJobeetBundle::layout.html.twig' %}
 
{% block stylesheets %}
  {{ parent() }}
  <link rel="stylesheet" href="{{ asset('bundles/ensjobeet/css/job.css') }}" type="text/css" media="all" />
{% endblock %}
 
<!-- the rest of the code -->

L'action de la page d'accueil des offres

Chaque action est représentée par une méthode d'une classe. Pour la page d'accueil des offres, la classe est JobController et la méthode est indexAction(). Elle récupère toutes les offres d'emploi de la BDD:

public function indexAction()
{
    $em = $this->getDoctrine()->getEntityManager();
 
    $entities = $em->getRepository('EnsJobeetBundle:Job')->findAll();
 
    return $this->render('EnsJobeetBundle:Job:index.html.twig', array(
        'entities' => $entities
    ));
}

Nous allons examiner de plus près le code: la méthode indexAction() obtient l'objet de gestion de l'entité Doctrine, qui est responsable de la gestion du processus de la persistance et de la recherche des objets vers et à partir de la BDD, et puis le dépôt, qui permettra de créer une requête pour récupérer toutes les offres. Elle renvoie un Doctrine ArrayCollection d'objets Job qui sont passés au template (la Vue).


Le template de la page d'accueil des offres

Le template index.html.twig génère un tableau HTML pour toutes les offres. Voici le code du template actuel:

<!-- src/Ens/JobeetBundle/Resources/views/Job/index.html.twig -->
{% extends 'EnsJobeetBundle::layout.html.twig' %}
 
{% block stylesheets %}
  {{ parent() }}
  <link rel="stylesheet" href="{{ asset('bundles/ensjobeet/css/jobs.css') }}" type="text/css" media="all" />
{% endblock %}
 
{% block content %}
    <h1>Job list</h1>
 
    <table class="records_list">
        <thead>
            <tr>
                <th>Id</th>
                <th>Type</th>
                <th>Company</th>
                <th>Logo</th>
                <th>Url</th>
                <th>Position</th>
                <th>Location</th>
                <th>Description</th>
                <th>How_to_apply</th>
                <th>Token</th>
                <th>Is_public</th>
                <th>Is_activated</th>
                <th>Email</th>
                <th>Expires_at</th>
                <th>Created_at</th>
                <th>Updated_at</th>
                <th>Actions</th>
            </tr>
        </thead>
        <tbody>
        {% for entity in entities %}
            <tr>
                <td><a href="{{ path('ens_job_show', { 'id': entity.id }) }}">{{ entity.id }}</a></td>
                <td>{{ entity.type }}</td>
                <td>{{ entity.company }}</td>
                <td>{{ entity.logo }}</td>
                <td>{{ entity.url }}</td>
                <td>{{ entity.position }}</td>
                <td>{{ entity.location }}</td>
                <td>{{ entity.description }}</td>
                <td>{{ entity.howtoapply }}</td>
                <td>{{ entity.token }}</td>
                <td>{{ entity.ispublic }}</td>
                <td>{{ entity.isactivated }}</td>
                <td>{{ entity.email }}</td>
                <td>{% if entity.expiresat %}{{ entity.expiresat|date('Y-m-d H:i:s') }}{% endif%}</td>
                <td>{% if entity.createdat %}{{ entity.createdat|date('Y-m-d H:i:s') }}{% endif%}</td>
                <td>{% if entity.updatedat %}{{ entity.updatedat|date('Y-m-d H:i:s') }}{% endif%}</td>
                <td>
                    <ul>
                        <li>
                            <a href="{{ path('ens_job_show', { 'id': entity.id }) }}">show</a>
                        </li>
                        <li>
                            <a href="{{ path('ens_job_edit', { 'id': entity.id }) }}">edit</a>
                        </li>
                    </ul>
                </td>
            </tr>
        {% endfor %}
        </tbody>
    </table>
 
    <ul>
        <li>
            <a href="{{ path('ens_job_new') }}">
                Create a new entry
            </a>
        </li>
    </ul>
{% endblock %}

Nous allons nettoyer tout ça un peu pour n'afficher qu'un sous-ensemble des colonnes disponibles. Remplacez le contenu du bloc Twig avec celui ci-dessous:

{% block content %}
    <div id="jobs">
      <table class="jobs">
        {% for entity in entities %}
          <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 }) }}">
                {{ entity.position }}
              </a>
            </td>
            <td class="company">{{ entity.company }}</td>
          </tr>
        {% endfor %}
      </table>
    </div>
{% endblock %}


Le template d'une offre

Maintenant, nous allons personnaliser le template de la page d'offre. Ouvrez le fichier show.html.twig et remplacez son contenu par le suivant:

<!-- src/Ens/JobeetBundle/Resources/views/Job/show.html.twig -->
{% extends 'EnsJobeetBundle::layout.html.twig' %}
 
{% block title %}
    {{ entity.company }} is looking for a {{ entity.position }}
{% endblock %}
 
{% block stylesheets %}
    {{ parent() }}
    <link rel="stylesheet" href="{{ asset('bundles/ensjobeet/css/job.css') }}" type="text/css" media="all" />
{% endblock %}
 
{% block content %}
    <div id="job">
      <h1>{{ entity.company }}</h1>
      <h2>{{ entity.location }}</h2>
      <h3>
        {{ entity.position }}
        <small> - {{ entity.type }}</small>
      </h3>
 
      {% if entity.logo %}
        <div class="logo">
          <a href="{{ entity.url }}">
            <img src="/uploads/jobs/{{ entity.logo }}"
              alt="{{ entity.company }} logo" />
          </a>
        </div>
      {% endif %}
 
      <div class="description">
        {{ entity.description|nl2br }}
      </div>
 
      <h4>How to apply?</h4>
 
      <p class="how_to_apply">{{ entity.howtoapply }}</p>
 
      <div class="meta">
        <small>posted on {{ entity.createdat|date('m/d/Y') }}</small>
      </div>
 
      <div style="padding: 20px 0">
        <a href="{{ path('ens_job_edit', { 'id': entity.id }) }}">
          Edit
        </a>
      </div>
    </div>
{% endblock %}


L'action de la page des offres

La page des offres est générée par l'action show, définie dans showAction() de JobController:

public function showAction($id)
{
    $em = $this->getDoctrine()->getEntityManager();
 
    $entity = $em->getRepository('EnsJobeetBundle:Job')->find($id);
 
    if (!$entity) {
        throw $this->createNotFoundException('Unable to find Job entity.');
    }
 
    $deleteForm = $this->createDeleteForm($id);
 
    return $this->render('EnsJobeetBundle:Job:show.html.twig', array(
        'entity'      => $entity,
        'delete_form' => $deleteForm->createView(),
 
    ));
}

Comme dans l'action index, le dépôt EnsJobeetBundle:job est utilisée pour récupérer une offre, cette fois en utilisant la méthode find(). Le paramètre de cette méthode est l'identifiant unique d'une offre, sa clé primaire. La section suivante explique pourquoi le paramètre $id de la fonction actionShow() contient la clé primaire de l'offre.

Si l'offre n'existe pas dans la BDD, nous voudrons renvoyer l'utilisateur vers une page d'erreur 404, ce que fait exactement throw $this->createNotFoundException().

En ce qui concerne les exceptions, la page affichée à l'utilisateur est différente dans l'environnement prod et dans l'environnement dev:


Chapitre précédent Chapitre suivant


Une question ? Une réaction ?

comments powered by Disqus