Development

Documentation/fr_FR/askeet/trunk/D7

You must first sign up to be able to contribute.

Version 7 (modified by forresst, 10 years ago)
Suite et fin de la traduction du jour 7

Cette partie de la documentation est en cours de traduction. Cela signifie qu'elle est traduite de manière soit incomplète, soit inexacte. En attendant que cette traduction soit terminée, vous pouvez consulter la version en anglais pour des informations plus fiables.

Calendrier de l’avent septième jour : Manipulation du modèle et de la vue

Précédemment dans Symfony

Six jours se sont déjà écoulés, et vous pouvez penser que l'application n'est pas très utile jusqu'ici. C'est parce que certains considèrent l'utilité d'une application par rapport au nombre de pages disponibles, et ils voient que l'askeet peut seulement lui afficher une liste de questions, lui afficher des réponses, et manipuler des sessions d'utilisateur.

La raison pour laquelle nous ne donnons pas tellement d'importance au nombre de pages, c'est qu'il est si facile d'ajouter de nouvelles pages avec symfony. Vous en voulez la preuve ? Ok, aujourd'hui nous afficherons une liste des dernières questions posées et une liste des réponses postées, une liste d'utilisateurs intéressés par une question, le profil d'un utilisateur, et nous ajouterons une barre de navigation à chaque page pour accéder à ces dispositifs. Comme cela ne représentera pas beaucoup de travail pendant une heure, nous installerons également la configuration de vue et irons voir ce qui a été fait pendant cette semaine. Prêt ? Partez.

La pré factorisation

Ainsi, nous allons ajouter les listes paginées avec des commandes de mise en page semblables à celles dans question/templates/_list.php. Nous n'aimons pas nous répéter, ainsi nous extrairons le code de mise en page à partir de celui des aides personnalisées. Une aide est une fonction de PHP rendue accessible aux templates (de la même manière que les aides link_to() et format_date()).

Créez le fichier GlobalHelper.php dans askeet/apps/frontend/lib/helper et ajoutez lui le code suivant:

[php]
<?php

function pager_navigation($pager, $uri)
{
  $navigation = '';

  if ($pager->haveToPaginate())
  {  
    $uri .= (preg_match('/\?/', $uri) ? '&' : '?').'page=';

    // First and previous page
    if ($pager->getPage() != 1)
    {
      $navigation .= link_to(image_tag('first.gif', 'align=absmiddle'), $uri.'1');
      $navigation .= link_to(image_tag('previous.gif', 'align=absmiddle'), $uri.$pager->getPreviousPage()).'&nbsp;';
    }

    // Pages one by one
    $links = array();
    foreach ($pager->getLinks() as $page)
    {
      $links[] = link_to_unless($page == $pager->getPage(), $page, $uri.$page);
    }
    $navigation .= join('&nbsp;&nbsp;', $links);

    // Next and last page
    if ($pager->getPage() != $pager->getCurrentMaxLink())
    {
      $navigation .= '&nbsp;'.link_to(image_tag('next.gif', 'align=absmiddle'), $uri.$pager->getNextPage());
      $navigation .= link_to(image_tag('last.gif', 'align=absmiddle'), $uri.$pager->getLastPage());
    }

  }

  return $navigation;
}

L'aide à la navigation dans les pages améliore le code que nous avons précédemment écrit : elle permet n'importe quelle règle de routage, afin de ne pas faire afficher les liens previous pour la première page ni les liens next pour la dernière page. Nous avons également ajouté quatre nouvelles images (first.gif, previous.gif, next.gif et last.gif) pour rendre les liens plus jolis. Récupérez-les dans le répertoire SVN de l'askeet. Vous réutiliserez probablement cette aide à l'avenir pour vos propres projets.

Pour employer cette aide dans le fragment de question/templates/_list.php, appelez la fonction d'aide comme suit:

[php]
<?php use_helper('Text', 'Global') ?>

<?php foreach($question_pager->getResults() as $question): ?>
  <div class="question">
    <div class="interested_block">
      <?php include_partial('interested_user', array('question' => $question)) ?>
    </div>

    <h2><?php echo link_to($question->getTitle(), 'question/show?stripped_title='.$question->getStrippedTitle()) ?></h2>

    <div class="question_body">
      <?php echo truncate_text($question->getBody(), 200) ?>
    </div>
  </div>
<?php endforeach; ?>

<div id="question_pager">
  <?php echo pager_navigation($question_pager, 'question/list') ?>
</div>

Le nom Global se réfère au fichier GlobalHelper.php que vous venez juste de créer.

