05/03/2023

0. Agenda

Lu 457 fois Licence Creative Commons

Menu

Ajoutons le lien vers la page agenda dans le menu dans /templates/_template.html.twig avec la fonction path() à laquelle on indique le nom de la route souhaitée:

    {# ... #}

            <div class="navbar-start">
                <a class="navbar-item" href="{{ path('event_agenda') }}">
                    Agenda
                </a>
            </div>

    {# ... #}

Controlleur

La page agenda doit lister les événements pour une date donnée. La date doit être récupérée depuis l'URL: /event/agenda?date=2022-09-16
Ajoutez un argument Symfony\Component\HttpFoundation\Request à la méthode concernée pour la récupérer. Si la date n'est pas fournie ou n'est pas au bon format, on prendra la date du jour:

    #[Route('/agenda', name: 'agenda')]
    public function agenda(Request $request, EventRepository $eventRepository): Response
    {
        // Récupération de la date courante
        $date = \DateTimeImmutable::createFromFormat('Y-m-d', $request->query->get('date', date('Y-m-d')));
        if (!$date) {
            $date = new \DateTimeImmutable();
        }
		
		// ...
	}

La classe DateTimeImmutable retourne une nouvelle instance lorsque l'on tente de la modifier. Elle nous sera utile pour ne pas changer la valeur de $date par la suite.

On calcule ensuite les dates précédentes et suivantes:

// Dates précédente/suivante
$previous = $date->sub(new \DateInterval('P1D'));
$next = $date->add(new \DateInterval('P1D'));

Ici on soustrait et additionne une intervalle de temps de 1 jour. Puisque $date est une instance de DateTimeImmutable, les méthodes add() et sub() n'ont pas affecté sa valeur et ont retourné une copie modifiée.

On construit enfin une période d'un mois, pour afficher une liste des prochains jours à l'utilisateur:

// Construction d'une période d'un mois
$periodStart = min($date, new \DateTimeImmutable());
$periodEnd = $periodStart->add(new \DateInterval('P1M'));
$period = new \DatePeriod($periodStart, new \DateInterval('P1D'), $periodEnd);

La période commence par la date du jour ou la date actuellement visualisée si elle est plus ancienne.
On ajoute une intervalle d'un mois pour délimiter la fin.

Enfin on récupère les événements du jour et on passe nos variables au template Twig:

$events = $eventRepository->findForDay($date);
return $this->render('event/agenda.html.twig', [
    'date' => $date,
    'previous' => $previous,
    'next' => $next,
    'period' => $period,
    'events' => $events,
]);

Template

Supprimmez tout le contenu existant du template /templates/event/agenda.html.twig si nécessaire.
On se base sur /templates/_template.html.twig:

{% extends "_template.html.twig" %}

On remplace ensuite le contenu du bloc title pour définir le titre de l'onglet dans le navigateur, en formattant la date avec le filtre date():

{% block title 'Agenda ' ~ date|date('d/m/Y') %}

Le ~ est l'opérateur de concaténation de Twig, l'équivalent du . en PHP.

On passe ensuite au bloc content pour définir la structure principale de la page:

{% block content %}
    <div class="section">
	
	</div>
{% endblock %}

Nous souhaiterions formater la date dans le titre de la page, en y intégrant le nom du jour de la semaine et le nom du mois, en français. Pour ce faire, il faut ajouter le package twig/intl-extra:

composer require twig/intl-extra

Attention: vous devez disposer de l'extension PHP intl. Vérifiez avec la commande php -m.

Retournons dans le template pour ajouter le titre avec la date formattée avec les filtre format_date() et capitalize et le nombre d'événements avec le filtre length:

<h1 class="title">Agenda du {{ date|format_date(locale='fr', pattern='EEEE d MMMM yyyy')|capitalize }}: {{ events|length }} événements</h1>

Le pattern utilise la syntaxe ICU et renverra par exemple: Vendredi 16 septembre 2022

Ajoutons ensuite un bloc de navigation pour voir les dates suivantes, précédentes, et lister les dates de la période d'un mois. Commencez par les liens "précédent"/"suivant" en utilisant la fonction path(). En passant un 2e argument sous la forme de tableau (syntaxe {cle: valeur}), on peut définir d'éventuels paramètres de l'URL:

<div class="is-flex">
    <a href="{{ path('event_agenda', {date: previous|date('Y-m-d')}) }}" class="button is-primary is-light is-large mr-4">
        <i class="fa-solid fa-angle-left"></i>
    </a>
    <a href="{{ path('event_agenda', {date: next|date('Y-m-d')}) }}" class="button is-primary is-light is-large ml-4">
        <i class="fa-solid fa-angle-right"></i>
    </a>
</div>

Entre les deux liens, ajoutez la liste des jours en utilisant une boucle. Sa syntaxe est similaire à la boucle foreach de PHP mais dont l'ordre des éléments est inversé: for element in tableau:

<div class="tabs">
    <ul>
        {% for day in period %}
		    {# On ajoute des classes CSS pour la date actuellement sélectionnée avec une condition ternaire #}
            <li class="has-text-centered {{ day|date('Y-m-d') == date|date('Y-m-d') ? 'has-text-weight-bold is-active' : '' }}">
                <a href="{{ path('event_agenda', {date: day|date('Y-m-d')}) }}">
				    {# On formatte la date en français avec le nom du jour de la semaine #}
                    {{ day|format_date(locale='fr', pattern='EEE')|upper }}<br>
                    {{ day|format_date(locale='fr', pattern='d') }}
                </a>
            </li>
        {% endfor %}
    </ul>
</div>

Le bloc de navigation est terminé. En dessous, il faut parcourir la liste des événements. On ajoute à nouveau une boucle mais dont la structure est découpée en 2 parties par un else qui sera exécuté s'il n'y a aucun événement:

{% for event in events %}
     {# J'apparais pour chaque élément du tableau #}
{% else %}
    {# Je n'apparais que si le tableau est vide #}
{% endfor %}

Ajoutez un bloc statique pour la partie else:

{% for event in events %}
     {# J'apparais pour chaque élément du tableau #}
{% else %}
    <div class="card">
        <div class="card-content">
            <div class="content">
                <p class="title is-4">
                    <span class="icon"><i class="fa-regular fa-face-sad-cry"></i></span>
                    <span>Oh non !</span>
                </p>
                <p class="subtitle is-6">
                    Aucun événement n'est prévu à cette date...
                </p>
            </div>
        </div>
    </div>
{% endfor %}

Et pour finir, ajoutez le bloc à afficher pour chaque événement, en affichant les détails (titre, description, ...) avec la syntaxe objet.propriete qui appelle automatiquement les getters de l'objet (obj.prop appelle obj.getProp()):

{% for event in events %}
    <div class="box columns my-6 p-0">
        <div class="column is-one-third p-0">
            <figure class="image is-3by2">
                <img src="https://loremflickr.com/920/640/event,music,poster?lock={{ event.id }}" alt="{{ event.title }}">
            </figure>
        </div>
        <div class="column">
            <div class="content">
                <h2 class="mb-2"><a href="#">{{ event.title }}</a></h2>
                <p class="is-size-6">
                    <i class="fa-regular fa-calendar"></i> {{ event.startAt|date('d/m/Y') }}<br>
                    <i class="fa-solid fa-clock"></i> {{ event.startAt|date('H\\hi') }} - {{ event.endAt|date('H\\hi') }}
                </p>
    
                <p>{{ event.description }}</p>
            </div>
        </div>
    </div>
{% else %}
    {# ... #}
{% endfor %}

On utilise des images aléatoires de LoremFlickr, n'hésitez pas à changer les mots-clés.

La page agenda est désormais complète ! Testez la navigation sur votre navigateur.
Notez par ailleurs que le Profiler comporte une section dédiée à Doctrine, ce qui permet notamment de voir les requêtes SQL exécutées.