Adrien Gras
Ateliers

Lowify

Un clone low-cost de Spotify : pages dynamiques avec PHP, HTML et une base de données.

Le but de cet atelier est de créer une première application PHP en utilisant des utilitaires de gestion de page et de base de données. Vous allez créer un clone "Low-cost" de Spotify, nommé Lowify.

Préparation

Attention

Si vous aviez déjà un projet en cours, assurez-vous de bien stopper les conteneurs Docker des projets précédents pour éviter les conflits de ports.

Vous trouverez la procédure ici : Changer de projet

Lancer la stack Docker

Rendez-vous dans le dossier racine du dépôt git puis dans 004-lowify. En suite, lancez la stack docker avec la commande suivante :

docker compose up -d --build

Vous pouvez en suite vérifier que tout fonctionne en allant à l'adresse http://localhost:80.

Attention

Si vous avez une erreur HTTPS, acceptez simplement le certificat auto-généré.

Attention

Pour ceux qui ont le problème de page qui ne se raffraichissent pas sur Windows

Vous allez devoir changer votre configuration docker pour ce projet. Pour se faire :

  1. Allez dans le dossier du projet
  2. Ouvrez le fichier docker-compose.yml
  3. Dans la section port, vous devriez avoir des notations comme suit :
    ports:
      - "80:80" # HTTP
      - "443:443" # HTTPS
      - "443:443/udp" # HTTP/3

  1. Changez les ports de gauche (ceux avant les :) pour des ports libres sur votre machine, par exemple :
    ports:
      - "8080:80" # HTTP
      - "444:443" # HTTPS
      - "444:443/udp" # HTTP/3

Lorsque dans la documentation il est fait référence à http://localhost ou https://localhost, vous devrez utiliser les ports que vous avez configurés : https://localhost:444 pour accéder à votre projet.

Ouvrir le projet dans PHPStorm

Ouvrez PHPStorm, puis :

  • Si vous êtes déjà dans un projet :
    • File > Open
    • Sélectionnez le dossier racine du dépôt git > 004-lowify/app
  • Si vous n'êtes pas dans un projet :
    • Open
    • Sélectionnez le dossier racine du dépôt git > 004-lowify/app

Configurer l'interpréteur PHP

Pour éviter que vous n'ayez à installer PHP sur votre machine, nous allons utiliser l'interpréteur PHP fourni par Docker.

Pour cela :

  1. Allez dans File > Settings > Languages & Frameworks > PHP
  2. Cliquez sur les ... à côté du champ CLI Interpreter
  3. Cliquez sur le + en haut à gauche pour ajouter un nouvel interpréteur
  4. Sélectionnez From Docker, Vagrant, WSL, Remote...
  5. Sélectionnez Docker Compose
  6. Si le champ Server est vide, cliquez sur les ... à côté et laissez les options par défaut, puis cliquez sur OK
  7. Dans le champ Configuration file, cliquez sur les ..., retirez tout le contenu et sélectionnez le fichier docker-compose.yml à la racine du dossier actuel : 004-lowify/docker-compose.yml
  8. Dans le champ Service, sélectionnez php
  9. Cliquez sur OK pour valider l'ajout de l'interpréteur
  10. Cliquez sur Apply puis OK pour fermer la fenêtre des paramètres

Introduction

Architecture des pages

L'application Lowify va être structurée de la manière suivante :

  • index.php sera la page d'accueil de l'application, elle listera les tops tendances et permettra une recherche.
  • artists.php listera tout les artistes disponibles.
  • artist.php sera la page de détail d'un artiste. Elle permettra d'afficher ses détails, de lister ses "tops titres" et ses albums.
  • album.php permettra d'afficher le détail d'un album et de ses titres.
  • search.php affichera les résultats de recherches.
  • error.php sera affiché lors d'une erreur de code métier.

Architecture des données

La base de donnée sera constituée de 3 tables pour garder le modèle simple.

