Custom Poll module - part 3 - Bloc en FO

Posté le: mer 27/03/2019 - 21:40 Par: rcowebdev

ll faut créer le répertoire Plugin/Block et y ajouter la classe Poll.php mais avant ca, on va se créer un PollManager afin de centraliser un peu les demandes côté BDD et l’exploiter dans notre nouveau bloc via injection de dépendance (d’ailleurs je devrais l’utiliser aussi dans la première partie, mais bon, on finit et on remettra tout ca au propre).

src/PollManager.php

<?php

namespace Drupal\Poll;

use Drupal\Core\Database\Connection;

/**
 * Poll manager
 */
class PollManager {
  
  /**
   * Database connection
   *
   * @var \Drupal\Core\Database\Connection
   */
  protected $connection;
 
  /**
   * @var Drupal\Core\Database\Connection
   */
  public function __construct(Connection $connection) {
    $this->connection = $connection;
  }
  
  /**
   * @param int $id
   *
   * @return array
   */
  public function getPoll($id) {
    $query = $this->connection->select('poll_question', 'pq');
    $query->innerJoin('poll_question_answer', 'pqa', 'pq.id = pqa.question_id'); // join ou innerJoin c'est pareil 
    $query->innerJoin('poll_answer', 'pa', 'pqa.answer_id = pa.id');
    $query->leftJoin('poll_result', 'pr', 'pr.answer_id = pa.id AND pr.question_id = pq.id');
    $query->condition('pq.id', $id);
    $query->fields('pq', ['id', 'name']);
    $query->fields('pa', ['id', 'name']);
    $query->addExpression('COUNT(pr.id)', 'nbr');
    $query->groupBy('pa.id, pq.id');
    $results = $query->execute()
      ->fetchAll();

    if ($results) {
      return [
          'question'    => $results[0]->name,
          'question_id' => $results[0]->id,
          'answers'     => $results
      ];
    }  
  }

  /**
   * @param int $questionId
   * @param int $answerId
   *
   * @return array
   */
  public function isAvailable($questionId, $answerId) {
    $query = $this->connection->select('poll_question', 'pq');
    $query->innerJoin('poll_question_answer', 'pqa', 'pq.id = pqa.question_id'); // join ou innerJoin c'est pareil 
    $query->condition('pq.id', $questionId);
    $query->condition('pqa.answer_id', $answerId);
    $query->range(0, 1);
    $query->fields('pq', ['id']);
    return $query->execute()
      ->fetchField();
  }

  /**
   * @param int     $questionId
   * @param varchar $clientIp
   *
   * @return array
   */
  public function hasPolled($questionId, $clientIp) {
    $query = $this->connection->select('poll_result', 'pr');
    $query->condition('pr.question_id', $questionId);
    $query->condition('pr.ip', $clientIp);
    $query->range(0, 1);
    $query->fields('pr', ['id']);
    return $query->execute()
      ->fetchField();
  }

  /**
   * @return array
   */
  public function getQuestions() {
    $query = $this->connection->select('poll_question', 'pq');
    $query->fields('pq', ['id', 'name']);
    return $query->execute()
      ->fetchAll();
  }

  /**
   * @param int     $questionId
   * @param int     $answerId
   * @param varchar $clientIp
   *
   * @return void
   */
  public function saveResult($questionId, $answerId, $clientIp) {
    $id = $this->connection->insert('poll_result')
      ->fields([
        'question_id' => $questionId,
        'answer_id'   => $answerId,
        'ip'          => $clientIp
      ])
      ->execute();
  }
}

poll.services.yml

services:
  poll.manager:
   class: Drupal\poll\PollManager
   arguments: ['@database', '@request_stack']
   tags:
     - { name: poll_manager }

src/Plugin/Block/Poll.php

<?php

namespace Drupal\poll\Plugin\Block;

use Drupal\Poll\PollManagerInterface;
use Drupal\Core\Block\BlockBase;
use Drupal\Core\Block\BlockPluginInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\RequestStack;

/**
 * Provides a Poll Block.
 *
 * @Block(
 *   id = "poll_block",
 *   admin_label = @Translation("Poll block")
 * )
 */
