Commentaires
Commençons à rentrer dans les codes. L'une des premières étapes que je te propose est de nous occuper des modèles et de la base de données.
Grâce à Laravel, aucune raison cependant de mettre les mains dans phpmyadmin ou dans des lignes de code SQL pour créer et peupler ses tables : ce sont les rôles des migrations et des seeders !

Les migrations sont des représentations, des schémas des tables en base de données.
Les seeders nous permettent de peupler notre base de données avec des données fictives. Cela nous permet de nous éviter d'inventer des faux utilisateurs à la main, des faux messages...

Une fois les migrations créées, on n'aura plus qu'à utiliser artisan pour créer nos tables en base de données automatiquement.

Les migrations


Les migrations se trouvent dans database > migrations (qui se lit : dans le dossier migrations lui-même dans le dossier database).

Arthur, l'apprenti développeurOula, j'ai déjà des fichiers dans ce dossier, c'est normal ?
Tout à fait ! Laravel propose déjà 4 migrations qui correspondent à des tables dont on a très souvent besoin dans chaque application :


  • Les utilisateurs : Laravel peut créer pour nous une table "users" qui contiendra nos utilisateurs. Nous la modifierons pour parfaire nos besoins

  • Les mots de passe oubliés : Laravel peut créer pour nous une table "password_resets" qui enregistrera les demandes de mots de passe oubliés

  • Les jobs : Laravel peut créer pour nous une table "failed_jobs". Celle-ci est plus compliquée à comprendre, nous ne l'utiliserons pas. Elle sert dans le cas où nous souhaitons réaliser de manière différée des opérations couteuses en temps. Super pratique dans le cas d'envoi d'emails de masse par exemple ou de traitement d'image : les jobs vont permettre de gérer ces tâches sans bloquer l'utilisateur

  • Les jetons d'authentification personnels : Laravel peut créer pour nous une table "personal_access_tokens qui enregistra des informations concernant des tokens d'authentification. Cela est utilisé par "Sanctum", une librairie de Laravel supplémentaire. C'est très pratique pour créer des API sécurisées avec Laravel.


Arthur, l'apprenti développeurAttends, dans la partie précédente tu as parlé de Nova, maintenant de Sanctum... Il y a beaucoup de librairies qui complètent Laravel ?
Énormément. Il y a Jetstream, Telescope, Cashier, Scout, Sail, et encore d'autres... Et là est tout l'avantage de Laravel, qui le revendique fièrement dans sa documentation dès la page d'installation : le framework est dit "progressif". C'est-à-dire qu'il grandit avec nos besoins. Si on a besoin d'exposer une API, pas de soucis, on peut utiliser Sanctum. On a besoin de faire un service de paiement ? Pas de problème, Cashier est là pour nous. Besoin de faire une fonction de recherche interne à notre site ? Utilisons Scout ! Et ainsi de suite. Intéressant, non ?

Avant de nous attaquer à la création de nos migrations, réfléchissons à nos besoins :
Nous allons réaliser un blog où les utilisateurs devront être inscrits par l'administrateur pour pouvoir poster des articles. Cependant, tout le monde pourra poster des commentaires et les administrateurs pourront les gérer.

Allez, imaginons les attributs de chaque entité que nous allons gérer :

Les utilisateurs :


  • id : l'identifiant unique de l'utilisateur

  • name : le nom et prénom de l'utilisateur

  • email : l'email de l'utilisateur

  • password : le mot de passe de l'utilisateur


Les posts :


  • id : l'identifiant unique du post

  • title : le titre du post

  • user_id : l'identifiant unique du user qui a posté l'article

  • content : le contenu du post

  • created_at : la date de création du post

  • updated_at : la date de modification du post


Les commentaires :


  • id : l'identifiant unique du commentaire

  • post_id : l'identifiant unique du post auquel appartient le commentaire

  • title : le titre du commentaire

  • author : l'auteur du commentaire (un pseudo)

  • content : le contenu du commentaire

  • reported : booléen pour savoir si le commentaire a été signalé

  • created_at : la date de création du commentaire

  • updated_at : la date de modification du commentaire


Arthur, l'apprenti développeurFastoche !
En effet, rien ne change par rapport à d'habitude. Petite précision cependant : étant donné que nous n'avons que 3 entités, on peut se permettre sans mal de faire un listing comme au dessus. Dans des projets plus avancés, il est essentiel de réaliser une analyse préalable grâce à des outils de modélisation comme UML pour concevoir les classes ou MERISE (Français !) pour concevoir des bases de données. Un cours sur le sujet est (ou sera bientôt !) disponible sur le site. Tu peux aussi t'aider d'outils comme Laravel Schema Designer qui te permettent de renseigner tes entités graphiquement puis de générer directement le code correspondant (attention, Laravel Schema Designer est un outil indépendant, je ne garantis rien !).