Astuce

Pour une application "réelle", ce modèle n'est pas viable.

Il faudrait par exemple :

  • Gérer les covers localement et non via des URLs externes, avec une table media.
  • Gérer les utilisateurs et leurs playlists.
  • Ajouter des tables de jointures pour gérer les relations "many to many" (ex : plusieurs artistes par chansons).
  • Etc.

Installation

J'ai préparé un script d'installation pour vous permettre de créer la base de données et d'insérer des données de test.

Astuce

Les données ont été générées via Faker PHP et sont totalement fictives.

Les images sont issues de Lorem Picsum.

Une fois votre stack docker lancée :

  • Rendez-vous sur https://localhost/install
  • Cliquez sur le bouton pour lancer l'installation
  • Vérifiez que tout est "OK" dans le résultat de l'installation

Consignes générales

Les consignes suivantes sont à garder en tête tout au long de l'atelier :

  • Toutes les pages doivent utiliser la classe HTMLPage pour générer le HTML.
  • Toutes les pages doivent utiliser la classe DatabaseManager pour interagir avec la base de données.
  • Je ne donne pas d'information sur le design des pages, vous êtes libre de faire ce que vous voulez, du moment que les fonctionnalités sont présentes et utiles.
  • Vous pouvez créer des fichiers CSS et JS supplémentaires si nécessaire, et les inclure dans les pages via la classe HTMLPage.
  • Vous pouvez créer des fonctions utilitaires supplémentaires si nécessaire, mais essayez de garder le code simple et lisible.
    • Si d'aventure, vous utilisez des fonctions utilitaires sur plusieurs pages, créez un fichier inc/utils.inc.php et incluez-le dans les pages concernées.
  • Le plan de vos scripts devrait être le suivant :
    • Inclusion des fichiers nécessaires (require_once)
    • Initialisation de la page (HTMLPage)
    • Initialisation de la base de données (DatabaseManager)
    • Récupération des données nécessaires
    • Génération du contenu HTML
    • Affichage de la page
  • Vous ne devez pas injecter de code PHP comme des boucles et conditions directement dans le HTML.
    • Vous devriez préparer des variables PHP contenant le HTML nécessaire, puis les injecter dans une variable globale de contenu $html via la méthode setContent() de la classe HTMLPage.
  • Vous devrez gérer les erreurs de code métier en redirigeant vers la page error.php avec un message d'erreur approprié.
    • Par exemple, si un artiste n'existe pas, vous devez rediriger vers error.php avec un message "Artiste non trouvé".

Recommandé - Configurer PHPStorm avec la base de données

Pour faciliter le développement, vous pouvez configurer PHPStorm pour qu'il puisse se connecter à la base de données MySQL.

Vous pourrez ainsi naviguer dans les tables et voir les données directement depuis l'IDE.

Pour cela :

  • Ouvrez le panneau "Database" (View > Tool Windows > Database)
  • Cliquez sur le + en haut à gauche > Data Source > MySQL
  • Configurez la connexion avec les paramètres suivants :
    • Host: localhost
    • Port: 3306
    • User: lowify
    • Password: lowifypassword
    • Database: lowify
  • Cliquez sur Test Connection pour vérifier que tout fonctionne
  • Cliquez sur OK pour enregistrer la connexion

Vous pourrez en suite aller explorer les tables dans le panneau "Database", et ouvrir une console SQL pour exécuter des requêtes.

Listage des artistes

Nous allons commencer par implémenter la page artists.php qui listera tous les artistes disponibles dans la base de données.

Nous allons procéder en deux étapes :

  1. Afficher la page à l'aide de la classe HTMLPage.
  2. Récupérer les artistes depuis la base de données à l'aide de la classe DatabaseManager.

Astuce

Vous aurez besoin des documentations des classes HTMLPage et DatabaseManager qui sont disponibles sur la page principale du cours.