Vérifiez que tout fonctionne comme avant avec l'adresse:

http://askeet/frontend_dev.php/

Refactorisation de la navigation des pages

Liste des dernières questions

Dans le module question, créez une nouvelle action recent:

[php]
public function executeRecent()
{
  $this->question_pager = QuestionPeer::getRecentPager($this->getRequestParameter('page', 1));
}

C'est aussi simple que cela. Nous considérons que la capacité de saisir les dernières questions devrait être une méthode de classe de QuestionPeer. La classe -Peer est consacrée aux listes de retour d'objets d'une classe donnée - Ceci est expliqué en détail dans le chapitre du modèle du book de symfony. La méthode de classe getRecent() doit être créée. Ouvrez la classe askeet/lib/model/QuestionPeer.php et ajoutez lui ceci:

[php]
public static function getRecentPager($page)
{
  $pager = new sfPropelPager('Question', sfConfig::get('app_pager_homepage_max'));
  $c = new Criteria();
  $c->addDescendingOrderByColumn(self::CREATED_AT);
  $pager->setCriteria($c);
  $pager->setPage($page);
  $pager->setPeerMethod('doSelectJoinUser');
  $pager->init();

  return $pager;
}


Le tri en ordre décroissant des dates de création choisira les dernières questions. Cette méthode utilise self au lieu de parent parce que c'est une fonction de classe, et pas une fonction d'objet. La raison pour laquelle nous faisons ici un doSelectJoinUser() au lieu d'un simple doSelect(), c'est parce que nous savons que le template aura besoin des coordonnées de l'auteur de la question. Cela signifierait une première demande de la liste des questions, plus une demande par question pour obtenir l'utilisateur concerné. La méthode doSelectJoinUser() fait tout cela dans une seule requête : quand nous demandons

[php]
$question->getUser();

...il n'y a qu'une requête envoyée à la base de données. Le joinUser nous permet de réduire le nombre de requête à 1 + le nombre de questions à seulement 1. La base de données nous remerciera de cette optimisation facile.

La documentation Propel vous donnera toutes les explications au sujet de ce grand dispositif.

Le template de la liste des dernières questions ressemblera beaucoup à la liste des questions affichées dans la page d'accueil. Créez askeet/apps/frontend/module/question/templates/recentSuccess.php avec:

[php]
<h1>recent questions</h1>

<?php include_partial('list', array('question_pager' => $question_pager)) ?>

Vous comprenez maintenant pourquoi nous avons refactorisé la liste des question dans un fragment pendant le jour cinq. En conclusion, vous avez besoin d'ajouter une règle à recent_questions dans le fichier de configuration frontend/config/routing.yml, comme expliqué durant le quatrième jour:

recent_questions:
  url:   /question/recent/:page
  param: { module: question, action: recent, page: 1 }

Mais attendez: le fragment question/_list crée des liens avec la règle de routage question/list, en l'utilisant, cela ne fonctionnera pas pour la liste des dernières questions. Nous avons besoin que la règle de routage soit passée en paramètre au fragment afin qu'elle puisse être réutilisée pour plusieurs pages. Donc changez la dernière ligne de recentSuccess.php par:

[php]
<?php include_partial('list', array('question_pager' => $question_pager, 'rule' => 'question/recent')) ?>

Et également changer les dernières lignes du fragment _list.php par:

[php]
<div id="question_pager">
  <?php echo pager_navigation($question_pager, $rule) ?>
</div>

N'oubliez pas d'ajouter également le paramètre de règle dans l'appel au fragment _list dans modules/question/templates/listSuccess.php.

[php]
<h1>popular questions</h1>

<?php echo include_partial('list', array('question_pager' => $question_pager, 'rule' => 'question/list')) ?>

Vider le cache (la configuration a été modifiée), et c'est tout..

Pour afficher la liste des dernières questions, saisissez dans la zone URL de votre navigateur:

http://askeet/question/recent

liste des dernières questions

Liste des dernières réponses