Arthur, l'apprenti développeurC'est noté, merci pour ces précisions ;)
Passons donc maintenant aux migrations directement. Nous allons débuter par la modification de la migrations associées aux users, qui actuellement, contient ce code :

{"language":"application/x-httpd-php","content":"<?php\n\nuse Illuminate\\Database\\Migrations\\Migration;\nuse Illuminate\\Database\\Schema\\Blueprint;\nuse Illuminate\\Support\\Facades\\Schema;\n\nreturn new class extends Migration\n{\n /**\n * Run the migrations.\n *\n * @return void\n */\n public function up()\n {\n Schema::create('users', function (Blueprint $table) {\n $table->id();\n $table->string('name');\n $table->string('email')->unique();\n $table->timestamp('email_verified_at')->nullable();\n $table->string('password');\n $table->rememberToken();\n $table->timestamps();\n });\n }\n\n /**\n * Reverse the migrations.\n *\n * @return void\n */\n public function down()\n {\n Schema::dropIfExists('users');\n }\n};","filename":"2014_10_12_000000_create_users_table.php"}

On peut remarquer deux méthodes : up() et down(). La première sert à dire quoi faire pour créer la table et l'autre pour la détruire.
C'est à dire que c'est dans la méthode up() qu'on va indiquer les colonnes (ou champs) qu'on va créer dans notre table "users".

Arthur, l'apprenti développeurEt c'est quoi la classe Schema ? Et la classe Migration ? Et puis dis donc, c'est quoi cette drôle de syntaxe de création de classe avec return new class ? Elle n'a pas de nom ?
Excellente remarque concernant la syntaxe de la création de notre classe... Il s'agit de ce qu'on appelle une classe anonyme. Ce n'est pas très courant. Mais ça a un vrai avantage. Lorsqu'on commence à avoir l'habitude de Laravel, on se retrouve vite avec de nommmbreuuussses migrations. Non pas que nous avons 50000 tables (quoi que...), mais surtout que nous pouvons créer des migrations pour modifier nos tables existantes. En effet, s'il y a bien une chose à retenir, c'est que si tu dois faire des modifications sur tes tables déjà créées, il faut refaire une migration et non pas modifier l'existante. Tout simplement car si tu modifies une migration existante et que tu veux que les modifications soient appliquées en base de données, et bien il faut d'abord.... supprimer toute la base de données. Ce qui, en production, peut être quelque peu gênant :p. Et donc, pour en revenir à pourquoi c'est avantageux, et bien c'est parce que parfois, en créant de plus en plus de migrations, on se retrouve avec une collision dans les noms, par faute d'inattention... Ainsi maintenant, étant donné que les migrations sont des classes anonymes (donc qui n'ont plus de noms), plus de risques de collisions ! Youpi ;).

Concernant maintenant la classe Migration, il s'agit de la classe Laravel native qui permet de gérer la manipulation de la base de données en créant/supprimant des tables.

Et enfin, la classe Schema est la classe qui nous offre les méthodes permettant de créer les colonnes de notre table avec des fonctions comme string, boolean, enum, text... qui représentent les types en base de données.

Arthur, l'apprenti développeurWouahou, ça en fait des infos. Ça a l'air classe cela dit !
Ça l'est ;). Reprenons maintenant la création de nos tables :

Dans notre table users, on aura donc :
- Un champ id, de type bigInteger, auto-incrémenté. C'est une clé primaire
- Un champ name, varchar
- Un champ email, varchar, unique
- Un champ password, varchar


Arthur, l'apprenti développeurMais attends, il y a aussi d'autres lignes comme $table->rememberToken(); par exemple. À quoi servent-elles ?
Bien vu ! En fait, il s'agit de méthodes très souvent utiles pour l'authentification :
- rememberToken() crée un varchar nullable remember_token qui stockera un identifiant de connexion
- timestamp('email_verified_at') crée un champ de type timestamp qui stockera la date à laquelle l'adresse email a été vérifiée (ce n'est pas obligatoire, c'est juste par défaut disponible).

Il y a aussi la méthode timestamps(), qui crée automatiquement deux champs : created_at et updated_at.

Cette table correspond donc à ce que nous voulions, on peut simplement enlever le email_verified_at si on le souhaite.

Passons à la table posts. Pour cela, nous allons utiliser une nouvelle fois artisan pour que cet utilitaire crée le fichier de migration :

{"language":"shell","content":"php artisan make:migration create_posts_table","filename":"console"}

Regarde bien la syntaxe : create_NOMDELATABLE_table. Si tu respectes cette syntaxe, le fichier de migration créé sera déjà prérempli avec le bon nom de table ;).