class Poll extends BlockBase implements BlockPluginInterface, ContainerFactoryPluginInterface {
  
  /**
   * @var Drupal\Poll\PollManagerInterface
   */
  protected $pollManager;

  /**
   * @var Symfony\Component\HttpFoundation\RequestStack
   */
  private $requestStack;

  /**
   * {@inheritdoc}
   */
  public function __construct(
    array $configuration, 
    $plugin_id, 
    $plugin_definition, 
    PollManagerInterface $pollManager, 
    RequestStack $request_stack
  ) {
    parent::__construct($configuration, $plugin_id, $plugin_definition);
    $this->pollManager  = $pollManager;
    $this->requestStack = $request_stack;
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
    return new static(
      $configuration,
      $plugin_id,
      $plugin_definition,
      $container->get('poll.manager'),
      $container->get('request_stack')
    );
  }

  /**
   * {@inheritdoc}
   */
  public function build() {
    $config     = $this->getConfiguration();
    $questionId = $this->configuration['poll_question'];
    $poll       = $this->pollManager->getPoll($questionId);
    $clientIp   = $this->requestStack->getCurrentRequest()->getClientIp();
    $theme      = 'poll_template';

    if ($this->pollManager->hasPolled($questionId, $clientIp)) {
      $theme = 'poll_result_template';
    }

  	return [
      '#theme'  => $theme,
      '#poll'   => $poll,
      '#cache' => ['max-age' => 0]
    ];
  }

  /**
   * {@inheritdoc}
   */
  public function blockForm($form, FormStateInterface $form_state) {
    $form       = parent::blockForm($form, $form_state);
    $config     = $this->getConfiguration();
    $questions  = $this->pollManager->getQuestions();
    $options    = [];

    foreach ($questions as $question) {
      $options[$question->id] = $question->name;
    }
    
    $form['poll_question'] = [
      '#type'           => 'select',
      '#title'          => $this->t('Question'),
      '#description'    => $this->t('Select the question'),
      '#options'        => $options,
      '#default_value'  => isset($config['poll_question']) ? $config['poll_question'] : '',
    ];

    return $form;
  }

   /**
   * {@inheritdoc}
   */
  public function blockSubmit($form, FormStateInterface $form_state) {
    parent::blockSubmit($form, $form_state);
    $values = $form_state->getValues();
    $this->configuration['poll_question'] = $values['poll_question'];
  }
}

En se rendant en BO dans le layout des blocs et via le lien place block on peut y ajouter notre bloc Poll (si rien ne se passe lorsque vous cliquez pour ajouter le bloc Poll, il faut vérifier la console du navigateur et c’est probabement une 500 et dans ce cas, il faut checker les logs serveurs (me concernant je n’avais pas respecté le contrat d’interface des Blocs et Drupal m’a envoyé boulé).

Bloc en place, maintenant, l’idée est de récupérer un sondage (une question et ses réponses), on verra plus tard pour les résultats. Alors j’ai cherché dans le Core des exemples de création de bloc mais je n’ai rien trouvé de très très concret donc je vais le faire un peu au feeling (quand je disais que tout ne sera pas parfait dans ce dossier, on est en plein dedans :p).

Tout est prêt pour afficher notre sondage en FO, il nous faut cependant un fichier de templating, aucune idée de comment procéder, en regardant la doc. ca n’a pas l’air très compliqué, on va essayer.

Drupal 8 conserve (encore mais pas pour très longtemps) la notion de hook et on va justement utiliser hook_theme. On crée un fichier poll.module à la racine du module poll.

poll.module

<?php

function poll_theme($existing, $type, $theme, $path) {
  return [
    'poll_template' => [
      'variables' => [
      	'poll' => null
      ],
    ]
  ];
}

On crée le dossier templates à la racine du module poll et on y place notre fichier de template twig poll-template.html.twig (poll-template fait référence à poll_template de notre hook_theme, les “_” sont remplacés par des “-”).

On pourrait faire un formulaire pour soumettre ses réponses mais je vais opter pour des liens, l’idéal serait de le soumettre via AJAX pour l’expérience utilisateur mais bon ce sera quand on mettra au propre.

On passe aux résultats ! :)

Mots clés
Créer un module Drupal 8
Drupal 8
Dossier