C'est à peu près la même chose que ci-dessus, et nous serons donc assez exhaustif sur celui-ci:

  • Créez le module answer:

    $ symfony init-module frontend answer
    
  • Créez une nouvelle action recent:

    [php]
    public function executeRecent()
    {
      $this->answer_pager = AnswerPeer::getRecentPager($this->getRequestParameter('page', 1));
    }
    
  • Etendez la classe AnswerPeer:

    [php]
    public static function getRecentPager($page)
    {
      $pager = new sfPropelPager('Answer', sfConfig::get('app_pager_homepage_max'));
      $c = new Criteria();
      $c->addDescendingOrderByColumn(self::CREATED_AT);
      $pager->setCriteria($c);
      $pager->setPage($page);
      $pager->setPeerMethod('doSelectJoinUser');
      $pager->init();
    
      return $pager;
    }
    
  • Créez un nouveau template recentSuccess.php:

    [php]
    <?php use_helper('Date', 'Global') ?>
    
    <h1>recent answers</h1>
    
    <div id="answers">
    <?php foreach ($answer_pager->getResults() as $answer): ?>
      <div class="answer">
        <h2><?php echo link_to($answer->getQuestion()->getTitle(), 'question/show?stripped_title='.$answer->getQuestion()->getStrippedTitle()) ?></h2>
        <?php echo count($answer->getRelevancys()) ?> points
        posted by <?php echo link_to($answer->getUser(), 'user/show?id='.$answer->getUser()->getId()) ?> 
        on <?php echo format_date($answer->getCreatedAt(), 'p') ?>
        <div>
          <?php echo $answer->getBody() ?>
        </div>
      </div>
    <?php endforeach ?>
    </div>        
    
    <div id="question_pager">
      <?php echo pager_navigation($answer_pager, 'answer/recent') ?>
    </div>
    
  • Testez le dans votre navigateur:

    http://askeet/answer/recent
    

liste des dernières réponses

Vous vous habituez à lui, n'est-ce pas ?

Note: Ceux qui ont prêté attention au jour 4, ont probablement reconnu le morceau de code utilisé pour afficher les détails d'une réponse. Puisque ce code est employé dans au moins deux endroits, nous allons refactoriser et créer un _answer.php partiel, destiné à être utilisé aussi bien dans question/show et dans answer/recent. Les détails peuvent être trouvés dans le répertoire SVN de l'askeet.

Profil utilisateur

Le nom de l'utilisateur dans une réponse sera relié à une action de user/show (qui reste à écrire). Ce sera le profil de l'utilisateur, et il montrera les dernières questions et réponses postées, ainsi que quelques détails au sujet de l'utilisateur.

La première chose à faire est de créer l'action:

[php]
public function executeShow()
{
  $this->subscriber = UserPeer::retrieveByPk($this->getRequestParameter('id', $this->getUser()->getSubscriberId()));
  $this->forward404Unless($this->subscriber);


$this->interests = $this->subscriber->getInterestsJoinQuestion(); $this->answers = $this->subscriber->getAnswersJoinQuestion(); $this->questions = $this->subscriber->getQuestions(); }

Les méthodes ->getInterestsJoinQuestion() et ->getAnswersJoinQuestion() sont des méthodes natives de la classe User. Vous pouvez examiner la classe askeet/lib/model/om/BaseUser.php pour voir comment elles fonctionnent.

Le template askeet/apps/frontend/modules/user/templates/showSuccess.php ne devrait pas vous poser de problème:

[php]
<h1><?php echo $subscriber ?>?s profile</h1>

<h2>Interests</h2>

<ul>
<?php foreach ($interests as $interest): $question = $interest->getQuestion() ?>
  <li><?php echo link_to($question->getTitle(), 'question/show?stripped_title='.$question->getStrippedTitle()) ?></li>
<?php endforeach; ?>
</ul>

<h2>Contributions</h2>

<ul>
<?php foreach ($answers as $answer): $question = $answer->getQuestion() ?>
  <li>
    <?php echo link_to($question->getTitle(), 'question/show?stripped_title='.$question->getStrippedTitle()) ?><br />
    <?php echo $answer->getBody() ?>
  </li>
<?php endforeach; ?>
</ul>

<h2>Questions</h2>

<ul>
<?php foreach ($questions as $question): ?>
  <li><?php echo link_to($question->getTitle(), 'question/show?stripped_title='.$question->getStrippedTitle()) ?></li>
<?php endforeach; ?>
</ul>

Naturellement, vous pourriez souhaiter limiter le nombre de résultat retourné par chacune des méthodes ->getInterestsJoinQuestion(), ->getAnswersJoinQuestion() et getQuestion() de l'objet User, comme l'ordre de tri. Il est tout simplement fait par une de ces méthodes, dans le fichier de classe askeet/lib/model/User.php, et nous ne dévoilerons pas ici comment le faire - mais la release d'aujourd'hui l'inclura.

Il est temps pour l'essai final. Voyons ce que le premier utilisateur a fait:

http://askeet/user/show/id/1

profil utilisateur