Si l'on retourne maintenant dans notre dossier des migrations, on a un nouveau fichier nommé DATE_create_posts_table.php qui contient déjà la base de notre fichier : les "use" utiles ainsi que la déclaration de la classe et les méthodes up et down.
Il ne nous reste plus qu'à compléter la méthode up() qui est l'équivalent grossier d'un CREATE TABLE AS en SQL.

{"language":"application/x-httpd-php","content":"<?php\n\nuse Illuminate\\Database\\Migrations\\Migration;\nuse Illuminate\\Database\\Schema\\Blueprint;\nuse Illuminate\\Support\\Facades\\Schema;\n\nreturn new class extends Migration\n{\n /**\n * Run the migrations.\n *\n * @return void\n */\n public function up()\n {\n Schema::create('posts', function (Blueprint $table) {\n $table->id();\n $table->string('title');\n $table->text('content');\n $table->timestamps();\n });\n }\n\n /**\n * Reverse the migrations.\n *\n * @return void\n */\n public function down()\n {\n Schema::dropIfExists('posts');\n }\n};","filename":"2022_02_08_081211_create_posts_table.php"}


Arthur, l'apprenti développeurMais attends, il ne manque pas user_id ?
En effet. Mais là, c'est un peu particulier car c'est une clé étrangère !
Nous allons donc utiliser deux nouvelles méthodes : foreignId() et constrained()

{"language":"application/x-httpd-php","content":"<?php\nSchema::create('posts', function (Blueprint $table) {\n\t$table->id();\n\t$table->string('title');\n\t$table->text('content');\n\t$table->foreignId('user_id')->constrained();\n\t$table->timestamps();\n});","filename":"2022_02_08_081211_create_posts_table.php"}

Cette syntaxe (et notamment la méthode foreignId()) est équivalente à la syntaxe plus verbeuse suivante :

{"language":"application/x-httpd-php","content":"<?php\n$table->foreignId('user_id')->constrained();\n// Équivalent à : \n\n// 1 : création de la colonne user_id\n$table->unsignedBigInteger('user_id');\n\n// 2 : création de la clé étrangère sur cette colonne\n$table->foreign('user_id')->references('id')->on('users');","filename":"exemple"}

Cette syntaxe a le mérite d'être plus compréhensible par un débutant ;).


On continue avec la table comments :

{"language":"shell","content":"php artisan make:migration create_comments_table","filename":"console"}

{"language":"application/x-httpd-php","content":"<?php\n\nuse Illuminate\\Database\\Migrations\\Migration;\nuse Illuminate\\Database\\Schema\\Blueprint;\nuse Illuminate\\Support\\Facades\\Schema;\n\nreturn new class extends Migration\n{\n /**\n * Run the migrations.\n *\n * @return void\n */\n public function up()\n {\n Schema::create('comments', function (Blueprint $table) {\n $table->id();\n $table->string('title');\n $table->string('author');\n $table->text('content');\n $table->foreignId('post_id')->constrained();\n $table->boolean('reported')->default(false);\n $table->timestamps();\n });\n }\n\n /**\n * Reverse the migrations.\n *\n * @return void\n */\n public function down()\n {\n Schema::dropIfExists('comments');\n }\n};","filename":"2022_02_08_081938_create_comments_table.php"}


