Posts

Since 2024
Add Comments to your Laravel Application with the Commenter Package
Add Comments to your Laravel Application with the Commenter Package

The Commenter package for Laravel "is a feature-rich modern package that addresses all your commenting needs in a Laravel application." The package includes an admin panel to manage comments across all "commentable" models. Commenter demo project example The package models include a Commenter, and a Commentable model. You can use this package with your Eloquent models by implementing the CommentableContract and CommenterContract interfaces. The following example illustrates that Post is commentable, and User is a commenter: use LakM\Comments\Concerns\Commentable; use LakM\Comments\Contracts\CommentableContract; class Post extends Model implements CommentableContract { use Commentable; } And the Commentable interface on the User model, for example: namespace App\Models; use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Notifications\Notifiable; use LakM\Comments\Concerns\Commenter; class User extends Authenticatable { use HasFactory, Notifiable, Commenter; } Commenter features: Simple, modern, and user-friendly interfaces. Mobile responsiveness. WYSIWYG editor. Syntax highlighting. Robust security features. Effective spam prevention. Reaction options. Support for threaded replies. User mention functionality. Display a list of users who reacted (auth mode only). Pagination. Support is available for both authentication mode and guest mode (mutually exclusive). Advanced filtering and sorting options. Responsive design using a combination of Livewire and Alpine.js. Optimized performance. And much more. To get started with this package, I recommend reading the Commenter documentation. You can view the source code on GitHub, and the package author also has a demo project to get a basic overview of this package. The post Add Comments to your Laravel Application with the Commenter Package appeared first on Laravel News. Join the Laravel Newsletter to get Laravel articles like this directly in your inbox.

Take the Annual State of Laravel 2024 Survey
Take the Annual State of Laravel 2024 Survey

The annual State of Laravel survey is open for 2024, and you can participate to help identify trends in how the ecosystem has changed over the last twelve months. The State of Laravel survey is "an attempt to gain insight into the representation of the diverse technologies and behaviors of this outstanding community." The survey continues to build on surfacing long-term trends in Laravel and surrounding ecosystems over the last four years. The survey collects statistics about developer demographics, tech stack usage, development tools, production, and opinion categories. Let's highlight a few trends we're seeing from the results of the State of Laravel Survey 2023: Which other programming languages do you use? JavaScript and TypeScript dominate the top "other" programming languages respondents used in projects. JavaScript and TypeScript are ubiquitous languages, so it is no surprise that virtually every respondent uses these languages. Outside of the JS ecosystem, Python remains strong, with 25% of users writing Python. Golang saw a slight uptick of +3% (~12%) in 2023 as well: What are you using to make your frontend reactive? Vue.js remains king and holds steady in the ~ 60% range of respondents using Vue on the front end in some capacity. React is increasing slowly, and I want to see if React sees a nice uptick in adoption in the 2024 survey. Livewire will be another front-end choice, and we've seen a steady increase in tutorials, projects, and developers sharing Livewire-related code. Filament also enters the survey under the new category Administration Panels, which might contribute to an uptick in Livewire usage. Which versions of PHP are you using for your applications? The 2024 survey introduces the adoption of PHP 8.3 to the PHP versions developers use in applications (multiple choice). Laravel has done an excellent job of supporting backward compatibility while using modern PHP 8 features. The latest version, Laravel 11, requires PHP ^8.2, and Laravel 10 requires PHP ^8.1. I'd expect most developers are using PHP 8.3 this year, especially with development options like Laravel Herd: Are you using any library to build administration panels? Administration panels are a new survey question this year and include Laravel Nova, Filament, and Backpack. I don't have any solid predictions in this category, but I would guess that we will see a healthy number of responses for all three options. There are other options in this space; it would be interesting to see how many people would tick the "other" box here as well. Take the Survey Take the State of Laravel 2024 survey to contribute your voice to the community. By sharing and participating, you can help us gain more insights into this amazing community. The post Take the Annual State of Laravel 2024 Survey appeared first on Laravel News. Join the Laravel Newsletter to get Laravel articles like this directly in your inbox.

Laravel Advanced String Package
Laravel Advanced String Package

Welcome to the first episode of a new "Creator Spotlight" video series where we interview Laravel developers about the things they are creating. Today we talk with Matt Stenson who created a Laravel Advanced String package that adds extra advanced string manipulation methods to the built-in Str class. This package provides extended functionality on strings, such as advanced password generation, data redaction, and more. Available Methods The String methods this package includes are: advPassword Generates a random, secure password. public static function advPassword( $length = 32, $letters = true, $numbers = true, $symbols = true, $spaces = false, $upperLetters = false, $lowerLetters = false, $exclude = [] ) charWrap Wraps a string at a given number of characters regardless of words. public static function charWrap( $string, $length = 80 ) emailDomain Extracts the domain part of an email address, including subdomains. public static function emailDomain( $string ) readTime Calculates the read time of a string. public static function readTime( $string, $wpm = 200 ) redactCreditCard Redacts credit card numbers in a string. public static function redactCreditCard( $string, $redacted = '********', $exclude = [] ) redactSsn Redacts Social Security Numbers (SSN) in a string. public static function redactSsn( $string, $redacted = '********', $dashes = true, $noDashes = true ) splitName Splits a full name into first name, middle name (if present), and last name, removing any prefixes and suffixes. This method can handle both "Firstname Lastname" and "Lastname, Firstname" formats. public static function splitName( $name ) More information For more info check out the Github Repo for installation details, API docs, and more. The post Laravel Advanced String Package appeared first on Laravel News. Join the Laravel Newsletter to get Laravel articles like this directly in your inbox.

The Best Laravel Tutorials and Resources for Developers
The Best Laravel Tutorials and Resources for Developers