Maintenant nous pouvons également lier un profil d'utilisateur à une question. Ajoutez la ligne suivante à question/templates/showSuccess.php et question/templates/_list.php au début du div question_body:

[php]
<div>asked by <?php echo link_to($question->getUser(), 'user/show?id='.$question->getUser()->getId()) ?> on <?php echo format_date($question->getCreatedAt(), 'f') ?></div>

N'oubliez pas de déclarer l'utilisation de l'aide Date dans _list.php.

Ajouter une barre de navigation

Nous changerons la disposition globale pour ajouter une barre latérale. Cette barre posséde le contenu dynamique, mais comme nous voulons régler sa position dans la mise en page, il ne peut pas faire partie de chaque template. En outre, en mettant le code de la barre dans le template, cela signifierait le répéter plusieurs fois, et vous savez que nous n'aimons pas faire cela.

C'est pourquoi la barre sera un composant. Un composant est le résultat d'une action (par exemple le code HTML résulte de l'exécution d'un template) mis à disposition dans une variable. Le chapitre sur la vue du book symfony explique ce qu'est un composant, et les différences entre un composant et un fragment.

Ajouter le composant dans le gabarit principal

Ouvrez le gabarit global (askeet/apps/frontend/templates/layout.php). Vous souvenez-vous de cette partie du code:

[php]
<div id="content_bar">
  <!-- Nothing for the moment -->
  <div class="verticalalign"></div>
</div>

Remplacer le commentaire par

[php]
  <?php include_component_slot('sidebar') ?>

Et c'est tout.

Préciser quelle action va dans le composant

Nous avons décidé d'utiliser quelque chose d'un peu plus puissant qu'un simple composant: un connecteur de composant. C'est un élément dont l'action peut être modifiée en fonction de l'appel de l'action - permettant du contenu contextuel. C'est la configuration de vue (écrite dans le fichier view.yml) qui définit quelle action correspond à un connecteur de composant:

default:
  components:
    sidebar:      [sidebar, default]

Dans cet exemple, le connecteur de composant nommé sidebar est déclaré pour être le résultat de l'action default du module sidebar.

La configuration de vue peut être définie pour l'application entière (dans le répertoire askeet/apps/frontend/config/) ou spécialement pour un module (dans le réertoire askeet/apps/frontend/modules/mymodule/config/). Pour notre cas, nous le définirons pour l'application entière, et nous le redéfinirons, si nécessaire, pour fournir dans un contexte spécifique des liens dans la barre latérale.

Alors ouvrez askeet/apps/frontend/config/view.yml et ajoutez la configuration du connecteur de composant illustrée ci-dessus. Vous trouverez plus d'informations sur la configuration de vue dans le chapitre relatif du livre de symfony.

Ecrire l'action sidebar/default et le template

D'abord, nous laisserons symfony initialiser le nouveau module sidebar:

$ symfony init-module frontend sidebar

Ensuite, nous avons besoin d'écrire un composant default. Dans le répertoire askeet/apps/frontend/modules/sidebar/actions/, renommez actions.class.php en components.class.php, et changez son contenu par:

[php]
<?php 

class sidebarComponents extends sfComponents
{
  public function executeDefault()
  {
  }
}

Une vue de composant est un template, tout comme pour une action. La différence est dans la nomination: Une vue de composant est nommée comme un fragment (commençant par le _) plutôt que comme un template (se terminant par Success). Créez ainsi un fragment askeet/apps/frontend/modules/sidebar/templates/_default.php (et effacez indexSuccess.php qui ne sera pas utilisé) avec le contenu suivant:

[php]
<?php echo link_to('ask a new question', 'question/add') ?>

<ul>
  <li><?php echo link_to('popular questions', 'question/list') ?></li>
  <li><?php echo link_to('latest questions', 'question/recent') ?></li>
  <li><?php echo link_to('latest answers', 'answer/recent') ?></li>
</ul>

Nous avons modifié le fichier de configuration view.yml, mais les actions dans l'environnement de production ne les voit pas. Ils utilisent la version en cache - celle qui ne contient pas la configuration des connecteurs de composant. Si vous voulez voir les modifications, soit vous effacez la mémoire cache soit vous naviguez dans l'environnement de développement:

$ symfony clear-cache

ou

http://askeet/frontend_dev.php/

La barre de navigation est affichée correctement sur chaque page

sidebar

Note: C'est un effet général de la configuration d'environnement de production. Ainsi vous devez vous rappeler qu'il faut employer l'environnement de développement pendant la phase de développement (quand vous changez beaucoup la configuration), et effacer la mémoire cache lorsque vous naviguez dans l'environnement de production après chaque changement dans la configuration.

Un peu plus sur la configuration de la vue

Pendant que nous sommes là, nous allons jeter un coup d'œil au fichier de configuration de l'application view.yml dans apps/config/:

default:
  http_metas:
    content-type: text/html; charset=utf-8

  metas:
    title:        symfony project
    robots:       index, follow
    description:  symfony project
    keywords:     symfony, project
    language:     en

  stylesheets:    [main, layout]

  javascripts:    []

  has_layout:     on
  layout:         layout

  components:
    sidebar:      [sidebar, default]

Le section metas contient une configuration pour les metas tags de l'ensemble du site. La clé title définit également que le titre est affiché dans la barre de titre de la fenêtre du navigateur. Ce titre est très important, parce que c'est la première chose que voit un utilisateur du site si elle est trouvée par un moteur de recherche. Il est donc nécessaire de le changer pour quelque chose de plus adapté au site askeet:

  metas:
    title:        askeet! ask questions, find answers
    robots:       index, follow
    description:  askeet!, a symfony project built in 24 hours
    keywords:     symfony, project, askeet, php5, question, answer
    language:     en

Rafraîchissez la page courante. Si vous ne constatez pas de changement, c'est parce que vous êtes dans l'environnement de production, et vous devriez d'abord effacer la mémoire cache, pour obtenir le bon titre de la fenêtre:

window title

Note: En plus de fournir un titre par défaut pour vos pages du projet, symfony par défaut crée un fichier robots.txt et favicon.ico dans le répertoire web racine (askeet/web/). N'oubliez pas de les changer également !

Note: Vous pourriez avoir besoin de changer le titre pour chaque page de votre site. Vous pouvez faire cela en définissant une configuration personnalisée de view.yml pour chaque module, mais cela ne vous permet que de donner des titres statiques. Alternativement, vous pouvez utiliser une valeur dynamique d'une action avec la méthode ->setTitle(), comme décrit dans le chapitre de configuration de vue:

  [php]
  $this->getResponse()->setTitle($title);

Regarder ce que nous avons fait

Il s'agit d'une tradition générale de s'arrêter et de regarder ce que vous avez fait lorsque vous avez atteint le septième jour. C'est une bonne occasion de documenter un certain nombre de choses, y compris le modèle de données courant et les actions disponibles.

En fait, vous devez documenter votre code pendant que vous l'écrivez, par exemple en utilisant PHP doc-style des commentaires pour chaque méthode. Le truc avec un projet symfony est que les noms utilisés dans les méthodes ou les fonctions servent souvent d'explication de leur but et de leur utilisation. Les méthodes sont gardés courtes, et sont donc très lisibles. La plupart du temps, les templates utilisent seulement les instructions foreach et if statements qui sont assez explicites. C'est pourquoi le code que vous trouverez dans le directoire SVN de l'askeet ne contiennent pas beaucoup de documentation - plus le fait que nous avons déjà écrit sept heures d'explication sur les travaux que nous avons fait!

Maintenant, jetez un oeil sur le diagramme mis à jour des entités et des relations:

ERD

La liste des actions disponibles est la suivante:

answer/
  recent
question/
  list
  show
  recent
sidebar/
  default (component)
user/
  show
  login
  logout
  handleErrorLogin

Le modèle contient également les méthodes suivantes:

Anwser()
  getRelevancyUpPercent()
  getRelevancyDownPercent()
AnswerPeer::
  getRecentPager()
Interest->
  save()
Question->
  setTitle()
QuestionPeer::
  getQuestionFromTitle()
  getHomepagePager()
  getRecentPager()
Relevancy
  save()
User->
  __toString()
  setPassword()

myUser->
  signIn()
  signOut()
  getSubscriberId()
  getSubscriber()
  getNickName()

...plus les outils personnalisés de classe et un validateur personnalisé, placé dans le répertoire askeet/apps/frontend/lib/.

Ce n'est pas mauvais pendant sept heures, n'est-ce pas ?

A demain

L'application a beaucoup progressé aujourd'hui, et c'était très rapide à faire. Tout est maintenant prêt pour injecter un certain AJAX dans l'interaction homme-machine. Demain, les utilisateurs pourront ouvrir une session et déclarer leur intérêt pour une question utilisant AJAX. Ne le manquez pas !

Vous pouvez encore télécharger la totalité du code d'aujourd'hui dans le répertoire SVN de l'askeet, tag release_day_7. La mailing-list d’askeet répondra à toutes vos questions plus rapidement que la vitesse de la lumière.