Et voilà ! Nos migrations sont créées ! Nous n'avons plus qu'à demander à artisan de créer les tables dans notre base de données. Avant, nous pouvons aussi supprimer les migrations qui ne nous sont pas utiles (comme personal_access_tokens ou failed_jobs). Attention cependant, même si tu supprimes le fichier de migration pour les personal_access_tokens, la table va quand même être créée (en effet, dans les fichiers sources de Laravel (dans le dossier vendor), cette migration est toujours présente. Si on supprime le fichier malencontreusement dans notre dossier database -> migrations comme ça, tout marche encore au niveau de notre API (mais nous on ne va pas exposer d'API)). Pour dire à Laravel de ne pas la créer, il faut aller dans app -> Providers -> AppServiceProvider.php et ajouter une petite ligne dans la méthode "register" :

{"language":"application/x-httpd-php","content":"<?php\n\nnamespace App\\Providers;\n\nuse Illuminate\\Support\\ServiceProvider;\nuse Laravel\\Sanctum\\Sanctum;\n\nclass AppServiceProvider extends ServiceProvider\n{\n /**\n * Register any application services.\n *\n * @return void\n */\n public function register()\n {\n Sanctum::ignoreMigrations();\n }\n\n /**\n * Bootstrap any application services.\n *\n * @return void\n */\n public function boot()\n {\n //\n }\n}","filename":"AppServiceProvider.php"}

Le fichier AppServiceProvider.php est un fichier qui est lu à chaque action de Laravel (donc quand tu utilises artisan, quand tu accèdes à une page de ton site web...).

Maintenant, nous pouvons enfin lancer la création de nos tables automatiquement avec Artisan :

{"language":"shell","content":"php artisan migrate","filename":"console"}

C'est gagné ! Tu peux maintenant aller regarder dans ta base de données ce qui a été créé si tu es curieux ;)

Arthur, l'apprenti développeurMince, j'ai une erreur Illuminate\Database\QueryException SQLSTATE[HY000] [2002] Connection refused...
Cela signifie que les données de connexion que tu as renseignées dans le .env ne sont pas correctes, il faut que tu les vérifies ;).


Les factories et les seedings


Passons maintenant au peuplement de notre base de données. Comme expliqué au début, on peut générer des données fictives pour tester notre application sans s'embêter grâce aux factories et aux seedings.

Comme les migrations, nous allons aller dans le dossier database pour cette partie.
Puis, dans factories. Un premier fichier est déjà là pour les Users, regardons son contenu.

{"language":"application/x-httpd-php","content":"<?php\n\nnamespace Database\\Factories;\n\nuse Illuminate\\Database\\Eloquent\\Factories\\Factory;\nuse Illuminate\\Support\\Str;\n\n/**\n * @extends \\Illuminate\\Database\\Eloquent\\Factories\\Factory<\\App\\Models\\User>\n */\nclass UserFactory extends Factory\n{\n /**\n * Define the model's default state.\n *\n * @return array\n */\n public function definition()\n {\n return [\n 'name' => $this->faker->name(),\n 'email' => $this->faker->unique()->safeEmail(),\n 'email_verified_at' => now(),\n 'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password\n 'remember_token' => Str::random(10),\n ];\n }\n\n /**\n * Indicate that the model's email address should be unverified.\n *\n * @return \\Illuminate\\Database\\Eloquent\\Factories\\Factory\n */\n public function unverified()\n {\n return $this->state(function (array $attributes) {\n return [\n 'email_verified_at' => null,\n ];\n });\n }\n}","filename":"UserFactory.php"}


Ce fichier, comme son nom l'indique, est un Factory. Il permet de création un utilisateur bidon en base de données grâce à Faker : https://fakerphp.github.io

Étudions rapidement ce fichier.

On a une méthode définition qui permet d'indiquer quelles valeurs renseignées aux colonnes de notre table users. Faker permet de générer des string aléatoires, des nombres aléatoires...
Ensuite on a une méthode "unverified", un peu particulière, qui utilise les "state" de Laravel. On n'ira pas plus loin dans les explications, si ce n'est que cette méthode permet d'éviter la vérification de l'adresse email dans le cas de la création de notre user bidon (ce qui parait logique).
En bref, ce Factory est déjà tout prêt ! Si vous voulez en savoir +, n'hésitez pas [Créer des factories].

Arthur, l'apprenti développeurIl y a quand même quelque chose qui m'étonne... Comment notre Factory sait dans quelle table il doit ajouter ces données ? Il parcourt toute la base de données à la recherche d'une table qui a toutes les colonnes renseignées dans la méthode definition() ?
Excellente question. Et non, fort heureusement, il ne parcourt pas toute la base de données pour savoir sur quelle table agir... En fait, c'est assez complexe. Laravel a un mécanisme de "découverte" automatique des factories et des modèles. Laravel va pouvoir lier un modèle (lui-même lié à une table) à un factory grâce à deux choses :
- Le respect des noms : si un modèle a pour nom User, alors notre Factory doit avoir pour nom UserFactory.
- L'utilisation du trait hasFactory dans les modèles

Arthur, l'apprenti développeurMais attends, on n'a pas encore défini de modèles !
C'est vrai ! Mais Laravel, si :). Il a déjà créé pour nous le modèle pour les users. On le verra dans la partie d'après.
Donc, tout ira pour le mieux pour nous !


Arthur, l'apprenti développeurMais, comment sait-on qu'un utilisateur est administrateur ?
Excellente question ! En effet, dans notre base de données, aucune colonne ne nous permet de savoir si quelqu'un est administrateur. C'est un oubli (volontaire) de ma part pour te montrer comment rajouter/modifier/supprimer une colonne quand on a déjà réalisé les migrations

Il faut donc créer une nouvelle migration. Comme expliqué un peu avant, ne surtout pas modifier l'ancienne car elle ne sera pas prise en compte si on fait php artisan:migrate, à moins de tout supprimer et de tout recommencer...
La solution : php artisan make:migration add_role_column_to_users_table
Encore une fois, regarde bien la syntaxe de du nom de la migration ;).

Son contenu sera le suivant :

{"language":"application/x-httpd-php","content":"<?php\n\nuse Illuminate\\Database\\Migrations\\Migration;\nuse Illuminate\\Database\\Schema\\Blueprint;\nuse Illuminate\\Support\\Facades\\Schema;\n\nreturn new class extends Migration\n{\n /**\n * Run the migrations.\n *\n * @return void\n */\n public function up()\n {\n Schema::table('users', function (Blueprint $table) {\n $table->enum('role', ['admin', 'user'])->default('user');\n });\n }\n\n /**\n * Reverse the migrations.\n *\n * @return void\n */\n public function down()\n {\n //\n }\n};\n","filename":"2022_02_08_084148_add_role_column_to_users_table.php"}

Excellent. Et maintenant, il nous suffit de faire php artisan migrate une nouvelle fois et le tour est joué ;)

Alors maintenant nous pouvons modifier notre fichier UserFactory pour intégrer le fait que nous voulons un utilisateur administrateur. N'oublie pas également d'enlever la méthode unverified() et la partie 'email_verified_at' si tu as enlevé la colonne de ta table users !

{"language":"application/x-httpd-php","content":"<?php\n\nnamespace Database\\Factories;\n\nuse Illuminate\\Database\\Eloquent\\Factories\\Factory;\nuse Illuminate\\Support\\Str;\n\n/**\n * @extends \\Illuminate\\Database\\Eloquent\\Factories\\Factory<\\App\\Models\\User>\n */\nclass UserFactory extends Factory\n{\n /**\n * Define the model's default state.\n *\n * @return array\n */\n public function definition()\n {\n return [\n 'name' => $this->faker->name(),\n 'email' => $this->faker->unique()->safeEmail(),\n 'role'=>'admin',\n 'email_verified_at' => now(),\n 'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password\n 'remember_token' => Str::random(10),\n ];\n }\n\n /**\n * Indicate that the model's email address should be unverified.\n *\n * @return \\Illuminate\\Database\\Eloquent\\Factories\\Factory\n */\n public function unverified()\n {\n return $this->state(function (array $attributes) {\n return [\n 'email_verified_at' => null,\n ];\n });\n }\n}\n","filename":"UserFactory.php"}


Arthur, l'apprenti développeurAttends attends, c'est quoi tout ce truc pour le password ?
Comme tu dois le savoir, on est tenu en Europe (et on est tenu dans le monde entier pour des mesures de sécurité) de stocker les mots de passe de manière chiffrée ou hashée. Le password que nous avons renseigné là est le résultat du mot "password" hashé avec la méthode de PHP "password_hash" et l'algorithme BCRYPT. Du coup, quand on voudra essayer de se connecter, n'oublions pas que le mot de passe est tout simplement "password" ;).

Arthur, l'apprenti développeurOk je comprends mieux !
Attaquons-nous maintenant au seeding pour finir avec cette partie. Le seeding nous servira simplement à faire tourner notre UserFactory pour insérer en base de données.
Rendez-vous dans seeders pour trouver DatabaseSeeder et y insérer ce code, explicite :

{"language":"application/x-httpd-php","content":"<?php\n\nnamespace Database\\Seeders;\n\nuse Illuminate\\Database\\Console\\Seeds\\WithoutModelEvents;\nuse Illuminate\\Database\\Seeder;\n\nclass DatabaseSeeder extends Seeder\n{\n /**\n * Seed the application's database.\n *\n * @return void\n */\n public function run()\n {\n \\App\\Models\\User::factory(1)->create();\n }\n}\n","filename":"DatabaseSeeder.php"}

Et enfin, dans notre terminal...

{"language":"shell","content":"php artisan db:seed","filename":""}

Et voilà ! Tu peux regarder le contenu de ta base de données pour vérifier qu'un utilisateur a bien été ajouté.

Cette partie était longue mais importante. Nous avons fait une belle introduction des possibilités offertes par Laravel pour interagir avec la base de données lors de sa création.



Attaquons-nous maintenant aux entités, une partie beaucoup plus courte. J'ai terminé cette partie
Demander de l'assistance