Commençons par créer le fichier artists.php à la racine du dossier app.

Consigne

  1. Créez le fichier artists.php à la racine du dossier app.
  2. Dans ce fichier, incluez le fichier inc/page.inc.php. Vous utiliserez la fonction require_once pour cela.
  3. Créez une instance de la classe HTMLPage avec le titre "Lowify - Artistes".
  4. Initialisez vos CSS et JS si nécessaire.
  5. Ajoutez le contenu HTML de la page (un simple titre H1 suffira pour l'instant) à l'aide de la méthode setContent().
  6. Affichez la page à l'aide de la méthode render().

Une fois que vous avez terminé, rendez-vous sur http://localhost/artists.php pour vérifier que la page s'affiche correctement avec le titre "Lowify - Artistes" et le contenu HTML que vous avez ajouté.

Ensuite, nous allons récupérer les artistes depuis la base de données et les afficher dans la page.

Consigne

  1. Dans le fichier artists.php, incluez le fichier inc/database.inc.php à l'aide de la fonction require_once.
  2. Créez une instance de la classe DatabaseManager.
    • dsn : mysql:host=mysql;dbname=lowify;charset=utf8mb4
    • username : lowify
    • password : lowifypassword
  3. Utilisez la méthode executeQuery() pour récupérer tous les artistes depuis la table artist.
  4. Générez le contenu HTML pour afficher la liste des artistes. Pour chaque artiste, affichez son nom et sa cover.
  5. Mettez à jour le contenu HTML de la page avec la liste des artistes.

Une fois que vous avez terminé, rendez-vous sur http://localhost/artists.php pour vérifier que la liste des artistes s'affiche correctement avec leurs noms et covers.

Détail d'un artiste

Maintenant que nous avons une page qui liste tous les artistes, nous allons créer la page artist.php qui affichera le détail d'un artiste spécifique.

Cette page devra afficher les informations suivantes :

  • Nom de l'artiste
  • Biographie
  • Cover
  • Nombre d'auditeurs mensuels formatté
  • Top titres (les 5 chansons les mieux notées de l'artiste) avec leurs noms, durées et notes
  • Albums (tous les albums de l'artiste) avec leurs noms, covers et année de sortie

Pour savoir quel artiste afficher, la page recevra un paramètre GET id qui contiendra l'identifiant de l'artiste.

Consigne

  1. Créez le fichier artist.php à la racine du dossier app.
  2. Importez les fichiers nécessaires et initialisez la page et la base de données comme dans l'exercice précédent.
  3. Récupérez le paramètre GET id pour savoir quel artiste afficher.
  4. Utilisez la méthode executeQuery() pour récupérer les informations de l'artiste depuis la table artist.
  5. Récupérez les 5 chansons les mieux notées de l'artiste depuis la table song.
    • Ordonnées par le champ note en ordre décroissant et limité à 5 résultats.
    • Vous devrez aussi récupérer les covers de chaque chanson en joignant la table album.
  6. Récupérez tous les albums de l'artiste depuis la table album.
    • Ordonnés par le champ release_date en ordre décroissant.
  7. Générez le contenu HTML pour afficher les informations de l'artiste.
    • Vous devrez formatter le nombre d'auditeurs mensuels en mode 1k pour 1 000, 1.5M pour 1 500 000, etc.
  8. Générez le contenu HTML pour afficher les top titres et les albums.
    • Pour chaque top titre, affichez son nom, sa durée et sa note.
    • La durée doit être formatée en mm:ss (minutes:secondes).
    • La cover de chaque titre doit être la cover de l'album auquel il appartient.
  9. Générez le contenu HTML pour afficher les albums.
    • Pour chaque album, affichez son nom, sa cover et son année de sortie.
  10. Mettez à jour le contenu HTML de la page avec les informations de l'artiste, les top titres et les albums.
  11. Affichez la page à l'aide de la méthode render().
  12. Modifiez le fichier artists.php pour que chaque nom d'artiste soit un lien vers la page artist.php?id=ARTIST_ID.

Gestion des erreurs

Actuellement, notre page artist.php ne gère pas les erreurs. Par exemple, si un utilisateur essaie d'accéder à un artiste qui n'existe pas, la page affichera une erreur PHP. Nous allons améliorer cela en redirigeant vers la page error.php avec un message d'erreur approprié lorsque l'artiste n'existe pas.

Consigne

  1. Créez le fichier error.php à la racine du dossier app.
  2. Dans ce fichier, incluez le fichier inc/page.inc.php et initialisez la page comme dans les exercices précédents.
  3. Récupérez le paramètre GET message qui contiendra le message d'erreur à afficher.
  4. Générez le contenu HTML pour afficher le message d'erreur et une redirection vers la page d'accueil.
  5. Mettez à jour le contenu HTML de la page avec le message d'erreur.
  6. Affichez la page à l'aide de la méthode render().
  7. Dans le fichier artist.php, après avoir récupéré les informations de l'artiste, vérifiez si l'artiste existe.
    • Si l'artiste n'existe pas (le tableau retourné est vide OU il y a eut une erreur), redirigez vers error.php avec un message d'erreur approprié.

Affichage d'un album

Nous allons maintenant créer la page album.php qui affichera le détail d'un album spécifique :

  • Nom de l'album
  • Nom de l'artiste avec un lien vers sa page
  • Cover
  • Date de sortie
  • Liste des titres de l'album avec
    • Nom
    • Durée formatée en mm:ss
    • Note

Comme pour la page artiste, la page album recevra un paramètre GET id qui contiendra l'identifiant de l'album à afficher.

Consigne

  1. Créez le fichier album.php à la racine du dossier app.
  2. Dans ce fichier, incluez le fichier inc/page.inc.php et initialisez la page comme dans les exercices précédents.
  3. Récupérez le paramètre GET id pour savoir quel album afficher.
  4. Utilisez la méthode executeQuery() pour récupérer les informations de l'album depuis la table album.
    • Si l'album n'existe pas, redirigez vers error.php avec un message d'erreur approprié.
  5. Récupérez les informations de l'artiste associé à l'album depuis la table artist.
  6. Récupérez tous les titres de l'album depuis la table song.
    • Ordonnés par le champ id pour garder l'ordre d'ajout.
  7. Générez le contenu HTML pour afficher les informations de l'album, de l'artiste et la liste des titres.
  8. Mettez à jour le contenu HTML de la page avec les informations de l'album.
  9. Affichez la page à l'aide de la méthode render().
  10. Modifiez le fichier artist.php pour que chaque nom d'album soit un lien vers la page album.php?id=ALBUM_ID.

Astuce

Si vous êtes bon en SQL, vous pourriez récupérer toutes les informations en 2 voir une seule requête.

Accueil et recherche

Maintenant que nous avons les pages pour afficher les artistes et les albums, nous allons créer la page d'accueil index.php qui affichera :

  • Un champ de recherche permettant de rechercher des artistes, albums et chansons par nom.
  • Les 5 artistes les plus populaires ("Top trending" basé sur le nombre d'auditeurs mensuels).
  • Les 5 albums les plus récents ("Top sorties" basé sur la date de sortie).
  • Les 5 albums les mieux notés ("Top albums" basé sur la note moyenne des chansons de l'album).

Nous allons déjà implémenter l'affichage des sections "Top trending", "Top sorties" et "Top albums".

Consigne

  1. Créez le fichier index.php à la racine du dossier app.
  2. Importez les fichiers nécessaires et initialisez la page et la base de données comme dans les exercices précédents.
  3. Récupérez les 5 artistes les plus populaires depuis la table artist.
    • Ordonnés par le champ monthly_listeners en ordre décroissant et limité à 5 résultats.
  4. Récupérez les 5 albums les plus récents depuis la table album.
    • Ordonnés par le champ release_date en ordre décroissant et limité à 5 résultats.
  5. Récupérez les 5 albums les mieux notés.
    • Vous devrez calculer la note moyenne des chansons de chaque album dans une sous-requête.
    • Ordonnés par la note moyenne en ordre décroissant et limité à 5 résultats.
  6. Générez le contenu HTML pour afficher les sections "Top trending", "Top sorties" et "Top albums".
  7. Mettez à jour le contenu HTML de la page avec les sections générées.
  8. Affichez la page à l'aide de la méthode render().

Nous allons maintenant implémenter la fonctionnalité de recherche dans l'accueil.

Consigne

  1. Dans le fichier index.php, dans le HTML de la page, ajoutez un formulaire de recherche avec un champ query et un bouton de soumission.
  2. Créez le fichier search.php à la racine du dossier app.
  3. Importez les fichiers nécessaires et initialisez la page et la base de données comme dans les exercices précédents.
  4. Récupérez le paramètre GET query qui contiendra la chaîne de recherche.
  5. Utilisez la méthode executeQuery() pour rechercher des artistes, albums et chansons dont le nom contient la chaîne de recherche.
    • Pour chaque type (artiste, album, chanson), vous pouvez effectuer une requête similaire à celle plus bas.
  6. Générez le contenu HTML pour afficher les résultats de la recherche, regroupés par type (artistes, albums, chansons).
    • Pour chaque artiste, vous afficherez son nom et sa cover.
    • Pour chaque album, vous afficherez son nom, sa cover, son année de sortie et le nom de l'artiste.
    • Pour chaque chanson, vous afficherez son nom, la durée formatée en mm:ss, la note, le nom de l'album et le nom de l'artiste.
  7. Mettez à jour le contenu HTML de la page avec les résultats de la recherche.
  8. Affichez la page à l'aide de la méthode render().
  9. Au clic sur les artistes et albums, redirigez vers leurs pages respectives (artist.php?id=ARTIST_ID et album.php?id=ALBUM_ID).
  10. Modifiez le formulaire de recherche dans index.php pour qu'il soumette les données vers search.php.

Astuce

Voici un exemple de requête SQL pour récupérer les artistes via une recherche sur leurs nom :

SELECT * 
FROM artist 
WHERE (
    MATCH(name) AGAINST(:search IN NATURAL LANGUAGE MODE) OR 
    name LIKE :search
)

Cette méthode de recherche est rudimentaire et ne devrait pas être utilisée en production.

A la place, utilisez un moteur de recherche dédié comme MeiliSearch ou Typesense.

Bonus

Modification du schéma de base de données

Pour effectuer les parties suivantes, vous allez avoir besoin de modifier le schéma de la base de données.

Voici la requête SQL pour faire les modifications nécessaires :

ALTER TABLE `song`
    ADD COLUMN `is_liked` tinyint(1) NOT NULL DEFAULT 0
    AFTER `duration`;

DROP TABLE IF EXISTS `playlist`;
DROP TABLE IF EXISTS `x_playlist_song`;

CREATE TABLE `playlist` (
    `id` int NOT NULL AUTO_INCREMENT,
    `name` varchar(255) NOT NULL,
    `duration` int NOT NULL DEFAULT 0,
    `nb_song` int NOT NULL DEFAULT 0,
    PRIMARY KEY (`id`),
    UNIQUE KEY `id` (`id`)
) ENGINE = InnoDB AUTO_INCREMENT = 379 DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci;

CREATE TABLE `x_playlist_song` (
    `id` int NOT NULL AUTO_INCREMENT,
    `song_id` int NOT NULL,
    `playlist_id` int NOT NULL,
    PRIMARY KEY (`id`),
    UNIQUE KEY `id` (`id`),
    KEY `song_id` (`song_id`),
    KEY `playlist_id` (`playlist_id`),
    CONSTRAINT `xps_fk_1` FOREIGN KEY (`song_id`) REFERENCES `song` (`id`),
    CONSTRAINT `xps_fk2` FOREIGN KEY (`playlist_id`) REFERENCES `playlist` (`id`)
) ENGINE = InnoDB AUTO_INCREMENT = 379 DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci;

Gestion des chansons likées

Nous allons gérer la fonctionnalité de "liker" une chanson.

Consigne

  1. Créez une page like_song.php à la racine du dossier app.
  2. Importez les fichiers nécessaires pour manipuler la base de donnée.
  3. Récupérez le paramètre GET id qui contiendra l'identifiant de la chanson à liker/deliker.
  4. Récupérez la chanson depuis la table song en utilisant l'identifiant. Gérez le cas où la chanson n'existe pas en redirigeant vers error.php.
  5. Si la chanson existe, inversez la valeur du champ is_liked (0 devient 1 et 1 devient 0).
  6. Mettez à jour la chanson dans la base de données avec la nouvelle valeur de is_liked.
  7. Redirigez l'utilisateur vers la page précédente (Vous pouvez utiliser header('Location: ' . $_SERVER['HTTP_REFERER']);).

Gestion des playlists

Nous allons maintenant implémenter la gestion des playlists.

Consigne

  1. Créez une page playlists.php à la racine du dossier app.
    • Cette page affichera la liste des playlists existantes avec un lien pour créer une nouvelle playlist.
    • Chaque playlist affichera son nom, le nombre de chansons et la durée totale formatée en hh:mm:ss.
    • Les playlist pourront être supprimés via un bouton de suppression.
  2. Créez une page playlist.php à la racine du dossier app.
    • Cette page affichera le détail d'une playlist spécifique.
    • Elle recevra en paramètre GET l'identifiant de la playlist à afficher.
    • La page affichera le nom de la playlist, le nombre de chansons, la durée totale formatée en hh:mm:ss et la liste des chansons avec leurs noms, durées formatées en mm:ss, notes, et un bouton pour supprimer la chanson de la playlist.
  3. Créez une page create_playlist.php à la racine du dossier app.
    • Cette page contiendra un formulaire pour créer une nouvelle playlist en saisissant son nom.
    • Lors de la soumission du formulaire, la nouvelle playlist sera ajoutée à la base de données.
  4. Créez une page add_to_playlist.php à la racine du dossier app.
    • Cette page permettra d'ajouter une chanson à une playlist.
    • Elle recevra en paramètre GET l'identifiant de la chanson à ajouter.
    • La page affichera un formulaire avec la possibilité de choisir une playlist existante pour y ajouter la chanson.
    • Lors de la soumission du formulaire, la chanson sera ajoutée à la playlist sélectionnée (via x_playlist_song), et la durée et le nombre de chansons de la playlist seront mis à jour.
  5. Créez une page remove_from_playlist.php à la racine du dossier app.
    • Cette page permettra de supprimer une chanson d'une playlist.
    • Elle recevra en paramètre GET l'identifiant de la chanson et de la playlist.
    • La chanson sera supprimée de la playlist dans la table x_playlist_song, et la durée et le nombre de chansons de la playlist seront mis à jour.
  6. Créez une page delete_playlist.php à la racine du dossier app.
    • Cette page permettra de supprimer une playlist.
    • Elle recevra en paramètre GET l'identifiant de la playlist à supprimer.
    • La playlist sera supprimée de la base de données ainsi que toutes les associations avec les chansons dans la table x_playlist_song.
  7. Partout où il y a des chansons d'affichés (détail artiste, détail album, recherche, etc.), ajoutez un lien ou un bouton pour ajouter la chanson à une playlist (redirigeant vers add_to_playlist.php?id=SONG_ID).

Sur cette page