Laravel is one of the most popular PHP frameworks, known for its elegant syntax and powerful features. According to the JetBrains Development Ecosystem Report 2023, over 60% of PHP developers use Laravel, and nowadays, many opt for this framework for their professional development. Whether you're a beginner just starting with web development or an experienced pro looking to boost your skills, this blog post will guide you through the best educational resources available for mastering Laravel. From online courses and books to YouTube channels and community resources, you'll find everything you need to become proficient in Laravel. Online Courses Online courses are a great way to learn Laravel at your own pace. They offer structured learning paths and hands-on projects to help you build real-world applications. Here are some of the best online courses available: Laracasts Often referred to as the "Netflix for developers", Laracasts, managed by well-known Laravel community member Jeffrey Way, offers a vast library of high-quality video tutorials focused primarily on Laravel. It also includes a series on modern PHP development, testing, and other related technologies. With its active community and discussion forums, Laracasts is an essential resource for any Laravel developer. The content ranges from beginner to advanced levels, covering fundamental concepts, real-world applications, and best practices. Here, you can find the most interesting ones depending on your experience: 30 Days to Learn Laravel is an extensive series of video tutorials by Jeffrey Way that covers Laravel from the basics to advanced topics. It includes topics like routing, controllers, Eloquent ORM, testing, and deployment. The hands-on approach helps learners build applications while understanding the underlying concepts of Laravel. For those who are already familiar with Laravel and ready to go deeper, discover the free PhpStorm for Laravel Developers series that is tailored to help Laravel developers maximize their efficiency using PhpStorm, a JetBrains IDE for PHP. This series covers a variety of topics, including PhpStorm basic setup and configuration, code navigation, refactoring, and debugging, specifically within a Laravel project. The series dives into advanced features like live templates, macros, and integration with Laravel tools such as Tinker and Artisan. Each episode is designed to help developers become more productive by leveraging PhpStorm’s powerful features tailored for Laravel development. This video course doesn’t require a Laracasts subscription. Subscription price: USD 15/month or USD 99/year, providing access to all content, including series on modern PHP development, testing, and more. Some courses are free. Laravel Daily Laravel Daily offers a variety of tutorials and tips focused on Laravel development and is managed by experienced web developer Povilas Korop. The content is updated regularly and covers both beginner and advanced topics in Laravel. If you’ve just started with Laravel, the Laravel 11 For Beginners: Your First Project free course might be an ideal choice for you. It offers a practical introduction to Laravel 11 and, instead of delving deep into theory, focuses on creating a small project to manage a blog. By completing this project, learners will understand the essential features of Laravel, such as routing, Blade templates, database migrations, and Eloquent ORM. For an intermediate level of experience, discover the Laravel Collections Chains: 15 Real Examples course, which focuses on mastering Laravel Collections through 15 practical, practical examples. Laravel Collections provide a fluent, convenient wrapper for working with arrays and data sets in Laravel. This course helps developers leverage the power of Collections to write more efficient and readable code, including various use cases such as data filtering, transformation, aggregation, and complex manipulations. Subscription price: USD 29/month or USD 129/year. Some courses are free. Codecourse Codecourse offers a variety of tutorials on web development at both the beginner and intermediate levels, with a strong focus on Laravel. Check out the Laravel Basics series, which takes you through the most-used parts of the framework to get you up and running in no time. Udemy Udemy is a large educational platform offering a variety of courses across different fields. One notable course for Laravel beginners is PHP with Laravel for Beginners - Become a Master in Laravel. This tutorial is designed to take a learner from a complete beginner to a Laravel master. It covers the basics of PHP and Laravel, including routing, controllers, views, and database interactions with Eloquent ORM. The course includes hands-on projects, allowing you to build real-world applications and solidify your understanding. With lifetime access to the course materials, you can learn at your own pace. Course price: USD 23 (prices may vary). Coursera Coursera is an online learning platform that partners with top universities and organizations worldwide to offer online courses, specializations, degrees, and professional certificates across a wide range of subjects. The Mastering Laravel Framework and PHP course covers the essentials of PHP and the Laravel framework, which is ideal for beginners with some prior PHP knowledge. It starts with setting up a development environment, followed by fundamental PHP concepts, including arrays, functions, and form handling. The course then delves into the Laravel framework, covering key features such as routing, middleware, controllers, and views. Students will learn to build web applications from scratch, applying best practices in Laravel. The course includes video lectures, readings, and quizzes. Course price: free to audit; сertificate available for a fee (typically around USD 49). Books Laravel: Up & Running by Matt Stauffer Approved by the creator of Laravel, Taylor Otwell, Laravel: Up & Running is a comprehensive guide that takes you through the essentials of Laravel, from installation to advanced features. Matt Stauffer, a renowned Laravel developer, explains concepts clearly and provides practical examples. The book covers routing, middleware, controllers, Eloquent ORM, Blade templating, and more. It also dives into advanced topics like testing, queues, and real-time event broadcasting. “Not only is Matt one of the most knowledgeable members of the Laravel community, he is also a fantastic teacher. I’m proud to recommend this book as a thorough, extensive guide to the Laravel framework.” – Taylor Otwell, Creator of Laravel This book is a must-have for both new and experienced Laravel developers looking to solidify and deepen their knowledge. Price: USD 39 (may vary). YouTube Channels YouTube is a fantastic resource for learning Laravel through video tutorials. Here are some of the best channels to help you get started: Traversy Media Traversy Media, created by Brad Traversy, is renowned for its high-quality web development tutorials. On this channel, you can find the Laravel Crash Course tutorial designed for beginners, which covers everything from setting up a Laravel project to deploying it. The Net Ninja The Net Ninja is another well-known source that provides detailed and well-structured tutorials on web development. The Laravel Tutorial for Beginners playlist covers the basics of Laravel, from installation to building a full-fledged web application. Each video is short and focused, making it easy to follow along and grasp the concepts. Documentation and Community Resources Official Laravel documentation The Laravel documentation is a comprehensive and up-to-date resource covering every aspect of the framework. It includes detailed explanations, code examples, and guides on various features such as routing, middleware, authentication, and more. It's an essential resource for both new and experienced developers working with Laravel. Regular updates ensure that you have the latest information on new features and best practices. Laravel News Laravel News is a community-driven portal that provides the latest news, tutorials, and packages for Laravel. It features articles on new releases, best practices, and tips for improving your Laravel projects. The site also includes interviews with prominent Laravel developers and information about upcoming events and conferences. Laravel News is an excellent way to stay updated on the latest trends and developments in the Laravel ecosystem. It's a valuable resource for continuous learning and keeping your skills sharp. Laravel Forum The Laracasts forum is a vibrant community where you can ask questions, share knowledge, and discuss all things Laravel. The forums are frequented by experienced developers and Laravel experts who provide helpful advice and solutions. You can find discussions on a wide range of topics, from troubleshooting specific issues to best practices for Laravel development. Participating in the forums is a great way to connect with other developers and learn from their experiences. It's an excellent resource for getting support and staying engaged with the Laravel community. Laravel Community Laravel.io is a Laravel community portal that provides various resources to help developers learn, share, and connect with others. It includes a forum, a compendium of articles written by community members covering a wide range of topics, a real-time chat that allows developers to communicate with each other for quick assistance, information, and more. Closing thoughts This guide provides a comprehensive overview of the best resources available for learning Laravel. Whether you prefer structured online courses, in-depth books, engaging YouTube tutorials, or active community participation, there's something here for every learner. Dive in, start exploring, and take your Laravel skills to the next level! The post The Best Laravel Tutorials and Resources for Developers appeared first on Laravel News. Join the Laravel Newsletter to get Laravel articles like this directly in your inbox.

Visual EXPLAIN for MySQL and Laravel
Visual EXPLAIN for MySQL and Laravel

The MySQL Visual Explain tool by Tobias Petry helps users analyze slow queries by providing an easy-to-understand visual representation of MySQL’s EXPLAIN output. This tool enables you to decipher MySQL’s default, often cryptic EXPLAIN output, making it accessible even for those without deep database expertise, providing an excellent visualization of query performance: MySQL Explain example visualization The MySQL Visual Explain website has an accompanying API and an integration for laravel. The Laravel package adds methods to the query builder with various options such as automatically running a visual explain and providing a link, dumping the visual explain, or outputting the URL and stopping execution: // $url will be e.g. https://mysqlexplain.com/explain/01j2gcrbsjet9r8rav114vgfsy $url = Film::where('description', 'like', '%astronaut%') ->visualExplain(); // URL to EXPLAIN will be printed to screen $users = Film::where('description', 'like', '%astronaut%') ->dumpVisualExplain() ->get(); // URL to EXPLAIN will be printed to screen & execution is stopped Film::where('description', 'like', '%astronaut%') ->ddVisualExplain(); // Submit raw queries use Tpetry\MysqlExplain\Facades\MysqlExplain; $url = MysqlExplain::submitQuery( DB::connection('mysql'), 'SELECT * FROM actor WHERE first_name = ?', ['PENEL\'OPE'], ); For more information, you can visit MySQL Visual Explain. The post Visual EXPLAIN for MySQL and Laravel appeared first on Laravel News. Join the Laravel Newsletter to get Laravel articles like this directly in your inbox.

Introducing Built with Laravel
Introducing Built with Laravel

Today I'm excited to share a new resource I've built for the Laravel community: BuiltWithLaravel.com, a list of companies and organizations using Laravel in the non-Laravel world. This is the list to use when you want to show someone how widespread Laravel's adoption is. The Back Story At Tighten, I spend a lot of my time meeting with potential clients. Some of our prospective partners have already committed to Laravel, so the conversation is easier. But many folks considering working with us are just kicking the tires because someone in their organization has recommended Laravel. Inevitably, someone in the organization needs convincing that Laravel is legitimate. They've heard bad things about PHP, or maybe they just have never heard of Laravel, and that's enough to scare them off. One of my key tools in this type of conversation is to show them a list of other companies that have chosen Laravel and thrived as a result of it. But that list has always been a personal list, sitting in my private notes somewhere. Earlier this year, I set out to build the ultimate list of companies using Laravel that would show our potential clients that Laravel truly is a key player in building stable, scalable web applications. And I set out to do it publicly, so I'm helping everyone, not just Tighten. BuiltWithLaravel.com After the former owner of the builtwithlaravel.com domain generously donated it to my cause (thanks Ollie!), I brought on a designer and we spent the month of July designing and building out the site. And it's live now! Built with Laravel Each organization shows you any specific technologies we know they're using, and any specific public-facing sites we know use Laravel. Sometimes we know an organization uses Laravel internally, but we can't say specifically where; they're still listed on the site, but with just a logo. Asking for Your Help I spent most of my time and energy building the site instead of the list, but here's the great news: there's a suggestion form so you can share more sites! Here are the primary criteria for being featured on this site: A company or organization that uses Laravel somewhere in the organization, even if not on their public web page The company doesn't purely exist to target the Laravel ecosystem (or if they do, they are so deeply successful it's impressive in its own right) We can publicly share attribution of how we know they're using it No packages or other code, or marketing sites for individual developers or agencies Basically: If you were trying to convince the CEO of a Silicon Valley startup or a Fortune 500 company that Laravel is legit, would this organization help convince them? If so, I want it here. Please note that I'll be manually reviewing and screenshotting sites for now, so it'll take a while to get a good workflow set up at Tighten so other folks can help me. So please be patient with any delays! Other Work If you're looking for a broader list of sites using Laravel, the closest I've found is Wire in the Wild, a list of projects and sites using Livewire, and Made with Laravel, a list of projects and sites using Laravel. I want to honor the contribution of both of those sites to our community, and also specifically name that they have a different focus: each of them highlight a huge swath of smaller projects and packages, and each of them succeed when they have more content. Built with Laravel, on the other hand, is focused on a small, hand curated list of impressive organizations. Not better, just different! So, please, check out the site, and let me know who you think we're missing! The post Introducing Built with Laravel appeared first on Laravel News. Join the Laravel Newsletter to get Laravel articles like this directly in your inbox.

A guide to Laravel's model events
A guide to Laravel's model events

Model events are a really handy feature in Laravel that can help you to automatically run logic when certain actions are performed on your Eloquent models. But they can sometimes lead to weird side effects if they're not used correctly. In this article, we're going to look at what model events are and how to use them in your Laravel application. We'll also look at how to test your model events and some of the gotchas to be aware of when using them. Finally, we'll take a look at some alternative approaches to model events that you might want to consider using. What are Events and Listeners? You may have already heard of "events" and "listeners". But if you haven't, here's a quick summary of what they are: Events These are things that happen in your application that you want to act on—for example, a user registering on your site, a user logging in, etc. Typically, in Laravel, events are PHP classes. Apart from events provided by the framework or third-party packages, they're usually kept in the app/Events directory. Here's an example of a simple event class that you might want to dispatch whenever a user registers on your site: 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) { // } } In the basic example above, we have an App\Events\UserRegistered event class that accepts a User model instance in its constructor. This event class is a simple data container that holds the user instance that was registered. When dispatched, the event will trigger any listeners that are listening for it. Here's a simple example of how you might dispatch that event when a user registers: use App\Events\UserRegistered; use App\Models\User; $user = User::create([ 'name' => 'Eric Barnes', 'email' => 'eric@example.com', ]); UserRegistered::dispatch($user); In the example above, we're creating a new user and then dispatching the App\Events\UserRegistered event with the user instance. Assuming the listeners are registered correctly, this will trigger any listeners that are listening for the App\Events\UserRegistered event. Listeners Listeners are blocks of code that you want to run when a specific event occurs. For instance, sticking with our user registration example, you might want to send a welcome email to the user when they register. You could create a listener that listens for the App\Events\UserRegistered event and sends the welcome email. In Laravel, listeners are typically (but not always - we'll cover this later) classes found in the app/Listeners directory. An example of a listener that sends a welcome email to a user when they register might look like this: 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()); } } As we can see in the code example above, the App\Listeners\SendWelcomeEmail listener class has a handle method that accepts an App\Events\UserRegistered event instance. This method is responsible for sending a welcome email to the user. For a more in-depth explanation of events and listeners, you might want to check out the official documentation: https://laravel.com/docs/11.x/events What are Model Events? In your Laravel applications, you'll typically need to manually dispatch events when certain actions occur. As we saw in our example above, we can use the following code to dispatch an event: UserRegistered::dispatch($user); However, when working with Eloquent models in Laravel, there are some events which are automatically dispatched for us, so we don't need to manually dispatch them. We just need to define listeners for them if we want to perform an action when they occur. The list below shows the events are automatically dispatched by Eloquent models along with their triggers: retrieved - retrieved from the database. creating - model is being created. created - model has been created. updating - model is being updated. updated - model has been updated. saving - model is being created or updated. saved - model has been created or updated. deleting - model is being deleted. deleted - model has been deleted. trashed - model has been soft deleted. forceDeleting - model is being force deleted. forceDeleted - model has been force deleted restoring - model is being restored from a soft delete. restored - model has been restored from a soft delete. replicating - model is being replicated. In the list above, you may notice some of the event names are similar; for example, creating and created. The events ended in ing are performed before the action occurs and the changes are persisted in the database. Whereas the events ended in ed are performed after the action occurs and the changes are persisted in the database. Let's take a look at how we can use these model events in our Laravel applications. Listening to Model Events Using dispatchesEvents One way to listen to model events is by defining a dispatchesEvents property on your model. This property allows you to map Eloquent model events to the event classes that should be dispatched when the event occurs. This means you can then define your listeners as you would with any other event. To provide more context, let's take a look at an example. Imagine we are building a blogging application that has two models: App\Models\Post and App\Models\Author. We'll say both of these models support soft deletes. When we save a new App\Models\Post, we want to calculate the reading time of the post based on the length of the content. When we soft-delete an author, we want to soft-delete all the author's posts. Setting Up the Models We might have an App\Models\Author model that looks like so: declare(strict_types=1); namespace App\Models; use App\Events\AuthorDeleted; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\SoftDeletes; final class Author extends Model { use HasFactory; use SoftDeletes; protected $dispatchesEvents = [ 'deleted' => AuthorDeleted::class, ]; public function posts(): HasMany { return $this->hasMany(Post::class); } } In the model above, we have: Added a dispatchesEvents property that maps the deleted model event to the App\Events\AuthorDeleted event class. This means when the model is deleted, a new App\Events\AuthorDeleted event will be dispatched. We'll create this event class in a few moments. Defined a posts relationship. Enabled soft deletes on the model by using the Illuminate\Database\Eloquent\SoftDeletes trait. Now let's create our App\Models\Post model: declare(strict_types=1); namespace App\Models; use App\Events\PostSaving; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\SoftDeletes; final class Post extends Model { use HasFactory; use SoftDeletes; protected $dispatchesEvents = [ 'saving' => PostSaving::class, ]; public function author(): BelongsTo { return $this->belongsTo(Author::class); } } In the App\Models\Post model above, we have: Added a dispatchesEvents property that maps the saving model event to the App\Events\PostSaving event class. This means when the model is created or is updated, a new App\Events\PostSaving event will be dispatched. We'll create this event class in a few moments. Defined an author relationship. Enabled soft deletes on the model by using the Illuminate\Database\Eloquent\SoftDeletes trait. Our models are now prepared, so let's create our App\Events\AuthorDeleted and App\Events\PostSaving event classes. Creating the Event Classes We will create an App\Events\PostSaving event class that will be dispatched when a new post is being saved: declare(strict_types=1); namespace App\Events; use App\Models\Post; use Illuminate\Broadcasting\InteractsWithSockets; use Illuminate\Foundation\Events\Dispatchable; use Illuminate\Queue\SerializesModels; final class PostSaving { use Dispatchable; use InteractsWithSockets; use SerializesModels; public function __construct(public Post $post) { // } } In the code above, we can see the App\Events\PostSaving event class that accepts an App\Models\Post model instance in its constructor. This event class is a simple data container that holds the post instance that is being saved. Similarly, we can create an App\Events\AuthorDeleted event class that will be dispatched when an author is deleted: declare(strict_types=1); namespace App\Events; use App\Models\Author; use Illuminate\Broadcasting\InteractsWithSockets; use Illuminate\Foundation\Events\Dispatchable; use Illuminate\Queue\SerializesModels; final class AuthorDeleted { use Dispatchable; use InteractsWithSockets; use SerializesModels; public function __construct(public Author $author) { // } } In the App\Events\AuthorDeleted class above, we can see that the constructor accepts an App\Models\Author model instance. Now we can move on to creating our listeners. Creating the Listeners Let's first create a listener that can be used to calculate the estimated reading time of a post. We'll create a new App\Listeners\CalculateReadTime listener class: declare(strict_types=1); namespace App\Listeners; use App\Events\PostSaving; use Illuminate\Support\Str; final readonly class CalculateReadTime { public function handle(PostSaving $event): void { $event->post->read_time_in_seconds = (int) ceil( (Str::wordCount($event->post->content) / 265) * 60 ); } } As we can see in the code above, we've got a single handle method. This is the method that will automatically be called when the App\Events\PostSaving event is dispatched. It accepts an instance of the App\Events\PostSaving event class which contains the post that is being saved. In the handle method, we're using a naive formula to calculate the reading time of the post. In this instance, we're assuming that the average reading speed is 265 words per minute. We're calculating the reading time in seconds and then setting the read_time_in_seconds attribute on the post model. Since this listener will be called when the saving model event is fired, this means that the read_time_in_seconds attribute will be calculated every time a post is created or updated before it's persisted to the database. We can also create a listener that will soft-delete all the related posts when an author is soft-deleted. We can create a new App\Listeners\SoftDeleteAuthorRelationships listener class: declare(strict_types=1); namespace App\Listeners; use App\Events\AuthorDeleted; final readonly class SoftDeleteAuthorRelationships { public function handle(AuthorDeleted $event): void { $event->author->posts()->delete(); // Soft delete any other relationships here... } } In the listener above, the handle method is accepting an instance of the App\Events\AuthorDeleted event class. This event class contains the author that is being deleted. We're then deleting the author's posts using the delete method on the posts relationship. As a result, whenever an App\Models\Author model is soft-deleted, all the author's posts will also be soft-deleted. As a side note, it's worth noting that you'd likely want to use a more robust, reusable solution for achieving this. But for the purposes of this article, we're keeping it simple. Listening to Model Events Using Closures Another approach you can use is to define your listeners as closures on the model itself. Let's take our previous example of soft-deleting posts when an author is soft-deleted. We can update our App\Models\Author model to include a closure that listens for the deleted model event: declare(strict_types=1); namespace App\Models; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\SoftDeletes; final class Author extends Model { use HasFactory; use SoftDeletes; protected static function booted(): void { self::deleted(static function (Author $author): void { $author->posts()->delete(); }); } public function posts(): HasMany { return $this->hasMany(Post::class); } } We can see in the model above, that we're defining our listener inside the model's booted method. We want to listen to the deleted model event, so we've used self::deleted. Similarly, if we wanted to create a listener for the created model event, we could use self::created, and so on. The self::deleted method accepts a closure which receives the App\Models\Author that's being deleted. This closure will be executed when the model is deleted, therefore deleting all the author's posts. I quite like this approach for very simple listeners. It keeps the logic inside the model class so it can be seen more easily by developers. Sometimes, extracting the logic out into a separate listener class can make the code harder to follow and track down, which can make it difficult to follow the flow of logic, especially if you're unfamiliar with the codebase. However, if the code inside these closures becomes more complex, it might be worth extracting the logic out into a separate listener class. A handy tip to know is that you can also use the Illuminate\Events\queueable function to make the closure queueable. This means the listener's code will be pushed onto the queue to be run in the background rather than in the same request lifecycle. We can update our listener to be queueable like so: declare(strict_types=1); namespace App\Models; use Illuminate\Database\Eloquent\Model; use function Illuminate\Events\queueable; final class Author extends Model { // ... protected static function booted(): void { self::deleted(queueable(static function (Author $author): void { $author->posts()->delete(); })); } // ... } As we can see in our example above, we've wrapped our closure in the Illuminate\Events\queueable function. Listening to Model Events Using Observers Another approach you can take to listen to model events is to use model observers. Model observers allow you to define all your listeners for a model in a single class. Typically, they are classes that exist in the app/Observers directory and they have methods that correspond to the model events you want to listen to. For example, if you want to listen to the deleted model event, you would define a deleted method in your observer class. If you wanted to listen to the created model event, you would define a created method in your observer class, and so on. Let's take a look at how we could create a model observer for our App\Models\Author model that listens for the deleted model event: declare(strict_types=1); namespace App\Observers; use App\Models\Author; final readonly class AuthorObserver { public function deleted(Author $author): void { $author->posts()->delete(); } } As we can see in the code above, we've created an observer that has a deleted method. This method accepts the instance of the App\Models\Author model that is being deleted. We're then deleting the author's posts using the delete method on the posts relationship. Let's say, as an example, we also wanted to define listeners for the created and updated model events. We could update our observer like so: declare(strict_types=1); namespace App\Observers; use App\Models\Author; final readonly class AuthorObserver { public function created(Author $author): void { // Logic to run when the author is created... } public function updated(Author $author): void { // Logic to run when the author is updated... } public function deleted(Author $author): void { $author->posts()->delete(); } } For the App\Observers\AuthorObserver methods to be run, we need to instruct Laravel to use it. To do this, we can make use of the #[Illuminate\Database\Eloquent\Attributes\ObservedBy] attribute. This allows us to associate the observer with the model, in a similar way to how we'd register global query scopes using the #[ScopedBy] attribute (like shown in Learn how to master Query Scopes in Laravel). We can update our App\Models\Author model to use the observer like so: declare(strict_types=1); namespace App\Models; use App\Observers\AuthorObserver; use Illuminate\Database\Eloquent\Attributes\ObservedBy; use Illuminate\Database\Eloquent\Model; #[ObservedBy(AuthorObserver::class)] final class Author extends Model { // ... } I really like this way of defining the listener's logic because it's immediately obvious when opening a model class that it has a registered observer. So although the logic is still "hidden" in a separate file, we can be made aware that we have listeners for at least one of the model's events. Testing Your Model Events No matter which of the model event approaches you use, you'll likely want to write some tests to ensure your logic is being run as expected. Let's take a look at how we might test the model events we've created in our examples above. We'll first write a test that ensures that an author's posts are soft-deleted when the author is soft-deleted. The test may look something like so: declare(strict_types=1); namespace Tests\Feature\Models; use App\Models\Author; use App\Models\Post; use Illuminate\Foundation\Testing\LazilyRefreshDatabase; use PHPUnit\Framework\Attributes\Test; use Tests\TestCase; final class AuthorTest extends TestCase { use LazilyRefreshDatabase; #[Test] public function author_can_be_soft_deleted(): void { // Create our author and post. $author = Author::factory()->create(); $post = Post::factory()->for($author)->create(); // Delete the author. $author->delete(); // Assert the author and their associated post // is soft-deleted. $this->assertSoftDeleted($author); $this->assertSoftDeleted($post); } } In the test above, we're creating a new author and a post for that author. We then soft-delete the author and assert that both the author and the post are soft-deleted. This is a really simple, yet effective, test that we can use to ensure that our logic is working as expected. The beauty of a test like this is that it should work with each of the approaches we've discussed in this article. So if you swap between any of the approaches we've discussed, your tests should still pass. Similarly, we can also write some tests to ensure the reading time of a post is calculated when the post is created or updated. The tests may look something like so: declare(strict_types=1); namespace Tests\Feature\Models; use App\Models\Author; use App\Models\Post; use Illuminate\Foundation\Testing\LazilyRefreshDatabase; use PHPUnit\Framework\Attributes\Test; use Tests\TestCase; final class PostTest extends TestCase { use LazilyRefreshDatabase; #[Test] public function read_time_is_calculated_when_storing_post(): void { $post = Post::factory() ->for(Author::factory()) ->create([ 'content' => 'This is a post with some content.' ]); $this->assertSame(2, $post->read_time_in_seconds); } #[Test] public function read_time_is_calculated_when_updating_post(): void { $post = Post::factory() ->for(Author::factory()) ->create(); $post->content = 'This is a post with some content. ...'; $post->save(); $this->assertSame(8, $post->read_time_in_seconds); } } We have two tests above: The first test ensures that the reading time of a post is calculated when the post is created. The second test ensures that the reading time of a post is calculated when the post is updated. Gotchas When Using Model Events Although model events can be really handy, there are a few gotchas to be aware of when using them. The model events are only dispatched from Eloquent models. This means, that if you're using the Illuminate\Support\Facades\DB facade to interact with a model's underlying data in the database, its events won't be dispatched. For instance, take this simple example where we're deleting the author using the Illuminate\Support\Facades\DB facade: use Illuminate\Support\Facades\DB; DB::table('authors') ->where('id', $author->id) ->delete(); Running the above code would delete the author from the database as expected. But the deleting and deleted model events wouldn't be dispatched. So if you've defined any listeners for these model events when the author is deleted, they won't be run. Similarly, if you're mass updating or deleting models using Eloquent, the saved, updated, deleting, and deleted model events won't be dispatched for the affected models. This is because the events are dispatched from the models themselves. But when mass updating and deleting, the models aren't actually retrieved from the database, so the events aren't dispatched. For example, say we use the following code to delete an author: use App\Models\Author; Author::query()->whereKey($author->id)->delete(); Since the delete method is called directly on the query builder, the deleting and deleted model events won't be dispatched for that author. Alternative Approaches to Consider I like using model events in my own projects. They act as a great way of decoupling my code and also allow me to automatically run logic when I don't have as much control over the code that's affecting the model. For example, if I'm deleting an author in Laravel Nova, I can still run some logic when the author is deleted. However, it's important to know when to consider using a different approach. To explain this point, let's take a look at a basic example of where we might want to avoid using model events. Expanding on our simple blogging application examples from earlier, let's imagine we want to run the following whenever we create a new post: Calculate the reading time of the post. Make an API call to X/Twitter to share the post. Send a notification to every subscriber on the platform. So we might create three separate listeners (one for each of these tasks) that are run every time we create a new instance of App\Models\Post. But now let's look back at one of our tests from earlier: declare(strict_types=1); namespace Tests\Feature\Models; use App\Models\Author; use App\Models\Post; use Illuminate\Foundation\Testing\LazilyRefreshDatabase; use PHPUnit\Framework\Attributes\Test; use Tests\TestCase; final class AuthorTest extends TestCase { use LazilyRefreshDatabase; #[Test] public function author_can_be_soft_deleted(): void { $author = Author::factory()->create(); $post = Post::factory()->for($author)->create(); $author->delete(); $this->assertSoftDeleted($author); $this->assertSoftDeleted($post); } } If we ran the test above, when the App\Models\Post model is created via its factory, it would also trigger those three actions. Of course, calculating the read time is a minor task so it doesn't matter too much. But we don't want to be attempting to make API calls or sending notifications during a test. These are unintended side effects. If the developer writing the tests isn't aware of these side effects, it might make it harder to track down why these actions are happening. We also want to avoid having to write any test-specific logic in our listeners that would prevent these actions from running during a test. This would make the application code more complex and harder to maintain. This is one of the scenarios where you might want to consider a more explicit approach rather than relying on automatic model events. One approach could be to extract your App\Models\Post creation code up into a service or action class. For example, a simple service class may look something like so: declare(strict_types=1); namespace App\Services; use App\DataTransferObjects\PostData; use App\Models\Post; use Illuminate\Support\Str; final readonly class PostService { public function createPost(PostData $postData): void { $post = Post::create([ 'title' => $postData->title, 'content' => $postData->content, 'author_id' => $postData->authorId, 'read_time_in_seconds' => $this->calculateReadTime($postData->content), ]); $this->sendPostCreatedNotification($post); $this->publishToTwitter($post); } public function updatePost(Post $post, PostData $postData): void { $post->update([ 'title' => $postData->title, 'content' => $postData->content, 'read_time_in_seconds' => $this->calculateReadTime($postData->content), ]); } private function calculateReadTime(string $content): int { return (int) ceil( (Str::wordCount($content) / 265) * 60 ); } private function sendPostCreatedNotification(Post $post): void { // Send a notification to all subscribers... } private function publishToTwitter(Post $post): void { // Make an API call to Twitter... } } In the class above, we're manually calling the code that calculates the reading time, sends a notification, and publishes it to Twitter. This means we have more control over when these actions are run. We can also easily mock these methods in our tests to prevent them from running. We still also have the benefit of being able to queue these actions if we need to (which we likely would in this scenario). As a result of doing this, we can remove the use of the model events and listeners for these actions. This means we can use this new App\Services\PostService class in our application code, and safely use the model factories in our test code. A bonus of doing this is that it can also make the code easier to follow. As I've briefly mentioned, a common criticism of using events and listeners is that it can hide business logic in unexpected places. So if a new developer joins the team, they may not know where or why certain actions are happening if they're triggered by a model event. However, if you would still like to use events and listeners for this kind of logic, you could consider using a more explicit approach. For example, you could dispatch an event from the service class that triggers the listeners. This way, you can still use the decoupling benefits of events and listeners, but you have more control over when the events are dispatched. For example, we could update the createPost method in our App\Services\PostService example above to dispatch an event: declare(strict_types=1); namespace App\Services; use App\DataTransferObjects\PostData; use App\Events\PostCreated; use App\Models\Post; use Illuminate\Support\Str; final readonly class PostService { public function createPost(PostData $postData): void { $post = Post::create([ 'title' => $postData->title, 'content' => $postData->content, 'author_id' => $postData->authorId, 'read_time_in_seconds' => $this->calculateReadTime($postData->content), ]); PostCreated::dispatch($post); } // ... } By using the approach above, we could still have separate listeners to make the API request to Twitter and send the notification. But we have more control over when these actions are run so they aren't run inside our tests when using model factories. There aren't any golden rules when deciding to use any of these approaches. It's all about what works best for you, your team, and the feature you're building. However, I tend to follow the following rules of thumb: If the action in the listener is only making minor changes to the model, consider using model events. Examples: generating slugs, calculating read times, etc. If the action is going to affect another model (whether that be automatically creating, updating, or deleting), then be more explicit and don't use model events. If the action is going to be working with external processes (API calls, file handling, triggering notifications, queued jobs), then be more explicit and don't use model events. Pros and Cons of Using Model Events To quickly summarise what we've covered in this article, here's a simple list of pros and cons of using model events: Pros Encourages you to decouple your code. Allows you to automatically trigger actions no matter where the model was created/updated/deleted. For example, you can trigger business logic if the model was created in Laravel Nova. You don't need to remember to dispatch the event every time you create/update/delete a model. Cons Can lead to unintended side effects. You may want to create/update/delete a model without triggering some of the listeners, but this might lead to unexpected behaviour. This can be particularly problematic when writing tests. Can hide business logic in unexpected places that's hard to track down. This can make the flow of your code harder to follow. Conclusion Hopefully, this article has given you an overview of what model events are and the different ways to use them. It should have also shown you how to test your model event code and some of the gotchas to be aware of when using them. You should hopefully now feel confident enough to make use of model events in your Laravel apps. The post A guide to Laravel's model events appeared first on Laravel News. Join the Laravel Newsletter to get Laravel articles like this directly in your inbox.

VS Code Snippets for Livewire and Alpine.js
VS Code Snippets for Livewire and Alpine.js

VS Code - as many code editors do - includes a feature called "snippets". The feature allows you to configure shortcuts for pieces of code that you use often. For example, you could set up a snippet so that you type a few characters like fun, press tab, and it'll expand into a whole function declaration. I've recently been making another round at optimizing my VS Code setup, so I skimmed through Caleb Porzio's Make VS Code Awesome course again. It reminded me how useful and powerful these snippets can be, so I started thinking about new ones I could add that would make my life easier. Today, I'll share some of those with you and maybe you can take a few away that will be useful to your workflow. Specifically, I'll be sharing ones I use when developing Livewire and Alpine.js apps. How to configure snippets in VS Code To start off, how do you configure snippets in VS Code? If you already know how, you can skip this section and get to the good part. First, open the Command Pallette by pressing cmd+shift+p on Mac or ctrl+shift+p on Windows. Start typing in "Snippets" and it should filter down to an option called "Snippets: Configure User Snippets". Select that option, then it will prompt you to select the language you want to configure snippets for. Following the basic example I mentioned earlier, if you select "javascript.json (JavaScript)" and paste the following snippet inside, you should have successfully configured a "function" snippet that'll be available when you're writing in a JS file. { "Function": { "prefix": "fun", "body": [ "function $1($2) {", " $3", "}" ] }, } The configuration of snippets is relatively simple. Each snippet is a JSON object. The key of the object is the snippet name. The prefix inside is the shortcut you'll use to insert the snippet into your code. The body is an array of lines that will be inserted as part of the snippet. You can also include placeholders in the snippet by using $1, $2, $3, etc. After you insert the snippet, you can continue pressing tab and it'll move your cursor to each of the placeholders. In this example, the placeholders make it really easy to add the function name, parameters, then body. PHP/Livewire Now that you know how to configure a snippet let's go over some of my favorites for PHP and Livewire classes. These can be configured by selecting "php.json (PHP)" in the Command Pallette. Class properties and methods cover the basics, but I recent added snippets for Livewire's mount method, #[Computed] properties (including the PHP attribute), and #[Url] properties. { "Public Property": { "prefix": "pub", "body": [ "public ${0};" ], "description": "PHP Public Property" }, "Computed Property": { "prefix": "comp", "body": [ "#[Computed]", "public function $1()", "{", " $2", "}" ] }, "Livewire Url Property": { "prefix": "url", "body": [ "#[Url$3]", "public $1 = $2;", ] }, "Livewire Mount Method": { "prefix": "mount", "body": [ "public function mount()", "{", " $1", "}" ] }, "Method": { "prefix": "met", "body": [ "public function $1($2)", "{", " $3", "}" ] }, "Private Method": { "prefix": "pmet", "body": [ "private function ${1}(${2})", "{", " ${0}", "}" ], "description": "PHP private function" }, } Blade and Alpine.js Some of my favorites for Blade and Alpine snippets are: con for console.log raw for console.log with the value wrapped in Alpine.raw (a tip I covered in a recent article) tailwindcdn for the Tailwind CDN script which is useful for quickly setting up Tailwind for demos and testing ideas out These can be configured by selecting "blade.json (Blade)" in the Command Pallette. { "Console Log": { "prefix": "con", "body": [ "console.log($1)" ] }, "Console Log (Alpine.raw)": { "prefix": "raw", "body": [ "console.log(Alpine.raw($1))" ] }, "Tailwind Play CDN": { "prefix": "tailwindcdn", "body": [ "<script src=\"https://cdn.tailwindcss.com\"></script>" ] }, } If you're using Livewire Volt's class API, you might find it useful to include you normal PHP snippets in the Blade configuration as well. I hope you were able to find some of the snippets useful or at least got some inspiration for snippets that will benefit your own workflow! Again, if you haven't checked out Caleb's Make VS Code Awesome course yet, I highly recommend it. It includes a bunch of other snippets that I didn't cover here, plus theming, keyboard shortcuts, extensions, and more. The post VS Code Snippets for Livewire and Alpine.js appeared first on Laravel News. Join the Laravel Newsletter to get Laravel articles like this directly in your inbox.

How to Migrate MySQL from DBngin to Laravel Herd
How to Migrate MySQL from DBngin to Laravel Herd

Ever since Laravel Herd Pro was launched with the ability to manage database and cache services like MySQL, Postgres, and Redis, I've been wanting to switch from DBngin to Herd's MySQL and Redis. Recently, with a little help from Marcel Pociot, I was able to figure out an easy way to move all my local databases over to Herd's MySQL service without losing any data. Warning before getting started, if you have any databases that are really important and you can't risk losing them, make a backup before following any of these instructions. I'm not responsible if something goes wrong and you lose some important data. Note: this method will only work if your MySQL services are on the same minor release version. For example: v8.0.33 to v8.0.36 will work. v8.0 to v8.2 will not work. 1. Stop DBngin's MySQL service. To stop DBngin's MySQL service, open the DBngin app and click stop beside the MySQL service. 2. Create the MySQL service in Herd Open Laravel Herd, and go to the "Services" tab. Click "Add Service" and create the MySQL service. I would suggest stopping the Herd MySQL service after it's added so we can safely move the data without corrupting anything. 3. Copy the data from DBngin to Herd First, you need to find out where DBngin stores all your database files. I found them the hard way, but Marcel told me an easier way. Open DBngin, right-click on the MySQL service, and click "Show database files". It should open a "mysql" folder that has a single folder inside. Open that folder, and you should see a folder for every database you have, plus some other configuration files and folders. To find out where Herd plans to store your databases, we can follow a similar path. Open Herd's "Services" tab again, right-click the MySQL service, and click "Open data directory". It should open the exact folder where it stores the files. Now, copy all the files and folders from the DBngin folder to the Herd folder. 4. Restart the MySQL service in Herd Once all the files are copied over, open up Herd to the "Services" tab and restart the MySQL service. Open up your database management tool of choice, connect to the MySQL service (probably using the same settings you were using previously), and you should see all your databases listed! Now, if you want to, and you're not using DBngin for other services (or have migrated them all to Herd), you can uninstall DBngin. One less app/service to maintain on your Mac. Conclusion I've really been enjoying the simplicity Herd and Herd Pro bring to the Laravel development experience. It's so simple to set up and includes pretty much everything needed for typical Laravel development these days. I can't wait to see what they add next! Let me know what your favorite Herd features are on Twitter. The post How to Migrate MySQL from DBngin to Laravel Herd appeared first on Laravel News. Join the Laravel Newsletter to get Laravel articles like this directly in your inbox.

API Versioning in Laravel 11
API Versioning in Laravel 11

With the release of Laravel 11, the application skeleton was slimmed down to remove extra files that aren't required on every project. Part of that change removed all service providers from the application source code except the AppServiceProvider. Another part of the updated application skeleton is removing API routes in the default installation. Suppose you plan on adding an API to your application or exclusively writing an API with Laravel. In that case, you can set up the api middleware group and routes with an Artisan command: php artisan install:api The install:api sets up the api.php route file (and configures it), a database migration for personal access tokens, and a sanctum configuration file. If you don't need to version your API, that's all you need to do. Versioning Your API in Separate Files A common approach to writing versioned APIs in Laravel is separating routes into different files. Doing so simplifies the overhead of reasoning about a specific API version and keeps things tidy. In Laravel 10 or earlier, a common approach is adding additional route files for each API version to the RouteServiceProvider: $this->routes(function () { Route::middleware('api') ->prefix('api') ->group(base_path('routes/api.php')); Route::middleware('api') ->prefix('api/v1') ->group(base_path('routes/api_v1.php')); Route::middleware('api') ->prefix('api/v2') ->group(base_path('routes/api_v2.php')); Route::middleware('web') ->group(base_path('routes/web.php')); }); In the above example, the routes/api.php typically offers a /user endpoint for Laravel sanctum, and the remainder of API routes are tucked in the appropriate version. Versioning Your API in Laravel 11 With route bootstrapping moving out of the RouteServiceProvider and into bootstrap/app.php, here are a few ways you can version your API. First, let's generate a few files to demonstrate setting up routing: touch routes/api_v1.php touch routes/api_v2.php php artisan make:controller --api Api/V1/PostsController php artisan make:controller --api Api/V2/PostsController Use whatever file name convention and location that suits you. Next, open the routes/api.php file and add the following lines to the bottom of the file: Route::prefix('v1')->group(base_path('routes/api_v1.php')); Route::prefix('v2')->group(base_path('routes/api_v2.php')); The above code being in api.php means that we are already working within the api route prefix and using the api middleware group. Next, let's add the example routes for each respective API version so we can visualize the route list for each version. Here's the api_v1.php file: <?php use App\Http\Controllers\Api\V1\PostsController; Route::apiResource('posts', PostsController::class); And the api_v2.php file: <?php use App\Http\Controllers\Api\V2\PostsController; Route::apiResource('posts', PostsController::class); With our route files defined, running route:list, we can see versioned routes! Pro tip: you can isolate versioned routes using the --path flag, making it easy to focus on a specific API version: php artisan route:list --path=api/v1 php artisan route:list --path=api/v2 Check out how tidy it looks if you just output routes for api/v2: Defining Versioned Routes in the App Bootstrap File Another approach I've seen is defining additional API routes in the bootstrap/app.php file using the then: argument, which accepts a Closure: return Application::configure(basePath: dirname(__DIR__)) ->withRouting( web: __DIR__.'/../routes/web.php', api: __DIR__.'/../routes/api.php', commands: __DIR__.'/../routes/console.php', health: '/up', then: function () { Route::middleware('api') ->prefix('api/v1') ->group(base_path('routes/api/api_v1.php')); Route::middleware('api') ->prefix('api/v2') ->group(base_path('routes/api/api_v2.php')); } ) // ... ; I prefer adding additional route files directly to routes/api.php, but this is another approach that would work. When defining routes in the bootstrap file, these groups aren't configured to use the api middleware group. Make sure to include the api middleware group with these routes! The post API Versioning in Laravel 11 appeared first on Laravel News. Join the Laravel Newsletter to get Laravel articles like this directly in your inbox.

How to Redirect Uppercase URLs to Lowercase with Laravel Middleware
How to Redirect Uppercase URLs to Lowercase with Laravel Middleware

Last week, I needed to redirect all requests that contained uppercase letters to their lowercase equivalents for SEO optimization. For example: From To /location/Atlanta /location/atlanta /docs/Laravel-Middleware /docs/laravel-middleware At the same time, the solution shouldn't change any query parameters: From To /locations/United-States?search=Georgia /location/united-states?search=Georgia It turns out we can do this with just a few lines of code in a Laravel middleware! First, we grab the path from the request and check if the it is the same lowercased. If not, we can use the url()->query() method to append the query string back onto the lowercase version of the path and permanently redirect to the lowercased path. <?php namespace App\Http\Middleware; use Closure; use Illuminate\Http\Request; use Symfony\Component\HttpFoundation\Response; class RedirectUppercase { /** * Handle an incoming request. * * @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next */ public function handle(Request $request, Closure $next): Response { $path = $request->path(); if (($lower = strtolower($path)) !== $path) { $url = url()->query($lower, $request->query()); return redirect($url, 301); } return $next($request); } } To register the middleware in a Laravel 11 app, I appended it to the web middleware group in the bootstrap/app.php file. <?php return Application::configure(basePath: dirname(__DIR__)) ->withRouting( // ... ) ->withMiddleware(function (Middleware $middleware) { $middleware->appendToGroup('web', \App\Http\Middleware\RedirectUppercase::class); }); Note: you may want to exclude this middleware from routes that use signed URLs or other case sensitive use-cases. I'm sure there are possible solutions with Nginx or Apache, but this was by far the simplest solution for me and it works across all environments of the app. I don't have to remember to make any changes on new servers. The post How to Redirect Uppercase URLs to Lowercase with Laravel Middleware appeared first on Laravel News. Join the Laravel Newsletter to get Laravel articles like this directly in your inbox.

Learn to master Query Scopes in Laravel
Learn to master Query Scopes in Laravel

When building your Laravel applications, you'll likely have to write queries that have constraints which are used in multiple places throughout your application. Maybe you're building a multi-tenant application and you're having to keep adding a where constraint to your queries to filter by the user's team. Or, maybe you're building a blog and you're having to keep adding a where constraint to your queries to filter by whether the blog post is published or not. In Laravel, we can make use of query scopes to help us keep these constraints tidy and reusable in a single place. In this article, we're going to take a look at local query scopes and global query scopes. We'll learn about the difference between the two, how to create your own, and how to write tests for them. By the end of the article, you should feel confident using query scopes in your Laravel applications. What are Query Scopes? Query scopes allow you to define constraints in your Eloquent queries in a reusable way. They are typically defined as methods on your Laravel models, or as a class that implements the Illuminate\Database\Eloquent\Scope interface. Not only are they great for defining reusable logic in a single place, but they can also make your code more readable by hiding complex query constraints behind a simple method call. Query scopes come in two different types: Local query scopes - You have to apply these scopes manually to your queries. Global query scopes - These scopes are applied to all queries on the model by default after the query is registered. If you've ever used Laravel's built-in "soft delete" functionality, you may have already used query scopes without realising it. Laravel makes use of local query scopes to provide you with methods such as withTrashed and onlyTrashed on your models. It also uses a global query scope to automatically add a whereNull('deleted_at') constraint to all queries on the model so that soft-deleted records aren't returned in queries by default. Let's take a look at how we can create and use local query scopes and global query scopes in our Laravel applications. Local Query Scopes Local query scopes are defined as methods on your Eloquent model and allow you to define constraints that can be manually applied to your model queries. Let's imagine we are building a blogging application that has an admin panel. In the admin panel, we have two pages: one for listing published blog posts and another for listing unpublished blog posts. We'll imagine the blog posts are accessed using an \App\Models\Article model and that the database table has a nullable published_at column that stores the date and time the blog post is to be published. If the published_at column is in the past, the blog post is considered published. If the published_at column is in the future or null, the blog post is considered unpublished. To get the published blog posts, we could write a query like this: use App\Models\Article; $publishedPosts = Article::query() ->where('published_at', '<=', now()) ->get(); To get the unpublished blog posts, we could write a query like this: use App\Models\Article; use Illuminate\Contracts\Database\Eloquent\Builder; $unpublishedPosts = Article::query() ->where(function (Builder $query): void { $query->whereNull('published_at') ->orWhere('published_at', '>', now()); }) ->get(); The queries above aren't particularly complex. However, let's imagine we are using them in multiple places throughout our application. As the number of occurrences grows, it becomes more likely that we'll make a mistake or forget to update the query in one place. For instance, a developer might accidentally use >= instead of <= when querying for published blog posts. Or, the logic for determining if a blog post is published might change, and we'll need to update all the queries. This is where query scopes can be extremely useful. So let's tidy up our queries by creating local query scopes on the \App\Models\Article model. Local query scopes are defined by creating a method that starts with the word scope and ends with the intended name of the scope. For example, a method called scopePublished will create a published scope on the model. The method should accept an Illuminate\Contracts\Database\Eloquent\Builder instance and return an Illuminate\Contracts\Database\Eloquent\Builder instance. We'll add both of the scopes to the \App\Models\Article model: declare(strict_types=1); namespace App\Models; use Illuminate\Contracts\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; final class Article extends Model { public function scopePublished(Builder $query): Builder { return $query->where('published_at', '<=', now()); } public function scopeNotPublished(Builder $query): Builder { return $query->where(function (Builder $query): Builder { return $query->whereNull('published_at') ->orWhere('published_at', '>', now()); }); } // ... } As we can see in the example above, we've moved our where constraints from our previous queries into two separate methods: scopePublished and scopeNotPublished. We can now use these scopes in our queries like this: use App\Models\Article; $publishedPosts = Article::query() ->published() ->get(); $unpublishedPosts = Article::query() ->notPublished() ->get(); In my personal opinion, I find these queries much easier to read and understand. It also means that if we need to write any queries in the future with the same constraint, we can reuse these scopes. Global Query Scopes Global query scopes perform a similar function to local query scopes. But rather than manually being applied on a query-by-query basis, they're automatically applied to all queries on the model. As we mentioned earlier, Laravel's built-in "soft delete" functionality makes use of the Illuminate\Database\Eloquent\SoftDeletingScope global query scope. This scope automatically adds a whereNull('deleted_at') constraint to all queries on the model. You can check out the source code on GitHub here if you're interested in seeing how it works under the hood. For example, imagine you're building a multi-tenant blogging application that has an admin panel. You'd only want to allow users to view articles that belonged to their team. So, you might write a query like this: use App\Models\Article; $articles = Article::query() ->where('team_id', Auth::user()->team_id) ->get(); This query is fine, but it's easy to forget to add the where constraint. If you were writing another query and forgot to add the constraint, you'd end up with a bug in your application that would allow users to interact with articles that didn't belong to their team. Of course, we don't want that to happen! To prevent this, we can create a global scope that we can apply automatically to all our App\Model\Article model queries. How to Create Global Query Scopes Let's create a global query scope that filters all queries by the team_id column. Please note, that we're keeping the example simple for the purposes of this article. In a real-world application, you'd likely want to use a more robust approach that handles things like the user not being authenticated, or the user belonging to multiple teams. But for now, let's keep it simple so we can focus on the concept of global query scopes. We'll start by running the following Artisan command in our terminal: php artisan make:scope TeamScope This should have created a new app/Models/Scopes/TeamScope.php file. We'll make some updates to this file and then look at the finished code: declare(strict_types=1); namespace App\Models\Scopes; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Scope; use Illuminate\Support\Facades\Auth; final readonly class TeamScope implements Scope { /** * Apply the scope to a given Eloquent query builder. */ public function apply(Builder $builder, Model $model): void { $builder->where('team_id', Auth::user()->team_id); } } In the code example above, we can see that we've got a new class that implements the Illuminate\Database\Eloquent\Scope interface and has a single method called apply. This is the method where we define the constraints we want to apply to the queries on the model. Our global scope is now ready to be used. We can add it to any models where we want to scope the queries down to the user's team. Let's apply it to the \App\Models\Article model. Applying Global Query Scopes There are several ways to apply a global scope to a model. The first way is to use the Illuminate\Database\Eloquent\Attributes\ScopedBy attribute on the model: declare(strict_types=1); namespace App\Models; use App\Models\Scopes\TeamScope; use Illuminate\Database\Eloquent\Attributes\ScopedBy; use Illuminate\Database\Eloquent\Model; #[ScopedBy(TeamScope::class)] final class Article extends Model { // ... } Another way is to use the addGlobalScope method in the booted method of the model: declare(strict_types=1); namespace App\Models; use App\Models\Scopes\TeamScope; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; final class Article extends Model { use HasFactory; protected static function booted(): void { static::addGlobalScope(new TeamScope()); } // ... } Both of these approaches will apply the where('team_id', Auth::user()->team_id) constraint to all queries on the \App\Models\Article model. This means you can now write queries without having to worry about filtering by the team_id column: use App\Models\Article; $articles = Article::query()->get(); If we assume the user is part of a team with the team_id of 1, the following SQL would be generated for the query above: select * from `articles` where `team_id` = 1 That's pretty cool, right!? Anonymous Global Query Scopes Another way to define and apply a global query scope is to use an anonymous global scope. Let's update our \App\Models\Article model to use an anonymous global scope: declare(strict_types=1); namespace App\Models; use Illuminate\Contracts\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Model; use Illuminate\Support\Facades\Auth; final class Article extends Model { protected static function booted(): void { static::addGlobalScope('team_scope', static function (Builder $builder): void { $builder->where('team_id', Auth::user()->team_id); }); } // ... } In the code example above, we've used the addGlobalScope method to define an anonymous global scope in the model's booted method. The addGlobalScope method takes two arguments: The name of the scope - This can be used to reference the scope later if you need to ignore it in a query The scope constraints - A closure that defines the constraints to apply to the queries Just like the other approaches, this will apply the where('team_id', Auth::user()->team_id) constraint to all queries on the \App\Models\Article model. In my experience, anonymous global scopes are less common than defining a global scope in a separate class. But it's good to know they're available to use if you need them. Ignoring Global Query Scopes There may be times when you want to write a query that doesn't use a global query scope that's been applied to a model. For example, you might be building a report or analytics query that needs to include all records, regardless of the global query scopes. If this is the case, you can use one of two methods to ignore global scopes. The first method is withoutGlobalScopes. This method allows you to ignore all global scopes on the model if no arguments are passed to it: use App\Models\Article; $articles = Article::query()->withoutGlobalScopes()->get(); Or, if you'd prefer to only ignore a given set of global scopes, you can the scope names to the withoutGlobalScopes method: use App\Models\Article; use App\Models\Scopes\TeamScope; $articles = Article::query() ->withoutGlobalScopes([ TeamScope::class, 'another_scope', ])->get(); In the example above, we're ignoring the App\Models\Scopes\TeamScope and another imaginary anonymous global scope called another_scope. Alternatively, if you'd prefer to ignore a single global scope, you can use the withoutGlobalScope method: use App\Models\Article; use App\Models\Scopes\TeamScope; $articles = Article::query()->withoutGlobalScope(TeamScope::class)->get(); Global Query Scope Gotchas It's important to remember that global query scopes are only applied to queries made through your models. If you're writing a database query using the Illuminate\Support\Facades\DB facade, the global query scopes won't be applied. For example, let's say you write this query that you'd expect would only grab the articles belonging to the logged-in user's team: use Illuminate\Support\Facades\DB; $articles = DB::table('articles')->get(); In the query above, the App\Models\Scopes\TeamScope global query scope won't be applied even if the scope is defined on the App\Models\Article model. So, you'll need to make sure you're manually applying the constraint in your database queries. Testing Local Query Scopes Now that we've learned about how to create and use query scopes, we'll take a look at how we can write tests for them. There are several ways to test query scopes, and the method you choose may depend on your personal preference or the contents of the scope you're writing. For instance, you may want to write more unit-style tests for the scopes. Or, you may want to write more integration-style tests that test the scope in the context of being used in something like a controller. Personally, I like to use a mixture of the two so that I can have confidence the scopes are adding the correct constraints, and that the scopes are actually being used in the queries. Let's take our example published and notPublished scopes from earlier and write some tests for them. We'll want to write two different tests (one for each scope): A test that checks the published scope only returns articles that have been published. A test that checks the notPublished scope only returns articles that haven't been published. Let's take a look at the tests and then discuss what's being done: declare(strict_types=1); namespace Tests\Feature\Models\Article; use App\Models\Article; use Illuminate\Foundation\Testing\LazilyRefreshDatabase; use PHPUnit\Framework\Attributes\Test; use Tests\TestCase; final class ScopesTest extends TestCase { use LazilyRefreshDatabase; protected function setUp(): void { parent::setUp(); // Create two published articles. $this->publishedArticles = Article::factory() ->count(2) ->create([ 'published_at' => now()->subDay(), ]); // Create an unpublished article that hasn't // been scheduled to publish. $this->unscheduledArticle = Article::factory() ->create([ 'published_at' => null, ]); // Create an unpublished article that has been // scheduled to publish. $this->scheduledArticle = Article::factory() ->create([ 'published_at' => now()->addDay(), ]); } #[Test] public function only_published_articles_are_returned(): void { $articles = Article::query()->published()->get(); $this->assertCount(2, $articles); $this->assertTrue($articles->contains($this->publishedArticles->first())); $this->assertTrue($articles->contains($this->publishedArticles->last())); } #[Test] public function only_not_published_articles_are_returned(): void { $articles = Article::query()->notPublished()->get(); $this->assertCount(2, $articles); $this->assertTrue($articles->contains($this->unscheduledArticle)); $this->assertTrue($articles->contains($this->scheduledArticle)); } } We can see in the test file above, we're first creating some data in the setUp method. We're creating two published articles, one unscheduled article, and one scheduled article. There is then a test (only_published_articles_are_returned) that checks the published scope only returns the published articles. And there is another test (only_not_published_articles_are_returned) that checks the notPublished scope only returns the articles that haven't been published. By doing this, we can now have confidence that our query scopes are applying the constraints as expected. Testing Scopes in Controllers As we mentioned, another way of testing query scopes is to test them in the context of being used in a controller. Whereas an isolated test for the scope can help to assert that a scope is adding the correct constraints to a query, it doesn't actually test that the scope is being used as intended in the application. For instance, you may have forgotten to add the published scope to a query in a controller method. These types of mistakes can be caught by writing tests that assert the correct data is returned when the scope is used in a controller method. Let's take our example of having a multi-tenant blogging application and write a test for a controller method that lists articles. We'll assume we have a very simple controller method like so: declare(strict_types=1); namespace App\Http\Controllers; use App\Models\Article; use Illuminate\Http\Request; final class ArticleController extends Controller { public function index() { return view('articles.index', [ 'articles' => Article::all(), ]); } } We'll assume that the App\Models\Article model has our App\Models\Scopes\TeamScope applied to it. We'll want to assert that only the articles belonging to the user's team are returned. The test case may look something like this: declare(strict_types=1); namespace Tests\Feature\Controllers\ArticleController; use App\Models\Article; use App\Models\Team; use App\Models\User; use Illuminate\Database\Eloquent\Collection; use Illuminate\Foundation\Testing\LazilyRefreshDatabase; use PHPUnit\Framework\Attributes\Test; use Tests\TestCase; final class IndexTest extends TestCase { use LazilyRefreshDatabase; #[Test] public function only_articles_belonging_to_the_team_are_returned(): void { // Create two new teams. $teamOne = Team::factory()->create(); $teamTwo = Team::factory()->create(); // Create a user that belongs to team one. $user = User::factory()->for($teamOne)->create(); // Create 3 articles for team one. $articlesForTeamOne = Article::factory() ->for($teamOne) ->count(3) ->create(); // Create 2 articles for team two. Article::factory() ->for($teamTwo) ->count(2) ->create(); // Act as the user and make a request to the controller method. We'll // assert that only the articles belonging to team one are returned. $this->actingAs($user) ->get('/articles') ->assertOk() ->assertViewIs('articles.index') ->assertViewHas( key: 'articles', value: fn (Collection $articles): bool => $articles->pluck('id')->all() === $articlesForTeamOne->pluck('id')->all() ); } } In the test above, we're creating two teams. We're then creating a user that belongs to team one. We're creating 3 articles for team one and 2 articles for team two. We're then acting as the user and making a request to the controller method that lists the articles. The controller method should only be returning the 3 articles that belong to team one, so we're asserting that only those articles are returned by comparing the IDs of the articles. This means we can then have confidence that the global query scope is being used as intended in the controller method. Conclusion In this article, we learned about local query scopes and global query scopes. We learned about the difference between the two, how to create your own and use them, and how to write tests for them. Hopefully, you should now feel confident using query scopes in your Laravel applications. The post Learn to master Query Scopes in Laravel appeared first on Laravel News. Join the Laravel Newsletter to get Laravel articles like this directly in your inbox.

Laravel Rest Api now supports Laravel Scout
Laravel Rest Api now supports Laravel Scout

With the latest release of Laravel Rest Api, the quick and easy way to manage your Api in Laravel, a native integration of Laravel Scout has been released. Now, just by specifying "search.text.value" in your search, you are able to make a full text search from your front-end side: // (POST) api/posts/search { "text": { "value": "my full text search" } } Fully integrates with other Laravel Rest Api features By using full text search you have some small limitations on the arguments you can provide but this doesn`t restrict other features which allow you to perform the following query : // (POST) api/posts/search { "text": { "value": "my full text search" }, "filters": [ { "field": "type", "operator": "=", "value": "romance" } ], "sorts": [ {"field": "user_id", "direction": "desc"}, {"field": "id", "direction": "asc"} ], "selects": [ {"field": "id"} ], "includes": [ { "relation": "posts", "filters": [ {"field": "id", "operator": "in", "value": [1, 3]} ], "limit": 2 }, { "relation": "user", "filters": [ { "field": "languages.pivot.boolean", "operator": "=", "value": true } ] } ], "aggregates": [ { "relation": "stars", "type": "max", "field": "rate", "filters": [ {"field": "approved", "value": true} ] } ], "instructions": [ { "name": "odd-even-id", "fields": [ { "name": "type", "value": "odd" } ] } ], "page": 2, "limit": 10 } Links The Laravel Rest Api package and documentation can be found on GitHub at lomkit/laravel-rest-api. The post Laravel Rest Api now supports Laravel Scout appeared first on Laravel News. Join the Laravel Newsletter to get Laravel articles like this directly in your inbox.