diff --git a/README.md b/README.md index 1031411..ad86680 100644 --- a/README.md +++ b/README.md @@ -18,25 +18,30 @@ Hey, like these tips? Also, check out my premium [Laravel courses](https://larav --- -Or if you want the Chinese version: -[中文版本](https://github.com/Lysice/laravel-tips-chinese/blob/master/README-zh.md) +## Other Versions + +- [Chinese version](https://github.com/Lysice/laravel-tips-chinese/blob/master/README-zh.md) +- [Spanish version](https://github.com/ErickMUOSD/laravel-tips-spanish) +- [Laravel tips in terminal](https://github.com/godruoyi/laravel-tips) +- [Laravel tips in Raycast](https://github.com/godruoyi/laravel-tips-raycast) + --- -**Update 8 November 2022**: Currently there are **341 tips** divided into 14 sections. +**Update 18 March 2024**: Currently there are **357 tips** divided into 14 sections. ## Table of contents -- [DB Models and Eloquent](db-models-and-eloquent.md) (94 tips) -- [Models Relations](models-relations.md) (35 tips) +- [DB Models and Eloquent](db-models-and-eloquent.md) (100 tips) +- [Models Relations](models-relations.md) (36 tips) - [Migrations](migrations.md) (15 tips) -- [Views](views.md) (19 tips) +- [Views](views.md) (20 tips) - [Routing](routing.md) (33 tips) - [Validation](validation.md) (24 tips) -- [Collections](collections.md) (9 tips) -- [Auth](auth.md) (5 tips) +- [Collections](collections.md) (8 tips) +- [Auth](auth.md) (6 tips) - [Mail](mail.md) (7 tips) -- [Artisan](artisan.md) (7 tips) +- [Artisan](artisan.md) (10 tips) - [Factories](factories.md) (8 tips) -- [Log and debug](log-and-debug.md) (7 tips) -- [API](api.md) (7 tips) -- [Other](other.md) (71 tips) +- [Log and debug](log-and-debug.md) (8 tips) +- [API](api.md) (9 tips) +- [Other](other.md) (73 tips) diff --git a/api.md b/api.md index 16904ce..de40db0 100644 --- a/api.md +++ b/api.md @@ -9,6 +9,8 @@ - [Get Bearer Token from Authorization header](#get-bearer-token-from-authorization-header) - [Sorting Your API Results](#sorting-your-api-results) - [Customize Exception Handler For API](#customize-exception-handler-for-api) +- [Force JSON Response For API Requests](#force-json-response-for-api-requests) +- [API Versioning](#api-versioning) ### API Resources: With or Without "data"? @@ -30,7 +32,7 @@ Tip given by [@phillipmwaniki](https://twitter.com/phillipmwaniki/status/1445230 You may conditionally include the count of a relationship in your resource response by using the whenCounted method. By doing so, the attribute is not included if the relationships' count is missing. ```php -public function toArraY($request) +public function toArray($request) { return [ 'id' => $this->id, @@ -43,7 +45,7 @@ public function toArraY($request) } ``` -Tip given by [@mvpopuk)(https://twitter.com/mvpopuk/status/1570480977507504128) +Tip given by [@mvpopuk](https://twitter.com/mvpopuk/status/1570480977507504128) ### API Return "Everything went ok" @@ -70,7 +72,7 @@ This will only append the department if it’s already loaded in the Employee mo Without `whenLoaded()` there is always a query for the department ```php -class EmplyeeResource extends JsonResource +class EmployeeResource extends JsonResource { public function toArray($request): array { @@ -145,6 +147,7 @@ Route::get('dogs', function (Request $request) { return $query->paginate(20); }); ``` +--- ### Customize Exception Handler For API @@ -208,3 +211,143 @@ There's a method `register()` in `App\Exceptions` class: } ``` +Tip given by [Feras Elsharif](https://github.com/ferasbbm) + +--- + +### Force JSON Response For API Requests + +If you have built an API and it encounters an error when the request does not contain "Accept: application/JSON " HTTP Header then the error will be returned as HTML or redirect response on API routes, so for avoid it we can force all API responses to JSON. + +The first step is creating middleware by running this command: + +```console +php artisan make:middleware ForceJsonResponse +``` + +Write this code on the handle function in `App/Http/Middleware/ForceJsonResponse.php` file: + +```php +public function handle($request, Closure $next) +{ + $request->headers->set('Accept', 'application/json'); + return $next($request); +} +``` + +Second, register the created middleware in app/Http/Kernel.php file: + +```php +protected $middlewareGroups = [ + 'api' => [ + \App\Http\Middleware\ForceJsonResponse::class, + ], +]; +``` + +Tip given by [Feras Elsharif](https://github.com/ferasbbm) + +--- + +### API Versioning + +#### When to version? + +If you are working on a project that may have multi-release in the future or your endpoints have a breaking change like a change in the format of the response data, and you want to ensure that the API version remains functional when changes are made to the code. + +#### Change The Default Route Files +The first step is to change the route map in the `App\Providers\RouteServiceProvider` file, so let's get started: + +#### Laravel 8 and above: + +Add a 'ApiNamespace' property + +```php +/** + * @var string + * + */ +protected string $ApiNamespace = 'App\Http\Controllers\Api'; +``` + +Inside the method boot, add the following code: + +```php +$this->routes(function () { + Route::prefix('api/v1') + ->middleware('api') + ->namespace($this->ApiNamespace.'\\V1') + ->group(base_path('routes/API/v1.php')); + } + + //for v2 + Route::prefix('api/v2') + ->middleware('api') + ->namespace($this->ApiNamespace.'\\V2') + ->group(base_path('routes/API/v2.php')); +}); +``` + + +#### Laravel 7 and below: + +Add a 'ApiNamespace' property + +```php +/** + * @var string + * + */ +protected string $ApiNamespace = 'App\Http\Controllers\Api'; +``` + +Inside the method map, add the following code: + +```php +// remove this $this->mapApiRoutes(); + $this->mapApiV1Routes(); + $this->mapApiV2Routes(); +``` + +And add these methods: + +```php + protected function mapApiV1Routes() + { + Route::prefix('api/v1') + ->middleware('api') + ->namespace($this->ApiNamespace.'\\V1') + ->group(base_path('routes/Api/v1.php')); + } + + protected function mapApiV2Routes() + { + Route::prefix('api/v2') + ->middleware('api') + ->namespace($this->ApiNamespace.'\\V2') + ->group(base_path('routes/Api/v2.php')); + } +``` + +#### Controller Folder Versioning + +``` +Controllers +└── Api + ├── V1 + │ └──AuthController.php + └── V2 + └──AuthController.php +``` + +#### Route File Versioning + +``` +routes +└── Api + │ └── v1.php + │ └── v2.php + └── web.php +``` + +Tip given by [Feras Elsharif](https://github.com/ferasbbm) diff --git a/artisan.md b/artisan.md index 74dca0c..7481bbf 100644 --- a/artisan.md +++ b/artisan.md @@ -3,12 +3,15 @@ ⬆️ [Go to main menu](README.md#laravel-tips) ⬅️ [Previous (Mail)](mail.md) ➡️ [Next (Factories)](factories.md) - [Artisan command parameters](#artisan-command-parameters) +- [Execute a Closure after command runs without errors or has any errors](#execute-a-closure-after-command-runs-without-errors-or-has-any-errors) +- [Run artisan commands on specific environments](#run-artisan-commands-on-specific-environments) - [Maintenance Mode](#maintenance-mode) - [Artisan command help](#artisan-command-help) - [Exact Laravel version](#exact-laravel-version) - [Launch Artisan command from anywhere](#launch-artisan-command-from-anywhere) - [Hide your custom command](#hide-your-custom-command) - [Skip method](#skip-method) +- [Connect to database via CLI](#connect-to-database-via-cli) ### Artisan command parameters @@ -27,6 +30,34 @@ $name = $this->anticipate('What is your name?', ['Taylor', 'Dayle']); $name = $this->choice('What is your name?', ['Taylor', 'Dayle'], $defaultIndex); ``` +### Execute a Closure after command runs without errors or has any errors + +With Laravel scheduler you can execute a Closure when a command runs without errors with the onSuccess() method and also when a command has any errors with the onFailure() method. + +```php +protected function schedule(Schedule $schedule) +{ + $schedule->command('newsletter:send') + ->mondays() + ->onSuccess(fn () => resolve(SendNewsletterSlackNotification::class)->handle(true)) + ->onFailure(fn () => resolve(SendNewsletterSlackNotification::class)->handle(false)); +} +``` + +Tip given by [@wendell_adriel](https://twitter.com/wendell_adriel) + +### Run artisan commands on specific environments + +Take control of your Laravel scheduled commands. Run them on specific environments for ultimate flexibility. + +```php +$schedule->command('reports:send') + ->daily() + ->environments(['production', 'staging']); +``` + +Tip given by [@LaraShout](https://twitter.com/LaraShout) + ### Maintenance Mode If you want to enable maintenance mode on your page, execute the down Artisan command: @@ -142,3 +173,18 @@ $schedule->command('emails:send')->daily()->skip(function () { Tip given by [@cosmeescobedo](https://twitter.com/cosmeescobedo/status/1494503181438492675) +### Connect to database via CLI + +To get access to your database via CLI directly, you can run the following without any parameters: + +```bash +php artisan db +``` + +If you are using multiple databases, you may specify a database connection name like: + +```bash +php artisan db mysql +``` + +This command is absent from the list generated by executing `php artisan list`. diff --git a/auth.md b/auth.md index ef40f63..5120573 100644 --- a/auth.md +++ b/auth.md @@ -3,10 +3,12 @@ ⬆️ [Go to main menu](README.md#laravel-tips) ⬅️ [Previous (Collections)](collections.md) ➡️ [Next (Mail)](mail.md) - [Check Multiple Permissions at Once](#check-multiple-permissions-at-once) +- [Authenticate users with more options](#authenticate-users-with-more-options) - [More Events on User Registration](#more-events-on-user-registration) - [Did you know about Auth::once()?](#did-you-know-about-authonce) - [Change API Token on users password update](#change-api-token-on-users-password-update) - [Override Permissions for Super Admin](#override-permissions-for-super-admin) +- [Custom Authentication Events](#custom-authentication-events) ### Check Multiple Permissions at Once @@ -20,6 +22,24 @@ In addition to `@can` Blade directive, did you know you can check multiple permi @endcanany ``` +### Authenticate users with more options + +If you only want to authenticate users that are also "activated", for example, it's as simple as passing an extra argument to `Auth::attempt()`. + +No need for complex middleware or global scopes. + +```php +Auth::attempt( + [ + ...$request->only('email', 'password'), + fn ($query) => $query->whereNotNull('activated_at') + ], + $this->boolean('remember') +); +``` + +Tip given by [@LukeDowning19](https://twitter.com/LukeDowning19) + ### More Events on User Registration Want to perform some actions after new user registration? Head to `app/Providers/EventServiceProvider.php` and add more Listeners classes, and then in those classes implement `handle()` method with `$event->user` object @@ -86,3 +106,67 @@ Gate::before(function($user, $ability) { }); ``` +If you want to do something in your Gate when there is no user at all, you need to add a type hint for `$user` allowing it to be `null`. For example, if you have a role called Anonymous for your non-logged-in users: + +```php +Gate::before(function (?User $user, $ability) { + if ($user === null) { + $role = Role::findByName('Anonymous'); + return $role->hasPermissionTo($ability) ? true : null; + } + return $user->hasRole('Super Admin') ? true : null; +}); +``` + +### Custom Authentication Events + +Laravel's authentication system fires various events during the authentication process, allowing you to hook into these events and perform additional actions or custom logic. + +For example, you might want to log users Login. +You can achieve this by listening to the `Illuminate\Auth\Events\Login` event. + +To implement it: +1. Create event listener classes for the events. You can generate these classes using Artisan commands: +```bash +php artisan make:listener LogSuccessfulLogin +``` +2. Write the logic to execute when the events occur: +```php +// app/Listeners/LogSuccessfulLogin.php +namespace App\Listeners; + +use Illuminate\Support\Facades\Log; +use Illuminate\Auth\Events\Login; + +class LogSuccessfulLogin +{ + public function handle(Login $event) + { + // Log the successful login + Log::info("User with ID ".$event->user->id." successfully logged in."); + } +} +``` + +For Laravel version 10.x or older, you need to register the newly created event listener manually: + +3. Register your event listeners in the `EventServiceProvider`: +```php +// app/Providers/EventServiceProvider.php +namespace App\Providers; + +use Illuminate\Auth\Events\Login; +use App\Listeners\LogSuccessfulLogin; + +class EventServiceProvider extends ServiceProvider +{ + protected $listen = [ + Login::class => [ + LogSuccessfulLogin::class, + ] + ]; + + // Other event listeners... +} +``` +Now, whenever a user logs in to your application, you can get noticed by checking the Laravel log file at `/storage/logs/laravel.log`. diff --git a/collections.md b/collections.md index 7774b31..41fdb07 100644 --- a/collections.md +++ b/collections.md @@ -7,7 +7,6 @@ - [Multiple Collection Methods in a Row](#multiple-collection-methods-in-a-row) - [Calculate Sum with Pagination](#calculate-sum-with-pagination) - [Serial no in foreach loop with pagination](#serial-no-in-foreach-loop-with-pagination) -- [Higher order collection methods](#higher-order-collection-methods) - [Higher order collection message](#higher-order-collection-message) - [Get an existing key or insert a value if it doesn't exist and return the value](#get-an-existing-key-or-insert-a-value-if-it-doesnt-exist-and-return-the-value) - [Static times method](#static-times-method) @@ -90,27 +89,6 @@ We can use foreach collection items index as serial no (SL) in pagination. it will solve the issue of next pages(?page=2&...) index count from continue. -### Higher order collection methods - -Collections have higher order methods, this are methods that can be chained , like `groupBy()` , `map()` ... Giving you a fluid syntax. This example calculates the -price per group of products on an offer. - -```php -$offer = [ - 'name' => 'offer1', - 'lines' => [ - ['group' => 1, 'price' => 10], - ['group' => 1, 'price' => 20], - ['group' => 2, 'price' => 30], - ['group' => 2, 'price' => 40], - ['group' => 3, 'price' => 50], - ['group' => 3, 'price' => 60] - ] -]; - -$totalPerGroup = collect($offer['lines'])->groupBy('group')->map(fn($group) => $group->sum('price')); -``` - ### Higher order collection message Collections also provide support for "higher order messages", which are short-cuts for performing common actions on collections. @@ -166,4 +144,3 @@ Collection::times(7, function ($number) { ``` Tip given by [@Teacoders](https://twitter.com/Teacoders/status/1509447909602906116) - diff --git a/db-models-and-eloquent.md b/db-models-and-eloquent.md index 4796450..2d81575 100644 --- a/db-models-and-eloquent.md +++ b/db-models-and-eloquent.md @@ -10,6 +10,9 @@ - [You can write transaction-aware code](#you-can-write-transaction-aware-code) - [Eloquent scopes inside of other relationships](#eloquent-scopes-inside-of-other-relationships) - [New `rawValue()` method since Laravel 9.37](#new-rawvalue-method-since-laravel-937) +- [Load data faster when the targeted value is an integer](#load-data-faster-when-the-targeted-value-is-an-integer) +- [Load data completed between two timestamps](#load-data-completed-between-two-timestamps) +- [Pass a raw query to order your results](#pass-a-raw-query-to-order-your-results) - [Eloquent where date methods](#eloquent-where-date-methods) - [Increments and decrements](#increments-and-decrements) - [No timestamp columns](#no-timestamp-columns) @@ -96,6 +99,9 @@ - [New scalar() method](#new-scalar-method) - [Select specific columns](#select-specific-columns) - [Chain conditional clauses to the query without writing if-else statements](#chain-conditional-clauses-to-the-query-without-writing-if-else-statements) +- [Override Connection Attribute in Models](#override-connection-attribute-in-models) +- [Using Column Names in Where Clauses (Dynamic Where Clauses)](#using-column-names-in-where-clauses-dynamic-where-clauses) +- [Using firstOrCreate()](#using-firstorcreate) ### Reuse or clone query() @@ -161,7 +167,7 @@ Natural language Search for `something` ```php -Comment::whereFulltext(['title', 'descriptiong'], 'something')->get(); +Comment::whereFulltext(['title', 'description'], 'something')->get(); ``` Natural language with Query Expansion @@ -241,7 +247,7 @@ class User extends Model static::created(function ($user) { // Will send the email only if the // transaction is committed - DB::afterCoommit(function () use ($user) { + DB::afterCommit(function () use ($user) { Mail::send(new WelcomeEmail($user)); }); }); @@ -282,15 +288,59 @@ Laravel 9.37 has a new `rawValue()` method to get a value from a SQL expression. ```php $first = TripModel::orderBy('date_at', 'ASC') ->rawValue('YEAR(`date_at`)'); -$last = TripMOdel::orderBy('date_at', 'DESC') +$last = TripModel::orderBy('date_at', 'DESC') ->rawValue('YEAR(`date_at`)'); -$fullname = UserMOdel::where('id', $id) +$fullname = UserModel::where('id', $id) ->rawValue('CONCAT(`first_name`, " ", `last_name`)'); ``` Tip given by [@LoydRG](https://twitter.com/LoydRG/status/1587689148768567298) +### Load data faster when the targeted value is an integer + +Instead of using the 𝘄𝗵𝗲𝗿𝗲𝗜𝗻() method to load a large range of data when the targeted value is an integer, use 𝘄𝗵𝗲𝗿𝗲𝗜𝗻𝘁𝗲𝗴𝗲𝗿𝗜𝗻𝗥𝗮𝘄() which is faster than 𝘄𝗵𝗲𝗿𝗲𝗜𝗻(). + +```php +// instead of using whereIn +Product::whereIn('id', range(1, 50))->get(); + +// use WhereIntegerInRaw method for faster loading +Product::whereIntegerInRaw('id', range(1, 50))->get(); +``` + +Tip given by [@LaraShout](https://twitter.com/LaraShout) + +### Load data completed between two timestamps + +Use 𝘄𝗵𝗲𝗿𝗲𝗕𝗲𝘁𝘄𝗲𝗲𝗻 to load records between two timestamps, you can pass the fallback value using the null coalescing operator (??). + +```php +// Load tasks completed between two timestamps +Task::whereBetween('completed_at', [ + $request->from ?? '2023-01-01', + $request->to ?? today()->toDateTimeString(), +]); +``` + +Tip given by [@LaraShout](https://twitter.com/LaraShout) + +### Pass a raw query to order your results + +You can pass a raw query to order your results. + +For example, sorting tasks by how long before the due date they were completed. + +```php +// Sort tasks by the task was completed before the due date +$tasks = Task::query() + ->whereNotNull('completed_at') + ->orderByRaw('due_at - completed_at DESC') + ->get(); +``` + +Tip given by [@cosmeescobedo](https://twitter.com/cosmeescobedo) + ### Eloquent where date methods In Eloquent, check the date with functions `whereDay()`, `whereMonth()`, `whereYear()`, `whereDate()` and `whereTime()`. @@ -543,7 +593,7 @@ Tip given by [@sachinkiranti](https://raisachin.com.np) ### Find Many and return specific columns -Eloquent method `find()` may accept multiple parameters, and then it returns a Collection of all records found with specificied columns, not all columns of model: +Eloquent method `find()` may accept multiple parameters, and then it returns a Collection of all records found with specified columns, not all columns of model: ```php // Will return Eloquent Model with first_name and email only @@ -556,7 +606,7 @@ Tip given by [@tahiriqbalnajam](https://github.com/tahiriqbalnajam) ### Find by Key -You can also find multiple records with `whereKey()` method which takes care of which field is exactly your primary key (`id` is the default but you may override it in Eloquent model): +You can also find multiple records with `whereKey()` method which takes care of which field is exactly your primary key (`id` is the default, but you may override it in Eloquent model): ```php $users = User::whereKey([1,2,3])->get(); @@ -910,7 +960,7 @@ class Category extends Model public function products() { - return $this->hasMany(Product::class); + return $this->hasMany(Product::class); } } ``` @@ -1134,11 +1184,11 @@ $post->author->name ?? '' // But you can do it on Eloquent relationship level: // this relation will return an empty App\Author model if no author is attached to the post public function author() { - return $this->belongsTo('App\Author')->withDefault(); + return $this->belongsTo(Author::class)->withDefault(); } // or public function author() { - return $this->belongsTo('App\Author')->withDefault([ + return $this->belongsTo(Author::class)->withDefault([ 'name' => 'Guest Author' ]); } @@ -1370,16 +1420,16 @@ Nice if you need to retrieve a specific set of models and don't want to have to ```php User::create(['id' => 1]); -User::create(['id' => 2); +User::create(['id' => 2]); User::create(['id' => 3]); -// Retrives the user... +// Retrieves the user... $user = User::findOrFail(1); // Throws a 404 because the user doesn't exist... User::findOrFail(99); -// Retrives all 3 users... +// Retrieves all 3 users... $users = User::findOrFail([1, 2, 3]); // Throws because it is unable to find *all* of the users @@ -1665,6 +1715,7 @@ protected function title(): Attribute return new Attribute( get: fn ($value) => strtoupper($value), set: fn ($value) => strtolower($value), + ); } ``` @@ -1674,7 +1725,7 @@ Tip given by [@Teacoders](https://twitter.com/Teacoders/status/14736978084568514 In case you are going to use the same accessors and mutators in many models , You can use custom casts instead. -Just create a `class` that implements `CastsAttributes` interface. The class should have two methods, the first is `get` to specify how models should be retrieved from the database and the second is `set` to specify how the the value will be stored in the database. +Just create a `class` that implements `CastsAttributes` interface. The class should have two methods, the first is `get` to specify how models should be retrieved from the database and the second is `set` to specify how the value will be stored in the database. ```php orderBy('authors_count', 'DESC') ->having('modules_count', '>', 10) ->firstOr(function() { - // THe Sky is the Limit ... + // The Sky is the Limit ... // You can perform any action here }); @@ -1750,7 +1801,7 @@ Tip given by [@bhaidar](https://twitter.com/bhaidar/status/1487757487566639113/) ### Directly convert created_at date to human readable format -Did you know you can directly convert created_at date to human readble format like 1 miniute ago, 1 month ago using diffForHumans() function. Laravel eloquent by default enables Carbon instance on created_at field. +Did you know you can directly convert created_at date to human readable format like 1 minute ago, 1 month ago using diffForHumans() function. Laravel eloquent by default enables Carbon instance on created_at field. ```php $post = Post::whereId($id)->first(); @@ -2056,7 +2107,7 @@ class RatingSorter extends Sorter $query ->selectRaw('AVG(product_ratings.rating) AS avg_rating') ->join('product_ratings', 'products.id', '=', 'product_ratings.product_id') - ->groupBy('products.id'); + ->groupBy('products.id') ->when( $this->direction === SortDirections::Desc, fn () => $query->orderByDesc('avg_rating') @@ -2070,3 +2121,102 @@ class RatingSorter extends Sorter Tip given by [@mmartin_joo](https://twitter.com/mmartin_joo/status/1521461317940350976) +### Override Connection Attribute in Models + +Overriding the database connection attribute for individual models in Laravel can be a powerful technique. Here are a few use cases where you might find it especially handy: + +#### 1. Multiple Database Connections + +If your application uses multiple database connections (e.g., MySQL, PostgreSQL, or different instances of the same database), you may want to specify which connection should be used for a particular model. By overriding the `$connection` property, you can easily manage these connections and ensure your models are interacting with the appropriate databases. + +#### 2. Data Sharding + +In cases where you're using data sharding to distribute your data across multiple databases, you might have different models that map to different shards. Overriding the connection attribute in each model allows you to define which shard should be used without affecting other models or the default connection. + +#### 3. Third-Party Integration + +When integrating with third-party services that provide their own database, you may need to use a specific connection for a model representing data from that service. Overriding the connection attribute in that model will ensure it connects to the right database while keeping your application's default settings intact. + +#### 4. Multi-Tenancy Applications + +In a multi-tenant application, you may have separate databases for each tenant. By overriding the `$connection` property dynamically in your models, you can easily switch between tenant databases based on the current user, ensuring data isolation and proper resource management. + +To override the connection attribute in a model, define the `$connection` property within the class: + +```php +get(); + } +} +``` + +Tip given by [@MNurullahSaglam](https://twitter.com/MNurullahSaglam/status/1699763337586749585) + +### Using firstOrCreate() + +You can use firstOrCreate() to find the first record matching the attributes or create it if it doesn't exist. + +#### Example Scenario + +Assume that you are importing a CSV file and you want to create a category if it doesn't exist. + +```php +name)->first(); + + if (!$category) { + $category = Category::create([ + 'name' => $request->name, + 'slug' => Str::slug($request->name), + ]); + } + + // you can use + $category = Category::firstOrCreate([ + 'name' => $request->name, + ], [ + 'slug' => Str::slug($request->name), + ]); + + return $category; + } +} +``` + +Tip given by [@MNurullahSaglam](https://twitter.com/MNurullahSaglam/status/1699773783748366478) diff --git a/factories.md b/factories.md index df3230f..67db314 100644 --- a/factories.md +++ b/factories.md @@ -89,7 +89,7 @@ The Laravel factory has a very useful `for()` method. You can use it to create ` public function run() { Product::factory() - ->count(3); + ->count(3) ->for(Category::factory()->create()) ->create(); } @@ -141,4 +141,3 @@ class EventSeeder extends Seeder ``` Tip given by [@justsanjit](https://twitter.com/justsanjit/status/1514428294418079746) - diff --git a/log-and-debug.md b/log-and-debug.md index dc01a45..7cdd90e 100644 --- a/log-and-debug.md +++ b/log-and-debug.md @@ -9,6 +9,7 @@ - [Log with context](#log-with-context) - [Quickly output an Eloquent query in its SQL form](#quickly-output-an-eloquent-query-in-its-sql-form) - [Log all the database queries during development](#log-all-the-database-queries-during-development) +- [Discover all events fired in one request](#discover-all-events-fired-in-one-request) ### Logging with parameters @@ -113,3 +114,32 @@ public function boot() Tip given by [@mmartin_joo](https://twitter.com/mmartin_joo/status/1473262634405449730) +### Discover all events fired in one request + +If you want to implement a new listener to a specific event but you don't know its name, you can log all events fired during the request. + +You can use the `\Illuminate\Support\Facades\Event::listen()` method on `boot()` method of `app/Providers/EventServiceProvider.php` to catch all events fired. + +**Important:** If you use the `Log` facade within this event listener then you will need to exclude events named `Illuminate\Log\Events\MessageLogged` to avoid an infinite loop. +(Example: `if ($event == 'Illuminate\\Log\\Events\\MessageLogged') return;`) + +```php +// Include Event... +use Illuminate\Support\Facades\Event; + +// In your EventServiceProvider class... +public function boot() +{ + parent::boot(); + + Event::listen('*', function ($event, array $data) { + // Log the event class + error_log($event); + + // Log the event data delegated to listener parameters + error_log(json_encode($data, JSON_PRETTY_PRINT)); + }); +} +``` + +Tip given by [@MuriloChianfa](https://github.com/MuriloChianfa) diff --git a/mail.md b/mail.md index 115a370..ec233e1 100644 --- a/mail.md +++ b/mail.md @@ -24,7 +24,7 @@ public function build() { return $this->subject('Inquiry') ->to('example@example.com') - ->markdown('email.inquery') + ->markdown('email.inquiry') ->attachData( $this->file, $this->file->getClientOriginalName(), @@ -98,7 +98,7 @@ class InvoicePaid extends Notification { return (new MailMessage) ->success() - ->line('We've received your payment) + ->line('We\'ve received your payment') ->when($user->isOnMonthlyPaymentPlan(), function (MailMessage $message) { $message->action('Save 20% by paying yearly', route('account.billing')); }) @@ -110,4 +110,3 @@ class InvoicePaid extends Notification Use the `when` or `unless` methods in you own classes by using the `Illuminate\Support\Traits\Conditionable` trait Tip given by [@Philo01](https://twitter.com/Philo01/status/1503302749525528582) - diff --git a/migrations.md b/migrations.md index e7011b5..c83c270 100644 --- a/migrations.md +++ b/migrations.md @@ -70,10 +70,10 @@ If you want to check what migrations are executed or not yet, no need to look at Example result: ``` -Migration name .......................................................................... Batch / Status -2014_10_12_000000_create_users_table ........................................................... [1] Ran -2014_10_12_100000_create_password_resets_table ................................................. [1] Ran -2019_08_19_000000_create_failed_jobs_table ..................................................... [1] Ran +Migration name .......................................................................... Batch / Status +2014_10_12_000000_create_users_table ........................................................... [1] Ran +2014_10_12_100000_create_password_resets_table ................................................. [1] Ran +2019_08_19_000000_create_failed_jobs_table ..................................................... [1] Ran ``` ### Create Migration with Spaces @@ -297,9 +297,8 @@ Schema::create('posts', function (Blueprint $table) { $table->id(); $table->string('title'); $table->timestamp('added_at')->useCurrent(); - $table->timespamps(); + $table->timestamps(); }); ``` Tip given by [@iamgurmandeep](https://twitter.com/iamgurmandeep/status/1517152425748148225) - diff --git a/models-relations.md b/models-relations.md index 38c5240..2140c3a 100644 --- a/models-relations.md +++ b/models-relations.md @@ -4,6 +4,7 @@ - [OrderBy on Eloquent relationships](#orderby-on-eloquent-relationships) - [Add where statement to Many-to-Many relation](#add-where-statement-to-many-to-many-relation) +- [Get the newest (or oldest) item of another relation](#get-the-newest-or-oldest-item-of-another-relation) - [Conditional relationships](#conditional-relationships) - [Raw DB Queries: havingRaw()](#raw-db-queries-havingraw) - [Eloquent has() deeper](#eloquent-has-deeper) @@ -78,6 +79,28 @@ class Developer extends Model Tip given by [@cosmeescobedo](https://twitter.com/cosmeescobedo/status/1582904416457269248) +### Get the newest (or oldest) item of another relation + +Since Laravel 8.42, in an Eloquent model, you can define a relation that will get the newest (or oldest) item of another relation. + +```php +/** + * Get the user's latest order. + */ +public function latestOrder() +{ + return $this->hasOne(Order::class)->latestOfMany(); +} + +/** + * Get the user's oldest order. + */ +public function oldestOrder() +{ + return $this->hasOne(Order::class)->oldestOfMany(); +} +``` + ### Conditional relationships If you notice that you use same relationship often with additional "where" condition, you can create a separate relationship method. @@ -90,9 +113,9 @@ public function comments() return $this->hasMany(Comment::class); } -public function approved_comments() +public function approvedComments() { - return $this->hasMany(Comment::class)->where('approved', 1); + return $this->comments()->where('approved', 1); } ``` @@ -445,11 +468,12 @@ class Tournament extends Model ```php class TournamentsController extends Controller - -public function whatever_method() { - $tournaments = Tournament::with(['countries' => function($query) { +{ + public function whatever_method() { + $tournaments = Tournament::with(['countries' => function($query) { $query->orderBy('position'); }])->latest()->get(); + } } ``` @@ -656,4 +680,3 @@ select * from posts where user_id = ? and (active = 1 or votes >= 100) ``` Tip given by [@BonnickJosh](https://twitter.com/BonnickJosh/status/1494779780562096139) - diff --git a/other.md b/other.md index b005aaa..c0b8042 100644 --- a/other.md +++ b/other.md @@ -12,6 +12,7 @@ - [Schedule Laravel job based on time zone](#schedule-laravel-job-based-on-time-zone) - [Use assertModelMissing instead assertDatabaseMissing](#use-assertmodelmissing-instead-assertdatabasemissing) - [Various options to format diffForHumans()](#various-options-to-format-diffforhumans) +- [Create custom disks at runtime](#create-custom-disks-at-runtime) - [When (NOT) to run "composer update"](#when-not-to-run-composer-update) - [Composer: Check for Newer Versions](#composer-check-for-newer-versions) - [Auto-Capitalize Translations](#auto-capitalize-translations) @@ -111,7 +112,7 @@ class TerminatingMiddleware { return $next($request); } - + public function terminate($request, $response) { // ... @@ -203,9 +204,9 @@ $schedule->command('reportg:generate') ->at('2:00'); ``` -If you are repeatedly assigning the same timezone to all of your sheduled tasks, you may wish to define a `sheduleTimezone` method in you `app\Console\Kernel` class: +If you are repeatedly assigning the same timezone to all of your schedule tasks, you may wish to define a `scheduleTimezone` method in you `app\Console\Kernel` class: ```php -protected function sheduleTimezone() +protected function scheduleTimezone() { return 'America/Chicago'; } @@ -228,7 +229,7 @@ public function allowed_user_can_delete_task() // Instead of assertDatabaseMissing to check if the model missing from the database $this->assertDatabaseMissing('tasks', ['id' => $task->id]); - // use directly assertModelMIssing + // use directly assertModelMissing $this->assertModelMissing($task); } ``` @@ -258,7 +259,7 @@ $user->created_at->diffForHumans([ 'join' => ', ', ]); ``` -=> `"17 hoours, 54 minutes, 50 seconds ago"` +=> `"17 hours, 54 minutes, 50 seconds ago"` ```php @@ -270,6 +271,22 @@ $user->created_at->diffForHumans([ ``` => `"17h, 54m, 50s ago"` +### Create custom disks at runtime + +Did you know that you can create custom disks at runtime without the need to have the config in your config/filesystems file? + +This can be handy to manage files in custom paths without the need of adding them to the config. + +```php +$avatarDisk = Storage::build([ + 'driver' => 'local', + 'root' => storage_path('app/avatars'), +]); +$avatarDisk->put('user_avatar.jpg', $image); +``` + +Tip given by [@wendell_adriel](https://twitter.com/wendell_adriel/) + ### When (NOT) to run "composer update" Not so much about Laravel, but... Never run `composer update` on production live server, it's slow and will "break" repository. Always run `composer update` locally on your computer, commit new `composer.lock` to the repository, and run `composer install` on the live server. @@ -523,11 +540,11 @@ protected function schedule(Schedule $schedule) If you want to search Laravel Docs for some keyword, by default it gives you only the TOP 5 results. Maybe there are more? -If you want to see ALL results, you may go to the Github Laravel docs repository and search there directly. https://github.com/laravel/docs +If you want to see ALL results, you may go to the GitHub Laravel docs repository and search there directly. https://github.com/laravel/docs ### Filter route:list -New in Laravel 8.34: `php artisan route:list` gets additional flag `--except-path`, so you would filter out the routes you don't want to see. [See original PR](New in Laravel 8.34: `php artisan route:list` gets additional flag `--except-path`, so you would filter out the routes you don't want to see. [See original PR](https://github.com/laravel/framework/pull/36619) +New in Laravel 8.34: `php artisan route:list` gets additional flag `--except-path`, so you would filter out the routes you don't want to see. [See original PR](https://github.com/laravel/framework/pull/36619) ### Blade directive for not repeating yourself @@ -694,9 +711,9 @@ We can schedule regular shell commands within Laravel scheduled command class Kernel extends ConsoleKernel { - protected function shedule(Schedule $shedule) + protected function schedule(Schedule $schedule) { - $shedule->exec('node /home/forge/script.js')->daily(); + $schedule->exec('node /home/forge/script.js')->daily(); } } ``` @@ -735,7 +752,7 @@ class MigrationsTest extends TestCase $this->expectNotToPerformAssertions(); - Artisan::call('migrate:fresh', ['--path' => '/databse/migrations/task1']); + Artisan::call('migrate:fresh', ['--path' => '/database/migrations/task1']); } } ``` @@ -802,7 +819,7 @@ Tip given by [@sky_0xs](https://twitter.com/sky_0xs/status/1458179766192853001) ### Temporary download URLs -You can use temporary download URLs for your cloud storage resources to prevent unwanted access. For example, when a user wants to download a file, we redirect to an s3 resource but have the URL expire in 5 seconds. +You can use temporary download URLs for your cloud storage resources to prevent unwanted access. For example, when a user wants to download a file, we redirect to a s3 resource but have the URL expire in 5 seconds. ```php public function download(File $file) @@ -818,7 +835,7 @@ Tip given by [@Philo01](https://twitter.com/Philo01/status/1458791323889197064) ### Dealing with deeply-nested arrays -If you have an complex array, you can use `data_get()` helper function to retrieve a value from a nested array using "dot" notation and wildcard. +If you have a complex array, you can use `data_get()` helper function to retrieve a value from a nested array using "dot" notation and wildcard. ```php $data = [ @@ -910,7 +927,7 @@ DateTime::createFromFormat('Y-m-d', '2021-10-12'); // 2021-10-12 00:00:00.0 DateTime::createFromFormat('!Y-m-d', '2021-10-12'); -2021-10-12 21:00:00.0 +// 2021-10-12 21:00:00.0 DateTime::createFromFormat('!Y-m-d H', '2021-10-12'); ``` @@ -1272,7 +1289,7 @@ Route::get('redirectRoute', function() { ``` -This helper work in the same way as `redirect()->route('home')`, but it is more concise than a old way. +This helper work in the same way as `redirect()->route('home')`, but it is more concise than an old way. Tip given by [@CatS0up](https://github.com/CatS0up) @@ -1474,6 +1491,7 @@ use Illuminate\Queue\SerializesModels; use Illuminate\Queue\InteractsWithQueue; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; +use Throwable; class CalculateSingleConsignment implements ShouldQueue { @@ -1481,12 +1499,26 @@ class CalculateSingleConsignment implements ShouldQueue // ... __construct() method, handle() method, etc. - public function failed() + public function failed(Throwable $exception) { - // Perfom any action here when job has failed + // Perform any action here when job has failed } } ``` Tip given by [@pauloimon](https://github.com/pauloimon) + +### Ignore Database when Job has failed + +If you ever need to bypass database when a job fails, you can do one of the below things to skip database: +- Set env `QUEUE_FAILED_DRIVER` with value `null`. Works from Laravel 8 and above. +- Set the `failed` value to `null` in `config/queue.php` file, replacing the array (like below code). This one works for Laravel 7 and older. + +```php + 'failed' => null, +``` + +Why you would want this? For applications where you do not need to store failed jobs and they needs to have very high TPS, skipping database can be very favourable as we are not hitting database, saving times & prevent database going down. + +Tip given by [@a-h-abid](https://github.com/a-h-abid) diff --git a/routing.md b/routing.md index d04fbcc..4ec52b6 100644 --- a/routing.md +++ b/routing.md @@ -530,7 +530,7 @@ foreach(glob(dirname(__FILE__).'/web/*', GLOB_NOSORT) as $route_file){ } ``` -Now every file inside _/routes/web/_ act as a web router file and you can organize your routes into diferent files. +Now every file inside _/routes/web/_ act as a web router file and you can organize your routes into different files. ### Route resources grouping @@ -620,11 +620,11 @@ If for some reason, your URL is having query parameters, you can retrieve the UR ```php // Original URL: https://www.amitmerchant.com?search=laravel&lang=en&sort=desc -$urlWithQUeryString = $request->fullUrlWithoutQuery([ +$urlWithQueryString = $request->fullUrlWithoutQuery([ 'lang', 'sort' ]); -echo $urlWithQUeryString; +echo $urlWithQueryString; // Outputs: https://www.amitmerchant.com?search=laravel ``` @@ -674,4 +674,3 @@ Route::controller(UsersController::class)->group(function () { ``` Tip given by [@justsanjit](https://twitter.com/justsanjit/status/1514943541612527616) - diff --git a/validation.md b/validation.md index 77fe63c..50c9105 100644 --- a/validation.md +++ b/validation.md @@ -95,6 +95,7 @@ Validator::make([ 'is_company' => 'required|boolean', 'company_name' => 'required_if_accepted:is_company', ]); +``` Tip given by [@iamgurmandeep](https://twitter.com/iamgurmandeep/status/1583420332693749761) @@ -520,4 +521,3 @@ class RegistrationController extends Controller ``` Tip given by [@mattkingshott](https://twitter.com/mattkingshott/status/1518590652682063873) - diff --git a/views.md b/views.md index f55963d..ac841dd 100644 --- a/views.md +++ b/views.md @@ -5,6 +5,7 @@ - [$loop variable in foreach](#loop-variable-in-foreach) - [You can use Blade to generate more than HTML](#you-can-use-blade-to-generate-more-than-html) - [Short attribute syntax for Blade Components](#short-attribute-syntax-for-blade-components) +- [Share one variable with multiple views](#share-one-variable-with-multiple-views) - [Does view file exist?](#does-view-file-exist) - [Error code Blade pages](#error-code-blade-pages) - [View without controllers](#view-without-controllers) @@ -74,6 +75,29 @@ Short syntax: ``` +### Share one variable with multiple views + +Have you ever needed to share one variable with multiple views in Laravel? Here's a simple solution for that. + +```php +use App\Models\Post; +use Illuminate\Support\Facades\View; +use Illuminate\Support\Facades\Schema; +use Illuminate\Support\ServiceProvider; + +class AppServiceProvider extends ServiceProvider +{ + public function boot() + { + if (Schema::hasTable('posts')) { + View::share('recentPosts', Post::latest()->take(3)->get()); + } + } +} +``` + +Tip given by [@codewithdary](https://twitter.com/codewithdary) + ### Does view file exist? You can check if View file exists before actually loading it. @@ -92,7 +116,7 @@ return view()->first(['custom.dashboard', 'dashboard'], $data); ### Error code Blade pages -If you want to create a specific error page for some HTTP code, like 500 - just create a blade file with this code as filename, in `resources/views/errors/500.blade.php`, or `403.blade.php` etc, and it will automatically be loaded in case of that error code. +If you want to create a specific error page for some HTTP code, like 503 - just create a blade file with this code as filename, in `resources/views/errors/503.blade.php`, or `403.blade.php` etc, and it will automatically be loaded in case of that error code. ### View without controllers @@ -198,7 +222,7 @@ This will try to load adminlte.header, if missing - will load default.header ### Use Laravel Blade-X variable binding to save even more space -```php +```blade // Using include, the old way @include("components.post", ["title" => $post->title]) @@ -213,7 +237,7 @@ Tip given by [@anwar_nairi](https://twitter.com/anwar_nairi/status/1442441888787 ### Blade components props -```php +```blade // button.blade.php @props(['rounded' => false]) @@ -236,7 +260,7 @@ Tip given by [@godismyjudge95](https://twitter.com/godismyjudge95/status/1448825 ### Blade Autocomplete typehint -```php +```blade @php /* @var App\Models\User $user */ @endphp @@ -253,7 +277,7 @@ Tip given by [@freekmurze](https://twitter.com/freekmurze/status/145546666392774 Did you know that if you pass colon (:) before the component parameter, you can directly pass variables without print statement `{{ }}`? -```php +```blade // you can do instead @@ -376,7 +400,7 @@ In Laravel 9, you'll be able to use the cool new "checked" Blade directive. This is going to be a nice addition that we can use to clean up our Blade views a little bit -```php +```blade // Before Laravel 9: active) ? 'checked' : '' }}/> active) ? '' : 'checked' }}/> @@ -394,7 +418,7 @@ In Laravel 9, you'll be able to use the cool new "selected" Blade directive for This is going to be a nice addition that we can use to clean up our Blade views a little bit -```php +```blade // Before Laravel 9: