Les événements du modèle de Laravel sont une fonctionnalité très pratique qui vous aide à exécuter automatiquement la logique lors de l'exécution de certaines opérations sur votre modèle éloquent. Cependant, s'ils sont utilisés mal, cela peut parfois conduire à d'étranges effets secondaires.
Cet article explorera ce que sont les événements de modèle et comment les utiliser dans les applications Laravel. Nous explorerons également comment tester les événements du modèle et certains problèmes à connaître lors de leur utilisation. Enfin, nous couvrirons certaines alternatives aux événements modèles que vous pouvez envisager d'utiliser.
Vous avez peut-être entendu parler des "événements" et des "auditeurs". Mais si vous n'en avez pas entendu parler, en voici un bref aperçu de ces éléments:
C'est ce qui se passe dans les applications sur lesquelles vous souhaitez agir - par exemple, les utilisateurs s'inscrivent sur votre site Web, les utilisateurs se connectent, etc.
Habituellement, dans Laravel, les événements sont des classes PHP. En plus des événements fournis par des cadres ou des packages tiers, ils sont généralement enregistrés dans le répertoire app/Events
.
Ce qui suit est un exemple de classe d'événements simples que vous souhaiterez peut-être planifier lorsqu'un utilisateur s'inscrit sur votre site Web:
declare(strict_types=1); namespace App\Events; use App\Models\User; use Illuminate\Broadcasting\InteractsWithSockets; use Illuminate\Foundation\Events\Dispatchable; use Illuminate\Queue\SerializesModels; final class UserRegistered { use Dispatchable; use InteractsWithSockets; use SerializesModels; public function __construct(public User $user) { // } }
Dans l'exemple de base ci-dessus, nous avons une classe d'événements AppEventsUserRegistered
qui accepte une instance de modèle User
dans son constructeur. Cette classe d'événements est un conteneur simple pour enregistrer les instances utilisateur enregistrées.
Ce qui suit est un exemple simple de la façon de planifier l'événement lorsqu'un utilisateur enregistre:
use App\Events\UserRegistered; use App\Models\User; $user = User::create([ 'name' => 'Eric Barnes', 'email' => 'eric@example.com', ]); UserRegistered::dispatch($user);
à l'aide de l'instance utilisateur. En supposant que l'auditeur est enregistré correctement, cela déclenchera tout écouteur qui écoute sur l'événement AppEventsUserRegistered
. AppEventsUserRegistered
Par exemple, respectez notre exemple d'enregistrement de l'utilisateur, vous pouvez envoyer un e-mail de bienvenue à l'utilisateur lorsque l'utilisateur enregistre. Vous pouvez créer un auditeur de l'événement
et envoyer un e-mail de bienvenue. AppEventsUserRegistered
. app/Listeners
declare(strict_types=1); namespace App\Listeners; use App\Events\UserRegistered; use App\Notifications\WelcomeNotification; use Illuminate\Support\Facades\Mail; final readonly class SendWelcomeEmail { public function handle(UserRegistered $event): void { $event->user->notify(new WelcomeNotification()); } }
a une méthode AppListenersSendWelcomeEmail
qui accepte une instance d'événement handle
. Cette méthode est responsable de l'envoi d'un e-mail de bienvenue à l'utilisateur. AppEventsUserRegistered
Pour des instructions plus approfondies sur les événements et les auditeurs, vous souhaiterez peut-être consulter la documentation officielle: https://www.php.cn/link/d9a8c56824cfbe66f28f85edbbe83e09
Dans votre application Laravel, vous devez généralement planifier manuellement les événements lorsque certaines actions se produisent. Comme nous l'avons vu dans l'exemple ci-dessus, nous pouvons planifier des événements en utilisant le code suivant:
declare(strict_types=1); namespace App\Events; use App\Models\User; use Illuminate\Broadcasting\InteractsWithSockets; use Illuminate\Foundation\Events\Dispatchable; use Illuminate\Queue\SerializesModels; final class UserRegistered { use Dispatchable; use InteractsWithSockets; use SerializesModels; public function __construct(public User $user) { // } }
Cependant, lorsque vous utilisez le modèle éloquent dans Laravel, certains événements nous sont automatiquement programmés, nous n'avons donc pas besoin de les planifier manuellement. Si nous voulons effectuer des opérations lorsque des événements se produisent, nous avons juste besoin de définir des auditeurs pour eux.
La liste suivante montre des événements et des déclencheurs planifiés automatiquement par le modèle éloquent:
Dans la liste ci-dessus, vous remarquez peut-être certains noms d'événements similaires; par exemple, creating
et created
. Les événements se terminant par ing
sont exécutés avant que l'opération ne se produise, et les modifications sont persistées dans la base de données. Les événements se terminant par ed
sont exécutés après l'opération, et les modifications sont persistées dans la base de données.
Voyons comment utiliser ces événements de modèle dans une application Laravel.
dispatchesEvents
Écoutez les événements du modèle Une façon d'écouter les événements du modèle est de définir une propriété dispatchesEvents
sur votre modèle.
Cette propriété vous permet de cartographier les événements de modèle éloquents à la classe d'événements qui devrait être planifiée lorsque l'événement se produit. Cela signifie que vous pouvez définir l'auditeur tout comme vous géreriez tout autre événement.
pour fournir plus de contexte, regardons un exemple.
Supposons que nous créons une application de blog avec deux modèles: AppModelsPost
et AppModelsAuthor
. Nous dirons que les deux modèles prennent en charge la suppression douce. Lorsque nous enregistrons un nouveau AppModelsPost
, nous voulons calculer le temps de lecture de l'article en fonction de la longueur du contenu. Lorsque nous supprimons doucement l'auteur, nous voulons que l'auteur supprime doucement tous les articles.
Nous pouvons avoir un modèle AppModelsAuthor
comme indiqué ci-dessous:
use App\Events\UserRegistered; use App\Models\User; $user = User::create([ 'name' => 'Eric Barnes', 'email' => 'eric@example.com', ]); UserRegistered::dispatch($user);
Dans le modèle ci-dessus, nous avons:
dispatchesEvents
qui mappe l'événement de modèle deleted
à la classe d'événements AppEventsAuthorDeleted
. Cela signifie que lorsque le modèle est supprimé, un nouvel événement AppEventsAuthorDeleted
sera planifié. Nous créerons cette classe d'événements plus tard. posts
. IlluminateDatabaseEloquentSoftDeletes
. Créons notre modèle AppModelsPost
:
declare(strict_types=1); namespace App\Events; use App\Models\User; use Illuminate\Broadcasting\InteractsWithSockets; use Illuminate\Foundation\Events\Dispatchable; use Illuminate\Queue\SerializesModels; final class UserRegistered { use Dispatchable; use InteractsWithSockets; use SerializesModels; public function __construct(public User $user) { // } }
Dans le modèle ci-dessus AppModelsPost
, nous avons:
dispatchesEvents
qui mappe l'événement de modèle saving
à la classe d'événements AppEventsPostSaving
. Cela signifie que lorsque le modèle est créé ou mis à jour, un nouvel événement AppEventsPostSaving
sera planifié. Nous créerons cette classe d'événements plus tard. author
. IlluminateDatabaseEloquentSoftDeletes
. Notre modèle est maintenant prêt, alors créons nos classes d'événements AppEventsAuthorDeleted
et AppEventsPostSaving
.
Nous créerons une classe d'événements AppEventsPostSaving
qui sera planifiée lors de l'enregistrement d'un nouvel article:
use App\Events\UserRegistered; use App\Models\User; $user = User::create([ 'name' => 'Eric Barnes', 'email' => 'eric@example.com', ]); UserRegistered::dispatch($user);
Dans le code ci-dessus, nous pouvons voir la classe d'événements AppEventsPostSaving
, qui accepte une instance de modèle AppModelsPost
dans son constructeur. Cette classe d'événements est un conteneur simple pour enregistrer l'instance de l'article enregistrée.
De même, nous pouvons créer une classe d'événements AppEventsAuthorDeleted
qui sera planifiée lors de la suppression de l'auteur:
declare(strict_types=1); namespace App\Listeners; use App\Events\UserRegistered; use App\Notifications\WelcomeNotification; use Illuminate\Support\Facades\Mail; final readonly class SendWelcomeEmail { public function handle(UserRegistered $event): void { $event->user->notify(new WelcomeNotification()); } }
Dans la classe ci-dessus AppEventsAuthorDeleted
, nous pouvons voir que le constructeur accepte une instance de modèle AppModelsAuthor
.
Maintenant, nous pouvons continuer à créer des auditeurs.
Créons d'abord un auditeur qui peut être utilisé pour calculer l'estimation du temps de lecture de l'article.
Nous allons créer une nouvelle classe d'écoute AppListenersCalculateReadTime
:
UserRegistered::dispatch($user);
Comme nous le voyons dans le code ci-dessus, nous n'avons qu'une méthode handle
. Il s'agit d'une méthode qui sera automatiquement appelée lors de la planification de l'événement AppEventsPostSaving
. Il accepte une instance de la classe d'événements AppEventsPostSaving
qui contient l'article enregistré.
Dans la méthode handle
, nous utilisons une formule simple pour calculer le temps de lecture de l'article. Dans cet exemple, nous supposons que la vitesse de lecture moyenne est de 265 mots par minute. Nous calculons le temps de lecture en secondes, puis en définissant l'attribut read_time_in_seconds
sur le modèle d'article.
Étant donné que cet écouteur sera appelé lorsque l'événement saving
Modèle est déclenché, cela signifie que l'attribut read_time_in_seconds
est calculé chaque fois que l'article est persistant dans la base de données avant de la créer ou de la mettre à jour.
Nous pouvons également créer un auditeur qui supprime doucement tous les articles associés lors de la suppression doucement de l'auteur.
Nous pouvons créer une nouvelle classe d'écoute AppListenersSoftDeleteAuthorRelationships
:
declare(strict_types=1); namespace App\Events; use App\Models\User; use Illuminate\Broadcasting\InteractsWithSockets; use Illuminate\Foundation\Events\Dispatchable; use Illuminate\Queue\SerializesModels; final class UserRegistered { use Dispatchable; use InteractsWithSockets; use SerializesModels; public function __construct(public User $user) { // } }
Dans l'auditeur ci-dessus, la méthode handle
accepte une instance de la classe d'événements AppEventsAuthorDeleted
. Cette classe d'événements contient l'auteur supprimé. Ensuite, nous utilisons la relation posts
pour supprimer l'article de l'auteur. delete
est supprimé doucement, tous les articles de tous les auteurs seront également supprimés doucement. AppModelsAuthor
Utilisez la fermeture pour écouter les événements du modèle
Jetons un coup d'œil à l'exemple d'articles de suppression douce lorsque nous avons doucement supprimé l'auteur. Nous pouvons mettre à jour notre modèle
pour contenir une fermeture qui écoute les événements de modèle AppModelsAuthor
: deleted
use App\Events\UserRegistered; use App\Models\User; $user = User::create([ 'name' => 'Eric Barnes', 'email' => 'eric@example.com', ]); UserRegistered::dispatch($user);
du modèle. Nous voulons écouter l'événement modèle booted
, nous avons donc utilisé deleted
. De même, si nous voulons créer un auditeur pour l'événement de modèle self::deleted
, nous pouvons utiliser created
et ainsi de suite. La méthode self::created
accepte une fermeture qui reçoit le self::deleted
supprimé. Cette fermeture sera exécutée lorsque le modèle sera supprimé, de sorte que tous les articles d'auteur seront supprimés. AppModelsAuthor
Une astuce pratique est que vous pouvez également utiliser la fonction
pour faire la file d'attente de fermetures. Cela signifie que le code de l'auditeur sera poussé dans la file d'attente pour s'exécuter en arrière-plan, plutôt que dans le même cycle de vie. Nous pouvons mettre à jour l'auditeur à la queregue comme suit: IlluminateEventsqueueable
declare(strict_types=1); namespace App\Listeners; use App\Events\UserRegistered; use App\Notifications\WelcomeNotification; use Illuminate\Support\Facades\Mail; final readonly class SendWelcomeEmail { public function handle(UserRegistered $event): void { $event->user->notify(new WelcomeNotification()); } }
. IlluminateEventsqueueable
Une autre façon de prendre pour écouter les événements de modèle est d'utiliser des observateurs de modèles. Les observateurs du modèle vous permettent de définir tous les auditeurs du modèle dans une classe.
Habituellement, ce sont des classes qui existent dans le répertoire app/Observers
, et ils ont des méthodes correspondant aux événements du modèle que vous souhaitez écouter. Par exemple, si vous souhaitez écouter un événement modèle deleted
, vous définissez une méthode deleted
dans la classe Observer. Si vous souhaitez écouter un événement modèle created
, vous définissez une méthode created
dans la classe Observer, etc.
Voyons comment créer un observateur de modèles pour notre modèle AppModelsAuthor
Écoute du modèle pour les événements de modèle deleted
:
declare(strict_types=1); namespace App\Events; use App\Models\User; use Illuminate\Broadcasting\InteractsWithSockets; use Illuminate\Foundation\Events\Dispatchable; use Illuminate\Queue\SerializesModels; final class UserRegistered { use Dispatchable; use InteractsWithSockets; use SerializesModels; public function __construct(public User $user) { // } }
Comme nous le voyons dans le code ci-dessus, nous créons un observateur avec la méthode deleted
. Cette méthode accepte les instances du modèle AppModelsAuthor
supprimé. Ensuite, nous utilisons la relation posts
pour supprimer l'article de l'auteur. delete
et created
. Nous pouvons mettre à jour nos observateurs comme ceci: updated
use App\Events\UserRegistered; use App\Models\User; $user = User::create([ 'name' => 'Eric Barnes', 'email' => 'eric@example.com', ]); UserRegistered::dispatch($user);
, nous devons demander à Laravel de l'utiliser. Pour ce faire, nous pouvons utiliser l'attribut AppObserversAuthorObserver
. Cela nous permet d'associer des observateurs au modèle, similaire à la façon dont nous enregistrons les lunettes de requête globales à l'aide de l'attribut #[IlluminateDatabaseEloquentAttributesObservedBy]
(comme indiqué pour comprendre comment maîtriser la portée de la requête dans Laravel). Nous pouvons mettre à jour notre modèle #[ScopedBy]
comme celui-ci pour utiliser l'observateur: AppModelsAuthor
declare(strict_types=1); namespace App\Listeners; use App\Events\UserRegistered; use App\Notifications\WelcomeNotification; use Illuminate\Support\Facades\Mail; final readonly class SendWelcomeEmail { public function handle(UserRegistered $event): void { $event->user->notify(new WelcomeNotification()); } }
Testez votre modèle d'événement
Voyons comment tester les événements du modèle que nous avons créés dans l'exemple ci-dessus.
Nous écrivons d'abord un test pour nous assurer que l'article de l'auteur est supprimé doucement lorsque l'auteur est supprimé doucement. Le test peut ressembler à ceci:
UserRegistered::dispatch($user);
Il s'agit d'un test très simple mais efficace que nous pouvons utiliser pour nous assurer que notre logique fonctionne comme prévu. L'avantage de ce test est qu'il devrait fonctionner avec chacune des méthodes dont nous discutons dans cet article. Donc, si vous basculez entre l'une des méthodes dont nous avons discuté dans cet article, votre test devrait toujours passer.
De même, nous pouvons rédiger certains tests pour nous assurer que le temps de lecture de l'article est calculé lors de la création ou de la mise à jour. Le test peut ressembler à ceci:
declare(strict_types=1); namespace App\Events; use App\Models\User; use Illuminate\Broadcasting\InteractsWithSockets; use Illuminate\Foundation\Events\Dispatchable; use Illuminate\Queue\SerializesModels; final class UserRegistered { use Dispatchable; use InteractsWithSockets; use SerializesModels; public function __construct(public User $user) { // } }
Nous avons deux tests dessus:
Bien que les événements du modèle soient très pratiques, il y a certains problèmes à connaître lors de leur utilisation.
Les événements du modèle sont prévus à partir du modèle éloquent uniquement. Cela signifie que si vous utilisez une façade IlluminateSupportFacadesDB
pour interagir avec les données sous-jacentes du modèle dans la base de données, ses événements ne seront pas planifiés.
Par exemple, regardons un exemple simple, nous utilisons la façade IlluminateSupportFacadesDB
pour supprimer l'auteur:
use App\Events\UserRegistered; use App\Models\User; $user = User::create([ 'name' => 'Eric Barnes', 'email' => 'eric@example.com', ]); UserRegistered::dispatch($user);
L'exécution du code ci-dessus supprimera l'auteur de la base de données comme prévu. Cependant, les événements du modèle deleting
et deleted
ne sont pas planifiés. Donc, si vous définissez des auditeurs pour ces événements de modèle lors de la suppression de l'auteur, ils ne seront pas exécutés.
De même, si vous utilisez Eloquent pour mettre à jour par lots ou supprimer un modèle, les événements de modèle saved
, updated
, deleting
et deleted
ne sont pas planifiés pour les modèles affectés. En effet, les événements sont prévus à partir du modèle lui-même. Cependant, lorsque les mises à jour par lots et les suppressions sont mises à jour, le modèle n'est pas réellement récupéré dans la base de données, donc les événements ne sont pas planifiés.
Par exemple, supposons que nous utilisons le code suivant pour supprimer l'auteur:
declare(strict_types=1); namespace App\Listeners; use App\Events\UserRegistered; use App\Notifications\WelcomeNotification; use Illuminate\Support\Facades\Mail; final readonly class SendWelcomeEmail { public function handle(UserRegistered $event): void { $event->user->notify(new WelcomeNotification()); } }
Étant donné que la méthode delete
est appelée directement sur le générateur de requête, les événements de modèle deleting
et deleted
ne sont pas prévus pour cet auteur.
J'aime utiliser des événements modèles dans mes projets. Ils servent de bon moyen de découpler mon code et me permettent également d'exécuter automatiquement la logique lorsque je n'ai pas beaucoup de contrôle sur le code qui affecte le modèle. Par exemple, si je supprime l'auteur dans Laravel Nova, je peux toujours exécuter une logique lors de la suppression de l'auteur.
Cependant, il est important de savoir quand envisager d'utiliser différentes méthodes.
Pour expliquer cela, examinons un exemple de base où nous pourrions vouloir éviter d'utiliser des événements de modèle. Étendez notre exemple de l'application de blog simple précédente, en supposant que nous voulons exécuter ce qui suit lors de la création d'un nouvel article:
afin que nous puissions créer trois auditeurs distincts (un pour chaque tâche) qui s'exécutent à chaque fois qu'une nouvelle instance AppModelsPost
est créée.
Mais passons maintenant en revue l'un de nos tests précédents:
declare(strict_types=1); namespace App\Events; use App\Models\User; use Illuminate\Broadcasting\InteractsWithSockets; use Illuminate\Foundation\Events\Dispatchable; use Illuminate\Queue\SerializesModels; final class UserRegistered { use Dispatchable; use InteractsWithSockets; use SerializesModels; public function __construct(public User $user) { // } }
Si nous exécutons le test ci-dessus, il déclenchera également ces trois opérations lorsque le modèle AppModelsPost
est créé via son usine. Bien sûr, le calcul du temps de lecture est une tâche secondaire, donc cela n'a pas beaucoup d'importance. Mais nous ne voulons pas essayer de passer des appels d'API ou d'envoyer des notifications pendant les tests. Ce sont des effets secondaires inattendus. Si le développeur qui rédige le test n'est pas au courant de ces effets secondaires, il peut être difficile de retrouver la raison pour laquelle ces opérations se produisent.
Nous voulons également éviter d'écrire une logique spécifique au test dans l'auditeur, ce qui empêche ces opérations de fonctionner pendant les tests. Cela rendra le code d'application plus complexe et plus difficile à maintenir.
C'est l'un des cas où vous voudrez peut-être envisager une approche plus explicite plutôt que de compter sur des événements de modèle automatique.
Une façon peut être d'extraire votre code de création AppModelsPost
dans un service ou une classe d'action. Par exemple, une classe de service simple peut ressembler à ceci:
use App\Events\UserRegistered; use App\Models\User; $user = User::create([ 'name' => 'Eric Barnes', 'email' => 'eric@example.com', ]); UserRegistered::dispatch($user);
Dans la classe ci-dessus, nous appelons manuellement le code qui calcule le temps de lecture, envoie des notifications et des publications à Twitter. Cela signifie que nous avons un meilleur contrôle sur le moment où ces opérations sont exécutées. Nous pouvons également se moquer facilement de ces méthodes dans les tests pour les empêcher de fonctionner. Nous pouvons toujours faire la queue de ces opérations si nécessaire (dans ce cas, nous le ferons très probablement).
Par conséquent, nous pouvons supprimer les événements du modèle et les auditeurs pour ces opérations. Cela signifie que nous pouvons utiliser cette nouvelle classe AppServicesPostService
dans notre code d'application et utiliser en toute sécurité l'usine Model dans notre code de test.
L'avantage supplémentaire de le faire est qu'il rend également le code plus facile à comprendre. Comme je l'ai brièvement mentionné, une critique commune de l'utilisation d'événements et d'auditeurs est qu'il peut cacher la logique commerciale dans des endroits inattendus. Donc, si les nouveaux développeurs rejoignent l'équipe, s'ils sont déclenchés par des événements modèles, ils ne savent peut-être pas où ou pourquoi certaines opérations se produisent.
Cependant, si vous souhaitez toujours utiliser des événements et des auditeurs pour une telle logique, vous pourriez envisager d'utiliser une approche plus explicite. Par exemple, vous pouvez planifier un événement de la classe de service pour déclencher l'auditeur. De cette façon, vous pouvez toujours utiliser les avantages du découplage des événements et des auditeurs, mais vous avez un meilleur contrôle sur le moment où les événements sont prévus.
Par exemple, nous pouvons mettre à jour la méthode AppServicesPostService
ci-dessus dans notre exemple createPost
pour planifier les événements:
declare(strict_types=1); namespace App\Listeners; use App\Events\UserRegistered; use App\Notifications\WelcomeNotification; use Illuminate\Support\Facades\Mail; final readonly class SendWelcomeEmail { public function handle(UserRegistered $event): void { $event->user->notify(new WelcomeNotification()); } }
En utilisant la méthode ci-dessus, nous pouvons toujours avoir un auditeur séparé pour faire des demandes d'API et envoyer des notifications à Twitter. Mais nous avons un meilleur contrôle sur le moment où ces opérations sont exécutées, elles ne sont donc pas exécutées lors des tests à l'aide de l'usine du modèle.
Il n'y a pas de règle d'or pour décider d'utiliser l'une de ces méthodes. Tout dépend de vous, de votre équipe et des fonctionnalités que vous construisez. Cependant, j'ai tendance à suivre les règles de base suivantes:
Pour résumer rapidement ce que nous avons introduit dans cet article, voici quelques-uns des avantages et des inconvénients de l'utilisation d'événements modèles:
J'espère que cet article donne un aperçu de ce que sont les événements de modèle et des différentes façons de les utiliser. Cela devrait également vous montrer comment tester les codes d'événements du modèle et certains problèmes à connaître lors de leur utilisation.
Vous devriez maintenant avoir suffisamment de confiance pour utiliser des événements de modèle dans votre application Laravel.
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!