From 1ab9932ec8489a4dab268be6868972cebd19ae43 Mon Sep 17 00:00:00 2001 From: Andrew Brown Date: Wed, 19 Feb 2025 14:27:42 -0600 Subject: [PATCH] [12.x] use fenced code blocks (#10172) * use fenced code blocks - better syntax highlighting - easier copy/pasting of code examples - consistency throughout docs block * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip --- artisan.md | 670 ++++--- authentication.md | 602 ++++--- authorization.md | 716 ++++---- billing.md | 1808 +++++++++++-------- blade.md | 432 +++-- broadcasting.md | 474 ++--- cache.md | 366 ++-- cashier-paddle.md | 1009 ++++++----- collections.md | 3550 +++++++++++++++++++++---------------- configuration.md | 54 +- console-tests.md | 56 +- container.md | 598 ++++--- contracts.md | 48 +- contributions.md | 70 +- controllers.md | 394 ++-- csrf.md | 30 +- database-testing.md | 98 +- database.md | 322 ++-- deployment.md | 14 +- dusk.md | 1488 ++++++++++------ eloquent-collections.md | 184 +- eloquent-factories.md | 644 ++++--- eloquent-mutators.md | 850 +++++---- eloquent-relationships.md | 2084 ++++++++++++---------- eloquent-resources.md | 854 +++++---- eloquent-serialization.md | 202 ++- eloquent.md | 1542 +++++++++------- encryption.md | 52 +- errors.md | 440 +++-- events.md | 846 +++++---- facades.md | 202 ++- filesystem.md | 582 +++--- fortify.md | 26 +- hashing.md | 76 +- helpers.md | 2078 +++++++++++++--------- homestead.md | 18 +- horizon.md | 272 +-- http-client.md | 626 ++++--- http-tests.md | 698 +++++--- localization.md | 168 +- logging.md | 420 +++-- mail.md | 1272 +++++++------ middleware.md | 468 ++--- migrations.md | 688 ++++--- mocking.md | 100 +- notifications.md | 1690 ++++++++++-------- packages.md | 326 ++-- pagination.md | 208 ++- passport.md | 844 +++++---- passwords.md | 160 +- pennant.md | 120 +- processes.md | 20 +- prompts.md | 2 +- providers.md | 222 +-- pulse.md | 2 +- queries.md | 1194 ++++++++----- queues.md | 2056 +++++++++++---------- rate-limiting.md | 118 +- redirects.md | 94 +- redis.md | 428 +++-- requests.md | 582 +++--- responses.md | 350 ++-- routing.md | 778 ++++---- sanctum.md | 254 +-- scheduling.md | 368 ++-- scout.md | 500 +++--- seeding.md | 122 +- session.md | 254 +-- socialite.md | 154 +- strings.md | 2334 ++++++++++++++---------- telescope.md | 344 ++-- testing.md | 68 +- urls.md | 230 ++- valet.md | 122 +- validation.md | 1670 +++++++++-------- verification.md | 104 +- views.md | 232 +-- vite.md | 16 +- 78 files changed, 25389 insertions(+), 18768 deletions(-) diff --git a/artisan.md b/artisan.md index 794326a4d6a..9db6f9db2f2 100644 --- a/artisan.md +++ b/artisan.md @@ -88,18 +88,22 @@ php artisan vendor:publish --provider="Laravel\Tinker\TinkerServiceProvider" Tinker utilizes an "allow" list to determine which Artisan commands are allowed to be run within its shell. By default, you may run the `clear-compiled`, `down`, `env`, `inspire`, `migrate`, `migrate:install`, `up`, and `optimize` commands. If you would like to allow more commands you may add them to the `commands` array in your `tinker.php` configuration file: - 'commands' => [ - // App\Console\Commands\ExampleCommand::class, - ], +```php +'commands' => [ + // App\Console\Commands\ExampleCommand::class, +], +``` #### Classes That Should Not Be Aliased Typically, Tinker automatically aliases classes as you interact with them in Tinker. However, you may wish to never alias some classes. You may accomplish this by listing the classes in the `dont_alias` array of your `tinker.php` configuration file: - 'dont_alias' => [ - App\Models\User::class, - ], +```php +'dont_alias' => [ + App\Models\User::class, +], +``` ## Writing Commands @@ -122,38 +126,40 @@ After generating your command, you should define appropriate values for the `sig Let's take a look at an example command. Note that we are able to request any dependencies we need via the command's `handle` method. The Laravel [service container](/docs/{{version}}/container) will automatically inject all dependencies that are type-hinted in this method's signature: - send(User::find($this->argument('user'))); - } + $drip->send(User::find($this->argument('user'))); } +} +``` > [!NOTE] > For greater code reuse, it is good practice to keep your console commands light and let them defer to application services to accomplish their tasks. In the example above, note that we inject a service class to do the "heavy lifting" of sending the e-mails. @@ -163,13 +169,17 @@ Let's take a look at an example command. Note that we are able to request any de If nothing is returned from the `handle` method and the command executes successfully, the command will exit with a `0` exit code, indicating success. However, the `handle` method may optionally return an integer to manually specify command's exit code: - $this->error('Something went wrong.'); +```php +$this->error('Something went wrong.'); - return 1; +return 1; +``` If you would like to "fail" the command from any method within the command, you may utilize the `fail` method. The `fail` method will immediately terminate execution of the command and return an exit code of `1`: - $this->fail('Something went wrong.'); +```php +$this->fail('Something went wrong.'); +``` ### Closure Commands @@ -178,9 +188,11 @@ Closure based commands provide an alternative to defining console commands as cl Even though the `routes/console.php` file does not define HTTP routes, it defines console based entry points (routes) into your application. Within this file, you may define all of your closure based console commands using the `Artisan::command` method. The `command` method accepts two arguments: the [command signature](#defining-input-expectations) and a closure which receives the command's arguments and options: - Artisan::command('mail:send {user}', function (string $user) { - $this->info("Sending email to: {$user}!"); - }); +```php +Artisan::command('mail:send {user}', function (string $user) { + $this->info("Sending email to: {$user}!"); +}); +``` The closure is bound to the underlying command instance, so you have full access to all of the helper methods you would typically be able to access on a full command class. @@ -189,21 +201,25 @@ The closure is bound to the underlying command instance, so you have full access In addition to receiving your command's arguments and options, command closures may also type-hint additional dependencies that you would like resolved out of the [service container](/docs/{{version}}/container): - use App\Models\User; - use App\Support\DripEmailer; +```php +use App\Models\User; +use App\Support\DripEmailer; - Artisan::command('mail:send {user}', function (DripEmailer $drip, string $user) { - $drip->send(User::find($user)); - }); +Artisan::command('mail:send {user}', function (DripEmailer $drip, string $user) { + $drip->send(User::find($user)); +}); +``` #### Closure Command Descriptions When defining a closure based command, you may use the `purpose` method to add a description to the command. This description will be displayed when you run the `php artisan list` or `php artisan help` commands: - Artisan::command('mail:send {user}', function (string $user) { - // ... - })->purpose('Send a marketing email to a user'); +```php +Artisan::command('mail:send {user}', function (string $user) { + // ... +})->purpose('Send a marketing email to a user'); +``` ### Isolatable Commands @@ -213,17 +229,19 @@ When defining a closure based command, you may use the `purpose` method to add a Sometimes you may wish to ensure that only one instance of a command can run at a time. To accomplish this, you may implement the `Illuminate\Contracts\Console\Isolatable` interface on your command class: - ### Options Options, like arguments, are another form of user input. Options are prefixed by two hyphens (`--`) when they are provided via the command line. There are two types of options: those that receive a value and those that don't. Options that don't receive a value serve as a boolean "switch". Let's take a look at an example of this type of option: - /** - * The name and signature of the console command. - * - * @var string - */ - protected $signature = 'mail:send {user} {--queue}'; +```php +/** + * The name and signature of the console command. + * + * @var string + */ +protected $signature = 'mail:send {user} {--queue}'; +``` In this example, the `--queue` switch may be specified when calling the Artisan command. If the `--queue` switch is passed, the value of the option will be `true`. Otherwise, the value will be `false`: @@ -318,12 +342,14 @@ php artisan mail:send 1 --queue Next, let's take a look at an option that expects a value. If the user must specify a value for an option, you should suffix the option name with a `=` sign: - /** - * The name and signature of the console command. - * - * @var string - */ - protected $signature = 'mail:send {user} {--queue=}'; +```php +/** + * The name and signature of the console command. + * + * @var string + */ +protected $signature = 'mail:send {user} {--queue=}'; +``` In this example, the user may pass a value for the option like so. If the option is not specified when invoking the command, its value will be `null`: @@ -333,14 +359,18 @@ php artisan mail:send 1 --queue=default You may assign default values to options by specifying the default value after the option name. If no option value is passed by the user, the default value will be used: - 'mail:send {user} {--queue=default}' +```php +'mail:send {user} {--queue=default}' +``` #### Option Shortcuts To assign a shortcut when defining an option, you may specify it before the option name and use the `|` character as a delimiter to separate the shortcut from the full option name: - 'mail:send {user} {--Q|queue}' +```php +'mail:send {user} {--Q|queue}' +``` When invoking the command on your terminal, option shortcuts should be prefixed with a single hyphen and no `=` character should be included when specifying a value for the option: @@ -353,7 +383,9 @@ php artisan mail:send 1 -Qdefault If you would like to define arguments or options to expect multiple input values, you may use the `*` character. First, let's take a look at an example that specifies such an argument: - 'mail:send {user*}' +```php +'mail:send {user*}' +``` When calling this method, the `user` arguments may be passed in order to the command line. For example, the following command will set the value of `user` to an array with `1` and `2` as its values: @@ -363,14 +395,18 @@ php artisan mail:send 1 2 This `*` character can be combined with an optional argument definition to allow zero or more instances of an argument: - 'mail:send {user?*}' +```php +'mail:send {user?*}' +``` #### Option Arrays When defining an option that expects multiple input values, each option value passed to the command should be prefixed with the option name: - 'mail:send {--id=*}' +```php +'mail:send {--id=*}' +``` Such a command may be invoked by passing multiple `--id` arguments: @@ -383,97 +419,109 @@ php artisan mail:send --id=1 --id=2 You may assign descriptions to input arguments and options by separating the argument name from the description using a colon. If you need a little extra room to define your command, feel free to spread the definition across multiple lines: - /** - * The name and signature of the console command. - * - * @var string - */ - protected $signature = 'mail:send - {user : The ID of the user} - {--queue : Whether the job should be queued}'; +```php +/** + * The name and signature of the console command. + * + * @var string + */ +protected $signature = 'mail:send + {user : The ID of the user} + {--queue : Whether the job should be queued}'; +``` ### Prompting for Missing Input If your command contains required arguments, the user will receive an error message when they are not provided. Alternatively, you may configure your command to automatically prompt the user when required arguments are missing by implementing the `PromptsForMissingInput` interface: - + * @var string */ - protected function promptForMissingArgumentsUsing(): array - { - return [ - 'user' => 'Which user ID should receive the mail?', - ]; - } + protected $signature = 'mail:send {user}'; -You may also provide placeholder text by using a tuple containing the question and placeholder: + // ... +} +``` +If Laravel needs to gather a required argument from the user, it will automatically ask the user for the argument by intelligently phrasing the question using either the argument name or description. If you wish to customize the question used to gather the required argument, you may implement the `promptForMissingArgumentsUsing` method, returning an array of questions keyed by the argument names: + +```php +/** + * Prompt for missing input arguments using the returned questions. + * + * @return array + */ +protected function promptForMissingArgumentsUsing(): array +{ return [ - 'user' => ['Which user ID should receive the mail?', 'E.g. 123'], + 'user' => 'Which user ID should receive the mail?', ]; +} +``` -If you would like complete control over the prompt, you may provide a closure that should prompt the user and return their answer: +You may also provide placeholder text by using a tuple containing the question and placeholder: - use App\Models\User; - use function Laravel\Prompts\search; +```php +return [ + 'user' => ['Which user ID should receive the mail?', 'E.g. 123'], +]; +``` - // ... +If you would like complete control over the prompt, you may provide a closure that should prompt the user and return their answer: - return [ - 'user' => fn () => search( - label: 'Search for a user:', - placeholder: 'E.g. Taylor Otwell', - options: fn ($value) => strlen($value) > 0 - ? User::where('name', 'like', "%{$value}%")->pluck('name', 'id')->all() - : [] - ), - ]; +```php +use App\Models\User; +use function Laravel\Prompts\search; + +// ... + +return [ + 'user' => fn () => search( + label: 'Search for a user:', + placeholder: 'E.g. Taylor Otwell', + options: fn ($value) => strlen($value) > 0 + ? User::where('name', 'like', "%{$value}%")->pluck('name', 'id')->all() + : [] + ), +]; +``` > [!NOTE] The comprehensive [Laravel Prompts](/docs/{{version}}/prompts) documentation includes additional information on the available prompts and their usage. If you wish to prompt the user to select or enter [options](#options), you may include prompts in your command's `handle` method. However, if you only wish to prompt the user when they have also been automatically prompted for missing arguments, then you may implement the `afterPromptingForMissingArguments` method: - use Symfony\Component\Console\Input\InputInterface; - use Symfony\Component\Console\Output\OutputInterface; - use function Laravel\Prompts\confirm; +```php +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use function Laravel\Prompts\confirm; - // ... +// ... - /** - * Perform actions after the user was prompted for missing arguments. - */ - protected function afterPromptingForMissingArguments(InputInterface $input, OutputInterface $output): void - { - $input->setOption('queue', confirm( - label: 'Would you like to queue the mail?', - default: $this->option('queue') - )); - } +/** + * Perform actions after the user was prompted for missing arguments. + */ +protected function afterPromptingForMissingArguments(InputInterface $input, OutputInterface $output): void +{ + $input->setOption('queue', confirm( + label: 'Would you like to queue the mail?', + default: $this->option('queue') + )); +} +``` ## Command I/O @@ -483,25 +531,31 @@ If you wish to prompt the user to select or enter [options](#options), you may i While your command is executing, you will likely need to access the values for the arguments and options accepted by your command. To do so, you may use the `argument` and `option` methods. If an argument or option does not exist, `null` will be returned: - /** - * Execute the console command. - */ - public function handle(): void - { - $userId = $this->argument('user'); - } +```php +/** + * Execute the console command. + */ +public function handle(): void +{ + $userId = $this->argument('user'); +} +``` If you need to retrieve all of the arguments as an `array`, call the `arguments` method: - $arguments = $this->arguments(); +```php +$arguments = $this->arguments(); +``` Options may be retrieved just as easily as arguments using the `option` method. To retrieve all of the options as an array, call the `options` method: - // Retrieve a specific option... - $queueName = $this->option('queue'); +```php +// Retrieve a specific option... +$queueName = $this->option('queue'); - // Retrieve all options as an array... - $options = $this->options(); +// Retrieve all options as an array... +$options = $this->options(); +``` ### Prompting for Input @@ -511,103 +565,129 @@ Options may be retrieved just as easily as arguments using the `option` method. In addition to displaying output, you may also ask the user to provide input during the execution of your command. The `ask` method will prompt the user with the given question, accept their input, and then return the user's input back to your command: - /** - * Execute the console command. - */ - public function handle(): void - { - $name = $this->ask('What is your name?'); +```php +/** + * Execute the console command. + */ +public function handle(): void +{ + $name = $this->ask('What is your name?'); - // ... - } + // ... +} +``` The `ask` method also accepts an optional second argument which specifies the default value that should be returned if no user input is provided: - $name = $this->ask('What is your name?', 'Taylor'); +```php +$name = $this->ask('What is your name?', 'Taylor'); +``` The `secret` method is similar to `ask`, but the user's input will not be visible to them as they type in the console. This method is useful when asking for sensitive information such as passwords: - $password = $this->secret('What is the password?'); +```php +$password = $this->secret('What is the password?'); +``` #### Asking for Confirmation If you need to ask the user for a simple "yes or no" confirmation, you may use the `confirm` method. By default, this method will return `false`. However, if the user enters `y` or `yes` in response to the prompt, the method will return `true`. - if ($this->confirm('Do you wish to continue?')) { - // ... - } +```php +if ($this->confirm('Do you wish to continue?')) { + // ... +} +``` If necessary, you may specify that the confirmation prompt should return `true` by default by passing `true` as the second argument to the `confirm` method: - if ($this->confirm('Do you wish to continue?', true)) { - // ... - } +```php +if ($this->confirm('Do you wish to continue?', true)) { + // ... +} +``` #### Auto-Completion The `anticipate` method can be used to provide auto-completion for possible choices. The user can still provide any answer, regardless of the auto-completion hints: - $name = $this->anticipate('What is your name?', ['Taylor', 'Dayle']); +```php +$name = $this->anticipate('What is your name?', ['Taylor', 'Dayle']); +``` Alternatively, you may pass a closure as the second argument to the `anticipate` method. The closure will be called each time the user types an input character. The closure should accept a string parameter containing the user's input so far, and return an array of options for auto-completion: - $name = $this->anticipate('What is your address?', function (string $input) { - // Return auto-completion options... - }); +```php +$name = $this->anticipate('What is your address?', function (string $input) { + // Return auto-completion options... +}); +``` #### Multiple Choice Questions If you need to give the user a predefined set of choices when asking a question, you may use the `choice` method. You may set the array index of the default value to be returned if no option is chosen by passing the index as the third argument to the method: - $name = $this->choice( - 'What is your name?', - ['Taylor', 'Dayle'], - $defaultIndex - ); +```php +$name = $this->choice( + 'What is your name?', + ['Taylor', 'Dayle'], + $defaultIndex +); +``` In addition, the `choice` method accepts optional fourth and fifth arguments for determining the maximum number of attempts to select a valid response and whether multiple selections are permitted: - $name = $this->choice( - 'What is your name?', - ['Taylor', 'Dayle'], - $defaultIndex, - $maxAttempts = null, - $allowMultipleSelections = false - ); +```php +$name = $this->choice( + 'What is your name?', + ['Taylor', 'Dayle'], + $defaultIndex, + $maxAttempts = null, + $allowMultipleSelections = false +); +``` ### Writing Output To send output to the console, you may use the `line`, `info`, `comment`, `question`, `warn`, and `error` methods. Each of these methods will use appropriate ANSI colors for their purpose. For example, let's display some general information to the user. Typically, the `info` method will display in the console as green colored text: - /** - * Execute the console command. - */ - public function handle(): void - { - // ... +```php +/** + * Execute the console command. + */ +public function handle(): void +{ + // ... - $this->info('The command was successful!'); - } + $this->info('The command was successful!'); +} +``` To display an error message, use the `error` method. Error message text is typically displayed in red: - $this->error('Something went wrong!'); +```php +$this->error('Something went wrong!'); +``` You may use the `line` method to display plain, uncolored text: - $this->line('Display this on the screen'); +```php +$this->line('Display this on the screen'); +``` You may use the `newLine` method to display a blank line: - // Write a single blank line... - $this->newLine(); +```php +// Write a single blank line... +$this->newLine(); - // Write three blank lines... - $this->newLine(3); +// Write three blank lines... +$this->newLine(3); +``` #### Tables @@ -615,39 +695,45 @@ You may use the `newLine` method to display a blank line: The `table` method makes it easy to correctly format multiple rows / columns of data. All you need to do is provide the column names and the data for the table and Laravel will automatically calculate the appropriate width and height of the table for you: - use App\Models\User; +```php +use App\Models\User; - $this->table( - ['Name', 'Email'], - User::all(['name', 'email'])->toArray() - ); +$this->table( + ['Name', 'Email'], + User::all(['name', 'email'])->toArray() +); +``` #### Progress Bars For long running tasks, it can be helpful to show a progress bar that informs users how complete the task is. Using the `withProgressBar` method, Laravel will display a progress bar and advance its progress for each iteration over a given iterable value: - use App\Models\User; +```php +use App\Models\User; - $users = $this->withProgressBar(User::all(), function (User $user) { - $this->performTask($user); - }); +$users = $this->withProgressBar(User::all(), function (User $user) { + $this->performTask($user); +}); +``` Sometimes, you may need more manual control over how a progress bar is advanced. First, define the total number of steps the process will iterate through. Then, advance the progress bar after processing each item: - $users = App\Models\User::all(); +```php +$users = App\Models\User::all(); - $bar = $this->output->createProgressBar(count($users)); +$bar = $this->output->createProgressBar(count($users)); - $bar->start(); +$bar->start(); - foreach ($users as $user) { - $this->performTask($user); +foreach ($users as $user) { + $this->performTask($user); - $bar->advance(); - } + $bar->advance(); +} - $bar->finish(); +$bar->finish(); +``` > [!NOTE] > For more advanced options, check out the [Symfony Progress Bar component documentation](https://symfony.com/doc/7.0/components/console/helpers/progressbar.html). @@ -657,17 +743,21 @@ Sometimes, you may need more manual control over how a progress bar is advanced. By default, Laravel automatically registers all commands within the `app/Console/Commands` directory. However, you can instruct Laravel to scan other directories for Artisan commands using the `withCommands` method in your application's `bootstrap/app.php` file: - ->withCommands([ - __DIR__.'/../app/Domain/Orders/Commands', - ]) +```php +->withCommands([ + __DIR__.'/../app/Domain/Orders/Commands', +]) +``` If necessary, you may also manually register commands by providing the command's class name to the `withCommands` method: - use App\Domain\Orders\Commands\SendEmails; +```php +use App\Domain\Orders\Commands\SendEmails; - ->withCommands([ - SendEmails::class, - ]) +->withCommands([ + SendEmails::class, +]) +``` When Artisan boots, all the commands in your application will be resolved by the [service container](/docs/{{version}}/container) and registered with Artisan. @@ -676,110 +766,130 @@ When Artisan boots, all the commands in your application will be resolved by the Sometimes you may wish to execute an Artisan command outside of the CLI. For example, you may wish to execute an Artisan command from a route or controller. You may use the `call` method on the `Artisan` facade to accomplish this. The `call` method accepts either the command's signature name or class name as its first argument, and an array of command parameters as the second argument. The exit code will be returned: - use Illuminate\Support\Facades\Artisan; +```php +use Illuminate\Support\Facades\Artisan; - Route::post('/user/{user}/mail', function (string $user) { - $exitCode = Artisan::call('mail:send', [ - 'user' => $user, '--queue' => 'default' - ]); +Route::post('/user/{user}/mail', function (string $user) { + $exitCode = Artisan::call('mail:send', [ + 'user' => $user, '--queue' => 'default' + ]); - // ... - }); + // ... +}); +``` Alternatively, you may pass the entire Artisan command to the `call` method as a string: - Artisan::call('mail:send 1 --queue=default'); +```php +Artisan::call('mail:send 1 --queue=default'); +``` #### Passing Array Values If your command defines an option that accepts an array, you may pass an array of values to that option: - use Illuminate\Support\Facades\Artisan; +```php +use Illuminate\Support\Facades\Artisan; - Route::post('/mail', function () { - $exitCode = Artisan::call('mail:send', [ - '--id' => [5, 13] - ]); - }); +Route::post('/mail', function () { + $exitCode = Artisan::call('mail:send', [ + '--id' => [5, 13] + ]); +}); +``` #### Passing Boolean Values If you need to specify the value of an option that does not accept string values, such as the `--force` flag on the `migrate:refresh` command, you should pass `true` or `false` as the value of the option: - $exitCode = Artisan::call('migrate:refresh', [ - '--force' => true, - ]); +```php +$exitCode = Artisan::call('migrate:refresh', [ + '--force' => true, +]); +``` #### Queueing Artisan Commands Using the `queue` method on the `Artisan` facade, you may even queue Artisan commands so they are processed in the background by your [queue workers](/docs/{{version}}/queues). Before using this method, make sure you have configured your queue and are running a queue listener: - use Illuminate\Support\Facades\Artisan; +```php +use Illuminate\Support\Facades\Artisan; - Route::post('/user/{user}/mail', function (string $user) { - Artisan::queue('mail:send', [ - 'user' => $user, '--queue' => 'default' - ]); +Route::post('/user/{user}/mail', function (string $user) { + Artisan::queue('mail:send', [ + 'user' => $user, '--queue' => 'default' + ]); - // ... - }); + // ... +}); +``` Using the `onConnection` and `onQueue` methods, you may specify the connection or queue the Artisan command should be dispatched to: - Artisan::queue('mail:send', [ - 'user' => 1, '--queue' => 'default' - ])->onConnection('redis')->onQueue('commands'); +```php +Artisan::queue('mail:send', [ + 'user' => 1, '--queue' => 'default' +])->onConnection('redis')->onQueue('commands'); +``` ### Calling Commands From Other Commands Sometimes you may wish to call other commands from an existing Artisan command. You may do so using the `call` method. This `call` method accepts the command name and an array of command arguments / options: - /** - * Execute the console command. - */ - public function handle(): void - { - $this->call('mail:send', [ - 'user' => 1, '--queue' => 'default' - ]); +```php +/** + * Execute the console command. + */ +public function handle(): void +{ + $this->call('mail:send', [ + 'user' => 1, '--queue' => 'default' + ]); - // ... - } + // ... +} +``` If you would like to call another console command and suppress all of its output, you may use the `callSilently` method. The `callSilently` method has the same signature as the `call` method: - $this->callSilently('mail:send', [ - 'user' => 1, '--queue' => 'default' - ]); +```php +$this->callSilently('mail:send', [ + 'user' => 1, '--queue' => 'default' +]); +``` ## Signal Handling As you may know, operating systems allow signals to be sent to running processes. For example, the `SIGTERM` signal is how operating systems ask a program to terminate. If you wish to listen for signals in your Artisan console commands and execute code when they occur, you may use the `trap` method: - /** - * Execute the console command. - */ - public function handle(): void - { - $this->trap(SIGTERM, fn () => $this->shouldKeepRunning = false); +```php +/** + * Execute the console command. + */ +public function handle(): void +{ + $this->trap(SIGTERM, fn () => $this->shouldKeepRunning = false); - while ($this->shouldKeepRunning) { - // ... - } + while ($this->shouldKeepRunning) { + // ... } +} +``` To listen for multiple signals at once, you may provide an array of signals to the `trap` method: - $this->trap([SIGTERM, SIGQUIT], function (int $signal) { - $this->shouldKeepRunning = false; +```php +$this->trap([SIGTERM, SIGQUIT], function (int $signal) { + $this->shouldKeepRunning = false; - dump($signal); // SIGTERM / SIGQUIT - }); + dump($signal); // SIGTERM / SIGQUIT +}); +``` ## Stub Customization diff --git a/authentication.md b/authentication.md index a16e00ac148..dd82403a01e 100644 --- a/authentication.md +++ b/authentication.md @@ -135,48 +135,54 @@ Laravel Breeze is a minimal, simple implementation of all of Laravel's authentic After installing an authentication starter kit and allowing users to register and authenticate with your application, you will often need to interact with the currently authenticated user. While handling an incoming request, you may access the authenticated user via the `Auth` facade's `user` method: - use Illuminate\Support\Facades\Auth; +```php +use Illuminate\Support\Facades\Auth; - // Retrieve the currently authenticated user... - $user = Auth::user(); +// Retrieve the currently authenticated user... +$user = Auth::user(); - // Retrieve the currently authenticated user's ID... - $id = Auth::id(); +// Retrieve the currently authenticated user's ID... +$id = Auth::id(); +``` Alternatively, once a user is authenticated, you may access the authenticated user via an `Illuminate\Http\Request` instance. Remember, type-hinted classes will automatically be injected into your controller methods. By type-hinting the `Illuminate\Http\Request` object, you may gain convenient access to the authenticated user from any controller method in your application via the request's `user` method: - user(); + $user = $request->user(); - // ... + // ... - return redirect('/flights'); - } + return redirect('/flights'); } +} +``` #### Determining if the Current User is Authenticated To determine if the user making the incoming HTTP request is authenticated, you may use the `check` method on the `Auth` facade. This method will return `true` if the user is authenticated: - use Illuminate\Support\Facades\Auth; +```php +use Illuminate\Support\Facades\Auth; - if (Auth::check()) { - // The user is logged in... - } +if (Auth::check()) { + // The user is logged in... +} +``` > [!NOTE] > Even though it is possible to determine if a user is authenticated using the `check` method, you will typically use a middleware to verify that the user is authenticated before allowing the user access to certain routes / controllers. To learn more about this, check out the documentation on [protecting routes](/docs/{{version}}/authentication#protecting-routes). @@ -186,32 +192,38 @@ To determine if the user making the incoming HTTP request is authenticated, you [Route middleware](/docs/{{version}}/middleware) can be used to only allow authenticated users to access a given route. Laravel ships with an `auth` middleware, which is a [middleware alias](/docs/{{version}}/middleware#middleware-aliases) for the `Illuminate\Auth\Middleware\Authenticate` class. Since this middleware is already aliased internally by Laravel, all you need to do is attach the middleware to a route definition: - Route::get('/flights', function () { - // Only authenticated users may access this route... - })->middleware('auth'); +```php +Route::get('/flights', function () { + // Only authenticated users may access this route... +})->middleware('auth'); +``` #### Redirecting Unauthenticated Users When the `auth` middleware detects an unauthenticated user, it will redirect the user to the `login` [named route](/docs/{{version}}/routing#named-routes). You may modify this behavior using the method `redirectGuestsTo` of your application's `bootstrap/app.php` file: - use Illuminate\Http\Request; +```php +use Illuminate\Http\Request; - ->withMiddleware(function (Middleware $middleware) { - $middleware->redirectGuestsTo('/login'); +->withMiddleware(function (Middleware $middleware) { + $middleware->redirectGuestsTo('/login'); - // Using a closure... - $middleware->redirectGuestsTo(fn (Request $request) => route('login')); - }) + // Using a closure... + $middleware->redirectGuestsTo(fn (Request $request) => route('login')); +}) +``` #### Specifying a Guard When attaching the `auth` middleware to a route, you may also specify which "guard" should be used to authenticate the user. The guard specified should correspond to one of the keys in the `guards` array of your `auth.php` configuration file: - Route::get('/flights', function () { - // Only authenticated users may access this route... - })->middleware('auth:admin'); +```php +Route::get('/flights', function () { + // Only authenticated users may access this route... +})->middleware('auth:admin'); +``` ### Login Throttling @@ -228,37 +240,39 @@ You are not required to use the authentication scaffolding included with Laravel We will access Laravel's authentication services via the `Auth` [facade](/docs/{{version}}/facades), so we'll need to make sure to import the `Auth` facade at the top of the class. Next, let's check out the `attempt` method. The `attempt` method is normally used to handle authentication attempts from your application's "login" form. If authentication is successful, you should regenerate the user's [session](/docs/{{version}}/session) to prevent [session fixation](https://en.wikipedia.org/wiki/Session_fixation): - validate([ - 'email' => ['required', 'email'], - 'password' => ['required'], - ]); - - if (Auth::attempt($credentials)) { - $request->session()->regenerate(); - - return redirect()->intended('dashboard'); - } - - return back()->withErrors([ - 'email' => 'The provided credentials do not match our records.', - ])->onlyInput('email'); + $credentials = $request->validate([ + 'email' => ['required', 'email'], + 'password' => ['required'], + ]); + + if (Auth::attempt($credentials)) { + $request->session()->regenerate(); + + return redirect()->intended('dashboard'); } + + return back()->withErrors([ + 'email' => 'The provided credentials do not match our records.', + ])->onlyInput('email'); } +} +``` The `attempt` method accepts an array of key / value pairs as its first argument. The values in the array will be used to find the user in your database table. So, in the example above, the user will be retrieved by the value of the `email` column. If the user is found, the hashed password stored in the database will be compared with the `password` value passed to the method via the array. You should not hash the incoming request's `password` value, since the framework will automatically hash the value before comparing it to the hashed password in the database. An authenticated session will be started for the user if the two hashed passwords match. @@ -273,35 +287,41 @@ The `intended` method provided by Laravel's redirector will redirect the user to If you wish, you may also add extra query conditions to the authentication query in addition to the user's email and password. To accomplish this, we may simply add the query conditions to the array passed to the `attempt` method. For example, we may verify that the user is marked as "active": - if (Auth::attempt(['email' => $email, 'password' => $password, 'active' => 1])) { - // Authentication was successful... - } +```php +if (Auth::attempt(['email' => $email, 'password' => $password, 'active' => 1])) { + // Authentication was successful... +} +``` For complex query conditions, you may provide a closure in your array of credentials. This closure will be invoked with the query instance, allowing you to customize the query based on your application's needs: - use Illuminate\Database\Eloquent\Builder; - - if (Auth::attempt([ - 'email' => $email, - 'password' => $password, - fn (Builder $query) => $query->has('activeSubscription'), - ])) { - // Authentication was successful... - } +```php +use Illuminate\Database\Eloquent\Builder; + +if (Auth::attempt([ + 'email' => $email, + 'password' => $password, + fn (Builder $query) => $query->has('activeSubscription'), +])) { + // Authentication was successful... +} +``` > [!WARNING] > In these examples, `email` is not a required option, it is merely used as an example. You should use whatever column name corresponds to a "username" in your database table. The `attemptWhen` method, which receives a closure as its second argument, may be used to perform more extensive inspection of the potential user before actually authenticating the user. The closure receives the potential user and should return `true` or `false` to indicate if the user may be authenticated: - if (Auth::attemptWhen([ - 'email' => $email, - 'password' => $password, - ], function (User $user) { - return $user->isNotBanned(); - })) { - // Authentication was successful... - } +```php +if (Auth::attemptWhen([ + 'email' => $email, + 'password' => $password, +], function (User $user) { + return $user->isNotBanned(); +})) { + // Authentication was successful... +} +``` #### Accessing Specific Guard Instances @@ -310,9 +330,11 @@ Via the `Auth` facade's `guard` method, you may specify which guard instance you The guard name passed to the `guard` method should correspond to one of the guards configured in your `auth.php` configuration file: - if (Auth::guard('admin')->attempt($credentials)) { - // ... - } +```php +if (Auth::guard('admin')->attempt($credentials)) { + // ... +} +``` ### Remembering Users @@ -321,19 +343,23 @@ Many web applications provide a "remember me" checkbox on their login form. If y When this value is `true`, Laravel will keep the user authenticated indefinitely or until they manually logout. Your `users` table must include the string `remember_token` column, which will be used to store the "remember me" token. The `users` table migration included with new Laravel applications already includes this column: - use Illuminate\Support\Facades\Auth; +```php +use Illuminate\Support\Facades\Auth; - if (Auth::attempt(['email' => $email, 'password' => $password], $remember)) { - // The user is being remembered... - } +if (Auth::attempt(['email' => $email, 'password' => $password], $remember)) { + // The user is being remembered... +} +``` If your application offers "remember me" functionality, you may use the `viaRemember` method to determine if the currently authenticated user was authenticated using the "remember me" cookie: - use Illuminate\Support\Facades\Auth; +```php +use Illuminate\Support\Facades\Auth; - if (Auth::viaRemember()) { - // ... - } +if (Auth::viaRemember()) { + // ... +} +``` ### Other Authentication Methods @@ -343,46 +369,60 @@ If your application offers "remember me" functionality, you may use the `viaReme If you need to set an existing user instance as the currently authenticated user, you may pass the user instance to the `Auth` facade's `login` method. The given user instance must be an implementation of the `Illuminate\Contracts\Auth\Authenticatable` [contract](/docs/{{version}}/contracts). The `App\Models\User` model included with Laravel already implements this interface. This method of authentication is useful when you already have a valid user instance, such as directly after a user registers with your application: - use Illuminate\Support\Facades\Auth; +```php +use Illuminate\Support\Facades\Auth; - Auth::login($user); +Auth::login($user); +``` You may pass a boolean value as the second argument to the `login` method. This value indicates if "remember me" functionality is desired for the authenticated session. Remember, this means that the session will be authenticated indefinitely or until the user manually logs out of the application: - Auth::login($user, $remember = true); +```php +Auth::login($user, $remember = true); +``` If needed, you may specify an authentication guard before calling the `login` method: - Auth::guard('admin')->login($user); +```php +Auth::guard('admin')->login($user); +``` #### Authenticate a User by ID To authenticate a user using their database record's primary key, you may use the `loginUsingId` method. This method accepts the primary key of the user you wish to authenticate: - Auth::loginUsingId(1); +```php +Auth::loginUsingId(1); +``` You may pass a boolean value to the `remember` argument of the `loginUsingId` method. This value indicates if "remember me" functionality is desired for the authenticated session. Remember, this means that the session will be authenticated indefinitely or until the user manually logs out of the application: - Auth::loginUsingId(1, remember: true); +```php +Auth::loginUsingId(1, remember: true); +``` #### Authenticate a User Once You may use the `once` method to authenticate a user with the application for a single request. No sessions or cookies will be utilized when calling this method: - if (Auth::once($credentials)) { - // ... - } +```php +if (Auth::once($credentials)) { + // ... +} +``` ## HTTP Basic Authentication [HTTP Basic Authentication](https://en.wikipedia.org/wiki/Basic_access_authentication) provides a quick way to authenticate users of your application without setting up a dedicated "login" page. To get started, attach the `auth.basic` [middleware](/docs/{{version}}/middleware) to a route. The `auth.basic` middleware is included with the Laravel framework, so you do not need to define it: - Route::get('/profile', function () { - // Only authenticated users may access this route... - })->middleware('auth.basic'); +```php +Route::get('/profile', function () { + // Only authenticated users may access this route... +})->middleware('auth.basic'); +``` Once the middleware has been attached to the route, you will automatically be prompted for credentials when accessing the route in your browser. By default, the `auth.basic` middleware will assume the `email` column on your `users` database table is the user's "username". @@ -401,34 +441,38 @@ RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}] You may also use HTTP Basic Authentication without setting a user identifier cookie in the session. This is primarily helpful if you choose to use HTTP Authentication to authenticate requests to your application's API. To accomplish this, [define a middleware](/docs/{{version}}/middleware) that calls the `onceBasic` method. If no response is returned by the `onceBasic` method, the request may be passed further into the application: - middleware(AuthenticateOnceWithBasicAuth::class); +```php +Route::get('/api/user', function () { + // Only authenticated users may access this route... +})->middleware(AuthenticateOnceWithBasicAuth::class); +``` ## Logging Out @@ -437,23 +481,25 @@ To manually log users out of your application, you may use the `logout` method p In addition to calling the `logout` method, it is recommended that you invalidate the user's session and regenerate their [CSRF token](/docs/{{version}}/csrf). After logging the user out, you would typically redirect the user to the root of your application: - use Illuminate\Http\Request; - use Illuminate\Http\RedirectResponse; - use Illuminate\Support\Facades\Auth; +```php +use Illuminate\Http\Request; +use Illuminate\Http\RedirectResponse; +use Illuminate\Support\Facades\Auth; - /** - * Log the user out of the application. - */ - public function logout(Request $request): RedirectResponse - { - Auth::logout(); +/** + * Log the user out of the application. + */ +public function logout(Request $request): RedirectResponse +{ + Auth::logout(); - $request->session()->invalidate(); + $request->session()->invalidate(); - $request->session()->regenerateToken(); + $request->session()->regenerateToken(); - return redirect('/'); - } + return redirect('/'); +} +``` ### Invalidating Sessions on Other Devices @@ -462,17 +508,21 @@ Laravel also provides a mechanism for invalidating and "logging out" a user's se Before getting started, you should make sure that the `Illuminate\Session\Middleware\AuthenticateSession` middleware is included on the routes that should receive session authentication. Typically, you should place this middleware on a route group definition so that it can be applied to the majority of your application's routes. By default, the `AuthenticateSession` middleware may be attached to a route using the `auth.session` [middleware alias](/docs/{{version}}/middleware#middleware-aliases): - Route::middleware(['auth', 'auth.session'])->group(function () { - Route::get('/', function () { - // ... - }); +```php +Route::middleware(['auth', 'auth.session'])->group(function () { + Route::get('/', function () { + // ... }); +}); +``` Then, you may use the `logoutOtherDevices` method provided by the `Auth` facade. This method requires the user to confirm their current password, which your application should accept through an input form: - use Illuminate\Support\Facades\Auth; +```php +use Illuminate\Support\Facades\Auth; - Auth::logoutOtherDevices($currentPassword); +Auth::logoutOtherDevices($currentPassword); +``` When the `logoutOtherDevices` method is invoked, the user's other sessions will be invalidated entirely, meaning they will be "logged out" of all guards they were previously authenticated by. @@ -497,9 +547,11 @@ After confirming their password, a user will not be asked to confirm their passw First, we will define a route to display a view that requests the user to confirm their password: - Route::get('/confirm-password', function () { - return view('auth.confirm-password'); - })->middleware('auth')->name('password.confirm'); +```php +Route::get('/confirm-password', function () { + return view('auth.confirm-password'); +})->middleware('auth')->name('password.confirm'); +``` As you might expect, the view that is returned by this route should have a form containing a `password` field. In addition, feel free to include text within the view that explains that the user is entering a protected area of the application and must confirm their password. @@ -508,21 +560,23 @@ As you might expect, the view that is returned by this route should have a form Next, we will define a route that will handle the form request from the "confirm password" view. This route will be responsible for validating the password and redirecting the user to their intended destination: - use Illuminate\Http\Request; - use Illuminate\Support\Facades\Hash; - use Illuminate\Support\Facades\Redirect; - - Route::post('/confirm-password', function (Request $request) { - if (! Hash::check($request->password, $request->user()->password)) { - return back()->withErrors([ - 'password' => ['The provided password does not match our records.'] - ]); - } +```php +use Illuminate\Http\Request; +use Illuminate\Support\Facades\Hash; +use Illuminate\Support\Facades\Redirect; + +Route::post('/confirm-password', function (Request $request) { + if (! Hash::check($request->password, $request->user()->password)) { + return back()->withErrors([ + 'password' => ['The provided password does not match our records.'] + ]); + } - $request->session()->passwordConfirmed(); + $request->session()->passwordConfirmed(); - return redirect()->intended(); - })->middleware(['auth', 'throttle:6,1']); + return redirect()->intended(); +})->middleware(['auth', 'throttle:6,1']); +``` Before moving on, let's examine this route in more detail. First, the request's `password` field is determined to actually match the authenticated user's password. If the password is valid, we need to inform Laravel's session that the user has confirmed their password. The `passwordConfirmed` method will set a timestamp in the user's session that Laravel can use to determine when the user last confirmed their password. Finally, we can redirect the user to their intended destination. @@ -531,53 +585,59 @@ Before moving on, let's examine this route in more detail. First, the request's You should ensure that any route that performs an action which requires recent password confirmation is assigned the `password.confirm` middleware. This middleware is included with the default installation of Laravel and will automatically store the user's intended destination in the session so that the user may be redirected to that location after confirming their password. After storing the user's intended destination in the session, the middleware will redirect the user to the `password.confirm` [named route](/docs/{{version}}/routing#named-routes): - Route::get('/settings', function () { - // ... - })->middleware(['password.confirm']); +```php +Route::get('/settings', function () { + // ... +})->middleware(['password.confirm']); - Route::post('/settings', function () { - // ... - })->middleware(['password.confirm']); +Route::post('/settings', function () { + // ... +})->middleware(['password.confirm']); +``` ## Adding Custom Guards You may define your own authentication guards using the `extend` method on the `Auth` facade. You should place your call to the `extend` method within a [service provider](/docs/{{version}}/providers). Since Laravel already ships with an `AppServiceProvider`, we can place the code in that provider: - [ - 'api' => [ - 'driver' => 'jwt', - 'provider' => 'users', - ], +```php +'guards' => [ + 'api' => [ + 'driver' => 'jwt', + 'provider' => 'users', ], +], +``` ### Closure Request Guards @@ -586,81 +646,93 @@ The simplest way to implement a custom, HTTP request based authentication system To get started, call the `Auth::viaRequest` method within the `boot` method of your application's `AppServiceProvider`. The `viaRequest` method accepts an authentication driver name as its first argument. This name can be any string that describes your custom guard. The second argument passed to the method should be a closure that receives the incoming HTTP request and returns a user instance or, if authentication fails, `null`: - use App\Models\User; - use Illuminate\Http\Request; - use Illuminate\Support\Facades\Auth; - - /** - * Bootstrap any application services. - */ - public function boot(): void - { - Auth::viaRequest('custom-token', function (Request $request) { - return User::where('token', (string) $request->token)->first(); - }); - } +```php +use App\Models\User; +use Illuminate\Http\Request; +use Illuminate\Support\Facades\Auth; + +/** + * Bootstrap any application services. + */ +public function boot(): void +{ + Auth::viaRequest('custom-token', function (Request $request) { + return User::where('token', (string) $request->token)->first(); + }); +} +``` Once your custom authentication driver has been defined, you may configure it as a driver within the `guards` configuration of your `auth.php` configuration file: - 'guards' => [ - 'api' => [ - 'driver' => 'custom-token', - ], +```php +'guards' => [ + 'api' => [ + 'driver' => 'custom-token', ], +], +``` Finally, you may reference the guard when assigning the authentication middleware to a route: - Route::middleware('auth:api')->group(function () { - // ... - }); +```php +Route::middleware('auth:api')->group(function () { + // ... +}); +``` ## Adding Custom User Providers If you are not using a traditional relational database to store your users, you will need to extend Laravel with your own authentication user provider. We will use the `provider` method on the `Auth` facade to define a custom user provider. The user provider resolver should return an implementation of `Illuminate\Contracts\Auth\UserProvider`: - make('mongo.connection')); - }); - } + return new MongoUserProvider($app->make('mongo.connection')); + }); } +} +``` After you have registered the provider using the `provider` method, you may switch to the new user provider in your `auth.php` configuration file. First, define a `provider` that uses your new driver: - 'providers' => [ - 'users' => [ - 'driver' => 'mongo', - ], +```php +'providers' => [ + 'users' => [ + 'driver' => 'mongo', ], +], +``` Finally, you may reference this provider in your `guards` configuration: - 'guards' => [ - 'web' => [ - 'driver' => 'session', - 'provider' => 'users', - ], +```php +'guards' => [ + 'web' => [ + 'driver' => 'session', + 'provider' => 'users', ], +], +``` ### The User Provider Contract @@ -669,19 +741,21 @@ Finally, you may reference this provider in your `guards` configuration: Let's take a look at the `Illuminate\Contracts\Auth\UserProvider` contract: - id === $post->user_id; - }); - } +/** + * Bootstrap any application services. + */ +public function boot(): void +{ + Gate::define('update-post', function (User $user, Post $post) { + return $user->id === $post->user_id; + }); +} +``` Like controllers, gates may also be defined using a class callback array: - use App\Policies\PostPolicy; - use Illuminate\Support\Facades\Gate; +```php +use App\Policies\PostPolicy; +use Illuminate\Support\Facades\Gate; - /** - * Bootstrap any application services. - */ - public function boot(): void - { - Gate::define('update-post', [PostPolicy::class, 'update']); - } +/** + * Bootstrap any application services. + */ +public function boot(): void +{ + Gate::define('update-post', [PostPolicy::class, 'update']); +} +``` ### Authorizing Actions To authorize an action using gates, you should use the `allows` or `denies` methods provided by the `Gate` facade. Note that you are not required to pass the currently authenticated user to these methods. Laravel will automatically take care of passing the user into the gate closure. It is typical to call the gate authorization methods within your application's controllers before performing an action that requires authorization: - allows('update-post', $post)) { - // The user can update the post... - } +```php +if (Gate::forUser($user)->allows('update-post', $post)) { + // The user can update the post... +} - if (Gate::forUser($user)->denies('update-post', $post)) { - // The user can't update the post... - } +if (Gate::forUser($user)->denies('update-post', $post)) { + // The user can't update the post... +} +``` You may authorize multiple actions at a time using the `any` or `none` methods: - if (Gate::any(['update-post', 'delete-post'], $post)) { - // The user can update or delete the post... - } +```php +if (Gate::any(['update-post', 'delete-post'], $post)) { + // The user can update or delete the post... +} - if (Gate::none(['update-post', 'delete-post'], $post)) { - // The user can't update or delete the post... - } +if (Gate::none(['update-post', 'delete-post'], $post)) { + // The user can't update or delete the post... +} +``` #### Authorizing or Throwing Exceptions If you would like to attempt to authorize an action and automatically throw an `Illuminate\Auth\Access\AuthorizationException` if the user is not allowed to perform the given action, you may use the `Gate` facade's `authorize` method. Instances of `AuthorizationException` are automatically converted to a 403 HTTP response by Laravel: - Gate::authorize('update-post', $post); +```php +Gate::authorize('update-post', $post); - // The action is authorized... +// The action is authorized... +``` #### Supplying Additional Context The gate methods for authorizing abilities (`allows`, `denies`, `check`, `any`, `none`, `authorize`, `can`, `cannot`) and the authorization [Blade directives](#via-blade-templates) (`@can`, `@cannot`, `@canany`) can receive an array as their second argument. These array elements are passed as parameters to the gate closure, and can be used for additional context when making authorization decisions: - use App\Models\Category; - use App\Models\User; - use Illuminate\Support\Facades\Gate; +```php +use App\Models\Category; +use App\Models\User; +use Illuminate\Support\Facades\Gate; - Gate::define('create-post', function (User $user, Category $category, bool $pinned) { - if (! $user->canPublishToGroup($category->group)) { - return false; - } elseif ($pinned && ! $user->canPinPosts()) { - return false; - } +Gate::define('create-post', function (User $user, Category $category, bool $pinned) { + if (! $user->canPublishToGroup($category->group)) { + return false; + } elseif ($pinned && ! $user->canPinPosts()) { + return false; + } - return true; - }); + return true; +}); - if (Gate::check('create-post', [$category, $pinned])) { - // The user can create the post... - } +if (Gate::check('create-post', [$category, $pinned])) { + // The user can create the post... +} +``` ### Gate Responses So far, we have only examined gates that return simple boolean values. However, sometimes you may wish to return a more detailed response, including an error message. To do so, you may return an `Illuminate\Auth\Access\Response` from your gate: - use App\Models\User; - use Illuminate\Auth\Access\Response; - use Illuminate\Support\Facades\Gate; +```php +use App\Models\User; +use Illuminate\Auth\Access\Response; +use Illuminate\Support\Facades\Gate; - Gate::define('edit-settings', function (User $user) { - return $user->isAdmin - ? Response::allow() - : Response::deny('You must be an administrator.'); - }); +Gate::define('edit-settings', function (User $user) { + return $user->isAdmin + ? Response::allow() + : Response::deny('You must be an administrator.'); +}); +``` Even when you return an authorization response from your gate, the `Gate::allows` method will still return a simple boolean value; however, you may use the `Gate::inspect` method to get the full authorization response returned by the gate: - $response = Gate::inspect('edit-settings'); +```php +$response = Gate::inspect('edit-settings'); - if ($response->allowed()) { - // The action is authorized... - } else { - echo $response->message(); - } +if ($response->allowed()) { + // The action is authorized... +} else { + echo $response->message(); +} +``` When using the `Gate::authorize` method, which throws an `AuthorizationException` if the action is not authorized, the error message provided by the authorization response will be propagated to the HTTP response: - Gate::authorize('edit-settings'); +```php +Gate::authorize('edit-settings'); - // The action is authorized... +// The action is authorized... +``` #### Customizing The HTTP Response Status When an action is denied via a Gate, a `403` HTTP response is returned; however, it can sometimes be useful to return an alternative HTTP status code. You may customize the HTTP status code returned for a failed authorization check using the `denyWithStatus` static constructor on the `Illuminate\Auth\Access\Response` class: - use App\Models\User; - use Illuminate\Auth\Access\Response; - use Illuminate\Support\Facades\Gate; +```php +use App\Models\User; +use Illuminate\Auth\Access\Response; +use Illuminate\Support\Facades\Gate; - Gate::define('edit-settings', function (User $user) { - return $user->isAdmin - ? Response::allow() - : Response::denyWithStatus(404); - }); +Gate::define('edit-settings', function (User $user) { + return $user->isAdmin + ? Response::allow() + : Response::denyWithStatus(404); +}); +``` Because hiding resources via a `404` response is such a common pattern for web applications, the `denyAsNotFound` method is offered for convenience: - use App\Models\User; - use Illuminate\Auth\Access\Response; - use Illuminate\Support\Facades\Gate; +```php +use App\Models\User; +use Illuminate\Auth\Access\Response; +use Illuminate\Support\Facades\Gate; - Gate::define('edit-settings', function (User $user) { - return $user->isAdmin - ? Response::allow() - : Response::denyAsNotFound(); - }); +Gate::define('edit-settings', function (User $user) { + return $user->isAdmin + ? Response::allow() + : Response::denyAsNotFound(); +}); +``` ### Intercepting Gate Checks Sometimes, you may wish to grant all abilities to a specific user. You may use the `before` method to define a closure that is run before all other authorization checks: - use App\Models\User; - use Illuminate\Support\Facades\Gate; +```php +use App\Models\User; +use Illuminate\Support\Facades\Gate; - Gate::before(function (User $user, string $ability) { - if ($user->isAdministrator()) { - return true; - } - }); +Gate::before(function (User $user, string $ability) { + if ($user->isAdministrator()) { + return true; + } +}); +``` If the `before` closure returns a non-null result that result will be considered the result of the authorization check. You may use the `after` method to define a closure to be executed after all other authorization checks: - use App\Models\User; +```php +use App\Models\User; - Gate::after(function (User $user, string $ability, bool|null $result, mixed $arguments) { - if ($user->isAdministrator()) { - return true; - } - }); +Gate::after(function (User $user, string $ability, bool|null $result, mixed $arguments) { + if ($user->isAdministrator()) { + return true; + } +}); +``` Values returned by `after` closures will not override the result of the authorization check unless the gate or policy returned `null`. @@ -289,28 +317,32 @@ By default, Laravel automatically discover policies as long as the model and pol If you would like to define your own policy discovery logic, you may register a custom policy discovery callback using the `Gate::guessPolicyNamesUsing` method. Typically, this method should be called from the `boot` method of your application's `AppServiceProvider`: - use Illuminate\Support\Facades\Gate; +```php +use Illuminate\Support\Facades\Gate; - Gate::guessPolicyNamesUsing(function (string $modelClass) { - // Return the name of the policy class for the given model... - }); +Gate::guessPolicyNamesUsing(function (string $modelClass) { + // Return the name of the policy class for the given model... +}); +``` #### Manually Registering Policies Using the `Gate` facade, you may manually register policies and their corresponding models within the `boot` method of your application's `AppServiceProvider`: - use App\Models\Order; - use App\Policies\OrderPolicy; - use Illuminate\Support\Facades\Gate; +```php +use App\Models\Order; +use App\Policies\OrderPolicy; +use Illuminate\Support\Facades\Gate; - /** - * Bootstrap any application services. - */ - public function boot(): void - { - Gate::policy(Order::class, OrderPolicy::class); - } +/** + * Bootstrap any application services. + */ +public function boot(): void +{ + Gate::policy(Order::class, OrderPolicy::class); +} +``` ## Writing Policies @@ -322,23 +354,25 @@ Once the policy class has been registered, you may add methods for each action i The `update` method will receive a `User` and a `Post` instance as its arguments, and should return `true` or `false` indicating whether the user is authorized to update the given `Post`. So, in this example, we will verify that the user's `id` matches the `user_id` on the post: - id === $post->user_id; - } + return $user->id === $post->user_id; } +} +``` You may continue to define additional methods on the policy as needed for the various actions it authorizes. For example, you might define `view` or `delete` methods to authorize various `Post` related actions, but remember you are free to give your policy methods any name you like. @@ -352,128 +386,144 @@ If you used the `--model` option when generating your policy via the Artisan con So far, we have only examined policy methods that return simple boolean values. However, sometimes you may wish to return a more detailed response, including an error message. To do so, you may return an `Illuminate\Auth\Access\Response` instance from your policy method: - use App\Models\Post; - use App\Models\User; - use Illuminate\Auth\Access\Response; +```php +use App\Models\Post; +use App\Models\User; +use Illuminate\Auth\Access\Response; - /** - * Determine if the given post can be updated by the user. - */ - public function update(User $user, Post $post): Response - { - return $user->id === $post->user_id - ? Response::allow() - : Response::deny('You do not own this post.'); - } +/** + * Determine if the given post can be updated by the user. + */ +public function update(User $user, Post $post): Response +{ + return $user->id === $post->user_id + ? Response::allow() + : Response::deny('You do not own this post.'); +} +``` When returning an authorization response from your policy, the `Gate::allows` method will still return a simple boolean value; however, you may use the `Gate::inspect` method to get the full authorization response returned by the gate: - use Illuminate\Support\Facades\Gate; +```php +use Illuminate\Support\Facades\Gate; - $response = Gate::inspect('update', $post); +$response = Gate::inspect('update', $post); - if ($response->allowed()) { - // The action is authorized... - } else { - echo $response->message(); - } +if ($response->allowed()) { + // The action is authorized... +} else { + echo $response->message(); +} +``` When using the `Gate::authorize` method, which throws an `AuthorizationException` if the action is not authorized, the error message provided by the authorization response will be propagated to the HTTP response: - Gate::authorize('update', $post); +```php +Gate::authorize('update', $post); - // The action is authorized... +// The action is authorized... +``` #### Customizing the HTTP Response Status When an action is denied via a policy method, a `403` HTTP response is returned; however, it can sometimes be useful to return an alternative HTTP status code. You may customize the HTTP status code returned for a failed authorization check using the `denyWithStatus` static constructor on the `Illuminate\Auth\Access\Response` class: - use App\Models\Post; - use App\Models\User; - use Illuminate\Auth\Access\Response; +```php +use App\Models\Post; +use App\Models\User; +use Illuminate\Auth\Access\Response; - /** - * Determine if the given post can be updated by the user. - */ - public function update(User $user, Post $post): Response - { - return $user->id === $post->user_id - ? Response::allow() - : Response::denyWithStatus(404); - } +/** + * Determine if the given post can be updated by the user. + */ +public function update(User $user, Post $post): Response +{ + return $user->id === $post->user_id + ? Response::allow() + : Response::denyWithStatus(404); +} +``` Because hiding resources via a `404` response is such a common pattern for web applications, the `denyAsNotFound` method is offered for convenience: - use App\Models\Post; - use App\Models\User; - use Illuminate\Auth\Access\Response; +```php +use App\Models\Post; +use App\Models\User; +use Illuminate\Auth\Access\Response; - /** - * Determine if the given post can be updated by the user. - */ - public function update(User $user, Post $post): Response - { - return $user->id === $post->user_id - ? Response::allow() - : Response::denyAsNotFound(); - } +/** + * Determine if the given post can be updated by the user. + */ +public function update(User $user, Post $post): Response +{ + return $user->id === $post->user_id + ? Response::allow() + : Response::denyAsNotFound(); +} +``` ### Methods Without Models Some policy methods only receive an instance of the currently authenticated user. This situation is most common when authorizing `create` actions. For example, if you are creating a blog, you may wish to determine if a user is authorized to create any posts at all. In these situations, your policy method should only expect to receive a user instance: - /** - * Determine if the given user can create posts. - */ - public function create(User $user): bool - { - return $user->role == 'writer'; - } +```php +/** + * Determine if the given user can create posts. + */ +public function create(User $user): bool +{ + return $user->role == 'writer'; +} +``` ### Guest Users By default, all gates and policies automatically return `false` if the incoming HTTP request was not initiated by an authenticated user. However, you may allow these authorization checks to pass through to your gates and policies by declaring an "optional" type-hint or supplying a `null` default value for the user argument definition: - id === $post->user_id; - } + return $user?->id === $post->user_id; } +} +``` ### Policy Filters For certain users, you may wish to authorize all actions within a given policy. To accomplish this, define a `before` method on the policy. The `before` method will be executed before any other methods on the policy, giving you an opportunity to authorize the action before the intended policy method is actually called. This feature is most commonly used for authorizing application administrators to perform any action: - use App\Models\User; - - /** - * Perform pre-authorization checks. - */ - public function before(User $user, string $ability): bool|null - { - if ($user->isAdministrator()) { - return true; - } +```php +use App\Models\User; - return null; +/** + * Perform pre-authorization checks. + */ +public function before(User $user, string $ability): bool|null +{ + if ($user->isAdministrator()) { + return true; } + return null; +} +``` + If you would like to deny all authorization checks for a particular type of user then you may return `false` from the `before` method. If `null` is returned, the authorization check will fall through to the policy method. > [!WARNING] @@ -487,31 +537,33 @@ If you would like to deny all authorization checks for a particular type of user The `App\Models\User` model that is included with your Laravel application includes two helpful methods for authorizing actions: `can` and `cannot`. The `can` and `cannot` methods receive the name of the action you wish to authorize and the relevant model. For example, let's determine if a user is authorized to update a given `App\Models\Post` model. Typically, this will be done within a controller method: - user()->cannot('update', $post)) { - abort(403); - } - - // Update the post... - - return redirect('/posts'); + if ($request->user()->cannot('update', $post)) { + abort(403); } + + // Update the post... + + return redirect('/posts'); } +} +``` If a [policy is registered](#registering-policies) for the given model, the `can` method will automatically call the appropriate policy and return the boolean result. If no policy is registered for the model, the `can` method will attempt to call the closure-based Gate matching the given action name. @@ -520,31 +572,33 @@ If a [policy is registered](#registering-policies) for the given model, the `can Remember, some actions may correspond to policy methods like `create` that do not require a model instance. In these situations, you may pass a class name to the `can` method. The class name will be used to determine which policy to use when authorizing the action: - user()->cannot('create', Post::class)) { - abort(403); - } - - // Create the post... - - return redirect('/posts'); + if ($request->user()->cannot('create', Post::class)) { + abort(403); } + + // Create the post... + + return redirect('/posts'); } +} +``` ### Via the `Gate` Facade @@ -553,94 +607,106 @@ In addition to helpful methods provided to the `App\Models\User` model, you can Like the `can` method, this method accepts the name of the action you wish to authorize and the relevant model. If the action is not authorized, the `authorize` method will throw an `Illuminate\Auth\Access\AuthorizationException` exception which the Laravel exception handler will automatically convert to an HTTP response with a 403 status code: - #### Actions That Don't Require Models As previously discussed, some policy methods like `create` do not require a model instance. In these situations, you should pass a class name to the `authorize` method. The class name will be used to determine which policy to use when authorizing the action: - use App\Models\Post; - use Illuminate\Http\RedirectResponse; - use Illuminate\Http\Request; - use Illuminate\Support\Facades\Gate; +```php +use App\Models\Post; +use Illuminate\Http\RedirectResponse; +use Illuminate\Http\Request; +use Illuminate\Support\Facades\Gate; - /** - * Create a new blog post. - * - * @throws \Illuminate\Auth\Access\AuthorizationException - */ - public function create(Request $request): RedirectResponse - { - Gate::authorize('create', Post::class); +/** + * Create a new blog post. + * + * @throws \Illuminate\Auth\Access\AuthorizationException + */ +public function create(Request $request): RedirectResponse +{ + Gate::authorize('create', Post::class); - // The current user can create blog posts... + // The current user can create blog posts... - return redirect('/posts'); - } + return redirect('/posts'); +} +``` ### Via Middleware Laravel includes a middleware that can authorize actions before the incoming request even reaches your routes or controllers. By default, the `Illuminate\Auth\Middleware\Authorize` middleware may be attached to a route using the `can` [middleware alias](/docs/{{version}}/middleware#middleware-aliases), which is automatically registered by Laravel. Let's explore an example of using the `can` middleware to authorize that a user can update a post: - use App\Models\Post; +```php +use App\Models\Post; - Route::put('/post/{post}', function (Post $post) { - // The current user may update the post... - })->middleware('can:update,post'); +Route::put('/post/{post}', function (Post $post) { + // The current user may update the post... +})->middleware('can:update,post'); +``` In this example, we're passing the `can` middleware two arguments. The first is the name of the action we wish to authorize and the second is the route parameter we wish to pass to the policy method. In this case, since we are using [implicit model binding](/docs/{{version}}/routing#implicit-binding), an `App\Models\Post` model will be passed to the policy method. If the user is not authorized to perform the given action, an HTTP response with a 403 status code will be returned by the middleware. For convenience, you may also attach the `can` middleware to your route using the `can` method: - use App\Models\Post; +```php +use App\Models\Post; - Route::put('/post/{post}', function (Post $post) { - // The current user may update the post... - })->can('update', 'post'); +Route::put('/post/{post}', function (Post $post) { + // The current user may update the post... +})->can('update', 'post'); +``` #### Actions That Don't Require Models Again, some policy methods like `create` do not require a model instance. In these situations, you may pass a class name to the middleware. The class name will be used to determine which policy to use when authorizing the action: - Route::post('/post', function () { - // The current user may create posts... - })->middleware('can:create,App\Models\Post'); +```php +Route::post('/post', function () { + // The current user may create posts... +})->middleware('can:create,App\Models\Post'); +``` Specifying the entire class name within a string middleware definition can become cumbersome. For that reason, you may choose to attach the `can` middleware to your route using the `can` method: - use App\Models\Post; +```php +use App\Models\Post; - Route::post('/post', function () { - // The current user may create posts... - })->can('create', Post::class); +Route::post('/post', function () { + // The current user may create posts... +})->can('create', Post::class); +``` ### Via Blade Templates @@ -705,30 +771,34 @@ Like most of the other authorization methods, you may pass a class name to the ` When authorizing actions using policies, you may pass an array as the second argument to the various authorization functions and helpers. The first element in the array will be used to determine which policy should be invoked, while the rest of the array elements are passed as parameters to the policy method and can be used for additional context when making authorization decisions. For example, consider the following `PostPolicy` method definition which contains an additional `$category` parameter: - /** - * Determine if the given post can be updated by the user. - */ - public function update(User $user, Post $post, int $category): bool - { - return $user->id === $post->user_id && - $user->canUpdateCategory($category); - } +```php +/** + * Determine if the given post can be updated by the user. + */ +public function update(User $user, Post $post, int $category): bool +{ + return $user->id === $post->user_id && + $user->canUpdateCategory($category); +} +``` When attempting to determine if the authenticated user can update a given post, we can invoke this policy method like so: - /** - * Update the given blog post. - * - * @throws \Illuminate\Auth\Access\AuthorizationException - */ - public function update(Request $request, Post $post): RedirectResponse - { - Gate::authorize('update', [$post, $request->category]); +```php +/** + * Update the given blog post. + * + * @throws \Illuminate\Auth\Access\AuthorizationException + */ +public function update(Request $request, Post $post): RedirectResponse +{ + Gate::authorize('update', [$post, $request->category]); - // The current user can update the blog post... + // The current user can update the blog post... - return redirect('/posts'); - } + return redirect('/posts'); +} +``` ## Authorization & Inertia diff --git a/billing.md b/billing.md index 7f457c9d4c4..162272322d3 100644 --- a/billing.md +++ b/billing.md @@ -126,25 +126,29 @@ Lastly, to ensure Cashier properly handles all Stripe events, remember to [confi Before using Cashier, add the `Billable` trait to your billable model definition. Typically, this will be the `App\Models\User` model. This trait provides various methods to allow you to perform common billing tasks, such as creating subscriptions, applying coupons, and updating payment method information: - use Laravel\Cashier\Billable; +```php +use Laravel\Cashier\Billable; - class User extends Authenticatable - { - use Billable; - } +class User extends Authenticatable +{ + use Billable; +} +``` Cashier assumes your billable model will be the `App\Models\User` class that ships with Laravel. If you wish to change this you may specify a different model via the `useCustomerModel` method. This method should typically be called in the `boot` method of your `AppServiceProvider` class: - use App\Models\Cashier\User; - use Laravel\Cashier\Cashier; - - /** - * Bootstrap any application services. - */ - public function boot(): void - { - Cashier::useCustomerModel(User::class); - } +```php +use App\Models\Cashier\User; +use Laravel\Cashier\Cashier; + +/** + * Bootstrap any application services. + */ +public function boot(): void +{ + Cashier::useCustomerModel(User::class); +} +``` > [!WARNING] > If you're using a model other than Laravel's supplied `App\Models\User` model, you'll need to publish and alter the [Cashier migrations](#installation) provided to match your alternative model's table name. @@ -186,15 +190,17 @@ CASHIER_CURRENCY_LOCALE=nl_BE Thanks to [Stripe Tax](https://stripe.com/tax), it's possible to automatically calculate taxes for all invoices generated by Stripe. You can enable automatic tax calculation by invoking the `calculateTaxes` method in the `boot` method of your application's `App\Providers\AppServiceProvider` class: - use Laravel\Cashier\Cashier; +```php +use Laravel\Cashier\Cashier; - /** - * Bootstrap any application services. - */ - public function boot(): void - { - Cashier::calculateTaxes(); - } +/** + * Bootstrap any application services. + */ +public function boot(): void +{ + Cashier::calculateTaxes(); +} +``` Once tax calculation has been enabled, any new subscriptions and any one-off invoices that are generated will receive automatic tax calculation. @@ -216,26 +222,30 @@ Exceptions that are generated by API calls to Stripe will be logged through your You are free to extend the models used internally by Cashier by defining your own model and extending the corresponding Cashier model: - use Laravel\Cashier\Subscription as CashierSubscription; +```php +use Laravel\Cashier\Subscription as CashierSubscription; - class Subscription extends CashierSubscription - { - // ... - } +class Subscription extends CashierSubscription +{ + // ... +} +``` After defining your model, you may instruct Cashier to use your custom model via the `Laravel\Cashier\Cashier` class. Typically, you should inform Cashier about your custom models in the `boot` method of your application's `App\Providers\AppServiceProvider` class: - use App\Models\Cashier\Subscription; - use App\Models\Cashier\SubscriptionItem; - - /** - * Bootstrap any application services. - */ - public function boot(): void - { - Cashier::useSubscriptionModel(Subscription::class); - Cashier::useSubscriptionItemModel(SubscriptionItem::class); - } +```php +use App\Models\Cashier\Subscription; +use App\Models\Cashier\SubscriptionItem; + +/** + * Bootstrap any application services. + */ +public function boot(): void +{ + Cashier::useSubscriptionModel(Subscription::class); + Cashier::useSubscriptionItemModel(SubscriptionItem::class); +} +``` ## Quickstart @@ -250,21 +260,23 @@ Offering product and subscription billing via your application can be intimidati To charge customers for non-recurring, single-charge products, we'll utilize Cashier to direct customers to Stripe Checkout, where they will provide their payment details and confirm their purchase. Once the payment has been made via Checkout, the customer will be redirected to a success URL of your choosing within your application: - use Illuminate\Http\Request; +```php +use Illuminate\Http\Request; - Route::get('/checkout', function (Request $request) { - $stripePriceId = 'price_deluxe_album'; +Route::get('/checkout', function (Request $request) { + $stripePriceId = 'price_deluxe_album'; - $quantity = 1; + $quantity = 1; - return $request->user()->checkout([$stripePriceId => $quantity], [ - 'success_url' => route('checkout-success'), - 'cancel_url' => route('checkout-cancel'), - ]); - })->name('checkout'); + return $request->user()->checkout([$stripePriceId => $quantity], [ + 'success_url' => route('checkout-success'), + 'cancel_url' => route('checkout-cancel'), + ]); +})->name('checkout'); - Route::view('/checkout/success', 'checkout.success')->name('checkout-success'); - Route::view('/checkout/cancel', 'checkout.cancel')->name('checkout-cancel'); +Route::view('/checkout/success', 'checkout.success')->name('checkout-success'); +Route::view('/checkout/cancel', 'checkout.cancel')->name('checkout-cancel'); +``` As you can see in the example above, we will utilize Cashier's provided `checkout` method to redirect the customer to Stripe Checkout for a given "price identifier". When using Stripe, "prices" refer to [defined prices for specific products](https://stripe.com/docs/products-prices/how-products-and-prices-work). @@ -277,53 +289,57 @@ When selling products, it's common to keep track of completed orders and purchas To accomplish this, you may provide an array of `metadata` to the `checkout` method. Let's imagine that a pending `Order` is created within our application when a user begins the checkout process. Remember, the `Cart` and `Order` models in this example are illustrative and not provided by Cashier. You are free to implement these concepts based on the needs of your own application: - use App\Models\Cart; - use App\Models\Order; - use Illuminate\Http\Request; +```php +use App\Models\Cart; +use App\Models\Order; +use Illuminate\Http\Request; - Route::get('/cart/{cart}/checkout', function (Request $request, Cart $cart) { - $order = Order::create([ - 'cart_id' => $cart->id, - 'price_ids' => $cart->price_ids, - 'status' => 'incomplete', - ]); +Route::get('/cart/{cart}/checkout', function (Request $request, Cart $cart) { + $order = Order::create([ + 'cart_id' => $cart->id, + 'price_ids' => $cart->price_ids, + 'status' => 'incomplete', + ]); - return $request->user()->checkout($order->price_ids, [ - 'success_url' => route('checkout-success').'?session_id={CHECKOUT_SESSION_ID}', - 'cancel_url' => route('checkout-cancel'), - 'metadata' => ['order_id' => $order->id], - ]); - })->name('checkout'); + return $request->user()->checkout($order->price_ids, [ + 'success_url' => route('checkout-success').'?session_id={CHECKOUT_SESSION_ID}', + 'cancel_url' => route('checkout-cancel'), + 'metadata' => ['order_id' => $order->id], + ]); +})->name('checkout'); +``` As you can see in the example above, when a user begins the checkout process, we will provide all of the cart / order's associated Stripe price identifiers to the `checkout` method. Of course, your application is responsible for associating these items with the "shopping cart" or order as a customer adds them. We also provide the order's ID to the Stripe Checkout session via the `metadata` array. Finally, we have added the `CHECKOUT_SESSION_ID` template variable to the Checkout success route. When Stripe redirects customers back to your application, this template variable will automatically be populated with the Checkout session ID. Next, let's build the Checkout success route. This is the route that users will be redirected to after their purchase has been completed via Stripe Checkout. Within this route, we can retrieve the Stripe Checkout session ID and the associated Stripe Checkout instance in order to access our provided meta data and update our customer's order accordingly: - use App\Models\Order; - use Illuminate\Http\Request; - use Laravel\Cashier\Cashier; +```php +use App\Models\Order; +use Illuminate\Http\Request; +use Laravel\Cashier\Cashier; - Route::get('/checkout/success', function (Request $request) { - $sessionId = $request->get('session_id'); +Route::get('/checkout/success', function (Request $request) { + $sessionId = $request->get('session_id'); - if ($sessionId === null) { - return; - } + if ($sessionId === null) { + return; + } - $session = Cashier::stripe()->checkout->sessions->retrieve($sessionId); + $session = Cashier::stripe()->checkout->sessions->retrieve($sessionId); - if ($session->payment_status !== 'paid') { - return; - } + if ($session->payment_status !== 'paid') { + return; + } - $orderId = $session['metadata']['order_id'] ?? null; + $orderId = $session['metadata']['order_id'] ?? null; - $order = Order::findOrFail($orderId); + $order = Order::findOrFail($orderId); - $order->update(['status' => 'completed']); + $order->update(['status' => 'completed']); - return view('checkout-success', ['order' => $order]); - })->name('checkout-success'); + return view('checkout-success', ['order' => $order]); +})->name('checkout-success'); +``` Please refer to Stripe's documentation for more information on the [data contained by the Checkout session object](https://stripe.com/docs/api/checkout/sessions/object). @@ -339,18 +355,20 @@ To learn how to sell subscriptions using Cashier and Stripe Checkout, let's cons First, let's discover how a customer can subscribe to our services. Of course, you can imagine the customer might click a "subscribe" button for the Basic plan on our application's pricing page. This button or link should direct the user to a Laravel route which creates the Stripe Checkout session for their chosen plan: - use Illuminate\Http\Request; +```php +use Illuminate\Http\Request; - Route::get('/subscription-checkout', function (Request $request) { - return $request->user() - ->newSubscription('default', 'price_basic_monthly') - ->trialDays(5) - ->allowPromotionCodes() - ->checkout([ - 'success_url' => route('your-success-route'), - 'cancel_url' => route('your-cancel-route'), - ]); - }); +Route::get('/subscription-checkout', function (Request $request) { + return $request->user() + ->newSubscription('default', 'price_basic_monthly') + ->trialDays(5) + ->allowPromotionCodes() + ->checkout([ + 'success_url' => route('your-success-route'), + 'cancel_url' => route('your-cancel-route'), + ]); +}); +``` As you can see in the example above, we will redirect the customer to a Stripe Checkout session which will allow them to subscribe to our Basic plan. After a successful checkout or cancellation, the customer will be redirected back to the URL we provided to the `checkout` method. To know when their subscription has actually started (since some payment methods require a few seconds to process), we'll also need to [configure Cashier's webhook handling](#handling-stripe-webhooks). @@ -379,37 +397,41 @@ We can even easily determine if a user is subscribed to specific product or pric For convenience, you may wish to create a [middleware](/docs/{{version}}/middleware) which determines if the incoming request is from a subscribed user. Once this middleware has been defined, you may easily assign it to a route to prevent users that are not subscribed from accessing the route: - user()?->subscribed()) { - // Redirect user to billing page and ask them to subscribe... - return redirect('/billing'); - } - - return $next($request); + if (! $request->user()?->subscribed()) { + // Redirect user to billing page and ask them to subscribe... + return redirect('/billing'); } + + return $next($request); } +} +``` Once the middleware has been defined, you may assign it to a route: - use App\Http\Middleware\Subscribed; +```php +use App\Http\Middleware\Subscribed; - Route::get('/dashboard', function () { - // ... - })->middleware([Subscribed::class]); +Route::get('/dashboard', function () { + // ... +})->middleware([Subscribed::class]); +``` #### Allowing Customers to Manage Their Billing Plan @@ -426,11 +448,13 @@ First, define a link or button within your application that directs users to a L Next, let's define the route that initiates a Stripe Customer Billing Portal session and redirects the user to the Portal. The `redirectToBillingPortal` method accepts the URL that users should be returned to when exiting the Portal: - use Illuminate\Http\Request; +```php +use Illuminate\Http\Request; - Route::get('/billing', function (Request $request) { - return $request->user()->redirectToBillingPortal(route('dashboard')); - })->middleware(['auth'])->name('billing'); +Route::get('/billing', function (Request $request) { + return $request->user()->redirectToBillingPortal(route('dashboard')); +})->middleware(['auth'])->name('billing'); +``` > [!NOTE] > As long as you have configured Cashier's webhook handling, Cashier will automatically keep your application's Cashier-related database tables in sync by inspecting the incoming webhooks from Stripe. So, for example, when a user cancels their subscription via Stripe's Customer Billing Portal, Cashier will receive the corresponding webhook and mark the subscription as "canceled" in your application's database. @@ -443,84 +467,112 @@ Next, let's define the route that initiates a Stripe Customer Billing Portal ses You can retrieve a customer by their Stripe ID using the `Cashier::findBillable` method. This method will return an instance of the billable model: - use Laravel\Cashier\Cashier; +```php +use Laravel\Cashier\Cashier; - $user = Cashier::findBillable($stripeId); +$user = Cashier::findBillable($stripeId); +``` ### Creating Customers Occasionally, you may wish to create a Stripe customer without beginning a subscription. You may accomplish this using the `createAsStripeCustomer` method: - $stripeCustomer = $user->createAsStripeCustomer(); +```php +$stripeCustomer = $user->createAsStripeCustomer(); +``` Once the customer has been created in Stripe, you may begin a subscription at a later date. You may provide an optional `$options` array to pass in any additional [customer creation parameters that are supported by the Stripe API](https://stripe.com/docs/api/customers/create): - $stripeCustomer = $user->createAsStripeCustomer($options); +```php +$stripeCustomer = $user->createAsStripeCustomer($options); +``` You may use the `asStripeCustomer` method if you want to return the Stripe customer object for a billable model: - $stripeCustomer = $user->asStripeCustomer(); +```php +$stripeCustomer = $user->asStripeCustomer(); +``` The `createOrGetStripeCustomer` method may be used if you would like to retrieve the Stripe customer object for a given billable model but are not sure whether the billable model is already a customer within Stripe. This method will create a new customer in Stripe if one does not already exist: - $stripeCustomer = $user->createOrGetStripeCustomer(); +```php +$stripeCustomer = $user->createOrGetStripeCustomer(); +``` ### Updating Customers Occasionally, you may wish to update the Stripe customer directly with additional information. You may accomplish this using the `updateStripeCustomer` method. This method accepts an array of [customer update options supported by the Stripe API](https://stripe.com/docs/api/customers/update): - $stripeCustomer = $user->updateStripeCustomer($options); +```php +$stripeCustomer = $user->updateStripeCustomer($options); +``` ### Balances Stripe allows you to credit or debit a customer's "balance". Later, this balance will be credited or debited on new invoices. To check the customer's total balance you may use the `balance` method that is available on your billable model. The `balance` method will return a formatted string representation of the balance in the customer's currency: - $balance = $user->balance(); +```php +$balance = $user->balance(); +``` To credit a customer's balance, you may provide a value to the `creditBalance` method. If you wish, you may also provide a description: - $user->creditBalance(500, 'Premium customer top-up.'); +```php +$user->creditBalance(500, 'Premium customer top-up.'); +``` Providing a value to the `debitBalance` method will debit the customer's balance: - $user->debitBalance(300, 'Bad usage penalty.'); +```php +$user->debitBalance(300, 'Bad usage penalty.'); +``` The `applyBalance` method will create new customer balance transactions for the customer. You may retrieve these transaction records using the `balanceTransactions` method, which may be useful in order to provide a log of credits and debits for the customer to review: - // Retrieve all transactions... - $transactions = $user->balanceTransactions(); +```php +// Retrieve all transactions... +$transactions = $user->balanceTransactions(); - foreach ($transactions as $transaction) { - // Transaction amount... - $amount = $transaction->amount(); // $2.31 +foreach ($transactions as $transaction) { + // Transaction amount... + $amount = $transaction->amount(); // $2.31 - // Retrieve the related invoice when available... - $invoice = $transaction->invoice(); - } + // Retrieve the related invoice when available... + $invoice = $transaction->invoice(); +} +``` ### Tax IDs Cashier offers an easy way to manage a customer's tax IDs. For example, the `taxIds` method may be used to retrieve all of the [tax IDs](https://stripe.com/docs/api/customer_tax_ids/object) that are assigned to a customer as a collection: - $taxIds = $user->taxIds(); +```php +$taxIds = $user->taxIds(); +``` You can also retrieve a specific tax ID for a customer by its identifier: - $taxId = $user->findTaxId('txi_belgium'); +```php +$taxId = $user->findTaxId('txi_belgium'); +``` You may create a new Tax ID by providing a valid [type](https://stripe.com/docs/api/customer_tax_ids/object#tax_id_object-type) and value to the `createTaxId` method: - $taxId = $user->createTaxId('eu_vat', 'BE0123456789'); +```php +$taxId = $user->createTaxId('eu_vat', 'BE0123456789'); +``` The `createTaxId` method will immediately add the VAT ID to the customer's account. [Verification of VAT IDs is also done by Stripe](https://stripe.com/docs/invoicing/customer/tax-ids#validation); however, this is an asynchronous process. You can be notified of verification updates by subscribing to the `customer.tax_id.updated` webhook event and inspecting [the VAT IDs `verification` parameter](https://stripe.com/docs/api/customer_tax_ids/object#tax_id_object-verification). For more information on handling webhooks, please consult the [documentation on defining webhook handlers](#handling-stripe-webhooks). You may delete a tax ID using the `deleteTaxId` method: - $user->deleteTaxId('txi_belgium'); +```php +$user->deleteTaxId('txi_belgium'); +``` ### Syncing Customer Data With Stripe @@ -529,32 +581,36 @@ Typically, when your application's users update their name, email address, or ot To automate this, you may define an event listener on your billable model that reacts to the model's `updated` event. Then, within your event listener, you may invoke the `syncStripeCustomerDetails` method on the model: - use App\Models\User; - use function Illuminate\Events\queueable; - - /** - * The "booted" method of the model. - */ - protected static function booted(): void - { - static::updated(queueable(function (User $customer) { - if ($customer->hasStripeId()) { - $customer->syncStripeCustomerDetails(); - } - })); - } +```php +use App\Models\User; +use function Illuminate\Events\queueable; + +/** + * The "booted" method of the model. + */ +protected static function booted(): void +{ + static::updated(queueable(function (User $customer) { + if ($customer->hasStripeId()) { + $customer->syncStripeCustomerDetails(); + } + })); +} +``` Now, every time your customer model is updated, its information will be synced with Stripe. For convenience, Cashier will automatically sync your customer's information with Stripe on the initial creation of the customer. You may customize the columns used for syncing customer information to Stripe by overriding a variety of methods provided by Cashier. For example, you may override the `stripeName` method to customize the attribute that should be considered the customer's "name" when Cashier syncs customer information to Stripe: - /** - * Get the customer name that should be synced to Stripe. - */ - public function stripeName(): string|null - { - return $this->company_name; - } +```php +/** + * Get the customer name that should be synced to Stripe. + */ +public function stripeName(): string|null +{ + return $this->company_name; +} +``` Similarly, you may override the `stripeEmail`, `stripePhone`, `stripeAddress`, and `stripePreferredLocales` methods. These methods will sync information to their corresponding customer parameters when [updating the Stripe customer object](https://stripe.com/docs/api/customers/update). If you wish to take total control over the customer information sync process, you may override the `syncStripeCustomerDetails` method. @@ -563,23 +619,29 @@ Similarly, you may override the `stripeEmail`, `stripePhone`, `stripeAddress`, a Stripe offers [an easy way to set up a billing portal](https://stripe.com/docs/billing/subscriptions/customer-portal) so that your customer can manage their subscription, payment methods, and view their billing history. You can redirect your users to the billing portal by invoking the `redirectToBillingPortal` method on the billable model from a controller or route: - use Illuminate\Http\Request; +```php +use Illuminate\Http\Request; - Route::get('/billing-portal', function (Request $request) { - return $request->user()->redirectToBillingPortal(); - }); +Route::get('/billing-portal', function (Request $request) { + return $request->user()->redirectToBillingPortal(); +}); +``` By default, when the user is finished managing their subscription, they will be able to return to the `home` route of your application via a link within the Stripe billing portal. You may provide a custom URL that the user should return to by passing the URL as an argument to the `redirectToBillingPortal` method: - use Illuminate\Http\Request; +```php +use Illuminate\Http\Request; - Route::get('/billing-portal', function (Request $request) { - return $request->user()->redirectToBillingPortal(route('billing')); - }); +Route::get('/billing-portal', function (Request $request) { + return $request->user()->redirectToBillingPortal(route('billing')); +}); +``` If you would like to generate the URL to the billing portal without generating an HTTP redirect response, you may invoke the `billingPortalUrl` method: - $url = $request->user()->billingPortalUrl(route('billing')); +```php +$url = $request->user()->billingPortalUrl(route('billing')); +``` ## Payment Methods @@ -594,9 +656,11 @@ In order to create subscriptions or perform "one-off" charges with Stripe, you w When storing a customer's credit card information for future use by a subscription, the Stripe "Setup Intents" API must be used to securely gather the customer's payment method details. A "Setup Intent" indicates to Stripe the intention to charge a customer's payment method. Cashier's `Billable` trait includes the `createSetupIntent` method to easily create a new Setup Intent. You should invoke this method from the route or controller that will render the form which gathers your customer's payment method details: - return view('update-payment-method', [ - 'intent' => $user->createSetupIntent() - ]); +```php +return view('update-payment-method', [ + 'intent' => $user->createSetupIntent() +]); +``` After you have created the Setup Intent and passed it to the view, you should attach its secret to the element that will gather the payment method. For example, consider this "update payment method" form: @@ -715,51 +779,69 @@ If the card is verified successfully, you may pass the `paymentMethod.id` to you The `paymentMethods` method on the billable model instance returns a collection of `Laravel\Cashier\PaymentMethod` instances: - $paymentMethods = $user->paymentMethods(); +```php +$paymentMethods = $user->paymentMethods(); +``` By default, this method will return payment methods of every type. To retrieve payment methods of a specific type, you may pass the `type` as an argument to the method: - $paymentMethods = $user->paymentMethods('sepa_debit'); +```php +$paymentMethods = $user->paymentMethods('sepa_debit'); +``` To retrieve the customer's default payment method, the `defaultPaymentMethod` method may be used: - $paymentMethod = $user->defaultPaymentMethod(); +```php +$paymentMethod = $user->defaultPaymentMethod(); +``` You can retrieve a specific payment method that is attached to the billable model using the `findPaymentMethod` method: - $paymentMethod = $user->findPaymentMethod($paymentMethodId); +```php +$paymentMethod = $user->findPaymentMethod($paymentMethodId); +``` ### Payment Method Presence To determine if a billable model has a default payment method attached to their account, invoke the `hasDefaultPaymentMethod` method: - if ($user->hasDefaultPaymentMethod()) { - // ... - } +```php +if ($user->hasDefaultPaymentMethod()) { + // ... +} +``` You may use the `hasPaymentMethod` method to determine if a billable model has at least one payment method attached to their account: - if ($user->hasPaymentMethod()) { - // ... - } +```php +if ($user->hasPaymentMethod()) { + // ... +} +``` This method will determine if the billable model has any payment method at all. To determine if a payment method of a specific type exists for the model, you may pass the `type` as an argument to the method: - if ($user->hasPaymentMethod('sepa_debit')) { - // ... - } +```php +if ($user->hasPaymentMethod('sepa_debit')) { + // ... +} +``` ### Updating the Default Payment Method The `updateDefaultPaymentMethod` method may be used to update a customer's default payment method information. This method accepts a Stripe payment method identifier and will assign the new payment method as the default billing payment method: - $user->updateDefaultPaymentMethod($paymentMethod); +```php +$user->updateDefaultPaymentMethod($paymentMethod); +``` To sync your default payment method information with the customer's default payment method information in Stripe, you may use the `updateDefaultPaymentMethodFromStripe` method: - $user->updateDefaultPaymentMethodFromStripe(); +```php +$user->updateDefaultPaymentMethodFromStripe(); +``` > [!WARNING] > The default payment method on a customer can only be used for invoicing and creating new subscriptions. Due to limitations imposed by Stripe, it may not be used for single charges. @@ -769,7 +851,9 @@ To sync your default payment method information with the customer's default paym To add a new payment method, you may call the `addPaymentMethod` method on the billable model, passing the payment method identifier: - $user->addPaymentMethod($paymentMethod); +```php +$user->addPaymentMethod($paymentMethod); +``` > [!NOTE] > To learn how to retrieve payment method identifiers please review the [payment method storage documentation](#storing-payment-methods). @@ -779,19 +863,27 @@ To add a new payment method, you may call the `addPaymentMethod` method on the b To delete a payment method, you may call the `delete` method on the `Laravel\Cashier\PaymentMethod` instance you wish to delete: - $paymentMethod->delete(); +```php +$paymentMethod->delete(); +``` The `deletePaymentMethod` method will delete a specific payment method from the billable model: - $user->deletePaymentMethod('pm_visa'); +```php +$user->deletePaymentMethod('pm_visa'); +``` The `deletePaymentMethods` method will delete all of the payment method information for the billable model: - $user->deletePaymentMethods(); +```php +$user->deletePaymentMethods(); +``` By default, this method will delete payment methods of every type. To delete payment methods of a specific type you can pass the `type` as an argument to the method: - $user->deletePaymentMethods('sepa_debit'); +```php +$user->deletePaymentMethods('sepa_debit'); +``` > [!WARNING] > If a user has an active subscription, your application should not allow them to delete their default payment method. @@ -806,15 +898,17 @@ Subscriptions provide a way to set up recurring payments for your customers. Str To create a subscription, first retrieve an instance of your billable model, which typically will be an instance of `App\Models\User`. Once you have retrieved the model instance, you may use the `newSubscription` method to create the model's subscription: - use Illuminate\Http\Request; +```php +use Illuminate\Http\Request; - Route::post('/user/subscribe', function (Request $request) { - $request->user()->newSubscription( - 'default', 'price_monthly' - )->create($request->paymentMethodId); +Route::post('/user/subscribe', function (Request $request) { + $request->user()->newSubscription( + 'default', 'price_monthly' + )->create($request->paymentMethodId); - // ... - }); + // ... +}); +``` The first argument passed to the `newSubscription` method should be the internal type of the subscription. If your application only offers a single subscription, you might call this `default` or `primary`. This subscription type is only for internal application usage and is not meant to be shown to users. In addition, it should not contain spaces and it should never be changed after creating the subscription. The second argument is the specific price the user is subscribing to. This value should correspond to the price's identifier in Stripe. @@ -828,86 +922,110 @@ The `create` method, which accepts [a Stripe payment method identifier](#storing Instead of collecting a customer's recurring payments automatically, you may instruct Stripe to email an invoice to the customer each time their recurring payment is due. Then, the customer may manually pay the invoice once they receive it. The customer does not need to provide a payment method up front when collecting recurring payments via invoices: - $user->newSubscription('default', 'price_monthly')->createAndSendInvoice(); +```php +$user->newSubscription('default', 'price_monthly')->createAndSendInvoice(); +``` The amount of time a customer has to pay their invoice before their subscription is canceled is determined by the `days_until_due` option. By default, this is 30 days; however, you may provide a specific value for this option if you wish: - $user->newSubscription('default', 'price_monthly')->createAndSendInvoice([], [ - 'days_until_due' => 30 - ]); +```php +$user->newSubscription('default', 'price_monthly')->createAndSendInvoice([], [ + 'days_until_due' => 30 +]); +``` #### Quantities If you would like to set a specific [quantity](https://stripe.com/docs/billing/subscriptions/quantities) for the price when creating the subscription, you should invoke the `quantity` method on the subscription builder before creating the subscription: - $user->newSubscription('default', 'price_monthly') - ->quantity(5) - ->create($paymentMethod); +```php +$user->newSubscription('default', 'price_monthly') + ->quantity(5) + ->create($paymentMethod); +``` #### Additional Details If you would like to specify additional [customer](https://stripe.com/docs/api/customers/create) or [subscription](https://stripe.com/docs/api/subscriptions/create) options supported by Stripe, you may do so by passing them as the second and third arguments to the `create` method: - $user->newSubscription('default', 'price_monthly')->create($paymentMethod, [ - 'email' => $email, - ], [ - 'metadata' => ['note' => 'Some extra information.'], - ]); +```php +$user->newSubscription('default', 'price_monthly')->create($paymentMethod, [ + 'email' => $email, +], [ + 'metadata' => ['note' => 'Some extra information.'], +]); +``` #### Coupons If you would like to apply a coupon when creating the subscription, you may use the `withCoupon` method: - $user->newSubscription('default', 'price_monthly') - ->withCoupon('code') - ->create($paymentMethod); +```php +$user->newSubscription('default', 'price_monthly') + ->withCoupon('code') + ->create($paymentMethod); +``` Or, if you would like to apply a [Stripe promotion code](https://stripe.com/docs/billing/subscriptions/discounts/codes), you may use the `withPromotionCode` method: - $user->newSubscription('default', 'price_monthly') - ->withPromotionCode('promo_code_id') - ->create($paymentMethod); +```php +$user->newSubscription('default', 'price_monthly') + ->withPromotionCode('promo_code_id') + ->create($paymentMethod); +``` The given promotion code ID should be the Stripe API ID assigned to the promotion code and not the customer facing promotion code. If you need to find a promotion code ID based on a given customer facing promotion code, you may use the `findPromotionCode` method: - // Find a promotion code ID by its customer facing code... - $promotionCode = $user->findPromotionCode('SUMMERSALE'); +```php +// Find a promotion code ID by its customer facing code... +$promotionCode = $user->findPromotionCode('SUMMERSALE'); - // Find an active promotion code ID by its customer facing code... - $promotionCode = $user->findActivePromotionCode('SUMMERSALE'); +// Find an active promotion code ID by its customer facing code... +$promotionCode = $user->findActivePromotionCode('SUMMERSALE'); +``` In the example above, the returned `$promotionCode` object is an instance of `Laravel\Cashier\PromotionCode`. This class decorates an underlying `Stripe\PromotionCode` object. You can retrieve the coupon related to the promotion code by invoking the `coupon` method: - $coupon = $user->findPromotionCode('SUMMERSALE')->coupon(); +```php +$coupon = $user->findPromotionCode('SUMMERSALE')->coupon(); +``` The coupon instance allows you to determine the discount amount and whether the coupon represents a fixed discount or percentage based discount: - if ($coupon->isPercentage()) { - return $coupon->percentOff().'%'; // 21.5% - } else { - return $coupon->amountOff(); // $5.99 - } +```php +if ($coupon->isPercentage()) { + return $coupon->percentOff().'%'; // 21.5% +} else { + return $coupon->amountOff(); // $5.99 +} +``` You can also retrieve the discounts that are currently applied to a customer or subscription: - $discount = $billable->discount(); +```php +$discount = $billable->discount(); - $discount = $subscription->discount(); +$discount = $subscription->discount(); +``` The returned `Laravel\Cashier\Discount` instances decorate an underlying `Stripe\Discount` object instance. You may retrieve the coupon related to this discount by invoking the `coupon` method: - $coupon = $subscription->discount()->coupon(); +```php +$coupon = $subscription->discount()->coupon(); +``` If you would like to apply a new coupon or promotion code to a customer or subscription, you may do so via the `applyCoupon` or `applyPromotionCode` methods: - $billable->applyCoupon('coupon_id'); - $billable->applyPromotionCode('promotion_code_id'); +```php +$billable->applyCoupon('coupon_id'); +$billable->applyPromotionCode('promotion_code_id'); - $subscription->applyCoupon('coupon_id'); - $subscription->applyPromotionCode('promotion_code_id'); +$subscription->applyCoupon('coupon_id'); +$subscription->applyPromotionCode('promotion_code_id'); +``` Remember, you should use the Stripe API ID assigned to the promotion code and not the customer facing promotion code. Only one coupon or promotion code can be applied to a customer or subscription at a given time. @@ -918,11 +1036,13 @@ For more info on this subject, please consult the Stripe documentation regarding If you would like to add a subscription to a customer who already has a default payment method you may invoke the `add` method on the subscription builder: - use App\Models\User; +```php +use App\Models\User; - $user = User::find(1); +$user = User::find(1); - $user->newSubscription('default', 'price_monthly')->add(); +$user->newSubscription('default', 'price_monthly')->add(); +``` #### Creating Subscriptions From the Stripe Dashboard @@ -938,67 +1058,81 @@ Finally, you should always make sure to only add one active subscription per typ Once a customer is subscribed to your application, you may easily check their subscription status using a variety of convenient methods. First, the `subscribed` method returns `true` if the customer has an active subscription, even if the subscription is currently within its trial period. The `subscribed` method accepts the type of the subscription as its first argument: - if ($user->subscribed('default')) { - // ... - } +```php +if ($user->subscribed('default')) { + // ... +} +``` The `subscribed` method also makes a great candidate for a [route middleware](/docs/{{version}}/middleware), allowing you to filter access to routes and controllers based on the user's subscription status: - user() && ! $request->user()->subscribed('default')) { - // This user is not a paying customer... - return redirect('/billing'); - } - - return $next($request); + if ($request->user() && ! $request->user()->subscribed('default')) { + // This user is not a paying customer... + return redirect('/billing'); } + + return $next($request); } +} +``` If you would like to determine if a user is still within their trial period, you may use the `onTrial` method. This method can be useful for determining if you should display a warning to the user that they are still on their trial period: - if ($user->subscription('default')->onTrial()) { - // ... - } +```php +if ($user->subscription('default')->onTrial()) { + // ... +} +``` The `subscribedToProduct` method may be used to determine if the user is subscribed to a given product based on a given Stripe product's identifier. In Stripe, products are collections of prices. In this example, we will determine if the user's `default` subscription is actively subscribed to the application's "premium" product. The given Stripe product identifier should correspond to one of your product's identifiers in the Stripe dashboard: - if ($user->subscribedToProduct('prod_premium', 'default')) { - // ... - } +```php +if ($user->subscribedToProduct('prod_premium', 'default')) { + // ... +} +``` By passing an array to the `subscribedToProduct` method, you may determine if the user's `default` subscription is actively subscribed to the application's "basic" or "premium" product: - if ($user->subscribedToProduct(['prod_basic', 'prod_premium'], 'default')) { - // ... - } +```php +if ($user->subscribedToProduct(['prod_basic', 'prod_premium'], 'default')) { + // ... +} +``` The `subscribedToPrice` method may be used to determine if a customer's subscription corresponds to a given price ID: - if ($user->subscribedToPrice('price_basic_monthly', 'default')) { - // ... - } +```php +if ($user->subscribedToPrice('price_basic_monthly', 'default')) { + // ... +} +``` The `recurring` method may be used to determine if the user is currently subscribed and is no longer within their trial period: - if ($user->subscription('default')->recurring()) { - // ... - } +```php +if ($user->subscription('default')->recurring()) { + // ... +} +``` > [!WARNING] > If a user has two subscriptions with the same type, the most recent subscription will always be returned by the `subscription` method. For example, a user might have two subscription records with the type of `default`; however, one of the subscriptions may be an old, expired subscription, while the other is the current, active subscription. The most recent subscription will always be returned while older subscriptions are kept in the database for historical review. @@ -1008,21 +1142,27 @@ The `recurring` method may be used to determine if the user is currently subscri To determine if the user was once an active subscriber but has canceled their subscription, you may use the `canceled` method: - if ($user->subscription('default')->canceled()) { - // ... - } +```php +if ($user->subscription('default')->canceled()) { + // ... +} +``` You may also determine if a user has canceled their subscription but are still on their "grace period" until the subscription fully expires. For example, if a user cancels a subscription on March 5th that was originally scheduled to expire on March 10th, the user is on their "grace period" until March 10th. Note that the `subscribed` method still returns `true` during this time: - if ($user->subscription('default')->onGracePeriod()) { - // ... - } +```php +if ($user->subscription('default')->onGracePeriod()) { + // ... +} +``` To determine if the user has canceled their subscription and is no longer within their "grace period", you may use the `ended` method: - if ($user->subscription('default')->ended()) { - // ... - } +```php +if ($user->subscription('default')->ended()) { + // ... +} +``` #### Incomplete and Past Due Status @@ -1031,13 +1171,15 @@ If a subscription requires a secondary payment action after creation the subscri Similarly, if a secondary payment action is required when swapping prices the subscription will be marked as `past_due`. When your subscription is in either of these states it will not be active until the customer has confirmed their payment. Determining if a subscription has an incomplete payment may be accomplished using the `hasIncompletePayment` method on the billable model or a subscription instance: - if ($user->hasIncompletePayment('default')) { - // ... - } +```php +if ($user->hasIncompletePayment('default')) { + // ... +} - if ($user->subscription('default')->hasIncompletePayment()) { - // ... - } +if ($user->subscription('default')->hasIncompletePayment()) { + // ... +} +``` When a subscription has an incomplete payment, you should direct the user to Cashier's payment confirmation page, passing the `latestPayment` identifier. You may use the `latestPayment` method available on subscription instance to retrieve this identifier: @@ -1049,16 +1191,18 @@ When a subscription has an incomplete payment, you should direct the user to Cas If you would like the subscription to still be considered active when it's in a `past_due` or `incomplete` state, you may use the `keepPastDueSubscriptionsActive` and `keepIncompleteSubscriptionsActive` methods provided by Cashier. Typically, these methods should be called in the `register` method of your `App\Providers\AppServiceProvider`: - use Laravel\Cashier\Cashier; - - /** - * Register any application services. - */ - public function register(): void - { - Cashier::keepPastDueSubscriptionsActive(); - Cashier::keepIncompleteSubscriptionsActive(); - } +```php +use Laravel\Cashier\Cashier; + +/** + * Register any application services. + */ +public function register(): void +{ + Cashier::keepPastDueSubscriptionsActive(); + Cashier::keepIncompleteSubscriptionsActive(); +} +``` > [!WARNING] > When a subscription is in an `incomplete` state it cannot be changed until the payment is confirmed. Therefore, the `swap` and `updateQuantity` methods will throw an exception when the subscription is in an `incomplete` state. @@ -1068,57 +1212,69 @@ If you would like the subscription to still be considered active when it's in a Most subscription states are also available as query scopes so that you may easily query your database for subscriptions that are in a given state: - // Get all active subscriptions... - $subscriptions = Subscription::query()->active()->get(); +```php +// Get all active subscriptions... +$subscriptions = Subscription::query()->active()->get(); - // Get all of the canceled subscriptions for a user... - $subscriptions = $user->subscriptions()->canceled()->get(); +// Get all of the canceled subscriptions for a user... +$subscriptions = $user->subscriptions()->canceled()->get(); +``` A complete list of available scopes is available below: - Subscription::query()->active(); - Subscription::query()->canceled(); - Subscription::query()->ended(); - Subscription::query()->incomplete(); - Subscription::query()->notCanceled(); - Subscription::query()->notOnGracePeriod(); - Subscription::query()->notOnTrial(); - Subscription::query()->onGracePeriod(); - Subscription::query()->onTrial(); - Subscription::query()->pastDue(); - Subscription::query()->recurring(); +```php +Subscription::query()->active(); +Subscription::query()->canceled(); +Subscription::query()->ended(); +Subscription::query()->incomplete(); +Subscription::query()->notCanceled(); +Subscription::query()->notOnGracePeriod(); +Subscription::query()->notOnTrial(); +Subscription::query()->onGracePeriod(); +Subscription::query()->onTrial(); +Subscription::query()->pastDue(); +Subscription::query()->recurring(); +``` ### Changing Prices After a customer is subscribed to your application, they may occasionally want to change to a new subscription price. To swap a customer to a new price, pass the Stripe price's identifier to the `swap` method. When swapping prices, it is assumed that the user would like to re-activate their subscription if it was previously canceled. The given price identifier should correspond to a Stripe price identifier available in the Stripe dashboard: - use App\Models\User; +```php +use App\Models\User; - $user = App\Models\User::find(1); +$user = App\Models\User::find(1); - $user->subscription('default')->swap('price_yearly'); +$user->subscription('default')->swap('price_yearly'); +``` If the customer is on trial, the trial period will be maintained. Additionally, if a "quantity" exists for the subscription, that quantity will also be maintained. If you would like to swap prices and cancel any trial period the customer is currently on, you may invoke the `skipTrial` method: - $user->subscription('default') - ->skipTrial() - ->swap('price_yearly'); +```php +$user->subscription('default') + ->skipTrial() + ->swap('price_yearly'); +``` If you would like to swap prices and immediately invoice the customer instead of waiting for their next billing cycle, you may use the `swapAndInvoice` method: - $user = User::find(1); +```php +$user = User::find(1); - $user->subscription('default')->swapAndInvoice('price_yearly'); +$user->subscription('default')->swapAndInvoice('price_yearly'); +``` #### Prorations By default, Stripe prorates charges when swapping between prices. The `noProrate` method may be used to update the subscription's price without prorating the charges: - $user->subscription('default')->noProrate()->swap('price_yearly'); +```php +$user->subscription('default')->noProrate()->swap('price_yearly'); +``` For more information on subscription proration, consult the [Stripe documentation](https://stripe.com/docs/billing/subscriptions/prorations). @@ -1130,27 +1286,33 @@ For more information on subscription proration, consult the [Stripe documentatio Sometimes subscriptions are affected by "quantity". For example, a project management application might charge $10 per month per project. You may use the `incrementQuantity` and `decrementQuantity` methods to easily increment or decrement your subscription quantity: - use App\Models\User; +```php +use App\Models\User; - $user = User::find(1); +$user = User::find(1); - $user->subscription('default')->incrementQuantity(); +$user->subscription('default')->incrementQuantity(); - // Add five to the subscription's current quantity... - $user->subscription('default')->incrementQuantity(5); +// Add five to the subscription's current quantity... +$user->subscription('default')->incrementQuantity(5); - $user->subscription('default')->decrementQuantity(); +$user->subscription('default')->decrementQuantity(); - // Subtract five from the subscription's current quantity... - $user->subscription('default')->decrementQuantity(5); +// Subtract five from the subscription's current quantity... +$user->subscription('default')->decrementQuantity(5); +``` Alternatively, you may set a specific quantity using the `updateQuantity` method: - $user->subscription('default')->updateQuantity(10); +```php +$user->subscription('default')->updateQuantity(10); +``` The `noProrate` method may be used to update the subscription's quantity without prorating the charges: - $user->subscription('default')->noProrate()->updateQuantity(10); +```php +$user->subscription('default')->noProrate()->updateQuantity(10); +``` For more information on subscription quantities, consult the [Stripe documentation](https://stripe.com/docs/subscriptions/quantities). @@ -1159,7 +1321,9 @@ For more information on subscription quantities, consult the [Stripe documentati If your subscription is a [subscription with multiple products](#subscriptions-with-multiple-products), you should pass the ID of the price whose quantity you wish to increment or decrement as the second argument to the increment / decrement methods: - $user->subscription('default')->incrementQuantity(1, 'price_chat'); +```php +$user->subscription('default')->incrementQuantity(1, 'price_chat'); +``` ### Subscriptions With Multiple Products @@ -1168,44 +1332,56 @@ If your subscription is a [subscription with multiple products](#subscriptions-w You may specify multiple products for a given subscription by passing an array of prices as the second argument to the `newSubscription` method: - use Illuminate\Http\Request; +```php +use Illuminate\Http\Request; - Route::post('/user/subscribe', function (Request $request) { - $request->user()->newSubscription('default', [ - 'price_monthly', - 'price_chat', - ])->create($request->paymentMethodId); +Route::post('/user/subscribe', function (Request $request) { + $request->user()->newSubscription('default', [ + 'price_monthly', + 'price_chat', + ])->create($request->paymentMethodId); - // ... - }); + // ... +}); +``` In the example above, the customer will have two prices attached to their `default` subscription. Both prices will be charged on their respective billing intervals. If necessary, you may use the `quantity` method to indicate a specific quantity for each price: - $user = User::find(1); +```php +$user = User::find(1); - $user->newSubscription('default', ['price_monthly', 'price_chat']) - ->quantity(5, 'price_chat') - ->create($paymentMethod); +$user->newSubscription('default', ['price_monthly', 'price_chat']) + ->quantity(5, 'price_chat') + ->create($paymentMethod); +``` If you would like to add another price to an existing subscription, you may invoke the subscription's `addPrice` method: - $user = User::find(1); +```php +$user = User::find(1); - $user->subscription('default')->addPrice('price_chat'); +$user->subscription('default')->addPrice('price_chat'); +``` The example above will add the new price and the customer will be billed for it on their next billing cycle. If you would like to bill the customer immediately you may use the `addPriceAndInvoice` method: - $user->subscription('default')->addPriceAndInvoice('price_chat'); +```php +$user->subscription('default')->addPriceAndInvoice('price_chat'); +``` If you would like to add a price with a specific quantity, you can pass the quantity as the second argument of the `addPrice` or `addPriceAndInvoice` methods: - $user = User::find(1); +```php +$user = User::find(1); - $user->subscription('default')->addPrice('price_chat', 5); +$user->subscription('default')->addPrice('price_chat', 5); +``` You may remove prices from subscriptions using the `removePrice` method: - $user->subscription('default')->removePrice('price_chat'); +```php +$user->subscription('default')->removePrice('price_chat'); +``` > [!WARNING] > You may not remove the last price on a subscription. Instead, you should simply cancel the subscription. @@ -1215,50 +1391,60 @@ You may remove prices from subscriptions using the `removePrice` method: You may also change the prices attached to a subscription with multiple products. For example, imagine a customer has a `price_basic` subscription with a `price_chat` add-on product and you want to upgrade the customer from the `price_basic` to the `price_pro` price: - use App\Models\User; +```php +use App\Models\User; - $user = User::find(1); +$user = User::find(1); - $user->subscription('default')->swap(['price_pro', 'price_chat']); +$user->subscription('default')->swap(['price_pro', 'price_chat']); +``` When executing the example above, the underlying subscription item with the `price_basic` is deleted and the one with the `price_chat` is preserved. Additionally, a new subscription item for the `price_pro` is created. You can also specify subscription item options by passing an array of key / value pairs to the `swap` method. For example, you may need to specify the subscription price quantities: - $user = User::find(1); +```php +$user = User::find(1); - $user->subscription('default')->swap([ - 'price_pro' => ['quantity' => 5], - 'price_chat' - ]); +$user->subscription('default')->swap([ + 'price_pro' => ['quantity' => 5], + 'price_chat' +]); +``` If you want to swap a single price on a subscription, you may do so using the `swap` method on the subscription item itself. This approach is particularly useful if you would like to preserve all of the existing metadata on the subscription's other prices: - $user = User::find(1); +```php +$user = User::find(1); - $user->subscription('default') - ->findItemOrFail('price_basic') - ->swap('price_pro'); +$user->subscription('default') + ->findItemOrFail('price_basic') + ->swap('price_pro'); +``` #### Proration By default, Stripe will prorate charges when adding or removing prices from a subscription with multiple products. If you would like to make a price adjustment without proration, you should chain the `noProrate` method onto your price operation: - $user->subscription('default')->noProrate()->removePrice('price_chat'); +```php +$user->subscription('default')->noProrate()->removePrice('price_chat'); +``` #### Quantities If you would like to update quantities on individual subscription prices, you may do so using the [existing quantity methods](#subscription-quantity) by passing the ID of the price as an additional argument to the method: - $user = User::find(1); +```php +$user = User::find(1); - $user->subscription('default')->incrementQuantity(5, 'price_chat'); +$user->subscription('default')->incrementQuantity(5, 'price_chat'); - $user->subscription('default')->decrementQuantity(3, 'price_chat'); +$user->subscription('default')->decrementQuantity(3, 'price_chat'); - $user->subscription('default')->updateQuantity(10, 'price_chat'); +$user->subscription('default')->updateQuantity(10, 'price_chat'); +``` > [!WARNING] > When a subscription has multiple prices the `stripe_price` and `quantity` attributes on the `Subscription` model will be `null`. To access the individual price attributes, you should use the `items` relationship available on the `Subscription` model. @@ -1268,21 +1454,25 @@ If you would like to update quantities on individual subscription prices, you ma When a subscription has multiple prices, it will have multiple subscription "items" stored in your database's `subscription_items` table. You may access these via the `items` relationship on the subscription: - use App\Models\User; +```php +use App\Models\User; - $user = User::find(1); +$user = User::find(1); - $subscriptionItem = $user->subscription('default')->items->first(); +$subscriptionItem = $user->subscription('default')->items->first(); - // Retrieve the Stripe price and quantity for a specific item... - $stripePrice = $subscriptionItem->stripe_price; - $quantity = $subscriptionItem->quantity; +// Retrieve the Stripe price and quantity for a specific item... +$stripePrice = $subscriptionItem->stripe_price; +$quantity = $subscriptionItem->quantity; +``` You can also retrieve a specific price using the `findItemOrFail` method: - $user = User::find(1); +```php +$user = User::find(1); - $subscriptionItem = $user->subscription('default')->findItemOrFail('price_chat'); +$subscriptionItem = $user->subscription('default')->findItemOrFail('price_chat'); +``` ### Multiple Subscriptions @@ -1291,23 +1481,29 @@ Stripe allows your customers to have multiple subscriptions simultaneously. For When your application creates subscriptions, you may provide the type of the subscription to the `newSubscription` method. The type may be any string that represents the type of subscription the user is initiating: - use Illuminate\Http\Request; +```php +use Illuminate\Http\Request; - Route::post('/swimming/subscribe', function (Request $request) { - $request->user()->newSubscription('swimming') - ->price('price_swimming_monthly') - ->create($request->paymentMethodId); +Route::post('/swimming/subscribe', function (Request $request) { + $request->user()->newSubscription('swimming') + ->price('price_swimming_monthly') + ->create($request->paymentMethodId); - // ... - }); + // ... +}); +``` In this example, we initiated a monthly swimming subscription for the customer. However, they may want to swap to a yearly subscription at a later time. When adjusting the customer's subscription, we can simply swap the price on the `swimming` subscription: - $user->subscription('swimming')->swap('price_swimming_yearly'); +```php +$user->subscription('swimming')->swap('price_swimming_yearly'); +``` Of course, you may also cancel the subscription entirely: - $user->subscription('swimming')->cancel(); +```php +$user->subscription('swimming')->cancel(); +``` ### Usage Based Billing @@ -1316,57 +1512,69 @@ Of course, you may also cancel the subscription entirely: To start using usage billing, you will first need to create a new product in your Stripe dashboard with a [usage based billing model](https://docs.stripe.com/billing/subscriptions/usage-based/implementation-guide) and a [meter](https://docs.stripe.com/billing/subscriptions/usage-based/recording-usage#configure-meter). After creating the meter, store the associated event name and meter ID, which you will need to report and retrieve usage. Then, use the `meteredPrice` method to add the metered price ID to a customer subscription: - use Illuminate\Http\Request; +```php +use Illuminate\Http\Request; - Route::post('/user/subscribe', function (Request $request) { - $request->user()->newSubscription('default') - ->meteredPrice('price_metered') - ->create($request->paymentMethodId); +Route::post('/user/subscribe', function (Request $request) { + $request->user()->newSubscription('default') + ->meteredPrice('price_metered') + ->create($request->paymentMethodId); - // ... - }); + // ... +}); +``` You may also start a metered subscription via [Stripe Checkout](#checkout): - $checkout = Auth::user() - ->newSubscription('default', []) - ->meteredPrice('price_metered') - ->checkout(); +```php +$checkout = Auth::user() + ->newSubscription('default', []) + ->meteredPrice('price_metered') + ->checkout(); - return view('your-checkout-view', [ - 'checkout' => $checkout, - ]); +return view('your-checkout-view', [ + 'checkout' => $checkout, +]); +``` #### Reporting Usage As your customer uses your application, you will report their usage to Stripe so that they can be billed accurately. To report the usage of a metered event, you may use the `reportMeterEvent` method on your `Billable` model: - $user = User::find(1); +```php +$user = User::find(1); - $user->reportMeterEvent('emails-sent'); +$user->reportMeterEvent('emails-sent'); +``` By default, a "usage quantity" of 1 is added to the billing period. Alternatively, you may pass a specific amount of "usage" to add to the customer's usage for the billing period: - $user = User::find(1); +```php +$user = User::find(1); - $user->reportMeterEvent('emails-sent', quantity: 15); +$user->reportMeterEvent('emails-sent', quantity: 15); +``` To retrieve a customer's event summary for a meter, you may use a `Billable` instance's `meterEventSummaries` method: - $user = User::find(1); +```php +$user = User::find(1); - $meterUsage = $user->meterEventSummaries($meterId); +$meterUsage = $user->meterEventSummaries($meterId); - $meterUsage->first()->aggregated_value // 10 +$meterUsage->first()->aggregated_value // 10 +``` Please refer to Stripe's [Meter Event Summary object documentation](https://docs.stripe.com/api/billing/meter-event_summary/object) for more information on meter event summaries. To [list all meters](https://docs.stripe.com/api/billing/meter/list), you may use a `Billable` instance's `meters` method: - $user = User::find(1); +```php +$user = User::find(1); - $user->meters(); +$user->meters(); +``` ### Subscription Taxes @@ -1376,31 +1584,35 @@ To [list all meters](https://docs.stripe.com/api/billing/meter/list), you may us To specify the tax rates a user pays on a subscription, you should implement the `taxRates` method on your billable model and return an array containing the Stripe tax rate IDs. You can define these tax rates in [your Stripe dashboard](https://dashboard.stripe.com/test/tax-rates): - /** - * The tax rates that should apply to the customer's subscriptions. - * - * @return array - */ - public function taxRates(): array - { - return ['txr_id']; - } +```php +/** + * The tax rates that should apply to the customer's subscriptions. + * + * @return array + */ +public function taxRates(): array +{ + return ['txr_id']; +} +``` The `taxRates` method enables you to apply a tax rate on a customer-by-customer basis, which may be helpful for a user base that spans multiple countries and tax rates. If you're offering subscriptions with multiple products, you may define different tax rates for each price by implementing a `priceTaxRates` method on your billable model: - /** - * The tax rates that should apply to the customer's subscriptions. - * - * @return array> - */ - public function priceTaxRates(): array - { - return [ - 'price_monthly' => ['txr_id'], - ]; - } +```php +/** + * The tax rates that should apply to the customer's subscriptions. + * + * @return array> + */ +public function priceTaxRates(): array +{ + return [ + 'price_monthly' => ['txr_id'], + ]; +} +``` > [!WARNING] > The `taxRates` method only applies to subscription charges. If you use Cashier to make "one-off" charges, you will need to manually specify the tax rate at that time. @@ -1410,7 +1622,9 @@ If you're offering subscriptions with multiple products, you may define differen When changing the hard-coded tax rate IDs returned by the `taxRates` method, the tax settings on any existing subscriptions for the user will remain the same. If you wish to update the tax value for existing subscriptions with the new `taxRates` values, you should call the `syncTaxRates` method on the user's subscription instance: - $user->subscription('default')->syncTaxRates(); +```php +$user->subscription('default')->syncTaxRates(); +``` This will also sync any item tax rates for a subscription with multiple products. If your application is offering subscriptions with multiple products, you should ensure that your billable model implements the `priceTaxRates` method [discussed above](#subscription-taxes). @@ -1419,13 +1633,15 @@ This will also sync any item tax rates for a subscription with multiple products Cashier also offers the `isNotTaxExempt`, `isTaxExempt`, and `reverseChargeApplies` methods to determine if the customer is tax exempt. These methods will call the Stripe API to determine a customer's tax exemption status: - use App\Models\User; +```php +use App\Models\User; - $user = User::find(1); +$user = User::find(1); - $user->isTaxExempt(); - $user->isNotTaxExempt(); - $user->reverseChargeApplies(); +$user->isTaxExempt(); +$user->isNotTaxExempt(); +$user->reverseChargeApplies(); +``` > [!WARNING] > These methods are also available on any `Laravel\Cashier\Invoice` object. However, when invoked on an `Invoice` object, the methods will determine the exemption status at the time the invoice was created. @@ -1435,17 +1651,19 @@ Cashier also offers the `isNotTaxExempt`, `isTaxExempt`, and `reverseChargeAppli By default, the billing cycle anchor is the date the subscription was created or, if a trial period is used, the date that the trial ends. If you would like to modify the billing anchor date, you may use the `anchorBillingCycleOn` method: - use Illuminate\Http\Request; +```php +use Illuminate\Http\Request; - Route::post('/user/subscribe', function (Request $request) { - $anchor = Carbon::parse('first day of next month'); +Route::post('/user/subscribe', function (Request $request) { + $anchor = Carbon::parse('first day of next month'); - $request->user()->newSubscription('default', 'price_monthly') - ->anchorBillingCycleOn($anchor->startOfDay()) - ->create($request->paymentMethodId); + $request->user()->newSubscription('default', 'price_monthly') + ->anchorBillingCycleOn($anchor->startOfDay()) + ->create($request->paymentMethodId); - // ... - }); + // ... +}); +``` For more information on managing subscription billing cycles, consult the [Stripe billing cycle documentation](https://stripe.com/docs/billing/subscriptions/billing-cycle) @@ -1454,7 +1672,9 @@ For more information on managing subscription billing cycles, consult the [Strip To cancel a subscription, call the `cancel` method on the user's subscription: - $user->subscription('default')->cancel(); +```php +$user->subscription('default')->cancel(); +``` When a subscription is canceled, Cashier will automatically set the `ends_at` column in your `subscriptions` database table. This column is used to know when the `subscribed` method should begin returning `false`. @@ -1462,36 +1682,48 @@ For example, if a customer cancels a subscription on March 1st, but the subscrip You may determine if a user has canceled their subscription but are still on their "grace period" using the `onGracePeriod` method: - if ($user->subscription('default')->onGracePeriod()) { - // ... - } +```php +if ($user->subscription('default')->onGracePeriod()) { + // ... +} +``` If you wish to cancel a subscription immediately, call the `cancelNow` method on the user's subscription: - $user->subscription('default')->cancelNow(); +```php +$user->subscription('default')->cancelNow(); +``` If you wish to cancel a subscription immediately and invoice any remaining un-invoiced metered usage or new / pending proration invoice items, call the `cancelNowAndInvoice` method on the user's subscription: - $user->subscription('default')->cancelNowAndInvoice(); +```php +$user->subscription('default')->cancelNowAndInvoice(); +``` You may also choose to cancel the subscription at a specific moment in time: - $user->subscription('default')->cancelAt( - now()->addDays(10) - ); +```php +$user->subscription('default')->cancelAt( + now()->addDays(10) +); +``` Finally, you should always cancel user subscriptions before deleting the associated user model: - $user->subscription('default')->cancelNow(); +```php +$user->subscription('default')->cancelNow(); - $user->delete(); +$user->delete(); +``` ### Resuming Subscriptions If a customer has canceled their subscription and you wish to resume it, you may invoke the `resume` method on the subscription. The customer must still be within their "grace period" in order to resume a subscription: - $user->subscription('default')->resume(); +```php +$user->subscription('default')->resume(); +``` If the customer cancels a subscription and then resumes that subscription before the subscription has fully expired the customer will not be billed immediately. Instead, their subscription will be re-activated and they will be billed on the original billing cycle. @@ -1503,15 +1735,17 @@ If the customer cancels a subscription and then resumes that subscription before If you would like to offer trial periods to your customers while still collecting payment method information up front, you should use the `trialDays` method when creating your subscriptions: - use Illuminate\Http\Request; +```php +use Illuminate\Http\Request; - Route::post('/user/subscribe', function (Request $request) { - $request->user()->newSubscription('default', 'price_monthly') - ->trialDays(10) - ->create($request->paymentMethodId); +Route::post('/user/subscribe', function (Request $request) { + $request->user()->newSubscription('default', 'price_monthly') + ->trialDays(10) + ->create($request->paymentMethodId); - // ... - }); + // ... +}); +``` This method will set the trial period ending date on the subscription record within the database and instruct Stripe to not begin billing the customer until after this date. When using the `trialDays` method, Cashier will overwrite any default trial period configured for the price in Stripe. @@ -1520,35 +1754,43 @@ This method will set the trial period ending date on the subscription record wit The `trialUntil` method allows you to provide a `DateTime` instance that specifies when the trial period should end: - use Carbon\Carbon; +```php +use Carbon\Carbon; - $user->newSubscription('default', 'price_monthly') - ->trialUntil(Carbon::now()->addDays(10)) - ->create($paymentMethod); +$user->newSubscription('default', 'price_monthly') + ->trialUntil(Carbon::now()->addDays(10)) + ->create($paymentMethod); +``` You may determine if a user is within their trial period using either the `onTrial` method of the user instance or the `onTrial` method of the subscription instance. The two examples below are equivalent: - if ($user->onTrial('default')) { - // ... - } +```php +if ($user->onTrial('default')) { + // ... +} - if ($user->subscription('default')->onTrial()) { - // ... - } +if ($user->subscription('default')->onTrial()) { + // ... +} +``` You may use the `endTrial` method to immediately end a subscription trial: - $user->subscription('default')->endTrial(); +```php +$user->subscription('default')->endTrial(); +``` To determine if an existing trial has expired, you may use the `hasExpiredTrial` methods: - if ($user->hasExpiredTrial('default')) { - // ... - } +```php +if ($user->hasExpiredTrial('default')) { + // ... +} - if ($user->subscription('default')->hasExpiredTrial()) { - // ... - } +if ($user->subscription('default')->hasExpiredTrial()) { + // ... +} +``` #### Defining Trial Days in Stripe / Cashier @@ -1560,58 +1802,70 @@ You may choose to define how many trial days your price's receive in the Stripe If you would like to offer trial periods without collecting the user's payment method information up front, you may set the `trial_ends_at` column on the user record to your desired trial ending date. This is typically done during user registration: - use App\Models\User; +```php +use App\Models\User; - $user = User::create([ - // ... - 'trial_ends_at' => now()->addDays(10), - ]); +$user = User::create([ + // ... + 'trial_ends_at' => now()->addDays(10), +]); +``` > [!WARNING] > Be sure to add a [date cast](/docs/{{version}}/eloquent-mutators#date-casting) for the `trial_ends_at` attribute within your billable model's class definition. Cashier refers to this type of trial as a "generic trial", since it is not attached to any existing subscription. The `onTrial` method on the billable model instance will return `true` if the current date is not past the value of `trial_ends_at`: - if ($user->onTrial()) { - // User is within their trial period... - } +```php +if ($user->onTrial()) { + // User is within their trial period... +} +``` Once you are ready to create an actual subscription for the user, you may use the `newSubscription` method as usual: - $user = User::find(1); +```php +$user = User::find(1); - $user->newSubscription('default', 'price_monthly')->create($paymentMethod); +$user->newSubscription('default', 'price_monthly')->create($paymentMethod); +``` To retrieve the user's trial ending date, you may use the `trialEndsAt` method. This method will return a Carbon date instance if a user is on a trial or `null` if they aren't. You may also pass an optional subscription type parameter if you would like to get the trial ending date for a specific subscription other than the default one: - if ($user->onTrial()) { - $trialEndsAt = $user->trialEndsAt('main'); - } +```php +if ($user->onTrial()) { + $trialEndsAt = $user->trialEndsAt('main'); +} +``` You may also use the `onGenericTrial` method if you wish to know specifically that the user is within their "generic" trial period and has not yet created an actual subscription: - if ($user->onGenericTrial()) { - // User is within their "generic" trial period... - } +```php +if ($user->onGenericTrial()) { + // User is within their "generic" trial period... +} +``` ### Extending Trials The `extendTrial` method allows you to extend the trial period of a subscription after the subscription has been created. If the trial has already expired and the customer is already being billed for the subscription, you can still offer them an extended trial. The time spent within the trial period will be deducted from the customer's next invoice: - use App\Models\User; +```php +use App\Models\User; - $subscription = User::find(1)->subscription('default'); +$subscription = User::find(1)->subscription('default'); - // End the trial 7 days from now... - $subscription->extendTrial( - now()->addDays(7) - ); +// End the trial 7 days from now... +$subscription->extendTrial( + now()->addDays(7) +); - // Add an additional 5 days to the trial... - $subscription->extendTrial( - $subscription->trial_ends_at->addDays(5) - ); +// Add an additional 5 days to the trial... +$subscription->extendTrial( + $subscription->trial_ends_at->addDays(5) +); +``` ## Handling Stripe Webhooks @@ -1666,11 +1920,13 @@ php artisan cashier:webhook --disabled Since Stripe webhooks need to bypass Laravel's [CSRF protection](/docs/{{version}}/csrf), you should ensure that Laravel does not attempt to validate the CSRF token for incoming Stripe webhooks. To accomplish this, you should exclude `stripe/*` from CSRF protection in your application's `bootstrap/app.php` file: - ->withMiddleware(function (Middleware $middleware) { - $middleware->validateCsrfTokens(except: [ - 'stripe/*', - ]); - }) +```php +->withMiddleware(function (Middleware $middleware) { + $middleware->validateCsrfTokens(except: [ + 'stripe/*', + ]); +}) +``` ### Defining Webhook Event Handlers @@ -1682,24 +1938,26 @@ Cashier automatically handles subscription cancellations for failed charges and Both events contain the full payload of the Stripe webhook. For example, if you wish to handle the `invoice.payment_succeeded` webhook, you may register a [listener](/docs/{{version}}/events#defining-listeners) that will handle the event: - payload['type'] === 'invoice.payment_succeeded') { - // Handle the incoming event... - } + if ($event->payload['type'] === 'invoice.payment_succeeded') { + // Handle the incoming event... } } +} +``` ### Verifying Webhook Signatures @@ -1716,35 +1974,43 @@ To enable webhook verification, ensure that the `STRIPE_WEBHOOK_SECRET` environm If you would like to make a one-time charge against a customer, you may use the `charge` method on a billable model instance. You will need to [provide a payment method identifier](#payment-methods-for-single-charges) as the second argument to the `charge` method: - use Illuminate\Http\Request; +```php +use Illuminate\Http\Request; - Route::post('/purchase', function (Request $request) { - $stripeCharge = $request->user()->charge( - 100, $request->paymentMethodId - ); +Route::post('/purchase', function (Request $request) { + $stripeCharge = $request->user()->charge( + 100, $request->paymentMethodId + ); - // ... - }); + // ... +}); +``` The `charge` method accepts an array as its third argument, allowing you to pass any options you wish to the underlying Stripe charge creation. More information regarding the options available to you when creating charges may be found in the [Stripe documentation](https://stripe.com/docs/api/charges/create): - $user->charge(100, $paymentMethod, [ - 'custom_option' => $value, - ]); +```php +$user->charge(100, $paymentMethod, [ + 'custom_option' => $value, +]); +``` You may also use the `charge` method without an underlying customer or user. To accomplish this, invoke the `charge` method on a new instance of your application's billable model: - use App\Models\User; +```php +use App\Models\User; - $stripeCharge = (new User)->charge(100, $paymentMethod); +$stripeCharge = (new User)->charge(100, $paymentMethod); +``` The `charge` method will throw an exception if the charge fails. If the charge is successful, an instance of `Laravel\Cashier\Payment` will be returned from the method: - try { - $payment = $user->charge(100, $paymentMethod); - } catch (Exception $e) { - // ... - } +```php +try { + $payment = $user->charge(100, $paymentMethod); +} catch (Exception $e) { + // ... +} +``` > [!WARNING] > The `charge` method accepts the payment amount in the lowest denominator of the currency used by your application. For example, if customers are paying in United States Dollars, amounts should be specified in pennies. @@ -1754,27 +2020,35 @@ The `charge` method will throw an exception if the charge fails. If the charge i Sometimes you may need to make a one-time charge and offer a PDF invoice to your customer. The `invoicePrice` method lets you do just that. For example, let's invoice a customer for five new shirts: - $user->invoicePrice('price_tshirt', 5); +```php +$user->invoicePrice('price_tshirt', 5); +``` The invoice will be immediately charged against the user's default payment method. The `invoicePrice` method also accepts an array as its third argument. This array contains the billing options for the invoice item. The fourth argument accepted by the method is also an array which should contain the billing options for the invoice itself: - $user->invoicePrice('price_tshirt', 5, [ - 'discounts' => [ - ['coupon' => 'SUMMER21SALE'] - ], - ], [ - 'default_tax_rates' => ['txr_id'], - ]); +```php +$user->invoicePrice('price_tshirt', 5, [ + 'discounts' => [ + ['coupon' => 'SUMMER21SALE'] + ], +], [ + 'default_tax_rates' => ['txr_id'], +]); +``` Similarly to `invoicePrice`, you may use the `tabPrice` method to create a one-time charge for multiple items (up to 250 items per invoice) by adding them to the customer's "tab" and then invoicing the customer. For example, we may invoice a customer for five shirts and two mugs: - $user->tabPrice('price_tshirt', 5); - $user->tabPrice('price_mug', 2); - $user->invoice(); +```php +$user->tabPrice('price_tshirt', 5); +$user->tabPrice('price_mug', 2); +$user->invoice(); +``` Alternatively, you may use the `invoiceFor` method to make a "one-off" charge against the customer's default payment method: - $user->invoiceFor('One Time Fee', 500); +```php +$user->invoiceFor('One Time Fee', 500); +``` Although the `invoiceFor` method is available for you to use, it is recommended that you use the `invoicePrice` and `tabPrice` methods with pre-defined prices. By doing so, you will have access to better analytics and data within your Stripe dashboard regarding your sales on a per-product basis. @@ -1786,29 +2060,33 @@ Although the `invoiceFor` method is available for you to use, it is recommended You can create a new Stripe payment intent by invoking the `pay` method on a billable model instance. Calling this method will create a payment intent that is wrapped in a `Laravel\Cashier\Payment` instance: - use Illuminate\Http\Request; +```php +use Illuminate\Http\Request; - Route::post('/pay', function (Request $request) { - $payment = $request->user()->pay( - $request->get('amount') - ); +Route::post('/pay', function (Request $request) { + $payment = $request->user()->pay( + $request->get('amount') + ); - return $payment->client_secret; - }); + return $payment->client_secret; +}); +``` After creating the payment intent, you can return the client secret to your application's frontend so that the user can complete the payment in their browser. To read more about building entire payment flows using Stripe payment intents, please consult the [Stripe documentation](https://stripe.com/docs/payments/accept-a-payment?platform=web). When using the `pay` method, the default payment methods that are enabled within your Stripe dashboard will be available to the customer. Alternatively, if you only want to allow for some specific payment methods to be used, you may use the `payWith` method: - use Illuminate\Http\Request; +```php +use Illuminate\Http\Request; - Route::post('/pay', function (Request $request) { - $payment = $request->user()->payWith( - $request->get('amount'), ['card', 'bancontact'] - ); +Route::post('/pay', function (Request $request) { + $payment = $request->user()->payWith( + $request->get('amount'), ['card', 'bancontact'] + ); - return $payment->client_secret; - }); + return $payment->client_secret; +}); +``` > [!WARNING] > The `pay` and `payWith` methods accept the payment amount in the lowest denominator of the currency used by your application. For example, if customers are paying in United States Dollars, amounts should be specified in pennies. @@ -1818,9 +2096,11 @@ When using the `pay` method, the default payment methods that are enabled within If you need to refund a Stripe charge, you may use the `refund` method. This method accepts the Stripe [payment intent ID](#payment-methods-for-single-charges) as its first argument: - $payment = $user->charge(100, $paymentMethodId); +```php +$payment = $user->charge(100, $paymentMethodId); - $user->refund($payment->id); +$user->refund($payment->id); +``` ## Invoices @@ -1830,52 +2110,68 @@ If you need to refund a Stripe charge, you may use the `refund` method. This met You may easily retrieve an array of a billable model's invoices using the `invoices` method. The `invoices` method returns a collection of `Laravel\Cashier\Invoice` instances: - $invoices = $user->invoices(); +```php +$invoices = $user->invoices(); +``` If you would like to include pending invoices in the results, you may use the `invoicesIncludingPending` method: - $invoices = $user->invoicesIncludingPending(); +```php +$invoices = $user->invoicesIncludingPending(); +``` You may use the `findInvoice` method to retrieve a specific invoice by its ID: - $invoice = $user->findInvoice($invoiceId); +```php +$invoice = $user->findInvoice($invoiceId); +``` #### Displaying Invoice Information When listing the invoices for the customer, you may use the invoice's methods to display the relevant invoice information. For example, you may wish to list every invoice in a table, allowing the user to easily download any of them: - - @foreach ($invoices as $invoice) - - - - - - @endforeach -
{{ $invoice->date()->toFormattedDateString() }}{{ $invoice->total() }}Download
+```blade + + @foreach ($invoices as $invoice) + + + + + + @endforeach +
{{ $invoice->date()->toFormattedDateString() }}{{ $invoice->total() }}Download
+``` ### Upcoming Invoices To retrieve the upcoming invoice for a customer, you may use the `upcomingInvoice` method: - $invoice = $user->upcomingInvoice(); +```php +$invoice = $user->upcomingInvoice(); +``` Similarly, if the customer has multiple subscriptions, you can also retrieve the upcoming invoice for a specific subscription: - $invoice = $user->subscription('default')->upcomingInvoice(); +```php +$invoice = $user->subscription('default')->upcomingInvoice(); +``` ### Previewing Subscription Invoices Using the `previewInvoice` method, you can preview an invoice before making price changes. This will allow you to determine what your customer's invoice will look like when a given price change is made: - $invoice = $user->subscription('default')->previewInvoice('price_yearly'); +```php +$invoice = $user->subscription('default')->previewInvoice('price_yearly'); +``` You may pass an array of prices to the `previewInvoice` method in order to preview invoices with multiple new prices: - $invoice = $user->subscription('default')->previewInvoice(['price_yearly', 'price_metered']); +```php +$invoice = $user->subscription('default')->previewInvoice(['price_yearly', 'price_metered']); +``` ### Generating Invoice PDFs @@ -1888,50 +2184,56 @@ composer require dompdf/dompdf From within a route or controller, you may use the `downloadInvoice` method to generate a PDF download of a given invoice. This method will automatically generate the proper HTTP response needed to download the invoice: - use Illuminate\Http\Request; +```php +use Illuminate\Http\Request; - Route::get('/user/invoice/{invoice}', function (Request $request, string $invoiceId) { - return $request->user()->downloadInvoice($invoiceId); - }); +Route::get('/user/invoice/{invoice}', function (Request $request, string $invoiceId) { + return $request->user()->downloadInvoice($invoiceId); +}); +``` By default, all data on the invoice is derived from the customer and invoice data stored in Stripe. The filename is based on your `app.name` config value. However, you can customize some of this data by providing an array as the second argument to the `downloadInvoice` method. This array allows you to customize information such as your company and product details: - return $request->user()->downloadInvoice($invoiceId, [ - 'vendor' => 'Your Company', - 'product' => 'Your Product', - 'street' => 'Main Str. 1', - 'location' => '2000 Antwerp, Belgium', - 'phone' => '+32 499 00 00 00', - 'email' => 'info@example.com', - 'url' => 'https://example.com', - 'vendorVat' => 'BE123456789', - ]); +```php +return $request->user()->downloadInvoice($invoiceId, [ + 'vendor' => 'Your Company', + 'product' => 'Your Product', + 'street' => 'Main Str. 1', + 'location' => '2000 Antwerp, Belgium', + 'phone' => '+32 499 00 00 00', + 'email' => 'info@example.com', + 'url' => 'https://example.com', + 'vendorVat' => 'BE123456789', +]); +``` The `downloadInvoice` method also allows for a custom filename via its third argument. This filename will automatically be suffixed with `.pdf`: - return $request->user()->downloadInvoice($invoiceId, [], 'my-invoice'); +```php +return $request->user()->downloadInvoice($invoiceId, [], 'my-invoice'); #### Custom Invoice Renderer Cashier also makes it possible to use a custom invoice renderer. By default, Cashier uses the `DompdfInvoiceRenderer` implementation, which utilizes the [dompdf](https://github.com/dompdf/dompdf) PHP library to generate Cashier's invoices. However, you may use any renderer you wish by implementing the `Laravel\Cashier\Contracts\InvoiceRenderer` interface. For example, you may wish to render an invoice PDF using an API call to a third-party PDF rendering service: - use Illuminate\Support\Facades\Http; - use Laravel\Cashier\Contracts\InvoiceRenderer; - use Laravel\Cashier\Invoice; +use Illuminate\Support\Facades\Http; +use Laravel\Cashier\Contracts\InvoiceRenderer; +use Laravel\Cashier\Invoice; - class ApiInvoiceRenderer implements InvoiceRenderer +class ApiInvoiceRenderer implements InvoiceRenderer +{ + /** + * Render the given invoice and return the raw PDF bytes. + */ + public function render(Invoice $invoice, array $data = [], array $options = []): string { - /** - * Render the given invoice and return the raw PDF bytes. - */ - public function render(Invoice $invoice, array $data = [], array $options = []): string - { - $html = $invoice->view($data)->render(); - - return Http::get('https://example.com/html-to-pdf', ['html' => $html])->get()->body(); - } + $html = $invoice->view($data)->render(); + + return Http::get('https://example.com/html-to-pdf', ['html' => $html])->get()->body(); } +} +``` Once you have implemented the invoice renderer contract, you should update the `cashier.invoices.renderer` configuration value in your application's `config/cashier.php` configuration file. This configuration value should be set to the class name of your custom renderer implementation. @@ -1947,73 +2249,85 @@ The following documentation contains information on how to get started using Str You may perform a checkout for an existing product that has been created within your Stripe dashboard using the `checkout` method on a billable model. The `checkout` method will initiate a new Stripe Checkout session. By default, you're required to pass a Stripe Price ID: - use Illuminate\Http\Request; +```php +use Illuminate\Http\Request; - Route::get('/product-checkout', function (Request $request) { - return $request->user()->checkout('price_tshirt'); - }); +Route::get('/product-checkout', function (Request $request) { + return $request->user()->checkout('price_tshirt'); +}); +``` If needed, you may also specify a product quantity: - use Illuminate\Http\Request; +```php +use Illuminate\Http\Request; - Route::get('/product-checkout', function (Request $request) { - return $request->user()->checkout(['price_tshirt' => 15]); - }); +Route::get('/product-checkout', function (Request $request) { + return $request->user()->checkout(['price_tshirt' => 15]); +}); +``` When a customer visits this route they will be redirected to Stripe's Checkout page. By default, when a user successfully completes or cancels a purchase they will be redirected to your `home` route location, but you may specify custom callback URLs using the `success_url` and `cancel_url` options: - use Illuminate\Http\Request; +```php +use Illuminate\Http\Request; - Route::get('/product-checkout', function (Request $request) { - return $request->user()->checkout(['price_tshirt' => 1], [ - 'success_url' => route('your-success-route'), - 'cancel_url' => route('your-cancel-route'), - ]); - }); +Route::get('/product-checkout', function (Request $request) { + return $request->user()->checkout(['price_tshirt' => 1], [ + 'success_url' => route('your-success-route'), + 'cancel_url' => route('your-cancel-route'), + ]); +}); +``` When defining your `success_url` checkout option, you may instruct Stripe to add the checkout session ID as a query string parameter when invoking your URL. To do so, add the literal string `{CHECKOUT_SESSION_ID}` to your `success_url` query string. Stripe will replace this placeholder with the actual checkout session ID: - use Illuminate\Http\Request; - use Stripe\Checkout\Session; - use Stripe\Customer; +```php +use Illuminate\Http\Request; +use Stripe\Checkout\Session; +use Stripe\Customer; - Route::get('/product-checkout', function (Request $request) { - return $request->user()->checkout(['price_tshirt' => 1], [ - 'success_url' => route('checkout-success').'?session_id={CHECKOUT_SESSION_ID}', - 'cancel_url' => route('checkout-cancel'), - ]); - }); +Route::get('/product-checkout', function (Request $request) { + return $request->user()->checkout(['price_tshirt' => 1], [ + 'success_url' => route('checkout-success').'?session_id={CHECKOUT_SESSION_ID}', + 'cancel_url' => route('checkout-cancel'), + ]); +}); - Route::get('/checkout-success', function (Request $request) { - $checkoutSession = $request->user()->stripe()->checkout->sessions->retrieve($request->get('session_id')); +Route::get('/checkout-success', function (Request $request) { + $checkoutSession = $request->user()->stripe()->checkout->sessions->retrieve($request->get('session_id')); - return view('checkout.success', ['checkoutSession' => $checkoutSession]); - })->name('checkout-success'); + return view('checkout.success', ['checkoutSession' => $checkoutSession]); +})->name('checkout-success'); +``` #### Promotion Codes By default, Stripe Checkout does not allow [user redeemable promotion codes](https://stripe.com/docs/billing/subscriptions/discounts/codes). Luckily, there's an easy way to enable these for your Checkout page. To do so, you may invoke the `allowPromotionCodes` method: - use Illuminate\Http\Request; +```php +use Illuminate\Http\Request; - Route::get('/product-checkout', function (Request $request) { - return $request->user() - ->allowPromotionCodes() - ->checkout('price_tshirt'); - }); +Route::get('/product-checkout', function (Request $request) { + return $request->user() + ->allowPromotionCodes() + ->checkout('price_tshirt'); +}); +``` ### Single Charge Checkouts You can also perform a simple charge for an ad-hoc product that has not been created in your Stripe dashboard. To do so you may use the `checkoutCharge` method on a billable model and pass it a chargeable amount, a product name, and an optional quantity. When a customer visits this route they will be redirected to Stripe's Checkout page: - use Illuminate\Http\Request; +```php +use Illuminate\Http\Request; - Route::get('/charge-checkout', function (Request $request) { - return $request->user()->checkoutCharge(1200, 'T-Shirt', 5); - }); +Route::get('/charge-checkout', function (Request $request) { + return $request->user()->checkoutCharge(1200, 'T-Shirt', 5); +}); +``` > [!WARNING] > When using the `checkoutCharge` method, Stripe will always create a new product and price in your Stripe dashboard. Therefore, we recommend that you create the products up front in your Stripe dashboard and use the `checkout` method instead. @@ -2026,37 +2340,43 @@ You can also perform a simple charge for an ad-hoc product that has not been cre You may also use Stripe Checkout to initiate subscriptions. After defining your subscription with Cashier's subscription builder methods, you may call the `checkout `method. When a customer visits this route they will be redirected to Stripe's Checkout page: - use Illuminate\Http\Request; +```php +use Illuminate\Http\Request; - Route::get('/subscription-checkout', function (Request $request) { - return $request->user() - ->newSubscription('default', 'price_monthly') - ->checkout(); - }); +Route::get('/subscription-checkout', function (Request $request) { + return $request->user() + ->newSubscription('default', 'price_monthly') + ->checkout(); +}); +``` Just as with product checkouts, you may customize the success and cancellation URLs: - use Illuminate\Http\Request; +```php +use Illuminate\Http\Request; - Route::get('/subscription-checkout', function (Request $request) { - return $request->user() - ->newSubscription('default', 'price_monthly') - ->checkout([ - 'success_url' => route('your-success-route'), - 'cancel_url' => route('your-cancel-route'), - ]); - }); +Route::get('/subscription-checkout', function (Request $request) { + return $request->user() + ->newSubscription('default', 'price_monthly') + ->checkout([ + 'success_url' => route('your-success-route'), + 'cancel_url' => route('your-cancel-route'), + ]); +}); +``` Of course, you can also enable promotion codes for subscription checkouts: - use Illuminate\Http\Request; +```php +use Illuminate\Http\Request; - Route::get('/subscription-checkout', function (Request $request) { - return $request->user() - ->newSubscription('default', 'price_monthly') - ->allowPromotionCodes() - ->checkout(); - }); +Route::get('/subscription-checkout', function (Request $request) { + return $request->user() + ->newSubscription('default', 'price_monthly') + ->allowPromotionCodes() + ->checkout(); +}); +``` > [!WARNING] > Unfortunately Stripe Checkout does not support all subscription billing options when starting subscriptions. Using the `anchorBillingCycleOn` method on the subscription builder, setting proration behavior, or setting payment behavior will not have any effect during Stripe Checkout sessions. Please consult [the Stripe Checkout Session API documentation](https://stripe.com/docs/api/checkout/sessions/create) to review which parameters are available. @@ -2066,9 +2386,11 @@ Of course, you can also enable promotion codes for subscription checkouts: Of course, you can define a trial period when building a subscription that will be completed using Stripe Checkout: - $checkout = Auth::user()->newSubscription('default', 'price_monthly') - ->trialDays(3) - ->checkout(); +```php +$checkout = Auth::user()->newSubscription('default', 'price_monthly') + ->trialDays(3) + ->checkout(); +``` However, the trial period must be at least 48 hours, which is the minimum amount of trial time supported by Stripe Checkout. @@ -2082,7 +2404,9 @@ Remember, Stripe and Cashier update subscription statuses via webhooks, so there Checkout also supports collecting a customer's Tax ID. To enable this on a checkout session, invoke the `collectTaxIds` method when creating the session: - $checkout = $user->collectTaxIds()->checkout('price_tshirt'); +```php +$checkout = $user->collectTaxIds()->checkout('price_tshirt'); +``` When this method is invoked, a new checkbox will be available to the customer that allows them to indicate if they're purchasing as a company. If so, they will have the opportunity to provide their Tax ID number. @@ -2094,29 +2418,33 @@ When this method is invoked, a new checkbox will be available to the customer th Using the `Checkout::guest` method, you may initiate checkout sessions for guests of your application that do not have an "account": - use Illuminate\Http\Request; - use Laravel\Cashier\Checkout; +```php +use Illuminate\Http\Request; +use Laravel\Cashier\Checkout; - Route::get('/product-checkout', function (Request $request) { - return Checkout::guest()->create('price_tshirt', [ - 'success_url' => route('your-success-route'), - 'cancel_url' => route('your-cancel-route'), - ]); - }); +Route::get('/product-checkout', function (Request $request) { + return Checkout::guest()->create('price_tshirt', [ + 'success_url' => route('your-success-route'), + 'cancel_url' => route('your-cancel-route'), + ]); +}); +``` Similarly to when creating checkout sessions for existing users, you may utilize additional methods available on the `Laravel\Cashier\CheckoutBuilder` instance to customize the guest checkout session: - use Illuminate\Http\Request; - use Laravel\Cashier\Checkout; +```php +use Illuminate\Http\Request; +use Laravel\Cashier\Checkout; - Route::get('/product-checkout', function (Request $request) { - return Checkout::guest() - ->withPromotionCode('promo-code') - ->create('price_tshirt', [ - 'success_url' => route('your-success-route'), - 'cancel_url' => route('your-cancel-route'), - ]); - }); +Route::get('/product-checkout', function (Request $request) { + return Checkout::guest() + ->withPromotionCode('promo-code') + ->create('price_tshirt', [ + 'success_url' => route('your-success-route'), + 'cancel_url' => route('your-cancel-route'), + ]); +}); +``` After a guest checkout has been completed, Stripe can dispatch a `checkout.session.completed` webhook event, so make sure to [configure your Stripe webhook](https://dashboard.stripe.com/webhooks) to actually send this event to your application. Once the webhook has been enabled within the Stripe dashboard, you may [handle the webhook with Cashier](#handling-stripe-webhooks). The object contained in the webhook payload will be a [`checkout` object](https://stripe.com/docs/api/checkout/sessions/object) that you may inspect in order to fulfill your customer's order. @@ -2127,17 +2455,19 @@ Sometimes, payments for subscriptions or single charges can fail. When this happ First, you could redirect your customer to the dedicated payment confirmation page which is included with Cashier. This page already has an associated named route that is registered via Cashier's service provider. So, you may catch the `IncompletePayment` exception and redirect the user to the payment confirmation page: - use Laravel\Cashier\Exceptions\IncompletePayment; +```php +use Laravel\Cashier\Exceptions\IncompletePayment; - try { - $subscription = $user->newSubscription('default', 'price_monthly') - ->create($paymentMethod); - } catch (IncompletePayment $exception) { - return redirect()->route( - 'cashier.payment', - [$exception->payment->id, 'redirect' => route('home')] - ); - } +try { + $subscription = $user->newSubscription('default', 'price_monthly') + ->create($paymentMethod); +} catch (IncompletePayment $exception) { + return redirect()->route( + 'cashier.payment', + [$exception->payment->id, 'redirect' => route('home')] + ); +} +``` On the payment confirmation page, the customer will be prompted to enter their credit card information again and perform any additional actions required by Stripe, such as "3D Secure" confirmation. After confirming their payment, the user will be redirected to the URL provided by the `redirect` parameter specified above. Upon redirection, `message` (string) and `success` (integer) query string variables will be added to the URL. The payment page currently supports the following payment method types: @@ -2160,40 +2490,46 @@ Payment exceptions may be thrown for the following methods: `charge`, `invoiceFo Determining if an existing subscription has an incomplete payment may be accomplished using the `hasIncompletePayment` method on the billable model or a subscription instance: - if ($user->hasIncompletePayment('default')) { - // ... - } +```php +if ($user->hasIncompletePayment('default')) { + // ... +} - if ($user->subscription('default')->hasIncompletePayment()) { - // ... - } +if ($user->subscription('default')->hasIncompletePayment()) { + // ... +} +``` You can derive the specific status of an incomplete payment by inspecting the `payment` property on the exception instance: - use Laravel\Cashier\Exceptions\IncompletePayment; +```php +use Laravel\Cashier\Exceptions\IncompletePayment; - try { - $user->charge(1000, 'pm_card_threeDSecure2Required'); - } catch (IncompletePayment $exception) { - // Get the payment intent status... - $exception->payment->status; +try { + $user->charge(1000, 'pm_card_threeDSecure2Required'); +} catch (IncompletePayment $exception) { + // Get the payment intent status... + $exception->payment->status; - // Check specific conditions... - if ($exception->payment->requiresPaymentMethod()) { - // ... - } elseif ($exception->payment->requiresConfirmation()) { - // ... - } + // Check specific conditions... + if ($exception->payment->requiresPaymentMethod()) { + // ... + } elseif ($exception->payment->requiresConfirmation()) { + // ... } +} +``` ### Confirming Payments Some payment methods require additional data in order to confirm payments. For example, SEPA payment methods require additional "mandate" data during the payment process. You may provide this data to Cashier using the `withPaymentConfirmationOptions` method: - $subscription->withPaymentConfirmationOptions([ - 'mandate_data' => '...', - ])->swap('price_xxx'); +```php +$subscription->withPaymentConfirmationOptions([ + 'mandate_data' => '...', +])->swap('price_xxx'); +``` You may consult the [Stripe API documentation](https://stripe.com/docs/api/payment_intents/confirm) to review all of the options accepted when confirming payments. @@ -2238,21 +2574,27 @@ To ensure that off-session payment confirmation notifications are delivered, ver Many of Cashier's objects are wrappers around Stripe SDK objects. If you would like to interact with the Stripe objects directly, you may conveniently retrieve them using the `asStripe` method: - $stripeSubscription = $subscription->asStripeSubscription(); +```php +$stripeSubscription = $subscription->asStripeSubscription(); - $stripeSubscription->application_fee_percent = 5; +$stripeSubscription->application_fee_percent = 5; - $stripeSubscription->save(); +$stripeSubscription->save(); +``` You may also use the `updateStripeSubscription` method to update a Stripe subscription directly: - $subscription->updateStripeSubscription(['application_fee_percent' => 5]); +```php +$subscription->updateStripeSubscription(['application_fee_percent' => 5]); +``` You may invoke the `stripe` method on the `Cashier` class if you would like to use the `Stripe\StripeClient` client directly. For example, you could use this method to access the `StripeClient` instance and retrieve a list of prices from your Stripe account: - use Laravel\Cashier\Cashier; +```php +use Laravel\Cashier\Cashier; - $prices = Cashier::stripe()->prices->all(); +$prices = Cashier::stripe()->prices->all(); +``` ## Testing @@ -2263,7 +2605,9 @@ When testing, remember that Cashier itself already has a great test suite, so yo To get started, add the **testing** version of your Stripe secret to your `phpunit.xml` file: - +```xml + +``` Now, whenever you interact with Cashier while testing, it will send actual API requests to your Stripe testing environment. For convenience, you should pre-fill your Stripe testing account with subscriptions / prices that you may use during testing. diff --git a/blade.md b/blade.md index a6733cafe3a..6d95e614428 100644 --- a/blade.md +++ b/blade.md @@ -53,9 +53,11 @@ Blade is the simple, yet powerful templating engine that is included with Larave Blade views may be returned from routes or controllers using the global `view` helper. Of course, as mentioned in the documentation on [views](/docs/{{version}}/views), data may be passed to the Blade view using the `view` helper's second argument: - Route::get('/', function () { - return view('greeting', ['name' => 'Finn']); - }); +```php +Route::get('/', function () { + return view('greeting', ['name' => 'Finn']); +}); +``` ### Supercharging Blade With Livewire @@ -67,9 +69,11 @@ Want to take your Blade templates to the next level and build dynamic interfaces You may display data that is passed to your Blade views by wrapping the variable in curly braces. For example, given the following route: - Route::get('/', function () { - return view('welcome', ['name' => 'Samantha']); - }); +```php +Route::get('/', function () { + return view('welcome', ['name' => 'Samantha']); +}); +``` You may display the contents of the `name` variable like so: @@ -91,23 +95,25 @@ The current UNIX timestamp is {{ time() }}. By default, Blade (and the Laravel `e` function) will double encode HTML entities. If you would like to disable double encoding, call the `Blade::withoutDoubleEncoding` method from the `boot` method of your `AppServiceProvider`: - #### Displaying Unescaped Data @@ -691,15 +697,17 @@ When writing components for your own application, components are automatically d However, if you are building a package that utilizes Blade components, you will need to manually register your component class and its HTML tag alias. You should typically register your components in the `boot` method of your package's service provider: - use Illuminate\Support\Facades\Blade; +```php +use Illuminate\Support\Facades\Blade; - /** - * Bootstrap your package's services. - */ - public function boot(): void - { - Blade::component('package-alert', Alert::class); - } +/** + * Bootstrap your package's services. + */ +public function boot(): void +{ + Blade::component('package-alert', Alert::class); +} +``` Once your component has been registered, it may be rendered using its tag alias: @@ -709,15 +717,17 @@ Once your component has been registered, it may be rendered using its tag alias: Alternatively, you may use the `componentNamespace` method to autoload component classes by convention. For example, a `Nightshade` package might have `Calendar` and `ColorPicker` components that reside within the `Package\Views\Components` namespace: - use Illuminate\Support\Facades\Blade; +```php +use Illuminate\Support\Facades\Blade; - /** - * Bootstrap your package's services. - */ - public function boot(): void - { - Blade::componentNamespace('Nightshade\\Views\\Components', 'nightshade'); - } +/** + * Bootstrap your package's services. + */ +public function boot(): void +{ + Blade::componentNamespace('Nightshade\\Views\\Components', 'nightshade'); +} +``` This will allow the usage of package components by their vendor namespace using the `package-name::` syntax: @@ -747,15 +757,17 @@ If the component class is nested deeper within the `app/View/Components` directo If you would like to conditionally render your component, you may define a `shouldRender` method on your component class. If the `shouldRender` method returns `false` the component will not be rendered: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - /** - * Whether the component should be rendered - */ - public function shouldRender(): bool - { - return Str::length($this->message) > 0; - } +/** + * Whether the component should be rendered + */ +public function shouldRender(): bool +{ + return Str::length($this->message) > 0; +} +``` ### Index Components @@ -788,31 +800,33 @@ You may pass data to Blade components using HTML attributes. Hard-coded, primiti You should define all of the component's data attributes in its class constructor. All public properties on a component will automatically be made available to the component's view. It is not necessary to pass the data to the view from the component's `render` method: - selected; - } +```php +/** + * Determine if the given option is the currently selected option. + */ +public function isSelected(string $option): bool +{ + return $option === $this->selected; +} +``` You may execute this method from your component template by invoking the variable matching the name of the method: @@ -898,27 +916,31 @@ You may execute this method from your component template by invoking the variabl Blade components also allow you to access the component name, attributes, and slot inside the class's render method. However, in order to access this data, you should return a closure from your component's `render` method: - use Closure; +```php +use Closure; - /** - * Get the view / contents that represent the component. - */ - public function render(): Closure - { - return function () { - return '
Components content
'; - }; - } +/** + * Get the view / contents that represent the component. + */ +public function render(): Closure +{ + return function () { + return '
Components content
'; + }; +} +``` The closure returned by your component's `render` method may also receive a `$data` array as its only argument. This array will contain several elements that provide information about the component: - return function (array $data) { - // $data['componentName']; - // $data['attributes']; - // $data['slot']; +```php +return function (array $data) { + // $data['componentName']; + // $data['attributes']; + // $data['slot']; - return '
Components content
'; - } + return '
Components content
'; +} +``` > [!WARNING] > The elements in the `$data` array should never be directly embedded into the Blade string returned by your `render` method, as doing so could allow remote code execution via malicious attribute content. @@ -950,28 +972,30 @@ public function __construct( If you would like to prevent some public methods or properties from being exposed as variables to your component template, you may add them to an `$except` array property on your component: - ### Component Attributes @@ -1276,17 +1300,19 @@ To interact with slot attributes, you may access the `attributes` property of th For very small components, it may feel cumbersome to manage both the component class and the component's view template. For this reason, you may return the component's markup directly from the `render` method: - /** - * Get the view / contents that represent the component. - */ - public function render(): string - { - return <<<'blade' -
- {{ $slot }} -
- blade; - } +```php +/** + * Get the view / contents that represent the component. + */ +public function render(): string +{ + return <<<'blade' +
+ {{ $slot }} +
+ blade; +} +``` #### Generating Inline View Components @@ -1318,16 +1344,18 @@ When writing components for your own application, components are automatically d However, if you are building a package that utilizes Blade components or placing components in non-conventional directories, you will need to manually register your component class and its HTML tag alias so that Laravel knows where to find the component. You should typically register your components in the `boot` method of your package's service provider: - use Illuminate\Support\Facades\Blade; - use VendorPackage\View\Components\AlertComponent; +```php +use Illuminate\Support\Facades\Blade; +use VendorPackage\View\Components\AlertComponent; - /** - * Bootstrap your package's services. - */ - public function boot(): void - { - Blade::component('package-alert', AlertComponent::class); - } +/** + * Bootstrap your package's services. + */ +public function boot(): void +{ + Blade::component('package-alert', AlertComponent::class); +} +``` Once your component has been registered, it may be rendered using its tag alias: @@ -1339,15 +1367,17 @@ Once your component has been registered, it may be rendered using its tag alias: Alternatively, you may use the `componentNamespace` method to autoload component classes by convention. For example, a `Nightshade` package might have `Calendar` and `ColorPicker` components that reside within the `Package\Views\Components` namespace: - use Illuminate\Support\Facades\Blade; +```php +use Illuminate\Support\Facades\Blade; - /** - * Bootstrap your package's services. - */ - public function boot(): void - { - Blade::componentNamespace('Nightshade\\Views\\Components', 'nightshade'); - } +/** + * Bootstrap your package's services. + */ +public function boot(): void +{ + Blade::componentNamespace('Nightshade\\Views\\Components', 'nightshade'); +} +``` This will allow the usage of package components by their vendor namespace using the `package-name::` syntax: @@ -1471,13 +1501,15 @@ As previously discussed, anonymous components are typically defined by placing a The `anonymousComponentPath` method accepts the "path" to the anonymous component location as its first argument and an optional "namespace" that components should be placed under as its second argument. Typically, this method should be called from the `boot` method of one of your application's [service providers](/docs/{{version}}/providers): - /** - * Bootstrap any application services. - */ - public function boot(): void - { - Blade::anonymousComponentPath(__DIR__.'/../components'); - } +```php +/** + * Bootstrap any application services. + */ +public function boot(): void +{ + Blade::anonymousComponentPath(__DIR__.'/../components'); +} +``` When component paths are registered without a specified prefix as in the example above, they may be rendered in your Blade components without a corresponding prefix as well. For example, if a `panel.blade.php` component exists in the path registered above, it may be rendered like so: @@ -1487,7 +1519,9 @@ When component paths are registered without a specified prefix as in the example Prefix "namespaces" may be provided as the second argument to the `anonymousComponentPath` method: - Blade::anonymousComponentPath(__DIR__.'/../components', 'dashboard'); +```php +Blade::anonymousComponentPath(__DIR__.'/../components', 'dashboard'); +``` When a prefix is provided, components within that "namespace" may be rendered by prefixing to the component's namespace to the component name when the component is rendered: @@ -1556,11 +1590,13 @@ Remember, content that is injected into a component will be supplied to the defa Now that we have defined our layout and task list views, we just need to return the `task` view from a route: - use App\Models\Task; +```php +use App\Models\Task; - Route::get('/tasks', function () { - return view('tasks', ['tasks' => Task::all()]); - }); +Route::get('/tasks', function () { + return view('tasks', ['tasks' => Task::all()]); +}); +``` ### Layouts Using Template Inheritance @@ -1836,37 +1872,41 @@ Blade allows you to define your own custom directives using the `directive` meth The following example creates a `@datetime($var)` directive which formats a given `$var`, which should be an instance of `DateTime`: - format('m/d/Y H:i'); ?>"; - }); - } + // ... } + /** + * Bootstrap any application services. + */ + public function boot(): void + { + Blade::directive('datetime', function (string $expression) { + return "format('m/d/Y H:i'); ?>"; + }); + } +} +``` + As you can see, we will chain the `format` method onto whatever expression is passed into the directive. So, in this example, the final PHP generated by this directive will be: - format('m/d/Y H:i'); ?> +```php +format('m/d/Y H:i'); ?> +``` > [!WARNING] > After updating the logic of a Blade directive, you will need to delete all of the cached Blade views. The cached Blade views may be removed using the `view:clear` Artisan command. @@ -1878,18 +1918,20 @@ If you attempt to "echo" an object using Blade, the object's `__toString` method In these cases, Blade allows you to register a custom echo handler for that particular type of object. To accomplish this, you should invoke Blade's `stringable` method. The `stringable` method accepts a closure. This closure should type-hint the type of object that it is responsible for rendering. Typically, the `stringable` method should be invoked within the `boot` method of your application's `AppServiceProvider` class: - use Illuminate\Support\Facades\Blade; - use Money\Money; +```php +use Illuminate\Support\Facades\Blade; +use Money\Money; - /** - * Bootstrap any application services. - */ - public function boot(): void - { - Blade::stringable(function (Money $money) { - return $money->formatTo('en_GB'); - }); - } +/** + * Bootstrap any application services. + */ +public function boot(): void +{ + Blade::stringable(function (Money $money) { + return $money->formatTo('en_GB'); + }); +} +``` Once your custom echo handler has been defined, you may simply echo the object in your Blade template: @@ -1902,17 +1944,19 @@ Cost: {{ $money }} Programming a custom directive is sometimes more complex than necessary when defining simple, custom conditional statements. For that reason, Blade provides a `Blade::if` method which allows you to quickly define custom conditional directives using closures. For example, let's define a custom conditional that checks the configured default "disk" for the application. We may do this in the `boot` method of our `AppServiceProvider`: - use Illuminate\Support\Facades\Blade; +```php +use Illuminate\Support\Facades\Blade; - /** - * Bootstrap any application services. - */ - public function boot(): void - { - Blade::if('disk', function (string $value) { - return config('filesystems.default') === $value; - }); - } +/** + * Bootstrap any application services. + */ +public function boot(): void +{ + Blade::if('disk', function (string $value) { + return config('filesystems.default') === $value; + }); +} +``` Once the custom conditional has been defined, you can use it within your templates: diff --git a/broadcasting.md b/broadcasting.md index f8aa38019f1..cc6b664619a 100644 --- a/broadcasting.md +++ b/broadcasting.md @@ -336,77 +336,87 @@ Before diving into each component of event broadcasting, let's take a high level In our application, let's assume we have a page that allows users to view the shipping status for their orders. Let's also assume that an `OrderShipmentStatusUpdated` event is fired when a shipping status update is processed by the application: - use App\Events\OrderShipmentStatusUpdated; +```php +use App\Events\OrderShipmentStatusUpdated; - OrderShipmentStatusUpdated::dispatch($order); +OrderShipmentStatusUpdated::dispatch($order); +``` #### The `ShouldBroadcast` Interface When a user is viewing one of their orders, we don't want them to have to refresh the page to view status updates. Instead, we want to broadcast the updates to the application as they are created. So, we need to mark the `OrderShipmentStatusUpdated` event with the `ShouldBroadcast` interface. This will instruct Laravel to broadcast the event when it is fired: - order->id); - } +/** + * Get the channel the event should broadcast on. + */ +public function broadcastOn(): Channel +{ + return new PrivateChannel('orders.'.$this->order->id); +} +``` If you wish the event to broadcast on multiple channels, you may return an `array` instead: - use Illuminate\Broadcasting\PrivateChannel; +```php +use Illuminate\Broadcasting\PrivateChannel; - /** - * Get the channels the event should broadcast on. - * - * @return array - */ - public function broadcastOn(): array - { - return [ - new PrivateChannel('orders.'.$this->order->id), - // ... - ]; - } +/** + * Get the channels the event should broadcast on. + * + * @return array + */ +public function broadcastOn(): array +{ + return [ + new PrivateChannel('orders.'.$this->order->id), + // ... + ]; +} +``` #### Authorizing Channels Remember, users must be authorized to listen on private channels. We may define our channel authorization rules in our application's `routes/channels.php` file. In this example, we need to verify that any user attempting to listen on the private `orders.1` channel is actually the creator of the order: - use App\Models\Order; - use App\Models\User; +```php +use App\Models\Order; +use App\Models\User; - Broadcast::channel('orders.{orderId}', function (User $user, int $orderId) { - return $user->id === Order::findOrNew($orderId)->user_id; - }); +Broadcast::channel('orders.{orderId}', function (User $user, int $orderId) { + return $user->id === Order::findOrNew($orderId)->user_id; +}); +``` The `channel` method accepts two arguments: the name of the channel and a callback which returns `true` or `false` indicating whether the user is authorized to listen on the channel. @@ -431,41 +441,43 @@ To inform Laravel that a given event should be broadcast, you must implement the The `ShouldBroadcast` interface requires you to implement a single method: `broadcastOn`. The `broadcastOn` method should return a channel or array of channels that the event should broadcast on. The channels should be instances of `Channel`, `PrivateChannel`, or `PresenceChannel`. Instances of `Channel` represent public channels that any user may subscribe to, while `PrivateChannels` and `PresenceChannels` represent private channels that require [channel authorization](#authorizing-channels): - + */ + public function broadcastOn(): array { - use SerializesModels; - - /** - * Create a new event instance. - */ - public function __construct( - public User $user, - ) {} - - /** - * Get the channels the event should broadcast on. - * - * @return array - */ - public function broadcastOn(): array - { - return [ - new PrivateChannel('user.'.$this->user->id), - ]; - } + return [ + new PrivateChannel('user.'.$this->user->id), + ]; } +} +``` After implementing the `ShouldBroadcast` interface, you only need to [fire the event](/docs/{{version}}/events) as you normally would. Once the event has been fired, a [queued job](/docs/{{version}}/queues) will automatically broadcast the event using your specified broadcast driver. @@ -474,19 +486,23 @@ After implementing the `ShouldBroadcast` interface, you only need to [fire the e By default, Laravel will broadcast the event using the event's class name. However, you may customize the broadcast name by defining a `broadcastAs` method on the event: - /** - * The event's broadcast name. - */ - public function broadcastAs(): string - { - return 'server.created'; - } +```php +/** + * The event's broadcast name. + */ +public function broadcastAs(): string +{ + return 'server.created'; +} +``` If you customize the broadcast name using the `broadcastAs` method, you should make sure to register your listener with a leading `.` character. This will instruct Echo to not prepend the application's namespace to the event: - .listen('.server.created', function (e) { - .... - }); +```javascript +.listen('.server.created', function (e) { + .... +}); +``` ### Broadcast Data @@ -505,68 +521,78 @@ When an event is broadcast, all of its `public` properties are automatically ser However, if you wish to have more fine-grained control over your broadcast payload, you may add a `broadcastWith` method to your event. This method should return the array of data that you wish to broadcast as the event payload: - /** - * Get the data to broadcast. - * - * @return array - */ - public function broadcastWith(): array - { - return ['id' => $this->user->id]; - } +```php +/** + * Get the data to broadcast. + * + * @return array + */ +public function broadcastWith(): array +{ + return ['id' => $this->user->id]; +} +``` ### Broadcast Queue By default, each broadcast event is placed on the default queue for the default queue connection specified in your `queue.php` configuration file. You may customize the queue connection and name used by the broadcaster by defining `connection` and `queue` properties on your event class: - /** - * The name of the queue connection to use when broadcasting the event. - * - * @var string - */ - public $connection = 'redis'; +```php +/** + * The name of the queue connection to use when broadcasting the event. + * + * @var string + */ +public $connection = 'redis'; - /** - * The name of the queue on which to place the broadcasting job. - * - * @var string - */ - public $queue = 'default'; +/** + * The name of the queue on which to place the broadcasting job. + * + * @var string + */ +public $queue = 'default'; +``` Alternatively, you may customize the queue name by defining a `broadcastQueue` method on your event: - /** - * The name of the queue on which to place the broadcasting job. - */ - public function broadcastQueue(): string - { - return 'default'; - } +```php +/** + * The name of the queue on which to place the broadcasting job. + */ +public function broadcastQueue(): string +{ + return 'default'; +} +``` If you would like to broadcast your event using the `sync` queue instead of the default queue driver, you can implement the `ShouldBroadcastNow` interface instead of `ShouldBroadcast`: - ### Broadcast Conditions Sometimes you want to broadcast your event only if a given condition is true. You may define these conditions by adding a `broadcastWhen` method to your event class: - /** - * Determine if this event should broadcast. - */ - public function broadcastWhen(): bool - { - return $this->order->value > 100; - } +```php +/** + * Determine if this event should broadcast. + */ +public function broadcastWhen(): bool +{ + return $this->order->value > 100; +} +``` #### Broadcasting and Database Transactions @@ -575,18 +601,20 @@ When broadcast events are dispatched within database transactions, they may be p If your queue connection's `after_commit` configuration option is set to `false`, you may still indicate that a particular broadcast event should be dispatched after all open database transactions have been committed by implementing the `ShouldDispatchAfterCommit` interface on the event class: - [!NOTE] > To learn more about working around these issues, please review the documentation regarding [queued jobs and database transactions](/docs/{{version}}/queues#jobs-and-database-transactions). @@ -603,11 +631,13 @@ When broadcasting is enabled, Laravel automatically registers the `/broadcasting Next, we need to define the logic that will actually determine if the currently authenticated user can listen to a given channel. This is done in the `routes/channels.php` file that was created by the `install:broadcasting` Artisan command. In this file, you may use the `Broadcast::channel` method to register channel authorization callbacks: - use App\Models\User; +```php +use App\Models\User; - Broadcast::channel('orders.{orderId}', function (User $user, int $orderId) { - return $user->id === Order::findOrNew($orderId)->user_id; - }); +Broadcast::channel('orders.{orderId}', function (User $user, int $orderId) { + return $user->id === Order::findOrNew($orderId)->user_id; +}); +``` The `channel` method accepts two arguments: the name of the channel and a callback which returns `true` or `false` indicating whether the user is authorized to listen on the channel. @@ -624,12 +654,14 @@ php artisan channel:list Just like HTTP routes, channel routes may also take advantage of implicit and explicit [route model binding](/docs/{{version}}/routing#route-model-binding). For example, instead of receiving a string or numeric order ID, you may request an actual `Order` model instance: - use App\Models\Order; - use App\Models\User; +```php +use App\Models\Order; +use App\Models\User; - Broadcast::channel('orders.{order}', function (User $user, Order $order) { - return $user->id === $order->user_id; - }); +Broadcast::channel('orders.{order}', function (User $user, Order $order) { + return $user->id === $order->user_id; +}); +``` > [!WARNING] > Unlike HTTP route model binding, channel model binding does not support automatic [implicit model binding scoping](/docs/{{version}}/routing#implicit-model-binding-scoping). However, this is rarely a problem because most channels can be scoped based on a single model's unique, primary key. @@ -639,9 +671,11 @@ Just like HTTP routes, channel routes may also take advantage of implicit and ex Private and presence broadcast channels authenticate the current user via your application's default authentication guard. If the user is not authenticated, channel authorization is automatically denied and the authorization callback is never executed. However, you may assign multiple, custom guards that should authenticate the incoming request if necessary: - Broadcast::channel('channel', function () { - // ... - }, ['guards' => ['web', 'admin']]); +```php +Broadcast::channel('channel', function () { + // ... +}, ['guards' => ['web', 'admin']]); +``` ### Defining Channel Classes @@ -654,34 +688,38 @@ php artisan make:channel OrderChannel Next, register your channel in your `routes/channels.php` file: - use App\Broadcasting\OrderChannel; +```php +use App\Broadcasting\OrderChannel; - Broadcast::channel('orders.{order}', OrderChannel::class); +Broadcast::channel('orders.{order}', OrderChannel::class); +``` Finally, you may place the authorization logic for your channel in the channel class' `join` method. This `join` method will house the same logic you would have typically placed in your channel authorization closure. You may also take advantage of channel model binding: - id === $order->user_id; - } + return $user->id === $order->user_id; } +} +``` > [!NOTE] > Like many other classes in Laravel, channel classes will automatically be resolved by the [service container](/docs/{{version}}/container). So, you may type-hint any dependencies required by your channel in its constructor. @@ -691,18 +729,22 @@ Finally, you may place the authorization logic for your channel in the channel c Once you have defined an event and marked it with the `ShouldBroadcast` interface, you only need to fire the event using the event's dispatch method. The event dispatcher will notice that the event is marked with the `ShouldBroadcast` interface and will queue the event for broadcasting: - use App\Events\OrderShipmentStatusUpdated; +```php +use App\Events\OrderShipmentStatusUpdated; - OrderShipmentStatusUpdated::dispatch($order); +OrderShipmentStatusUpdated::dispatch($order); +``` ### Only to Others When building an application that utilizes event broadcasting, you may occasionally need to broadcast an event to all subscribers to a given channel except for the current user. You may accomplish this using the `broadcast` helper and the `toOthers` method: - use App\Events\OrderShipmentStatusUpdated; +```php +use App\Events\OrderShipmentStatusUpdated; - broadcast(new OrderShipmentStatusUpdated($update))->toOthers(); +broadcast(new OrderShipmentStatusUpdated($update))->toOthers(); +``` To better understand when you may want to use the `toOthers` method, let's imagine a task list application where a user may create a new task by entering a task name. To create a task, your application might make a request to a `/task` URL which broadcasts the task's creation and returns a JSON representation of the new task. When your JavaScript application receives the response from the end-point, it might directly insert the new task into its task list like so: @@ -734,36 +776,40 @@ var socketId = Echo.socketId(); If your application interacts with multiple broadcast connections and you want to broadcast an event using a broadcaster other than your default, you may specify which connection to push an event to using the `via` method: - use App\Events\OrderShipmentStatusUpdated; +```php +use App\Events\OrderShipmentStatusUpdated; - broadcast(new OrderShipmentStatusUpdated($update))->via('pusher'); +broadcast(new OrderShipmentStatusUpdated($update))->via('pusher'); +``` Alternatively, you may specify the event's broadcast connection by calling the `broadcastVia` method within the event's constructor. However, before doing so, you should ensure that the event class uses the `InteractsWithBroadcasting` trait: - broadcastVia('pusher'); - } + $this->broadcastVia('pusher'); } +} +``` ### Anonymous Events @@ -906,13 +952,15 @@ All presence channels are also private channels; therefore, users must be [autho The data returned by the authorization callback will be made available to the presence channel event listeners in your JavaScript application. If the user is not authorized to join the presence channel, you should return `false` or `null`: - use App\Models\User; +```php +use App\Models\User; - Broadcast::channel('chat.{roomId}', function (User $user, int $roomId) { - if ($user->canJoinRoom($roomId)) { - return ['id' => $user->id, 'name' => $user->name]; - } - }); +Broadcast::channel('chat.{roomId}', function (User $user, int $roomId) { + if ($user->canJoinRoom($roomId)) { + return ['id' => $user->id, 'name' => $user->name]; + } +}); +``` ### Joining Presence Channels @@ -942,23 +990,27 @@ The `here` callback will be executed immediately once the channel is joined succ Presence channels may receive events just like public or private channels. Using the example of a chatroom, we may want to broadcast `NewMessage` events to the room's presence channel. To do so, we'll return an instance of `PresenceChannel` from the event's `broadcastOn` method: - /** - * Get the channels the event should broadcast on. - * - * @return array - */ - public function broadcastOn(): array - { - return [ - new PresenceChannel('chat.'.$this->message->room_id), - ]; - } +```php +/** + * Get the channels the event should broadcast on. + * + * @return array + */ +public function broadcastOn(): array +{ + return [ + new PresenceChannel('chat.'.$this->message->room_id), + ]; +} +``` As with other events, you may use the `broadcast` helper and the `toOthers` method to exclude the current user from receiving the broadcast: - broadcast(new NewMessage($message)); +```php +broadcast(new NewMessage($message)); - broadcast(new NewMessage($message))->toOthers(); +broadcast(new NewMessage($message))->toOthers(); +``` As typical of other types of events, you may listen for events sent to presence channels using Echo's `listen` method: diff --git a/cache.md b/cache.md index 289641e6cfd..0693ffc80e7 100644 --- a/cache.md +++ b/cache.md @@ -50,31 +50,35 @@ php artisan migrate Using the Memcached driver requires the [Memcached PECL package](https://pecl.php.net/package/memcached) to be installed. You may list all of your Memcached servers in the `config/cache.php` configuration file. This file already contains a `memcached.servers` entry to get you started: - 'memcached' => [ - // ... +```php +'memcached' => [ + // ... - 'servers' => [ - [ - 'host' => env('MEMCACHED_HOST', '127.0.0.1'), - 'port' => env('MEMCACHED_PORT', 11211), - 'weight' => 100, - ], + 'servers' => [ + [ + 'host' => env('MEMCACHED_HOST', '127.0.0.1'), + 'port' => env('MEMCACHED_PORT', 11211), + 'weight' => 100, ], ], +], +``` If needed, you may set the `host` option to a UNIX socket path. If you do this, the `port` option should be set to `0`: - 'memcached' => [ - // ... +```php +'memcached' => [ + // ... - 'servers' => [ - [ - 'host' => '/var/run/memcached/memcached.sock', - 'port' => 0, - 'weight' => 100 - ], + 'servers' => [ + [ + 'host' => '/var/run/memcached/memcached.sock', + 'port' => 0, + 'weight' => 100 ], ], +], +``` #### Redis @@ -126,90 +130,106 @@ For more information on configuring MongoDB, please refer to the MongoDB [Cache To obtain a cache store instance, you may use the `Cache` facade, which is what we will use throughout this documentation. The `Cache` facade provides convenient, terse access to the underlying implementations of the Laravel cache contracts: - #### Accessing Multiple Cache Stores Using the `Cache` facade, you may access various cache stores via the `store` method. The key passed to the `store` method should correspond to one of the stores listed in the `stores` configuration array in your `cache` configuration file: - $value = Cache::store('file')->get('foo'); +```php +$value = Cache::store('file')->get('foo'); - Cache::store('redis')->put('bar', 'baz', 600); // 10 Minutes +Cache::store('redis')->put('bar', 'baz', 600); // 10 Minutes +``` ### Retrieving Items From the Cache The `Cache` facade's `get` method is used to retrieve items from the cache. If the item does not exist in the cache, `null` will be returned. If you wish, you may pass a second argument to the `get` method specifying the default value you wish to be returned if the item doesn't exist: - $value = Cache::get('key'); +```php +$value = Cache::get('key'); - $value = Cache::get('key', 'default'); +$value = Cache::get('key', 'default'); +``` You may even pass a closure as the default value. The result of the closure will be returned if the specified item does not exist in the cache. Passing a closure allows you to defer the retrieval of default values from a database or other external service: - $value = Cache::get('key', function () { - return DB::table(/* ... */)->get(); - }); +```php +$value = Cache::get('key', function () { + return DB::table(/* ... */)->get(); +}); +``` #### Determining Item Existence The `has` method may be used to determine if an item exists in the cache. This method will also return `false` if the item exists but its value is `null`: - if (Cache::has('key')) { - // ... - } +```php +if (Cache::has('key')) { + // ... +} +``` #### Incrementing / Decrementing Values The `increment` and `decrement` methods may be used to adjust the value of integer items in the cache. Both of these methods accept an optional second argument indicating the amount by which to increment or decrement the item's value: - // Initialize the value if it does not exist... - Cache::add('key', 0, now()->addHours(4)); - - // Increment or decrement the value... - Cache::increment('key'); - Cache::increment('key', $amount); - Cache::decrement('key'); - Cache::decrement('key', $amount); +```php +// Initialize the value if it does not exist... +Cache::add('key', 0, now()->addHours(4)); + +// Increment or decrement the value... +Cache::increment('key'); +Cache::increment('key', $amount); +Cache::decrement('key'); +Cache::decrement('key', $amount); +``` #### Retrieve and Store Sometimes you may wish to retrieve an item from the cache, but also store a default value if the requested item doesn't exist. For example, you may wish to retrieve all users from the cache or, if they don't exist, retrieve them from the database and add them to the cache. You may do this using the `Cache::remember` method: - $value = Cache::remember('users', $seconds, function () { - return DB::table('users')->get(); - }); +```php +$value = Cache::remember('users', $seconds, function () { + return DB::table('users')->get(); +}); +``` If the item does not exist in the cache, the closure passed to the `remember` method will be executed and its result will be placed in the cache. You may use the `rememberForever` method to retrieve an item from the cache or store it forever if it does not exist: - $value = Cache::rememberForever('users', function () { - return DB::table('users')->get(); - }); +```php +$value = Cache::rememberForever('users', function () { + return DB::table('users')->get(); +}); +``` #### Stale While Revalidate @@ -220,47 +240,61 @@ The flexible method accepts an array that specifies how long the cached value is If a request is made within the fresh period (before the first value), the cache is returned immediately without recalculation. If a request is made during the stale period (between the two values), the stale value is served to the user, and a [deferred function](/docs/{{version}}/helpers#deferred-functions) is registered to refresh the cached value after the response is sent to the user. If a request is made after the second value, the cache is considered expired, and the value is recalculated immediately, which may result in a slower response for the user: - $value = Cache::flexible('users', [5, 10], function () { - return DB::table('users')->get(); - }); +```php +$value = Cache::flexible('users', [5, 10], function () { + return DB::table('users')->get(); +}); +``` #### Retrieve and Delete If you need to retrieve an item from the cache and then delete the item, you may use the `pull` method. Like the `get` method, `null` will be returned if the item does not exist in the cache: - $value = Cache::pull('key'); +```php +$value = Cache::pull('key'); - $value = Cache::pull('key', 'default'); +$value = Cache::pull('key', 'default'); +``` ### Storing Items in the Cache You may use the `put` method on the `Cache` facade to store items in the cache: - Cache::put('key', 'value', $seconds = 10); +```php +Cache::put('key', 'value', $seconds = 10); +``` If the storage time is not passed to the `put` method, the item will be stored indefinitely: - Cache::put('key', 'value'); +```php +Cache::put('key', 'value'); +``` Instead of passing the number of seconds as an integer, you may also pass a `DateTime` instance representing the desired expiration time of the cached item: - Cache::put('key', 'value', now()->addMinutes(10)); +```php +Cache::put('key', 'value', now()->addMinutes(10)); +``` #### Store if Not Present The `add` method will only add the item to the cache if it does not already exist in the cache store. The method will return `true` if the item is actually added to the cache. Otherwise, the method will return `false`. The `add` method is an atomic operation: - Cache::add('key', 'value', $seconds); +```php +Cache::add('key', 'value', $seconds); +``` #### Storing Items Forever The `forever` method may be used to store an item in the cache permanently. Since these items will not expire, they must be manually removed from the cache using the `forget` method: - Cache::forever('key', 'value'); +```php +Cache::forever('key', 'value'); +``` > [!NOTE] > If you are using the Memcached driver, items that are stored "forever" may be removed when the cache reaches its size limit. @@ -270,17 +304,23 @@ The `forever` method may be used to store an item in the cache permanently. Sinc You may remove items from the cache using the `forget` method: - Cache::forget('key'); +```php +Cache::forget('key'); +``` You may also remove items by providing a zero or negative number of expiration seconds: - Cache::put('key', 'value', 0); +```php +Cache::put('key', 'value', 0); - Cache::put('key', 'value', -5); +Cache::put('key', 'value', -5); +``` You may clear the entire cache using the `flush` method: - Cache::flush(); +```php +Cache::flush(); +``` > [!WARNING] > Flushing the cache does not respect your configured cache "prefix" and will remove all entries from the cache. Consider this carefully when clearing a cache which is shared by other applications. @@ -290,19 +330,25 @@ You may clear the entire cache using the `flush` method: In addition to using the `Cache` facade, you may also use the global `cache` function to retrieve and store data via the cache. When the `cache` function is called with a single, string argument, it will return the value of the given key: - $value = cache('key'); +```php +$value = cache('key'); +``` If you provide an array of key / value pairs and an expiration time to the function, it will store values in the cache for the specified duration: - cache(['key' => 'value'], $seconds); +```php +cache(['key' => 'value'], $seconds); - cache(['key' => 'value'], now()->addMinutes(10)); +cache(['key' => 'value'], now()->addMinutes(10)); +``` When the `cache` function is called without any arguments, it returns an instance of the `Illuminate\Contracts\Cache\Factory` implementation, allowing you to call other caching methods: - cache()->remember('users', $seconds, function () { - return DB::table('users')->get(); - }); +```php +cache()->remember('users', $seconds, function () { + return DB::table('users')->get(); +}); +``` > [!NOTE] > When testing call to the global `cache` function, you may use the `Cache::shouldReceive` method just as if you were [testing the facade](/docs/{{version}}/mocking#mocking-facades). @@ -318,43 +364,51 @@ When the `cache` function is called without any arguments, it returns an instanc Atomic locks allow for the manipulation of distributed locks without worrying about race conditions. For example, [Laravel Forge](https://forge.laravel.com) uses atomic locks to ensure that only one remote task is being executed on a server at a time. You may create and manage locks using the `Cache::lock` method: - use Illuminate\Support\Facades\Cache; +```php +use Illuminate\Support\Facades\Cache; - $lock = Cache::lock('foo', 10); +$lock = Cache::lock('foo', 10); - if ($lock->get()) { - // Lock acquired for 10 seconds... +if ($lock->get()) { + // Lock acquired for 10 seconds... - $lock->release(); - } + $lock->release(); +} +``` The `get` method also accepts a closure. After the closure is executed, Laravel will automatically release the lock: - Cache::lock('foo', 10)->get(function () { - // Lock acquired for 10 seconds and automatically released... - }); +```php +Cache::lock('foo', 10)->get(function () { + // Lock acquired for 10 seconds and automatically released... +}); +``` If the lock is not available at the moment you request it, you may instruct Laravel to wait for a specified number of seconds. If the lock cannot be acquired within the specified time limit, an `Illuminate\Contracts\Cache\LockTimeoutException` will be thrown: - use Illuminate\Contracts\Cache\LockTimeoutException; +```php +use Illuminate\Contracts\Cache\LockTimeoutException; - $lock = Cache::lock('foo', 10); +$lock = Cache::lock('foo', 10); - try { - $lock->block(5); +try { + $lock->block(5); - // Lock acquired after waiting a maximum of 5 seconds... - } catch (LockTimeoutException $e) { - // Unable to acquire lock... - } finally { - $lock->release(); - } + // Lock acquired after waiting a maximum of 5 seconds... +} catch (LockTimeoutException $e) { + // Unable to acquire lock... +} finally { + $lock->release(); +} +``` The example above may be simplified by passing a closure to the `block` method. When a closure is passed to this method, Laravel will attempt to acquire the lock for the specified number of seconds and will automatically release the lock once the closure has been executed: - Cache::lock('foo', 10)->block(5, function () { - // Lock acquired after waiting a maximum of 5 seconds... - }); +```php +Cache::lock('foo', 10)->block(5, function () { + // Lock acquired after waiting a maximum of 5 seconds... +}); +``` ### Managing Locks Across Processes @@ -363,21 +417,27 @@ Sometimes, you may wish to acquire a lock in one process and release it in anoth In the example below, we will dispatch a queued job if a lock is successfully acquired. In addition, we will pass the lock's owner token to the queued job via the lock's `owner` method: - $podcast = Podcast::find($id); +```php +$podcast = Podcast::find($id); - $lock = Cache::lock('processing', 120); +$lock = Cache::lock('processing', 120); - if ($lock->get()) { - ProcessPodcast::dispatch($podcast, $lock->owner()); - } +if ($lock->get()) { + ProcessPodcast::dispatch($podcast, $lock->owner()); +} +``` Within our application's `ProcessPodcast` job, we can restore and release the lock using the owner token: - Cache::restoreLock('processing', $this->owner)->release(); +```php +Cache::restoreLock('processing', $this->owner)->release(); +``` If you would like to release a lock without respecting its current owner, you may use the `forceRelease` method: - Cache::lock('processing')->forceRelease(); +```php +Cache::lock('processing')->forceRelease(); +``` ## Adding Custom Cache Drivers @@ -387,31 +447,35 @@ If you would like to release a lock without respecting its current owner, you ma To create our custom cache driver, we first need to implement the `Illuminate\Contracts\Cache\Store` [contract](/docs/{{version}}/contracts). So, a MongoDB cache implementation might look something like this: - [!NOTE] > If you're wondering where to put your custom cache driver code, you could create an `Extensions` namespace within your `app` directory. However, keep in mind that Laravel does not have a rigid application structure and you are free to organize your application according to your preferences. @@ -421,37 +485,39 @@ We just need to implement each of these methods using a MongoDB connection. For To register the custom cache driver with Laravel, we will use the `extend` method on the `Cache` facade. Since other service providers may attempt to read cached values within their `boot` method, we will register our custom driver within a `booting` callback. By using the `booting` callback, we can ensure that the custom driver is registered just before the `boot` method is called on our application's service providers but after the `register` method is called on all of the service providers. We will register our `booting` callback within the `register` method of our application's `App\Providers\AppServiceProvider` class: - app->booting(function () { - Cache::extend('mongo', function (Application $app) { - return Cache::repository(new MongoStore); - }); + $this->app->booting(function () { + Cache::extend('mongo', function (Application $app) { + return Cache::repository(new MongoStore); }); - } + }); + } - /** - * Bootstrap any application services. - */ - public function boot(): void - { - // ... - } + /** + * Bootstrap any application services. + */ + public function boot(): void + { + // ... } +} +``` The first argument passed to the `extend` method is the name of the driver. This will correspond to your `driver` option in the `config/cache.php` configuration file. The second argument is a closure that should return an `Illuminate\Cache\Repository` instance. The closure will be passed an `$app` instance, which is an instance of the [service container](/docs/{{version}}/container). diff --git a/cashier-paddle.md b/cashier-paddle.md index ea04c2e240e..2400d1abfb1 100644 --- a/cashier-paddle.md +++ b/cashier-paddle.md @@ -110,22 +110,26 @@ After you have finished developing your application you may [apply for a Paddle Before using Cashier, you must add the `Billable` trait to your user model definition. This trait provides various methods to allow you to perform common billing tasks, such as creating subscriptions and updating payment method information: - use Laravel\Paddle\Billable; +```php +use Laravel\Paddle\Billable; - class User extends Authenticatable - { - use Billable; - } +class User extends Authenticatable +{ + use Billable; +} +``` If you have billable entities that are not users, you may also add the trait to those classes: - use Illuminate\Database\Eloquent\Model; - use Laravel\Paddle\Billable; +```php +use Illuminate\Database\Eloquent\Model; +use Laravel\Paddle\Billable; - class Team extends Model - { - use Billable; - } +class Team extends Model +{ + use Billable; +} +``` ### API Keys @@ -174,26 +178,30 @@ CASHIER_CURRENCY_LOCALE=nl_BE You are free to extend the models used internally by Cashier by defining your own model and extending the corresponding Cashier model: - use Laravel\Paddle\Subscription as CashierSubscription; +```php +use Laravel\Paddle\Subscription as CashierSubscription; - class Subscription extends CashierSubscription - { - // ... - } +class Subscription extends CashierSubscription +{ + // ... +} +``` After defining your model, you may instruct Cashier to use your custom model via the `Laravel\Paddle\Cashier` class. Typically, you should inform Cashier about your custom models in the `boot` method of your application's `App\Providers\AppServiceProvider` class: - use App\Models\Cashier\Subscription; - use App\Models\Cashier\Transaction; - - /** - * Bootstrap any application services. - */ - public function boot(): void - { - Cashier::useSubscriptionModel(Subscription::class); - Cashier::useTransactionModel(Transaction::class); - } +```php +use App\Models\Cashier\Subscription; +use App\Models\Cashier\Transaction; + +/** + * Bootstrap any application services. + */ +public function boot(): void +{ + Cashier::useSubscriptionModel(Subscription::class); + Cashier::useTransactionModel(Transaction::class); +} +``` ## Quickstart @@ -208,14 +216,16 @@ Offering product and subscription billing via your application can be intimidati To charge customers for non-recurring, single-charge products, we'll utilize Cashier to charge customers with Paddle's Checkout Overlay, where they will provide their payment details and confirm their purchase. Once the payment has been made via the Checkout Overlay, the customer will be redirected to a success URL of your choosing within your application: - use Illuminate\Http\Request; +```php +use Illuminate\Http\Request; - Route::get('/buy', function (Request $request) { - $checkout = $request->user()->checkout('pri_deluxe_album') - ->returnTo(route('dashboard')); +Route::get('/buy', function (Request $request) { + $checkout = $request->user()->checkout('pri_deluxe_album') + ->returnTo(route('dashboard')); - return view('buy', ['checkout' => $checkout]); - })->name('checkout'); + return view('buy', ['checkout' => $checkout]); +})->name('checkout'); +``` As you can see in the example above, we will utilize Cashier's provided `checkout` method to create a checkout object to present the customer the Paddle Checkout Overlay for a given "price identifier". When using Paddle, "prices" refer to [defined prices for specific products](https://developer.paddle.com/build/products/create-products-prices). @@ -236,22 +246,24 @@ When selling products, it's common to keep track of completed orders and purchas To accomplish this, you may provide an array of custom data to the `checkout` method. Let's imagine that a pending `Order` is created within our application when a user begins the checkout process. Remember, the `Cart` and `Order` models in this example are illustrative and not provided by Cashier. You are free to implement these concepts based on the needs of your own application: - use App\Models\Cart; - use App\Models\Order; - use Illuminate\Http\Request; +```php +use App\Models\Cart; +use App\Models\Order; +use Illuminate\Http\Request; - Route::get('/cart/{cart}/checkout', function (Request $request, Cart $cart) { - $order = Order::create([ - 'cart_id' => $cart->id, - 'price_ids' => $cart->price_ids, - 'status' => 'incomplete', - ]); +Route::get('/cart/{cart}/checkout', function (Request $request, Cart $cart) { + $order = Order::create([ + 'cart_id' => $cart->id, + 'price_ids' => $cart->price_ids, + 'status' => 'incomplete', + ]); - $checkout = $request->user()->checkout($order->price_ids) - ->customData(['order_id' => $order->id]); + $checkout = $request->user()->checkout($order->price_ids) + ->customData(['order_id' => $order->id]); - return view('billing', ['checkout' => $checkout]); - })->name('checkout'); + return view('billing', ['checkout' => $checkout]); +})->name('checkout'); +``` As you can see in the example above, when a user begins the checkout process, we will provide all of the cart / order's associated Paddle price identifiers to the `checkout` method. Of course, your application is responsible for associating these items with the "shopping cart" or order as a customer adds them. We also provide the order's ID to the Paddle Checkout Overlay via the `customData` method. @@ -259,40 +271,44 @@ Of course, you will likely want to mark the order as "complete" once the custome To get started, listen for the `TransactionCompleted` event dispatched by Cashier. Typically, you should register the event listener in the `boot` method of your application's `AppServiceProvider`: - use App\Listeners\CompleteOrder; - use Illuminate\Support\Facades\Event; - use Laravel\Paddle\Events\TransactionCompleted; - - /** - * Bootstrap any application services. - */ - public function boot(): void - { - Event::listen(TransactionCompleted::class, CompleteOrder::class); - } +```php +use App\Listeners\CompleteOrder; +use Illuminate\Support\Facades\Event; +use Laravel\Paddle\Events\TransactionCompleted; + +/** + * Bootstrap any application services. + */ +public function boot(): void +{ + Event::listen(TransactionCompleted::class, CompleteOrder::class); +} +``` In this example, the `CompleteOrder` listener might look like the following: - namespace App\Listeners; +```php +namespace App\Listeners; - use App\Models\Order; - use Laravel\Paddle\Cashier; - use Laravel\Paddle\Events\TransactionCompleted; +use App\Models\Order; +use Laravel\Paddle\Cashier; +use Laravel\Paddle\Events\TransactionCompleted; - class CompleteOrder +class CompleteOrder +{ + /** + * Handle the incoming Cashier webhook event. + */ + public function handle(TransactionCompleted $event): void { - /** - * Handle the incoming Cashier webhook event. - */ - public function handle(TransactionCompleted $event): void - { - $orderId = $event->payload['data']['custom_data']['order_id'] ?? null; + $orderId = $event->payload['data']['custom_data']['order_id'] ?? null; - $order = Order::findOrFail($orderId); + $order = Order::findOrFail($orderId); - $order->update(['status' => 'completed']); - } + $order->update(['status' => 'completed']); } +} +``` Please refer to Paddle's documentation for more information on the [data contained by the `transaction.completed` event](https://developer.paddle.com/webhooks/transactions/transaction-completed). @@ -308,14 +324,16 @@ To learn how to sell subscriptions using Cashier and Paddle's Checkout Overlay, First, let's discover how a customer can subscribe to our services. Of course, you can imagine the customer might click a "subscribe" button for the Basic plan on our application's pricing page. This button will invoke a Paddle Checkout Overlay for their chosen plan. To get started, let's initiate a checkout session via the `checkout` method: - use Illuminate\Http\Request; +```php +use Illuminate\Http\Request; - Route::get('/subscribe', function (Request $request) { - $checkout = $request->user()->checkout('price_basic_monthly') - ->returnTo(route('dashboard')); +Route::get('/subscribe', function (Request $request) { + $checkout = $request->user()->checkout('price_basic_monthly') + ->returnTo(route('dashboard')); - return view('subscribe', ['checkout' => $checkout]); - })->name('subscribe'); + return view('subscribe', ['checkout' => $checkout]); +})->name('subscribe'); +``` In the `subscribe` view, we will include a button to display the Checkout Overlay. The `paddle-button` Blade component is included with Cashier Paddle; however, you may also [manually render an overlay checkout](#manually-rendering-an-overlay-checkout): @@ -352,60 +370,68 @@ We can even easily determine if a user is subscribed to specific product or pric For convenience, you may wish to create a [middleware](/docs/{{version}}/middleware) which determines if the incoming request is from a subscribed user. Once this middleware has been defined, you may easily assign it to a route to prevent users that are not subscribed from accessing the route: - user()?->subscribed()) { - // Redirect user to billing page and ask them to subscribe... - return redirect('/subscribe'); - } - - return $next($request); + if (! $request->user()?->subscribed()) { + // Redirect user to billing page and ask them to subscribe... + return redirect('/subscribe'); } + + return $next($request); } +} +``` Once the middleware has been defined, you may assign it to a route: - use App\Http\Middleware\Subscribed; +```php +use App\Http\Middleware\Subscribed; - Route::get('/dashboard', function () { - // ... - })->middleware([Subscribed::class]); +Route::get('/dashboard', function () { + // ... +})->middleware([Subscribed::class]); +``` #### Allowing Customers to Manage Their Billing Plan Of course, customers may want to change their subscription plan to another product or "tier". In our example from above, we'd want to allow the customer to change their plan from a monthly subscription to a yearly subscription. For this you'll need to implement something like a button that leads to the below route: - use Illuminate\Http\Request; +```php +use Illuminate\Http\Request; - Route::put('/subscription/{price}/swap', function (Request $request, $price) { - $user->subscription()->swap($price); // With "$price" being "price_basic_yearly" for this example. +Route::put('/subscription/{price}/swap', function (Request $request, $price) { + $user->subscription()->swap($price); // With "$price" being "price_basic_yearly" for this example. - return redirect()->route('dashboard'); - })->name('subscription.swap'); + return redirect()->route('dashboard'); +})->name('subscription.swap'); +``` Besides swapping plans you'll also need to allow your customers to cancel their subscription. Like swapping plans, provide a button that leads to the following route: - use Illuminate\Http\Request; +```php +use Illuminate\Http\Request; - Route::put('/subscription/cancel', function (Request $request, $price) { - $user->subscription()->cancel(); +Route::put('/subscription/cancel', function (Request $request, $price) { + $user->subscription()->cancel(); - return redirect()->route('dashboard'); - })->name('subscription.cancel'); + return redirect()->route('dashboard'); +})->name('subscription.cancel'); +``` And now your subscription will get canceled at the end of its billing period. @@ -424,14 +450,16 @@ Before processing checkout payments using Paddle, you should define your applica Before displaying the Checkout Overlay widget, you must generate a checkout session using Cashier. A checkout session will inform the checkout widget of the billing operation that should be performed: - use Illuminate\Http\Request; +```php +use Illuminate\Http\Request; - Route::get('/buy', function (Request $request) { - $checkout = $user->checkout('pri_34567') - ->returnTo(route('dashboard')); +Route::get('/buy', function (Request $request) { + $checkout = $user->checkout('pri_34567') + ->returnTo(route('dashboard')); - return view('billing', ['checkout' => $checkout]); - }); + return view('billing', ['checkout' => $checkout]); +}); +``` Cashier includes a `paddle-button` [Blade component](/docs/{{version}}/blade#components). You may pass the checkout session to this component as a "prop". Then, when this button is clicked, Paddle's checkout widget will be displayed: @@ -459,14 +487,16 @@ The Paddle checkout widget is asynchronous. Once the user creates a subscription You may also manually render an overlay checkout without using Laravel's built-in Blade components. To get started, generate the checkout session [as demonstrated in previous examples](#overlay-checkout): - use Illuminate\Http\Request; +```php +use Illuminate\Http\Request; - Route::get('/buy', function (Request $request) { - $checkout = $user->checkout('pri_34567') - ->returnTo(route('dashboard')); +Route::get('/buy', function (Request $request) { + $checkout = $user->checkout('pri_34567') + ->returnTo(route('dashboard')); - return view('billing', ['checkout' => $checkout]); - }); + return view('billing', ['checkout' => $checkout]); +}); +``` Next, you may use Paddle.js to initialize the checkout. In this example, we will create a link that is assigned the `paddle_button` class. Paddle.js will detect this class and display the overlay checkout when the link is clicked: @@ -496,14 +526,16 @@ If you don't want to make use of Paddle's "overlay" style checkout widget, Paddl To make it easy for you to get started with inline checkout, Cashier includes a `paddle-checkout` Blade component. To get started, you should [generate a checkout session](#overlay-checkout): - use Illuminate\Http\Request; +```php +use Illuminate\Http\Request; - Route::get('/buy', function (Request $request) { - $checkout = $user->checkout('pri_34567') - ->returnTo(route('dashboard')); +Route::get('/buy', function (Request $request) { + $checkout = $user->checkout('pri_34567') + ->returnTo(route('dashboard')); - return view('billing', ['checkout' => $checkout]); - }); + return view('billing', ['checkout' => $checkout]); +}); +``` Then, you may pass the checkout session to the component's `checkout` attribute: @@ -524,14 +556,16 @@ Please consult Paddle's [guide on Inline Checkout](https://developer.paddle.com/ You may also manually render an inline checkout without using Laravel's built-in Blade components. To get started, generate the checkout session [as demonstrated in previous examples](#inline-checkout): - use Illuminate\Http\Request; +```php +use Illuminate\Http\Request; - Route::get('/buy', function (Request $request) { - $checkout = $user->checkout('pri_34567') - ->returnTo(route('dashboard')); +Route::get('/buy', function (Request $request) { + $checkout = $user->checkout('pri_34567') + ->returnTo(route('dashboard')); - return view('billing', ['checkout' => $checkout]); - }); + return view('billing', ['checkout' => $checkout]); +}); +``` Next, you may use Paddle.js to initialize the checkout. In this example, we will demonstrate this using [Alpine.js](https://github.com/alpinejs/alpine); however, you are free to modify this example for your own frontend stack: @@ -554,15 +588,17 @@ $options['settings']['frameInitialHeight'] = 366; Sometimes, you may need to create a checkout session for users that do not need an account with your application. To do so, you may use the `guest` method: - use Illuminate\Http\Request; - use Laravel\Paddle\Checkout; +```php +use Illuminate\Http\Request; +use Laravel\Paddle\Checkout; - Route::get('/buy', function (Request $request) { - $checkout = Checkout::guest(['pri_34567']) - ->returnTo(route('home')); +Route::get('/buy', function (Request $request) { + $checkout = Checkout::guest(['pri_34567']) + ->returnTo(route('home')); - return view('billing', ['checkout' => $checkout]); - }); + return view('billing', ['checkout' => $checkout]); +}); +``` Then, you may provide the checkout session to the [Paddle button](#overlay-checkout) or [inline checkout](#inline-checkout) Blade components. @@ -571,18 +607,22 @@ Then, you may provide the checkout session to the [Paddle button](#overlay-check Paddle allows you to customize prices per currency, essentially allowing you to configure different prices for different countries. Cashier Paddle allows you to retrieve all of these prices using the `previewPrices` method. This method accepts the price IDs you wish to retrieve prices for: - use Laravel\Paddle\Cashier; +```php +use Laravel\Paddle\Cashier; - $prices = Cashier::previewPrices(['pri_123', 'pri_456']); +$prices = Cashier::previewPrices(['pri_123', 'pri_456']); +``` The currency will be determined based on the IP address of the request; however, you may optionally provide a specific country to retrieve prices for: - use Laravel\Paddle\Cashier; +```php +use Laravel\Paddle\Cashier; - $prices = Cashier::previewPrices(['pri_123', 'pri_456'], ['address' => [ - 'country_code' => 'BE', - 'postal_code' => '1234', - ]]); +$prices = Cashier::previewPrices(['pri_123', 'pri_456'], ['address' => [ + 'country_code' => 'BE', + 'postal_code' => '1234', +]]); +``` After retrieving the prices you may display them however you wish: @@ -611,9 +651,11 @@ For more information, [checkout Paddle's API documentation regarding price previ If a user is already a customer and you would like to display the prices that apply to that customer, you may do so by retrieving the prices directly from the customer instance: - use App\Models\User; +```php +use App\Models\User; - $prices = User::find(1)->previewPrices(['pri_123', 'pri_456']); +$prices = User::find(1)->previewPrices(['pri_123', 'pri_456']); +``` Internally, Cashier will use the user's customer ID to retrieve the prices in their currency. So, for example, a user living in the United States will see prices in US dollars while a user in Belgium will see prices in Euros. If no matching currency can be found, the default currency of the product will be used. You can customize all prices of a product or subscription plan in the Paddle control panel. @@ -622,11 +664,13 @@ Internally, Cashier will use the user's customer ID to retrieve the prices in th You may also choose to display prices after a discount. When calling the `previewPrices` method, you provide the discount ID via the `discount_id` option: - use Laravel\Paddle\Cashier; +```php +use Laravel\Paddle\Cashier; - $prices = Cashier::previewPrices(['pri_123', 'pri_456'], [ - 'discount_id' => 'dsc_123' - ]); +$prices = Cashier::previewPrices(['pri_123', 'pri_456'], [ + 'discount_id' => 'dsc_123' +]); +``` Then, display the calculated prices: @@ -646,21 +690,23 @@ Then, display the calculated prices: Cashier allows you to define some useful defaults for your customers when creating checkout sessions. Setting these defaults allow you to pre-fill a customer's email address and name so that they can immediately move on to the payment portion of the checkout widget. You can set these defaults by overriding the following methods on your billable model: - /** - * Get the customer's name to associate with Paddle. - */ - public function paddleName(): string|null - { - return $this->name; - } - - /** - * Get the customer's email address to associate with Paddle. - */ - public function paddleEmail(): string|null - { - return $this->email; - } +```php +/** + * Get the customer's name to associate with Paddle. + */ +public function paddleName(): string|null +{ + return $this->name; +} + +/** + * Get the customer's email address to associate with Paddle. + */ +public function paddleEmail(): string|null +{ + return $this->email; +} +``` These defaults will be used for every action in Cashier that generates a [checkout session](#checkout-sessions). @@ -669,20 +715,26 @@ These defaults will be used for every action in Cashier that generates a [checko You can retrieve a customer by their Paddle Customer ID using the `Cashier::findBillable` method. This method will return an instance of the billable model: - use Laravel\Paddle\Cashier; +```php +use Laravel\Paddle\Cashier; - $user = Cashier::findBillable($customerId); +$user = Cashier::findBillable($customerId); +``` ### Creating Customers Occasionally, you may wish to create a Paddle customer without beginning a subscription. You may accomplish this using the `createAsCustomer` method: - $customer = $user->createAsCustomer(); +```php +$customer = $user->createAsCustomer(); +``` An instance of `Laravel\Paddle\Customer` is returned. Once the customer has been created in Paddle, you may begin a subscription at a later date. You may provide an optional `$options` array to pass in any additional [customer creation parameters that are supported by the Paddle API](https://developer.paddle.com/api-reference/customers/create-customer): - $customer = $user->createAsCustomer($options); +```php +$customer = $user->createAsCustomer($options); +``` ## Subscriptions @@ -692,22 +744,26 @@ An instance of `Laravel\Paddle\Customer` is returned. Once the customer has been To create a subscription, first retrieve an instance of your billable model from your database, which will typically be an instance of `App\Models\User`. Once you have retrieved the model instance, you may use the `subscribe` method to create the model's checkout session: - use Illuminate\Http\Request; +```php +use Illuminate\Http\Request; - Route::get('/user/subscribe', function (Request $request) { - $checkout = $request->user()->subscribe($premium = 12345, 'default') - ->returnTo(route('home')); +Route::get('/user/subscribe', function (Request $request) { + $checkout = $request->user()->subscribe($premium = 12345, 'default') + ->returnTo(route('home')); - return view('billing', ['checkout' => $checkout]); - }); + return view('billing', ['checkout' => $checkout]); +}); +``` The first argument given to the `subscribe` method is the specific price the user is subscribing to. This value should correspond to the price's identifier in Paddle. The `returnTo` method accepts a URL that your user will be redirected to after they successfully complete the checkout. The second argument passed to the `subscribe` method should be the internal "type" of the subscription. If your application only offers a single subscription, you might call this `default` or `primary`. This subscription type is only for internal application usage and is not meant to be displayed to users. In addition, it should not contain spaces and it should never be changed after creating the subscription. You may also provide an array of custom metadata regarding the subscription using the `customData` method: - $checkout = $request->user()->subscribe($premium = 12345, 'default') - ->customData(['key' => 'value']) - ->returnTo(route('home')); +```php +$checkout = $request->user()->subscribe($premium = 12345, 'default') + ->customData(['key' => 'value']) + ->returnTo(route('home')); +``` Once a subscription checkout session has been created, the checkout session may be provided to the `paddle-button` [Blade component](#overlay-checkout) that is included with Cashier Paddle: @@ -724,99 +780,119 @@ After the user has finished their checkout, a `subscription_created` webhook wil Once a user is subscribed to your application, you may check their subscription status using a variety of convenient methods. First, the `subscribed` method returns `true` if the user has a valid subscription, even if the subscription is currently within its trial period: - if ($user->subscribed()) { - // ... - } +```php +if ($user->subscribed()) { + // ... +} +``` If your application offers multiple subscriptions, you may specify the subscription when invoking the `subscribed` method: - if ($user->subscribed('default')) { - // ... - } +```php +if ($user->subscribed('default')) { + // ... +} +``` The `subscribed` method also makes a great candidate for a [route middleware](/docs/{{version}}/middleware), allowing you to filter access to routes and controllers based on the user's subscription status: - user() && ! $request->user()->subscribed()) { - // This user is not a paying customer... - return redirect('/billing'); - } - - return $next($request); + if ($request->user() && ! $request->user()->subscribed()) { + // This user is not a paying customer... + return redirect('/billing'); } + + return $next($request); } +} +``` If you would like to determine if a user is still within their trial period, you may use the `onTrial` method. This method can be useful for determining if you should display a warning to the user that they are still on their trial period: - if ($user->subscription()->onTrial()) { - // ... - } +```php +if ($user->subscription()->onTrial()) { + // ... +} +``` The `subscribedToPrice` method may be used to determine if the user is subscribed to a given plan based on a given Paddle price ID. In this example, we will determine if the user's `default` subscription is actively subscribed to the monthly price: - if ($user->subscribedToPrice($monthly = 'pri_123', 'default')) { - // ... - } +```php +if ($user->subscribedToPrice($monthly = 'pri_123', 'default')) { + // ... +} +``` The `recurring` method may be used to determine if the user is currently on an active subscription and is no longer within their trial period or on a grace period: - if ($user->subscription()->recurring()) { - // ... - } +```php +if ($user->subscription()->recurring()) { + // ... +} +``` #### Canceled Subscription Status To determine if the user was once an active subscriber but has canceled their subscription, you may use the `canceled` method: - if ($user->subscription()->canceled()) { - // ... - } +```php +if ($user->subscription()->canceled()) { + // ... +} +``` You may also determine if a user has canceled their subscription, but are still on their "grace period" until the subscription fully expires. For example, if a user cancels a subscription on March 5th that was originally scheduled to expire on March 10th, the user is on their "grace period" until March 10th. In addition, the `subscribed` method will still return `true` during this time: - if ($user->subscription()->onGracePeriod()) { - // ... - } +```php +if ($user->subscription()->onGracePeriod()) { + // ... +} +``` #### Past Due Status If a payment fails for a subscription, it will be marked as `past_due`. When your subscription is in this state it will not be active until the customer has updated their payment information. You may determine if a subscription is past due using the `pastDue` method on the subscription instance: - if ($user->subscription()->pastDue()) { - // ... - } +```php +if ($user->subscription()->pastDue()) { + // ... +} +``` When a subscription is past due, you should instruct the user to [update their payment information](#updating-payment-information). If you would like subscriptions to still be considered valid when they are `past_due`, you may use the `keepPastDueSubscriptionsActive` method provided by Cashier. Typically, this method should be called in the `register` method of your `AppServiceProvider`: - use Laravel\Paddle\Cashier; +```php +use Laravel\Paddle\Cashier; - /** - * Register any application services. - */ - public function register(): void - { - Cashier::keepPastDueSubscriptionsActive(); - } +/** + * Register any application services. + */ +public function register(): void +{ + Cashier::keepPastDueSubscriptionsActive(); +} +``` > [!WARNING] > When a subscription is in a `past_due` state it cannot be changed until payment information has been updated. Therefore, the `swap` and `updateQuantity` methods will throw an exception when the subscription is in a `past_due` state. @@ -826,57 +902,67 @@ If you would like subscriptions to still be considered valid when they are `past Most subscription states are also available as query scopes so that you may easily query your database for subscriptions that are in a given state: - // Get all valid subscriptions... - $subscriptions = Subscription::query()->valid()->get(); +```php +// Get all valid subscriptions... +$subscriptions = Subscription::query()->valid()->get(); - // Get all of the canceled subscriptions for a user... - $subscriptions = $user->subscriptions()->canceled()->get(); +// Get all of the canceled subscriptions for a user... +$subscriptions = $user->subscriptions()->canceled()->get(); +``` A complete list of available scopes is available below: - Subscription::query()->valid(); - Subscription::query()->onTrial(); - Subscription::query()->expiredTrial(); - Subscription::query()->notOnTrial(); - Subscription::query()->active(); - Subscription::query()->recurring(); - Subscription::query()->pastDue(); - Subscription::query()->paused(); - Subscription::query()->notPaused(); - Subscription::query()->onPausedGracePeriod(); - Subscription::query()->notOnPausedGracePeriod(); - Subscription::query()->canceled(); - Subscription::query()->notCanceled(); - Subscription::query()->onGracePeriod(); - Subscription::query()->notOnGracePeriod(); +```php +Subscription::query()->valid(); +Subscription::query()->onTrial(); +Subscription::query()->expiredTrial(); +Subscription::query()->notOnTrial(); +Subscription::query()->active(); +Subscription::query()->recurring(); +Subscription::query()->pastDue(); +Subscription::query()->paused(); +Subscription::query()->notPaused(); +Subscription::query()->onPausedGracePeriod(); +Subscription::query()->notOnPausedGracePeriod(); +Subscription::query()->canceled(); +Subscription::query()->notCanceled(); +Subscription::query()->onGracePeriod(); +Subscription::query()->notOnGracePeriod(); +``` ### Subscription Single Charges Subscription single charges allow you to charge subscribers with a one-time charge on top of their subscriptions. You must provide one or multiple price ID's when invoking the `charge` method: - // Charge a single price... - $response = $user->subscription()->charge('pri_123'); +```php +// Charge a single price... +$response = $user->subscription()->charge('pri_123'); - // Charge multiple prices at once... - $response = $user->subscription()->charge(['pri_123', 'pri_456']); +// Charge multiple prices at once... +$response = $user->subscription()->charge(['pri_123', 'pri_456']); +``` The `charge` method will not actually charge the customer until the next billing interval of their subscription. If you would like to bill the customer immediately, you may use the `chargeAndInvoice` method instead: - $response = $user->subscription()->chargeAndInvoice('pri_123'); +```php +$response = $user->subscription()->chargeAndInvoice('pri_123'); +``` ### Updating Payment Information Paddle always saves a payment method per subscription. If you want to update the default payment method for a subscription, you should redirect your customer to Paddle's hosted payment method update page using the `redirectToUpdatePaymentMethod` method on the subscription model: - use Illuminate\Http\Request; +```php +use Illuminate\Http\Request; - Route::get('/update-payment-method', function (Request $request) { - $user = $request->user(); +Route::get('/update-payment-method', function (Request $request) { + $user = $request->user(); - return $user->subscription()->redirectToUpdatePaymentMethod(); - }); + return $user->subscription()->redirectToUpdatePaymentMethod(); +}); +``` When a user has finished updating their information, a `subscription_updated` webhook will be dispatched by Paddle and the subscription details will be updated in your application's database. @@ -885,32 +971,42 @@ When a user has finished updating their information, a `subscription_updated` we After a user has subscribed to your application, they may occasionally want to change to a new subscription plan. To update the subscription plan for a user, you should pass the Paddle price's identifier to the subscription's `swap` method: - use App\Models\User; +```php +use App\Models\User; - $user = User::find(1); +$user = User::find(1); - $user->subscription()->swap($premium = 'pri_456'); +$user->subscription()->swap($premium = 'pri_456'); +``` If you would like to swap plans and immediately invoice the user instead of waiting for their next billing cycle, you may use the `swapAndInvoice` method: - $user = User::find(1); +```php +$user = User::find(1); - $user->subscription()->swapAndInvoice($premium = 'pri_456'); +$user->subscription()->swapAndInvoice($premium = 'pri_456'); +``` #### Prorations By default, Paddle prorates charges when swapping between plans. The `noProrate` method may be used to update the subscriptions without prorating the charges: - $user->subscription('default')->noProrate()->swap($premium = 'pri_456'); +```php +$user->subscription('default')->noProrate()->swap($premium = 'pri_456'); +``` If you would like to disable proration and invoice customers immediately, you may use the `swapAndInvoice` method in combination with `noProrate`: - $user->subscription('default')->noProrate()->swapAndInvoice($premium = 'pri_456'); +```php +$user->subscription('default')->noProrate()->swapAndInvoice($premium = 'pri_456'); +``` Or, to not bill your customer for a subscription change, you may utilize the `doNotBill` method: - $user->subscription('default')->doNotBill()->swap($premium = 'pri_456'); +```php +$user->subscription('default')->doNotBill()->swap($premium = 'pri_456'); +``` For more information on Paddle's proration policies, please consult Paddle's [proration documentation](https://developer.paddle.com/concepts/subscriptions/proration). @@ -919,32 +1015,40 @@ For more information on Paddle's proration policies, please consult Paddle's [pr Sometimes subscriptions are affected by "quantity". For example, a project management application might charge $10 per month per project. To easily increment or decrement your subscription's quantity, use the `incrementQuantity` and `decrementQuantity` methods: - $user = User::find(1); +```php +$user = User::find(1); - $user->subscription()->incrementQuantity(); +$user->subscription()->incrementQuantity(); - // Add five to the subscription's current quantity... - $user->subscription()->incrementQuantity(5); +// Add five to the subscription's current quantity... +$user->subscription()->incrementQuantity(5); - $user->subscription()->decrementQuantity(); +$user->subscription()->decrementQuantity(); - // Subtract five from the subscription's current quantity... - $user->subscription()->decrementQuantity(5); +// Subtract five from the subscription's current quantity... +$user->subscription()->decrementQuantity(5); +``` Alternatively, you may set a specific quantity using the `updateQuantity` method: - $user->subscription()->updateQuantity(10); +```php +$user->subscription()->updateQuantity(10); +``` The `noProrate` method may be used to update the subscription's quantity without prorating the charges: - $user->subscription()->noProrate()->updateQuantity(10); +```php +$user->subscription()->noProrate()->updateQuantity(10); +``` #### Quantities for Subscriptions With Multiple Products If your subscription is a [subscription with multiple products](#subscriptions-with-multiple-products), you should pass the ID of the price whose quantity you wish to increment or decrement as the second argument to the increment / decrement methods: - $user->subscription()->incrementQuantity(1, 'price_chat'); +```php +$user->subscription()->incrementQuantity(1, 'price_chat'); +``` ### Subscriptions With Multiple Products @@ -953,36 +1057,46 @@ If your subscription is a [subscription with multiple products](#subscriptions-w When creating subscription checkout sessions, you may specify multiple products for a given subscription by passing an array of prices as the first argument to the `subscribe` method: - use Illuminate\Http\Request; +```php +use Illuminate\Http\Request; - Route::post('/user/subscribe', function (Request $request) { - $checkout = $request->user()->subscribe([ - 'price_monthly', - 'price_chat', - ]); +Route::post('/user/subscribe', function (Request $request) { + $checkout = $request->user()->subscribe([ + 'price_monthly', + 'price_chat', + ]); - return view('billing', ['checkout' => $checkout]); - }); + return view('billing', ['checkout' => $checkout]); +}); +``` In the example above, the customer will have two prices attached to their `default` subscription. Both prices will be charged on their respective billing intervals. If necessary, you may pass an associative array of key / value pairs to indicate a specific quantity for each price: - $user = User::find(1); +```php +$user = User::find(1); - $checkout = $user->subscribe('default', ['price_monthly', 'price_chat' => 5]); +$checkout = $user->subscribe('default', ['price_monthly', 'price_chat' => 5]); +``` If you would like to add another price to an existing subscription, you must use the subscription's `swap` method. When invoking the `swap` method, you should also include the subscription's current prices and quantities as well: - $user = User::find(1); +```php +$user = User::find(1); - $user->subscription()->swap(['price_chat', 'price_original' => 2]); +$user->subscription()->swap(['price_chat', 'price_original' => 2]); +``` The example above will add the new price, but the customer will not be billed for it until their next billing cycle. If you would like to bill the customer immediately you may use the `swapAndInvoice` method: - $user->subscription()->swapAndInvoice(['price_chat', 'price_original' => 2]); +```php +$user->subscription()->swapAndInvoice(['price_chat', 'price_original' => 2]); +``` You may remove prices from subscriptions using the `swap` method and omitting the price you want to remove: - $user->subscription()->swap(['price_original' => 2]); +```php +$user->subscription()->swap(['price_original' => 2]); +``` > [!WARNING] > You may not remove the last price on a subscription. Instead, you should simply cancel the subscription. @@ -994,52 +1108,70 @@ Paddle allows your customers to have multiple subscriptions simultaneously. For When your application creates subscriptions, you may provide the type of the subscription to the `subscribe` method as the second argument. The type may be any string that represents the type of subscription the user is initiating: - use Illuminate\Http\Request; +```php +use Illuminate\Http\Request; - Route::post('/swimming/subscribe', function (Request $request) { - $checkout = $request->user()->subscribe($swimmingMonthly = 'pri_123', 'swimming'); +Route::post('/swimming/subscribe', function (Request $request) { + $checkout = $request->user()->subscribe($swimmingMonthly = 'pri_123', 'swimming'); - return view('billing', ['checkout' => $checkout]); - }); + return view('billing', ['checkout' => $checkout]); +}); +``` In this example, we initiated a monthly swimming subscription for the customer. However, they may want to swap to a yearly subscription at a later time. When adjusting the customer's subscription, we can simply swap the price on the `swimming` subscription: - $user->subscription('swimming')->swap($swimmingYearly = 'pri_456'); +```php +$user->subscription('swimming')->swap($swimmingYearly = 'pri_456'); +``` Of course, you may also cancel the subscription entirely: - $user->subscription('swimming')->cancel(); +```php +$user->subscription('swimming')->cancel(); +``` ### Pausing Subscriptions To pause a subscription, call the `pause` method on the user's subscription: - $user->subscription()->pause(); +```php +$user->subscription()->pause(); +``` When a subscription is paused, Cashier will automatically set the `paused_at` column in your database. This column is used to determine when the `paused` method should begin returning `true`. For example, if a customer pauses a subscription on March 1st, but the subscription was not scheduled to recur until March 5th, the `paused` method will continue to return `false` until March 5th. This is because a user is typically allowed to continue using an application until the end of their billing cycle. By default, pausing happens at the next billing interval so the customer can use the remainder of the period they paid for. If you want to pause a subscription immediately, you may use the `pauseNow` method: - $user->subscription()->pauseNow(); +```php +$user->subscription()->pauseNow(); +``` Using the `pauseUntil` method, you can pause the subscription until a specific moment in time: - $user->subscription()->pauseUntil(now()->addMonth()); +```php +$user->subscription()->pauseUntil(now()->addMonth()); +``` Or, you may use the `pauseNowUntil` method to immediately pause the subscription until a given point in time: - $user->subscription()->pauseNowUntil(now()->addMonth()); +```php +$user->subscription()->pauseNowUntil(now()->addMonth()); +``` You may determine if a user has paused their subscription but are still on their "grace period" using the `onPausedGracePeriod` method: - if ($user->subscription()->onPausedGracePeriod()) { - // ... - } +```php +if ($user->subscription()->onPausedGracePeriod()) { + // ... +} +``` To resume a paused subscription, you may invoke the `resume` method on the subscription: - $user->subscription()->resume(); +```php +$user->subscription()->resume(); +``` > [!WARNING] > A subscription cannot be modified while it is paused. If you want to swap to a different plan or update quantities you must resume the subscription first. @@ -1049,23 +1181,31 @@ To resume a paused subscription, you may invoke the `resume` method on the subsc To cancel a subscription, call the `cancel` method on the user's subscription: - $user->subscription()->cancel(); +```php +$user->subscription()->cancel(); +``` When a subscription is canceled, Cashier will automatically set the `ends_at` column in your database. This column is used to determine when the `subscribed` method should begin returning `false`. For example, if a customer cancels a subscription on March 1st, but the subscription was not scheduled to end until March 5th, the `subscribed` method will continue to return `true` until March 5th. This is done because a user is typically allowed to continue using an application until the end of their billing cycle. You may determine if a user has canceled their subscription but are still on their "grace period" using the `onGracePeriod` method: - if ($user->subscription()->onGracePeriod()) { - // ... - } +```php +if ($user->subscription()->onGracePeriod()) { + // ... +} +``` If you wish to cancel a subscription immediately, you may call the `cancelNow` method on the subscription: - $user->subscription()->cancelNow(); +```php +$user->subscription()->cancelNow(); +``` To stop a subscription on its grace period from canceling, you may invoke the `stopCancelation` method: - $user->subscription()->stopCancelation(); +```php +$user->subscription()->stopCancelation(); +``` > [!WARNING] > Paddle's subscriptions cannot be resumed after cancelation. If your customer wishes to resume their subscription, they will have to create a new subscription. @@ -1078,15 +1218,17 @@ To stop a subscription on its grace period from canceling, you may invoke the `s If you would like to offer trial periods to your customers while still collecting payment method information up front, you should use set a trial time in the Paddle dashboard on the price your customer is subscribing to. Then, initiate the checkout session as normal: - use Illuminate\Http\Request; +```php +use Illuminate\Http\Request; - Route::get('/user/subscribe', function (Request $request) { - $checkout = $request->user() - ->subscribe('pri_monthly') - ->returnTo(route('home')); +Route::get('/user/subscribe', function (Request $request) { + $checkout = $request->user() + ->subscribe('pri_monthly') + ->returnTo(route('home')); - return view('billing', ['checkout' => $checkout]); - }); + return view('billing', ['checkout' => $checkout]); +}); +``` When your application receives the `subscription_created` event, Cashier will set the trial period ending date on the subscription record within your application's database as well as instruct Paddle to not begin billing the customer until after this date. @@ -1095,88 +1237,109 @@ When your application receives the `subscription_created` event, Cashier will se You may determine if the user is within their trial period using either the `onTrial` method of the user instance or the `onTrial` method of the subscription instance. The two examples below are equivalent: - if ($user->onTrial()) { - // ... - } +```php +if ($user->onTrial()) { + // ... +} + +if ($user->subscription()->onTrial()) { + // ... +} +``` - if ($user->subscription()->onTrial()) { - // ... - } To determine if an existing trial has expired, you may use the `hasExpiredTrial` methods: - if ($user->hasExpiredTrial()) { - // ... - } +```php +if ($user->hasExpiredTrial()) { + // ... +} - if ($user->subscription()->hasExpiredTrial()) { - // ... - } +if ($user->subscription()->hasExpiredTrial()) { + // ... +} +``` To determine if a user is on trial for a specific subscription type, you may provide the type to the `onTrial` or `hasExpiredTrial` methods: - if ($user->onTrial('default')) { - // ... - } +```php +if ($user->onTrial('default')) { + // ... +} - if ($user->hasExpiredTrial('default')) { - // ... - } +if ($user->hasExpiredTrial('default')) { + // ... +} +``` ### Without Payment Method Up Front If you would like to offer trial periods without collecting the user's payment method information up front, you may set the `trial_ends_at` column on the customer record attached to your user to your desired trial ending date. This is typically done during user registration: - use App\Models\User; +```php +use App\Models\User; - $user = User::create([ - // ... - ]); +$user = User::create([ + // ... +]); - $user->createAsCustomer([ - 'trial_ends_at' => now()->addDays(10) - ]); +$user->createAsCustomer([ + 'trial_ends_at' => now()->addDays(10) +]); +``` Cashier refers to this type of trial as a "generic trial", since it is not attached to any existing subscription. The `onTrial` method on the `User` instance will return `true` if the current date is not past the value of `trial_ends_at`: - if ($user->onTrial()) { - // User is within their trial period... - } +```php +if ($user->onTrial()) { + // User is within their trial period... +} +``` Once you are ready to create an actual subscription for the user, you may use the `subscribe` method as usual: - use Illuminate\Http\Request; +```php +use Illuminate\Http\Request; - Route::get('/user/subscribe', function (Request $request) { - $checkout = $request->user() - ->subscribe('pri_monthly') - ->returnTo(route('home')); +Route::get('/user/subscribe', function (Request $request) { + $checkout = $request->user() + ->subscribe('pri_monthly') + ->returnTo(route('home')); - return view('billing', ['checkout' => $checkout]); - }); + return view('billing', ['checkout' => $checkout]); +}); +``` To retrieve the user's trial ending date, you may use the `trialEndsAt` method. This method will return a Carbon date instance if a user is on a trial or `null` if they aren't. You may also pass an optional subscription type parameter if you would like to get the trial ending date for a specific subscription other than the default one: - if ($user->onTrial('default')) { - $trialEndsAt = $user->trialEndsAt(); - } +```php +if ($user->onTrial('default')) { + $trialEndsAt = $user->trialEndsAt(); +} +``` You may use the `onGenericTrial` method if you wish to know specifically that the user is within their "generic" trial period and has not created an actual subscription yet: - if ($user->onGenericTrial()) { - // User is within their "generic" trial period... - } +```php +if ($user->onGenericTrial()) { + // User is within their "generic" trial period... +} +``` ### Extend or Activate a Trial You can extend an existing trial period on a subscription by invoking the `extendTrial` method and specifying the moment in time that the trial should end: - $user->subscription()->extendTrial(now()->addDays(5)); +```php +$user->subscription()->extendTrial(now()->addDays(5)); +``` Or, you may immediately activate a subscription by ending its trial by calling the `activate` method on the subscription: - $user->subscription()->activate(); +```php +$user->subscription()->activate(); +``` ## Handling Paddle Webhooks @@ -1203,11 +1366,13 @@ To ensure your application can handle Paddle webhooks, be sure to [configure the Since Paddle webhooks need to bypass Laravel's [CSRF protection](/docs/{{version}}/csrf), you should ensure that Laravel does not attempt to verify the CSRF token for incoming Paddle webhooks. To accomplish this, you should exclude `paddle/*` from CSRF protection in your application's `bootstrap/app.php` file: - ->withMiddleware(function (Middleware $middleware) { - $middleware->validateCsrfTokens(except: [ - 'paddle/*', - ]); - }) +```php +->withMiddleware(function (Middleware $middleware) { + $middleware->validateCsrfTokens(except: [ + 'paddle/*', + ]); +}) +``` #### Webhooks and Local Development @@ -1224,24 +1389,26 @@ Cashier automatically handles subscription cancelation on failed charges and oth Both events contain the full payload of the Paddle webhook. For example, if you wish to handle the `transaction.billed` webhook, you may register a [listener](/docs/{{version}}/events#defining-listeners) that will handle the event: - payload['event_type'] === 'transaction.billed') { - // Handle the incoming event... - } + if ($event->payload['event_type'] === 'transaction.billed') { + // Handle the incoming event... } } +} +``` Cashier also emit events dedicated to the type of the received webhook. In addition to the full payload from Paddle, they also contain the relevant models that were used to process the webhook such as the billable model, the subscription, or the receipt: @@ -1278,13 +1445,15 @@ To enable webhook verification, ensure that the `PADDLE_WEBHOOK_SECRET` environm If you would like to initiate a product purchase for a customer, you may use the `checkout` method on a billable model instance to generate a checkout session for the purchase. The `checkout` method accepts one or multiple price ID's. If necessary, an associative array may be used to provide the quantity of the product that is being purchased: - use Illuminate\Http\Request; +```php +use Illuminate\Http\Request; - Route::get('/buy', function (Request $request) { - $checkout = $request->user()->checkout(['pri_tshirt', 'pri_socks' => 5]); +Route::get('/buy', function (Request $request) { + $checkout = $request->user()->checkout(['pri_tshirt', 'pri_socks' => 5]); - return view('buy', ['checkout' => $checkout]); - }); + return view('buy', ['checkout' => $checkout]); +}); +``` After generating the checkout session, you may use Cashier's provided `paddle-button` [Blade component](#overlay-checkout) to allow the user to view the Paddle checkout widget and complete the purchase: @@ -1296,10 +1465,12 @@ After generating the checkout session, you may use Cashier's provided `paddle-bu A checkout session has a `customData` method, allowing you to pass any custom data you wish to the underlying transaction creation. Please consult [the Paddle documentation](https://developer.paddle.com/build/transactions/custom-data) to learn more about the options available to you when passing custom data: - $checkout = $user->checkout('pri_tshirt') - ->customData([ - 'custom_option' => $value, - ]); +```php +$checkout = $user->checkout('pri_tshirt') + ->customData([ + 'custom_option' => $value, + ]); +``` ### Refunding Transactions @@ -1308,20 +1479,24 @@ Refunding transactions will return the refunded amount to your customer's paymen For example, imagine we want to refund a specific transaction for prices `pri_123` and `pri_456`. We want to fully refund `pri_123`, but only refund two dollars for `pri_456`: - use App\Models\User; +```php +use App\Models\User; - $user = User::find(1); +$user = User::find(1); - $transaction = $user->transactions()->first(); +$transaction = $user->transactions()->first(); - $response = $transaction->refund('Accidental charge', [ - 'pri_123', // Fully refund this price... - 'pri_456' => 200, // Only partially refund this price... - ]); +$response = $transaction->refund('Accidental charge', [ + 'pri_123', // Fully refund this price... + 'pri_456' => 200, // Only partially refund this price... +]); +``` The example above refunds specific line items in a transaction. If you want to refund the entire transaction, simply provide a reason: - $response = $transaction->refund('Accidental charge'); +```php +$response = $transaction->refund('Accidental charge'); +``` For more information on refunds, please consult [Paddle's refund documentation](https://developer.paddle.com/build/transactions/create-transaction-adjustments). @@ -1333,10 +1508,12 @@ For more information on refunds, please consult [Paddle's refund documentation]( Just like refunding, you can also credit transactions. Crediting transactions will add the funds to the customer's balance so it may be used for future purchases. Crediting transactions can only be done for manually-collected transactions and not for automatically-collected transactions (like subscriptions) since Paddle handles subscription credits automatically: - $transaction = $user->transactions()->first(); +```php +$transaction = $user->transactions()->first(); - // Credit a specific line item fully... - $response = $transaction->credit('Compensation', 'pri_123'); +// Credit a specific line item fully... +$response = $transaction->credit('Compensation', 'pri_123'); +``` For more info, [see Paddle's documentation on crediting](https://developer.paddle.com/build/transactions/create-transaction-adjustments). @@ -1348,11 +1525,13 @@ For more info, [see Paddle's documentation on crediting](https://developer.paddl You may easily retrieve an array of a billable model's transactions via the `transactions` property: - use App\Models\User; +```php +use App\Models\User; - $user = User::find(1); +$user = User::find(1); - $transactions = $user->transactions; +$transactions = $user->transactions; +``` Transactions represent payments for your products and purchases and are accompanied by invoices. Only completed transactions are stored in your application's database. @@ -1373,26 +1552,30 @@ When listing the transactions for a customer, you may use the transaction instan The `download-invoice` route may look like the following: - use Illuminate\Http\Request; - use Laravel\Paddle\Transaction; +```php +use Illuminate\Http\Request; +use Laravel\Paddle\Transaction; - Route::get('/download-invoice/{transaction}', function (Request $request, Transaction $transaction) { - return $transaction->redirectToInvoicePdf(); - })->name('download-invoice'); +Route::get('/download-invoice/{transaction}', function (Request $request, Transaction $transaction) { + return $transaction->redirectToInvoicePdf(); +})->name('download-invoice'); +``` ### Past and Upcoming Payments You may use the `lastPayment` and `nextPayment` methods to retrieve and display a customer's past or upcoming payments for recurring subscriptions: - use App\Models\User; +```php +use App\Models\User; - $user = User::find(1); +$user = User::find(1); - $subscription = $user->subscription(); +$subscription = $user->subscription(); - $lastPayment = $subscription->lastPayment(); - $nextPayment = $subscription->nextPayment(); +$lastPayment = $subscription->lastPayment(); +$nextPayment = $subscription->nextPayment(); +``` Both of these methods will return an instance of `Laravel\Paddle\Payment`; however, `lastPayment` will return `null` when transactions have not been synced by webhooks yet, while `nextPayment` will return `null` when the billing cycle has ended (such as when a subscription has been canceled): diff --git a/collections.md b/collections.md index 6ad182c8d98..d4164de0e8f 100644 --- a/collections.md +++ b/collections.md @@ -16,11 +16,13 @@ The `Illuminate\Support\Collection` class provides a fluent, convenient wrapper for working with arrays of data. For example, check out the following code. We'll use the `collect` helper to create a new collection instance from the array, run the `strtoupper` function on each element, and then remove all empty elements: - $collection = collect(['taylor', 'abigail', null])->map(function (?string $name) { - return strtoupper($name); - })->reject(function (string $name) { - return empty($name); - }); +```php +$collection = collect(['taylor', 'abigail', null])->map(function (?string $name) { + return strtoupper($name); +})->reject(function (string $name) { + return empty($name); +}); +``` As you can see, the `Collection` class allows you to chain its methods to perform fluent mapping and reducing of the underlying array. In general, collections are immutable, meaning every `Collection` method returns an entirely new `Collection` instance. @@ -29,7 +31,9 @@ As you can see, the `Collection` class allows you to chain its methods to perfor As mentioned above, the `collect` helper returns a new `Illuminate\Support\Collection` instance for the given array. So, creating a collection is as simple as: - $collection = collect([1, 2, 3]); +```php +$collection = collect([1, 2, 3]); +``` > [!NOTE] > The results of [Eloquent](/docs/{{version}}/eloquent) queries are always returned as `Collection` instances. @@ -39,20 +43,22 @@ As mentioned above, the `collect` helper returns a new `Illuminate\Support\Colle Collections are "macroable", which allows you to add additional methods to the `Collection` class at run time. The `Illuminate\Support\Collection` class' `macro` method accepts a closure that will be executed when your macro is called. The macro closure may access the collection's other methods via `$this`, just as if it were a real method of the collection class. For example, the following code adds a `toUpper` method to the `Collection` class: - use Illuminate\Support\Collection; - use Illuminate\Support\Str; +```php +use Illuminate\Support\Collection; +use Illuminate\Support\Str; - Collection::macro('toUpper', function () { - return $this->map(function (string $value) { - return Str::upper($value); - }); +Collection::macro('toUpper', function () { + return $this->map(function (string $value) { + return Str::upper($value); }); +}); - $collection = collect(['first', 'second']); +$collection = collect(['first', 'second']); - $upper = $collection->toUpper(); +$upper = $collection->toUpper(); - // ['FIRST', 'SECOND'] +// ['FIRST', 'SECOND'] +``` Typically, you should declare collection macros in the `boot` method of a [service provider](/docs/{{version}}/providers). @@ -61,18 +67,20 @@ Typically, you should declare collection macros in the `boot` method of a [servi If necessary, you may define macros that accept additional arguments: - use Illuminate\Support\Collection; - use Illuminate\Support\Facades\Lang; +```php +use Illuminate\Support\Collection; +use Illuminate\Support\Facades\Lang; - Collection::macro('toLocale', function (string $locale) { - return $this->map(function (string $value) use ($locale) { - return Lang::get($value, [], $locale); - }); +Collection::macro('toLocale', function (string $locale) { + return $this->map(function (string $value) use ($locale) { + return Lang::get($value, [], $locale); }); +}); - $collection = collect(['first', 'second']); +$collection = collect(['first', 'second']); - $translated = $collection->toLocale('es'); +$translated = $collection->toLocale('es'); +``` ## Available Methods @@ -266,38 +274,46 @@ For the majority of the remaining collection documentation, we'll discuss each m The `after` method returns the item after the given item. `null` is returned if the given item is not found or is the last item: - $collection = collect([1, 2, 3, 4, 5]); +```php +$collection = collect([1, 2, 3, 4, 5]); - $collection->after(3); +$collection->after(3); - // 4 +// 4 - $collection->after(5); +$collection->after(5); - // null +// null +``` This method searches for the given item using "loose" comparison, meaning a string containing an integer value will be considered equal to an integer of the same value. To use "strict" comparison, you may provide the `strict` argument to the method: - collect([2, 4, 6, 8])->after('4', strict: true); +```php +collect([2, 4, 6, 8])->after('4', strict: true); - // null +// null +``` Alternatively, you may provide your own closure to search for the first item that passes a given truth test: - collect([2, 4, 6, 8])->after(function (int $item, int $key) { - return $item > 5; - }); +```php +collect([2, 4, 6, 8])->after(function (int $item, int $key) { + return $item > 5; +}); - // 8 +// 8 +``` #### `all()` {.collection-method} The `all` method returns the underlying array represented by the collection: - collect([1, 2, 3])->all(); +```php +collect([1, 2, 3])->all(); - // [1, 2, 3] +// [1, 2, 3] +``` #### `average()` {.collection-method} @@ -309,56 +325,62 @@ Alias for the [`avg`](#method-avg) method. The `avg` method returns the [average value](https://en.wikipedia.org/wiki/Average) of a given key: - $average = collect([ - ['foo' => 10], - ['foo' => 10], - ['foo' => 20], - ['foo' => 40] - ])->avg('foo'); +```php +$average = collect([ + ['foo' => 10], + ['foo' => 10], + ['foo' => 20], + ['foo' => 40] +])->avg('foo'); - // 20 +// 20 - $average = collect([1, 1, 2, 4])->avg(); +$average = collect([1, 1, 2, 4])->avg(); - // 2 +// 2 +``` #### `before()` {.collection-method} The `before` method is the opposite of the [`after`](#method-after) method. It returns the item before the given item. `null` is returned if the given item is not found or is the first item: - $collection = collect([1, 2, 3, 4, 5]); +```php +$collection = collect([1, 2, 3, 4, 5]); - $collection->before(3); +$collection->before(3); - // 2 +// 2 - $collection->before(1); +$collection->before(1); - // null +// null - collect([2, 4, 6, 8])->before('4', strict: true); +collect([2, 4, 6, 8])->before('4', strict: true); - // null +// null - collect([2, 4, 6, 8])->before(function (int $item, int $key) { - return $item > 5; - }); +collect([2, 4, 6, 8])->before(function (int $item, int $key) { + return $item > 5; +}); - // 4 +// 4 +``` #### `chunk()` {.collection-method} The `chunk` method breaks the collection into multiple, smaller collections of a given size: - $collection = collect([1, 2, 3, 4, 5, 6, 7]); +```php +$collection = collect([1, 2, 3, 4, 5, 6, 7]); - $chunks = $collection->chunk(4); +$chunks = $collection->chunk(4); - $chunks->all(); +$chunks->all(); - // [[1, 2, 3, 4], [5, 6, 7]] +// [[1, 2, 3, 4], [5, 6, 7]] +``` This method is especially useful in [views](/docs/{{version}}/views) when working with a grid system such as [Bootstrap](https://getbootstrap.com/docs/5.3/layout/grid/). For example, imagine you have a collection of [Eloquent](/docs/{{version}}/eloquent) models you want to display in a grid: @@ -377,85 +399,95 @@ This method is especially useful in [views](/docs/{{version}}/views) when workin The `chunkWhile` method breaks the collection into multiple, smaller collections based on the evaluation of the given callback. The `$chunk` variable passed to the closure may be used to inspect the previous element: - $collection = collect(str_split('AABBCCCD')); +```php +$collection = collect(str_split('AABBCCCD')); - $chunks = $collection->chunkWhile(function (string $value, int $key, Collection $chunk) { - return $value === $chunk->last(); - }); +$chunks = $collection->chunkWhile(function (string $value, int $key, Collection $chunk) { + return $value === $chunk->last(); +}); - $chunks->all(); +$chunks->all(); - // [['A', 'A'], ['B', 'B'], ['C', 'C', 'C'], ['D']] +// [['A', 'A'], ['B', 'B'], ['C', 'C', 'C'], ['D']] +``` #### `collapse()` {.collection-method} The `collapse` method collapses a collection of arrays into a single, flat collection: - $collection = collect([ - [1, 2, 3], - [4, 5, 6], - [7, 8, 9], - ]); +```php +$collection = collect([ + [1, 2, 3], + [4, 5, 6], + [7, 8, 9], +]); - $collapsed = $collection->collapse(); +$collapsed = $collection->collapse(); - $collapsed->all(); +$collapsed->all(); - // [1, 2, 3, 4, 5, 6, 7, 8, 9] +// [1, 2, 3, 4, 5, 6, 7, 8, 9] +``` #### `collapseWithKeys()` {.collection-method} The `collapseWithKeys` method flattens a collection of arrays or collections into a single collection, keeping the original keys intact: - $collection = collect([ - ['first' => collect([1, 2, 3])], - ['second' => [4, 5, 6]], - ['third' => collect([7, 8, 9])] - ]); +```php +$collection = collect([ + ['first' => collect([1, 2, 3])], + ['second' => [4, 5, 6]], + ['third' => collect([7, 8, 9])] +]); - $collapsed = $collection->collapseWithKeys(); +$collapsed = $collection->collapseWithKeys(); - $collapsed->all(); +$collapsed->all(); - // [ - // 'first' => [1, 2, 3], - // 'second' => [4, 5, 6], - // 'third' => [7, 8, 9], - // ] +// [ +// 'first' => [1, 2, 3], +// 'second' => [4, 5, 6], +// 'third' => [7, 8, 9], +// ] +``` #### `collect()` {.collection-method} The `collect` method returns a new `Collection` instance with the items currently in the collection: - $collectionA = collect([1, 2, 3]); +```php +$collectionA = collect([1, 2, 3]); - $collectionB = $collectionA->collect(); +$collectionB = $collectionA->collect(); - $collectionB->all(); +$collectionB->all(); - // [1, 2, 3] +// [1, 2, 3] +``` The `collect` method is primarily useful for converting [lazy collections](#lazy-collections) into standard `Collection` instances: - $lazyCollection = LazyCollection::make(function () { - yield 1; - yield 2; - yield 3; - }); +```php +$lazyCollection = LazyCollection::make(function () { + yield 1; + yield 2; + yield 3; +}); - $collection = $lazyCollection->collect(); +$collection = $lazyCollection->collect(); - $collection::class; +$collection::class; - // 'Illuminate\Support\Collection' +// 'Illuminate\Support\Collection' - $collection->all(); +$collection->all(); - // [1, 2, 3] +// [1, 2, 3] +``` > [!NOTE] > The `collect` method is especially useful when you have an instance of `Enumerable` and need a non-lazy collection instance. Since `collect()` is part of the `Enumerable` contract, you can safely use it to get a `Collection` instance. @@ -465,26 +497,30 @@ The `collect` method is primarily useful for converting [lazy collections](#lazy The `combine` method combines the values of the collection, as keys, with the values of another array or collection: - $collection = collect(['name', 'age']); +```php +$collection = collect(['name', 'age']); - $combined = $collection->combine(['George', 29]); +$combined = $collection->combine(['George', 29]); - $combined->all(); +$combined->all(); - // ['name' => 'George', 'age' => 29] +// ['name' => 'George', 'age' => 29] +``` #### `concat()` {.collection-method} The `concat` method appends the given `array` or collection's values onto the end of another collection: - $collection = collect(['John Doe']); +```php +$collection = collect(['John Doe']); - $concatenated = $collection->concat(['Jane Doe'])->concat(['name' => 'Johnny Doe']); +$concatenated = $collection->concat(['Jane Doe'])->concat(['name' => 'Johnny Doe']); - $concatenated->all(); +$concatenated->all(); - // ['John Doe', 'Jane Doe', 'Johnny Doe'] +// ['John Doe', 'Jane Doe', 'Johnny Doe'] +``` The `concat` method numerically reindexes keys for items concatenated onto the original collection. To maintain keys in associative collections, see the [merge](#method-merge) method. @@ -493,36 +529,42 @@ The `concat` method numerically reindexes keys for items concatenated onto the o The `contains` method determines whether the collection contains a given item. You may pass a closure to the `contains` method to determine if an element exists in the collection matching a given truth test: - $collection = collect([1, 2, 3, 4, 5]); +```php +$collection = collect([1, 2, 3, 4, 5]); - $collection->contains(function (int $value, int $key) { - return $value > 5; - }); +$collection->contains(function (int $value, int $key) { + return $value > 5; +}); - // false +// false +``` Alternatively, you may pass a string to the `contains` method to determine whether the collection contains a given item value: - $collection = collect(['name' => 'Desk', 'price' => 100]); +```php +$collection = collect(['name' => 'Desk', 'price' => 100]); - $collection->contains('Desk'); +$collection->contains('Desk'); - // true +// true - $collection->contains('New York'); +$collection->contains('New York'); - // false +// false +``` You may also pass a key / value pair to the `contains` method, which will determine if the given pair exists in the collection: - $collection = collect([ - ['product' => 'Desk', 'price' => 200], - ['product' => 'Chair', 'price' => 100], - ]); +```php +$collection = collect([ + ['product' => 'Desk', 'price' => 200], + ['product' => 'Chair', 'price' => 100], +]); - $collection->contains('product', 'Bookcase'); +$collection->contains('product', 'Bookcase'); - // false +// false +``` The `contains` method uses "loose" comparisons when checking item values, meaning a string with an integer value will be considered equal to an integer of the same value. Use the [`containsStrict`](#method-containsstrict) method to filter using "strict" comparisons. @@ -533,17 +575,19 @@ For the inverse of `contains`, see the [doesntContain](#method-doesntcontain) me The `containsOneItem` method determines whether the collection contains a single item: - collect([])->containsOneItem(); +```php +collect([])->containsOneItem(); - // false +// false - collect(['1'])->containsOneItem(); +collect(['1'])->containsOneItem(); - // true +// true - collect(['1', '2'])->containsOneItem(); +collect(['1', '2'])->containsOneItem(); - // false +// false +``` #### `containsStrict()` {.collection-method} @@ -558,93 +602,103 @@ This method has the same signature as the [`contains`](#method-contains) method; The `count` method returns the total number of items in the collection: - $collection = collect([1, 2, 3, 4]); +```php +$collection = collect([1, 2, 3, 4]); - $collection->count(); +$collection->count(); - // 4 +// 4 +``` #### `countBy()` {.collection-method} The `countBy` method counts the occurrences of values in the collection. By default, the method counts the occurrences of every element, allowing you to count certain "types" of elements in the collection: - $collection = collect([1, 2, 2, 2, 3]); +```php +$collection = collect([1, 2, 2, 2, 3]); - $counted = $collection->countBy(); +$counted = $collection->countBy(); - $counted->all(); +$counted->all(); - // [1 => 1, 2 => 3, 3 => 1] +// [1 => 1, 2 => 3, 3 => 1] +``` You pass a closure to the `countBy` method to count all items by a custom value: - $collection = collect(['alice@gmail.com', 'bob@yahoo.com', 'carlos@gmail.com']); +```php +$collection = collect(['alice@gmail.com', 'bob@yahoo.com', 'carlos@gmail.com']); - $counted = $collection->countBy(function (string $email) { - return substr(strrchr($email, "@"), 1); - }); +$counted = $collection->countBy(function (string $email) { + return substr(strrchr($email, "@"), 1); +}); - $counted->all(); +$counted->all(); - // ['gmail.com' => 2, 'yahoo.com' => 1] +// ['gmail.com' => 2, 'yahoo.com' => 1] +``` #### `crossJoin()` {.collection-method} The `crossJoin` method cross joins the collection's values among the given arrays or collections, returning a Cartesian product with all possible permutations: - $collection = collect([1, 2]); +```php +$collection = collect([1, 2]); - $matrix = $collection->crossJoin(['a', 'b']); +$matrix = $collection->crossJoin(['a', 'b']); - $matrix->all(); +$matrix->all(); - /* - [ - [1, 'a'], - [1, 'b'], - [2, 'a'], - [2, 'b'], - ] - */ +/* + [ + [1, 'a'], + [1, 'b'], + [2, 'a'], + [2, 'b'], + ] +*/ - $collection = collect([1, 2]); +$collection = collect([1, 2]); - $matrix = $collection->crossJoin(['a', 'b'], ['I', 'II']); +$matrix = $collection->crossJoin(['a', 'b'], ['I', 'II']); - $matrix->all(); +$matrix->all(); - /* - [ - [1, 'a', 'I'], - [1, 'a', 'II'], - [1, 'b', 'I'], - [1, 'b', 'II'], - [2, 'a', 'I'], - [2, 'a', 'II'], - [2, 'b', 'I'], - [2, 'b', 'II'], - ] - */ +/* + [ + [1, 'a', 'I'], + [1, 'a', 'II'], + [1, 'b', 'I'], + [1, 'b', 'II'], + [2, 'a', 'I'], + [2, 'a', 'II'], + [2, 'b', 'I'], + [2, 'b', 'II'], + ] +*/ +``` #### `dd()` {.collection-method} The `dd` method dumps the collection's items and ends execution of the script: - $collection = collect(['John Doe', 'Jane Doe']); +```php +$collection = collect(['John Doe', 'Jane Doe']); - $collection->dd(); +$collection->dd(); - /* - Collection { - #items: array:2 [ - 0 => "John Doe" - 1 => "Jane Doe" - ] - } - */ +/* + Collection { + #items: array:2 [ + 0 => "John Doe" + 1 => "Jane Doe" + ] + } +*/ +``` If you do not want to stop executing the script, use the [`dump`](#method-dump) method instead. @@ -653,13 +707,15 @@ If you do not want to stop executing the script, use the [`dump`](#method-dump) The `diff` method compares the collection against another collection or a plain PHP `array` based on its values. This method will return the values in the original collection that are not present in the given collection: - $collection = collect([1, 2, 3, 4, 5]); +```php +$collection = collect([1, 2, 3, 4, 5]); - $diff = $collection->diff([2, 4, 6, 8]); +$diff = $collection->diff([2, 4, 6, 8]); - $diff->all(); +$diff->all(); - // [1, 3, 5] +// [1, 3, 5] +``` > [!NOTE] > This method's behavior is modified when using [Eloquent Collections](/docs/{{version}}/eloquent-collections#method-diff). @@ -669,43 +725,47 @@ The `diff` method compares the collection against another collection or a plain The `diffAssoc` method compares the collection against another collection or a plain PHP `array` based on its keys and values. This method will return the key / value pairs in the original collection that are not present in the given collection: - $collection = collect([ - 'color' => 'orange', - 'type' => 'fruit', - 'remain' => 6, - ]); +```php +$collection = collect([ + 'color' => 'orange', + 'type' => 'fruit', + 'remain' => 6, +]); - $diff = $collection->diffAssoc([ - 'color' => 'yellow', - 'type' => 'fruit', - 'remain' => 3, - 'used' => 6, - ]); +$diff = $collection->diffAssoc([ + 'color' => 'yellow', + 'type' => 'fruit', + 'remain' => 3, + 'used' => 6, +]); - $diff->all(); +$diff->all(); - // ['color' => 'orange', 'remain' => 6] +// ['color' => 'orange', 'remain' => 6] +``` #### `diffAssocUsing()` {.collection-method} Unlike `diffAssoc`, `diffAssocUsing` accepts a user supplied callback function for the indices comparison: - $collection = collect([ - 'color' => 'orange', - 'type' => 'fruit', - 'remain' => 6, - ]); +```php +$collection = collect([ + 'color' => 'orange', + 'type' => 'fruit', + 'remain' => 6, +]); - $diff = $collection->diffAssocUsing([ - 'Color' => 'yellow', - 'Type' => 'fruit', - 'Remain' => 3, - ], 'strnatcasecmp'); +$diff = $collection->diffAssocUsing([ + 'Color' => 'yellow', + 'Type' => 'fruit', + 'Remain' => 3, +], 'strnatcasecmp'); - $diff->all(); +$diff->all(); - // ['color' => 'orange', 'remain' => 6] +// ['color' => 'orange', 'remain' => 6] +``` The callback must be a comparison function that returns an integer less than, equal to, or greater than zero. For more information, refer to the PHP documentation on [`array_diff_uassoc`](https://www.php.net/array_diff_uassoc#refsect1-function.array-diff-uassoc-parameters), which is the PHP function that the `diffAssocUsing` method utilizes internally. @@ -714,60 +774,68 @@ The callback must be a comparison function that returns an integer less than, eq The `diffKeys` method compares the collection against another collection or a plain PHP `array` based on its keys. This method will return the key / value pairs in the original collection that are not present in the given collection: - $collection = collect([ - 'one' => 10, - 'two' => 20, - 'three' => 30, - 'four' => 40, - 'five' => 50, - ]); +```php +$collection = collect([ + 'one' => 10, + 'two' => 20, + 'three' => 30, + 'four' => 40, + 'five' => 50, +]); - $diff = $collection->diffKeys([ - 'two' => 2, - 'four' => 4, - 'six' => 6, - 'eight' => 8, - ]); +$diff = $collection->diffKeys([ + 'two' => 2, + 'four' => 4, + 'six' => 6, + 'eight' => 8, +]); - $diff->all(); +$diff->all(); - // ['one' => 10, 'three' => 30, 'five' => 50] +// ['one' => 10, 'three' => 30, 'five' => 50] +``` #### `doesntContain()` {.collection-method} The `doesntContain` method determines whether the collection does not contain a given item. You may pass a closure to the `doesntContain` method to determine if an element does not exist in the collection matching a given truth test: - $collection = collect([1, 2, 3, 4, 5]); +```php +$collection = collect([1, 2, 3, 4, 5]); - $collection->doesntContain(function (int $value, int $key) { - return $value < 5; - }); +$collection->doesntContain(function (int $value, int $key) { + return $value < 5; +}); - // false +// false +``` Alternatively, you may pass a string to the `doesntContain` method to determine whether the collection does not contain a given item value: - $collection = collect(['name' => 'Desk', 'price' => 100]); +```php +$collection = collect(['name' => 'Desk', 'price' => 100]); - $collection->doesntContain('Table'); +$collection->doesntContain('Table'); - // true +// true - $collection->doesntContain('Desk'); +$collection->doesntContain('Desk'); - // false +// false +``` You may also pass a key / value pair to the `doesntContain` method, which will determine if the given pair does not exist in the collection: - $collection = collect([ - ['product' => 'Desk', 'price' => 200], - ['product' => 'Chair', 'price' => 100], - ]); +```php +$collection = collect([ + ['product' => 'Desk', 'price' => 200], + ['product' => 'Chair', 'price' => 100], +]); - $collection->doesntContain('product', 'Bookcase'); +$collection->doesntContain('product', 'Bookcase'); - // true +// true +``` The `doesntContain` method uses "loose" comparisons when checking item values, meaning a string with an integer value will be considered equal to an integer of the same value. @@ -776,31 +844,37 @@ The `doesntContain` method uses "loose" comparisons when checking item values, m The `dot` method flattens a multi-dimensional collection into a single level collection that uses "dot" notation to indicate depth: - $collection = collect(['products' => ['desk' => ['price' => 100]]]); +```php +$collection = collect(['products' => ['desk' => ['price' => 100]]]); - $flattened = $collection->dot(); +$flattened = $collection->dot(); - $flattened->all(); +$flattened->all(); - // ['products.desk.price' => 100] +// ['products.desk.price' => 100] +``` #### `dump()` {.collection-method} The `dump` method dumps the collection's items: - $collection = collect(['John Doe', 'Jane Doe']); +```php - $collection->dump(); +``````php +$collection = collect(['John Doe', 'Jane Doe']); - /* - Collection { - #items: array:2 [ - 0 => "John Doe" - 1 => "Jane Doe" - ] - } - */ +$collection->dump(); + +/* + Collection { + #items: array:2 [ + 0 => "John Doe" + 1 => "Jane Doe" + ] + } +*/ +``` If you want to stop executing the script after dumping the collection, use the [`dd`](#method-dd) method instead. @@ -809,23 +883,31 @@ If you want to stop executing the script after dumping the collection, use the [ The `duplicates` method retrieves and returns duplicate values from the collection: - $collection = collect(['a', 'b', 'a', 'c', 'b']); +```php + +``````php +$collection = collect(['a', 'b', 'a', 'c', 'b']); - $collection->duplicates(); +$collection->duplicates(); - // [2 => 'a', 4 => 'b'] +// [2 => 'a', 4 => 'b'] +``` If the collection contains arrays or objects, you can pass the key of the attributes that you wish to check for duplicate values: - $employees = collect([ - ['email' => 'abigail@example.com', 'position' => 'Developer'], - ['email' => 'james@example.com', 'position' => 'Designer'], - ['email' => 'victoria@example.com', 'position' => 'Developer'], - ]); +```php + +``````php +$employees = collect([ + ['email' => 'abigail@example.com', 'position' => 'Developer'], + ['email' => 'james@example.com', 'position' => 'Designer'], + ['email' => 'victoria@example.com', 'position' => 'Developer'], +]); - $employees->duplicates('position'); +$employees->duplicates('position'); - // [2 => 'Developer'] +// [2 => 'Developer'] +``` #### `duplicatesStrict()` {.collection-method} @@ -837,49 +919,61 @@ This method has the same signature as the [`duplicates`](#method-duplicates) met The `each` method iterates over the items in the collection and passes each item to a closure: - $collection = collect([1, 2, 3, 4]); +```php +$collection = collect([1, 2, 3, 4]); - $collection->each(function (int $item, int $key) { - // ... - }); +$collection->each(function (int $item, int $key) { + // ... +}); +``` If you would like to stop iterating through the items, you may return `false` from your closure: - $collection->each(function (int $item, int $key) { - if (/* condition */) { - return false; - } - }); +```php +$collection->each(function (int $item, int $key) { + if (/* condition */) { + return false; + } +}); +``` #### `eachSpread()` {.collection-method} The `eachSpread` method iterates over the collection's items, passing each nested item value into the given callback: - $collection = collect([['John Doe', 35], ['Jane Doe', 33]]); +```php +$collection = collect([['John Doe', 35], ['Jane Doe', 33]]); - $collection->eachSpread(function (string $name, int $age) { - // ... - }); +$collection->eachSpread(function (string $name, int $age) { + // ... +}); +``` You may stop iterating through the items by returning `false` from the callback: - $collection->eachSpread(function (string $name, int $age) { - return false; - }); +```php +$collection->eachSpread(function (string $name, int $age) { + return false; +}); +``` #### `ensure()` {.collection-method} The `ensure` method may be used to verify that all elements of a collection are of a given type or list of types. Otherwise, an `UnexpectedValueException` will be thrown: - return $collection->ensure(User::class); +```php +return $collection->ensure(User::class); - return $collection->ensure([User::class, Customer::class]); +return $collection->ensure([User::class, Customer::class]); +``` Primitive types such as `string`, `int`, `float`, `bool`, and `array` may also be specified: - return $collection->ensure('int'); +```php +return $collection->ensure('int'); +``` > [!WARNING] > The `ensure` method does not guarantee that elements of different types will not be added to the collection at a later time. @@ -889,34 +983,40 @@ Primitive types such as `string`, `int`, `float`, `bool`, and `array` may also b The `every` method may be used to verify that all elements of a collection pass a given truth test: - collect([1, 2, 3, 4])->every(function (int $value, int $key) { - return $value > 2; - }); +```php +collect([1, 2, 3, 4])->every(function (int $value, int $key) { + return $value > 2; +}); - // false +// false +``` If the collection is empty, the `every` method will return true: - $collection = collect([]); +```php +$collection = collect([]); - $collection->every(function (int $value, int $key) { - return $value > 2; - }); +$collection->every(function (int $value, int $key) { + return $value > 2; +}); - // true +// true +``` #### `except()` {.collection-method} The `except` method returns all items in the collection except for those with the specified keys: - $collection = collect(['product_id' => 1, 'price' => 100, 'discount' => false]); +```php +$collection = collect(['product_id' => 1, 'price' => 100, 'discount' => false]); - $filtered = $collection->except(['price', 'discount']); +$filtered = $collection->except(['price', 'discount']); - $filtered->all(); +$filtered->all(); - // ['product_id' => 1] +// ['product_id' => 1] +``` For the inverse of `except`, see the [only](#method-only) method. @@ -928,23 +1028,27 @@ For the inverse of `except`, see the [only](#method-only) method. The `filter` method filters the collection using the given callback, keeping only those items that pass a given truth test: - $collection = collect([1, 2, 3, 4]); +```php +$collection = collect([1, 2, 3, 4]); - $filtered = $collection->filter(function (int $value, int $key) { - return $value > 2; - }); +$filtered = $collection->filter(function (int $value, int $key) { + return $value > 2; +}); - $filtered->all(); +$filtered->all(); - // [3, 4] +// [3, 4] +``` If no callback is supplied, all entries of the collection that are equivalent to `false` will be removed: - $collection = collect([1, 2, 3, null, false, '', 0, []]); +```php +$collection = collect([1, 2, 3, null, false, '', 0, []]); - $collection->filter()->all(); +$collection->filter()->all(); - // [1, 2, 3] +// [1, 2, 3] +``` For the inverse of `filter`, see the [reject](#method-reject) method. @@ -953,127 +1057,147 @@ For the inverse of `filter`, see the [reject](#method-reject) method. The `first` method returns the first element in the collection that passes a given truth test: - collect([1, 2, 3, 4])->first(function (int $value, int $key) { - return $value > 2; - }); +```php +collect([1, 2, 3, 4])->first(function (int $value, int $key) { + return $value > 2; +}); - // 3 +// 3 +``` You may also call the `first` method with no arguments to get the first element in the collection. If the collection is empty, `null` is returned: - collect([1, 2, 3, 4])->first(); +```php +collect([1, 2, 3, 4])->first(); - // 1 +// 1 +``` #### `firstOrFail()` {.collection-method} The `firstOrFail` method is identical to the `first` method; however, if no result is found, an `Illuminate\Support\ItemNotFoundException` exception will be thrown: - collect([1, 2, 3, 4])->firstOrFail(function (int $value, int $key) { - return $value > 5; - }); +```php +collect([1, 2, 3, 4])->firstOrFail(function (int $value, int $key) { + return $value > 5; +}); - // Throws ItemNotFoundException... +// Throws ItemNotFoundException... +``` You may also call the `firstOrFail` method with no arguments to get the first element in the collection. If the collection is empty, an `Illuminate\Support\ItemNotFoundException` exception will be thrown: - collect([])->firstOrFail(); +```php +collect([])->firstOrFail(); - // Throws ItemNotFoundException... +// Throws ItemNotFoundException... +``` #### `firstWhere()` {.collection-method} The `firstWhere` method returns the first element in the collection with the given key / value pair: - $collection = collect([ - ['name' => 'Regena', 'age' => null], - ['name' => 'Linda', 'age' => 14], - ['name' => 'Diego', 'age' => 23], - ['name' => 'Linda', 'age' => 84], - ]); +```php +$collection = collect([ + ['name' => 'Regena', 'age' => null], + ['name' => 'Linda', 'age' => 14], + ['name' => 'Diego', 'age' => 23], + ['name' => 'Linda', 'age' => 84], +]); - $collection->firstWhere('name', 'Linda'); +$collection->firstWhere('name', 'Linda'); - // ['name' => 'Linda', 'age' => 14] +// ['name' => 'Linda', 'age' => 14] +``` You may also call the `firstWhere` method with a comparison operator: - $collection->firstWhere('age', '>=', 18); +```php +$collection->firstWhere('age', '>=', 18); - // ['name' => 'Diego', 'age' => 23] +// ['name' => 'Diego', 'age' => 23] +``` Like the [where](#method-where) method, you may pass one argument to the `firstWhere` method. In this scenario, the `firstWhere` method will return the first item where the given item key's value is "truthy": - $collection->firstWhere('age'); +```php +$collection->firstWhere('age'); - // ['name' => 'Linda', 'age' => 14] +// ['name' => 'Linda', 'age' => 14] +``` #### `flatMap()` {.collection-method} The `flatMap` method iterates through the collection and passes each value to the given closure. The closure is free to modify the item and return it, thus forming a new collection of modified items. Then, the array is flattened by one level: - $collection = collect([ - ['name' => 'Sally'], - ['school' => 'Arkansas'], - ['age' => 28] - ]); +```php +$collection = collect([ + ['name' => 'Sally'], + ['school' => 'Arkansas'], + ['age' => 28] +]); - $flattened = $collection->flatMap(function (array $values) { - return array_map('strtoupper', $values); - }); +$flattened = $collection->flatMap(function (array $values) { + return array_map('strtoupper', $values); +}); - $flattened->all(); +$flattened->all(); - // ['name' => 'SALLY', 'school' => 'ARKANSAS', 'age' => '28']; +// ['name' => 'SALLY', 'school' => 'ARKANSAS', 'age' => '28']; +``` #### `flatten()` {.collection-method} The `flatten` method flattens a multi-dimensional collection into a single dimension: - $collection = collect([ - 'name' => 'taylor', - 'languages' => [ - 'php', 'javascript' - ] - ]); +```php +$collection = collect([ + 'name' => 'taylor', + 'languages' => [ + 'php', 'javascript' + ] +]); - $flattened = $collection->flatten(); +$flattened = $collection->flatten(); - $flattened->all(); +$flattened->all(); - // ['taylor', 'php', 'javascript']; +// ['taylor', 'php', 'javascript']; +``` If necessary, you may pass the `flatten` method a "depth" argument: - $collection = collect([ - 'Apple' => [ - [ - 'name' => 'iPhone 6S', - 'brand' => 'Apple' - ], +```php +$collection = collect([ + 'Apple' => [ + [ + 'name' => 'iPhone 6S', + 'brand' => 'Apple' ], - 'Samsung' => [ - [ - 'name' => 'Galaxy S7', - 'brand' => 'Samsung' - ], + ], + 'Samsung' => [ + [ + 'name' => 'Galaxy S7', + 'brand' => 'Samsung' ], - ]); + ], +]); - $products = $collection->flatten(1); +$products = $collection->flatten(1); - $products->values()->all(); +$products->values()->all(); - /* - [ - ['name' => 'iPhone 6S', 'brand' => 'Apple'], - ['name' => 'Galaxy S7', 'brand' => 'Samsung'], - ] - */ +/* + [ + ['name' => 'iPhone 6S', 'brand' => 'Apple'], + ['name' => 'Galaxy S7', 'brand' => 'Samsung'], + ] +*/ +``` In this example, calling `flatten` without providing the depth would have also flattened the nested arrays, resulting in `['iPhone 6S', 'Apple', 'Galaxy S7', 'Samsung']`. Providing a depth allows you to specify the number of levels nested arrays will be flattened. @@ -1082,30 +1206,34 @@ In this example, calling `flatten` without providing the depth would have also f The `flip` method swaps the collection's keys with their corresponding values: - $collection = collect(['name' => 'taylor', 'framework' => 'laravel']); +```php +$collection = collect(['name' => 'taylor', 'framework' => 'laravel']); - $flipped = $collection->flip(); +$flipped = $collection->flip(); - $flipped->all(); +$flipped->all(); - // ['taylor' => 'name', 'laravel' => 'framework'] +// ['taylor' => 'name', 'laravel' => 'framework'] +``` #### `forget()` {.collection-method} The `forget` method removes an item from the collection by its key: - $collection = collect(['name' => 'taylor', 'framework' => 'laravel']); +```php +$collection = collect(['name' => 'taylor', 'framework' => 'laravel']); - // Forget a single key... - $collection->forget('name'); +// Forget a single key... +$collection->forget('name'); - // ['framework' => 'laravel'] +// ['framework' => 'laravel'] - // Forget multiple keys... - $collection->forget(['name', 'framework']); +// Forget multiple keys... +$collection->forget(['name', 'framework']); - // [] +// [] +``` > [!WARNING] > Unlike most other collection methods, `forget` does not return a new modified collection; it modifies and returns the collection it is called on. @@ -1115,200 +1243,226 @@ The `forget` method removes an item from the collection by its key: The `forPage` method returns a new collection containing the items that would be present on a given page number. The method accepts the page number as its first argument and the number of items to show per page as its second argument: - $collection = collect([1, 2, 3, 4, 5, 6, 7, 8, 9]); +```php +$collection = collect([1, 2, 3, 4, 5, 6, 7, 8, 9]); - $chunk = $collection->forPage(2, 3); +$chunk = $collection->forPage(2, 3); - $chunk->all(); +$chunk->all(); - // [4, 5, 6] +// [4, 5, 6] +``` #### `get()` {.collection-method} The `get` method returns the item at a given key. If the key does not exist, `null` is returned: - $collection = collect(['name' => 'taylor', 'framework' => 'laravel']); +```php +$collection = collect(['name' => 'taylor', 'framework' => 'laravel']); - $value = $collection->get('name'); +$value = $collection->get('name'); - // taylor +// taylor +``` You may optionally pass a default value as the second argument: - $collection = collect(['name' => 'taylor', 'framework' => 'laravel']); +```php +$collection = collect(['name' => 'taylor', 'framework' => 'laravel']); - $value = $collection->get('age', 34); +$value = $collection->get('age', 34); - // 34 +// 34 +``` You may even pass a callback as the method's default value. The result of the callback will be returned if the specified key does not exist: - $collection->get('email', function () { - return 'taylor@example.com'; - }); +```php +$collection->get('email', function () { + return 'taylor@example.com'; +}); - // taylor@example.com +// taylor@example.com +``` #### `groupBy()` {.collection-method} The `groupBy` method groups the collection's items by a given key: - $collection = collect([ - ['account_id' => 'account-x10', 'product' => 'Chair'], - ['account_id' => 'account-x10', 'product' => 'Bookcase'], - ['account_id' => 'account-x11', 'product' => 'Desk'], - ]); +```php +$collection = collect([ + ['account_id' => 'account-x10', 'product' => 'Chair'], + ['account_id' => 'account-x10', 'product' => 'Bookcase'], + ['account_id' => 'account-x11', 'product' => 'Desk'], +]); - $grouped = $collection->groupBy('account_id'); +$grouped = $collection->groupBy('account_id'); - $grouped->all(); +$grouped->all(); - /* - [ - 'account-x10' => [ - ['account_id' => 'account-x10', 'product' => 'Chair'], - ['account_id' => 'account-x10', 'product' => 'Bookcase'], - ], - 'account-x11' => [ - ['account_id' => 'account-x11', 'product' => 'Desk'], - ], - ] - */ +/* + [ + 'account-x10' => [ + ['account_id' => 'account-x10', 'product' => 'Chair'], + ['account_id' => 'account-x10', 'product' => 'Bookcase'], + ], + 'account-x11' => [ + ['account_id' => 'account-x11', 'product' => 'Desk'], + ], + ] +*/ +``` Instead of passing a string `key`, you may pass a callback. The callback should return the value you wish to key the group by: - $grouped = $collection->groupBy(function (array $item, int $key) { - return substr($item['account_id'], -3); - }); +```php +$grouped = $collection->groupBy(function (array $item, int $key) { + return substr($item['account_id'], -3); +}); - $grouped->all(); +$grouped->all(); - /* - [ - 'x10' => [ - ['account_id' => 'account-x10', 'product' => 'Chair'], - ['account_id' => 'account-x10', 'product' => 'Bookcase'], - ], - 'x11' => [ - ['account_id' => 'account-x11', 'product' => 'Desk'], - ], - ] - */ +/* + [ + 'x10' => [ + ['account_id' => 'account-x10', 'product' => 'Chair'], + ['account_id' => 'account-x10', 'product' => 'Bookcase'], + ], + 'x11' => [ + ['account_id' => 'account-x11', 'product' => 'Desk'], + ], + ] +*/ +``` Multiple grouping criteria may be passed as an array. Each array element will be applied to the corresponding level within a multi-dimensional array: - $data = new Collection([ - 10 => ['user' => 1, 'skill' => 1, 'roles' => ['Role_1', 'Role_3']], - 20 => ['user' => 2, 'skill' => 1, 'roles' => ['Role_1', 'Role_2']], - 30 => ['user' => 3, 'skill' => 2, 'roles' => ['Role_1']], - 40 => ['user' => 4, 'skill' => 2, 'roles' => ['Role_2']], - ]); +```php +$data = new Collection([ + 10 => ['user' => 1, 'skill' => 1, 'roles' => ['Role_1', 'Role_3']], + 20 => ['user' => 2, 'skill' => 1, 'roles' => ['Role_1', 'Role_2']], + 30 => ['user' => 3, 'skill' => 2, 'roles' => ['Role_1']], + 40 => ['user' => 4, 'skill' => 2, 'roles' => ['Role_2']], +]); - $result = $data->groupBy(['skill', function (array $item) { - return $item['roles']; - }], preserveKeys: true); +$result = $data->groupBy(['skill', function (array $item) { + return $item['roles']; +}], preserveKeys: true); - /* - [ - 1 => [ - 'Role_1' => [ - 10 => ['user' => 1, 'skill' => 1, 'roles' => ['Role_1', 'Role_3']], - 20 => ['user' => 2, 'skill' => 1, 'roles' => ['Role_1', 'Role_2']], - ], - 'Role_2' => [ - 20 => ['user' => 2, 'skill' => 1, 'roles' => ['Role_1', 'Role_2']], - ], - 'Role_3' => [ - 10 => ['user' => 1, 'skill' => 1, 'roles' => ['Role_1', 'Role_3']], - ], +/* +[ + 1 => [ + 'Role_1' => [ + 10 => ['user' => 1, 'skill' => 1, 'roles' => ['Role_1', 'Role_3']], + 20 => ['user' => 2, 'skill' => 1, 'roles' => ['Role_1', 'Role_2']], + ], + 'Role_2' => [ + 20 => ['user' => 2, 'skill' => 1, 'roles' => ['Role_1', 'Role_2']], + ], + 'Role_3' => [ + 10 => ['user' => 1, 'skill' => 1, 'roles' => ['Role_1', 'Role_3']], + ], + ], + 2 => [ + 'Role_1' => [ + 30 => ['user' => 3, 'skill' => 2, 'roles' => ['Role_1']], ], - 2 => [ - 'Role_1' => [ - 30 => ['user' => 3, 'skill' => 2, 'roles' => ['Role_1']], - ], - 'Role_2' => [ - 40 => ['user' => 4, 'skill' => 2, 'roles' => ['Role_2']], - ], + 'Role_2' => [ + 40 => ['user' => 4, 'skill' => 2, 'roles' => ['Role_2']], ], - ]; - */ + ], +]; +*/ +``` #### `has()` {.collection-method} The `has` method determines if a given key exists in the collection: - $collection = collect(['account_id' => 1, 'product' => 'Desk', 'amount' => 5]); +```php +$collection = collect(['account_id' => 1, 'product' => 'Desk', 'amount' => 5]); - $collection->has('product'); +$collection->has('product'); - // true +// true - $collection->has(['product', 'amount']); +$collection->has(['product', 'amount']); - // true +// true - $collection->has(['amount', 'price']); +$collection->has(['amount', 'price']); - // false +// false +``` #### `hasAny()` {.collection-method} The `hasAny` method determines whether any of the given keys exist in the collection: - $collection = collect(['account_id' => 1, 'product' => 'Desk', 'amount' => 5]); +```php +$collection = collect(['account_id' => 1, 'product' => 'Desk', 'amount' => 5]); - $collection->hasAny(['product', 'price']); +$collection->hasAny(['product', 'price']); - // true +// true - $collection->hasAny(['name', 'price']); +$collection->hasAny(['name', 'price']); - // false +// false +``` #### `implode()` {.collection-method} The `implode` method joins items in a collection. Its arguments depend on the type of items in the collection. If the collection contains arrays or objects, you should pass the key of the attributes you wish to join, and the "glue" string you wish to place between the values: - $collection = collect([ - ['account_id' => 1, 'product' => 'Desk'], - ['account_id' => 2, 'product' => 'Chair'], - ]); +```php +$collection = collect([ + ['account_id' => 1, 'product' => 'Desk'], + ['account_id' => 2, 'product' => 'Chair'], +]); - $collection->implode('product', ', '); +$collection->implode('product', ', '); - // Desk, Chair +// Desk, Chair +``` If the collection contains simple strings or numeric values, you should pass the "glue" as the only argument to the method: - collect([1, 2, 3, 4, 5])->implode('-'); +```php +collect([1, 2, 3, 4, 5])->implode('-'); - // '1-2-3-4-5' +// '1-2-3-4-5' +``` You may pass a closure to the `implode` method if you would like to format the values being imploded: - $collection->implode(function (array $item, int $key) { - return strtoupper($item['product']); - }, ', '); +```php +$collection->implode(function (array $item, int $key) { + return strtoupper($item['product']); +}, ', '); - // DESK, CHAIR +// DESK, CHAIR +``` #### `intersect()` {.collection-method} The `intersect` method removes any values from the original collection that are not present in the given `array` or collection. The resulting collection will preserve the original collection's keys: - $collection = collect(['Desk', 'Sofa', 'Chair']); +```php +$collection = collect(['Desk', 'Sofa', 'Chair']); - $intersect = $collection->intersect(['Desk', 'Chair', 'Bookcase']); +$intersect = $collection->intersect(['Desk', 'Chair', 'Bookcase']); - $intersect->all(); +$intersect->all(); - // [0 => 'Desk', 2 => 'Chair'] +// [0 => 'Desk', 2 => 'Chair'] +``` > [!NOTE] > This method's behavior is modified when using [Eloquent Collections](/docs/{{version}}/eloquent-collections#method-intersect). @@ -1318,197 +1472,225 @@ The `intersect` method removes any values from the original collection that are The `intersectUsing` method removes any values from the original collection that are not present in the given `array` or collection, using a custom callback to compare the values. The resulting collection will preserve the original collection's keys: - $collection = collect(['Desk', 'Sofa', 'Chair']); +```php +$collection = collect(['Desk', 'Sofa', 'Chair']); - $intersect = $collection->intersectUsing(['desk', 'chair', 'bookcase'], function ($a, $b) { - return strcasecmp($a, $b); - }); - - $intersect->all(); - - // [0 => 'Desk', 2 => 'Chair'] +$intersect = $collection->intersectUsing(['desk', 'chair', 'bookcase'], function ($a, $b) { + return strcasecmp($a, $b); +}); + +$intersect->all(); + +// [0 => 'Desk', 2 => 'Chair'] +``` #### `intersectAssoc()` {.collection-method} The `intersectAssoc` method compares the original collection against another collection or `array`, returning the key / value pairs that are present in all of the given collections: - $collection = collect([ - 'color' => 'red', - 'size' => 'M', - 'material' => 'cotton' - ]); +```php +$collection = collect([ + 'color' => 'red', + 'size' => 'M', + 'material' => 'cotton' +]); - $intersect = $collection->intersectAssoc([ - 'color' => 'blue', - 'size' => 'M', - 'material' => 'polyester' - ]); +$intersect = $collection->intersectAssoc([ + 'color' => 'blue', + 'size' => 'M', + 'material' => 'polyester' +]); - $intersect->all(); +$intersect->all(); - // ['size' => 'M'] +// ['size' => 'M'] +``` #### `intersectAssocUsing()` {.collection-method} The `intersectAssocUsing` method compares the original collection against another collection or `array`, returning the key / value pairs that are present in both, using a custom comparison callback to determine equality for both keys and values: - $collection = collect([ - 'color' => 'red', - 'Size' => 'M', - 'material' => 'cotton', - ]); - - $intersect = $collection->intersectAssocUsing([ - 'color' => 'blue', - 'size' => 'M', - 'material' => 'polyester', - ], function ($a, $b) { - return strcasecmp($a, $b); - }); +```php +$collection = collect([ + 'color' => 'red', + 'Size' => 'M', + 'material' => 'cotton', +]); - $intersect->all(); +$intersect = $collection->intersectAssocUsing([ + 'color' => 'blue', + 'size' => 'M', + 'material' => 'polyester', +], function ($a, $b) { + return strcasecmp($a, $b); +}); - // ['Size' => 'M'] +$intersect->all(); + +// ['Size' => 'M'] +``` #### `intersectByKeys()` {.collection-method} The `intersectByKeys` method removes any keys and their corresponding values from the original collection that are not present in the given `array` or collection: - $collection = collect([ - 'serial' => 'UX301', 'type' => 'screen', 'year' => 2009, - ]); +```php +$collection = collect([ + 'serial' => 'UX301', 'type' => 'screen', 'year' => 2009, +]); - $intersect = $collection->intersectByKeys([ - 'reference' => 'UX404', 'type' => 'tab', 'year' => 2011, - ]); +$intersect = $collection->intersectByKeys([ + 'reference' => 'UX404', 'type' => 'tab', 'year' => 2011, +]); - $intersect->all(); +$intersect->all(); - // ['type' => 'screen', 'year' => 2009] +// ['type' => 'screen', 'year' => 2009] +``` #### `isEmpty()` {.collection-method} The `isEmpty` method returns `true` if the collection is empty; otherwise, `false` is returned: - collect([])->isEmpty(); +```php +collect([])->isEmpty(); - // true +// true +``` #### `isNotEmpty()` {.collection-method} The `isNotEmpty` method returns `true` if the collection is not empty; otherwise, `false` is returned: - collect([])->isNotEmpty(); +```php +collect([])->isNotEmpty(); - // false +// false +``` #### `join()` {.collection-method} The `join` method joins the collection's values with a string. Using this method's second argument, you may also specify how the final element should be appended to the string: - collect(['a', 'b', 'c'])->join(', '); // 'a, b, c' - collect(['a', 'b', 'c'])->join(', ', ', and '); // 'a, b, and c' - collect(['a', 'b'])->join(', ', ' and '); // 'a and b' - collect(['a'])->join(', ', ' and '); // 'a' - collect([])->join(', ', ' and '); // '' +```php +collect(['a', 'b', 'c'])->join(', '); // 'a, b, c' +collect(['a', 'b', 'c'])->join(', ', ', and '); // 'a, b, and c' +collect(['a', 'b'])->join(', ', ' and '); // 'a and b' +collect(['a'])->join(', ', ' and '); // 'a' +collect([])->join(', ', ' and '); // '' +``` #### `keyBy()` {.collection-method} The `keyBy` method keys the collection by the given key. If multiple items have the same key, only the last one will appear in the new collection: - $collection = collect([ - ['product_id' => 'prod-100', 'name' => 'Desk'], - ['product_id' => 'prod-200', 'name' => 'Chair'], - ]); +```php +$collection = collect([ + ['product_id' => 'prod-100', 'name' => 'Desk'], + ['product_id' => 'prod-200', 'name' => 'Chair'], +]); - $keyed = $collection->keyBy('product_id'); +$keyed = $collection->keyBy('product_id'); - $keyed->all(); +$keyed->all(); - /* - [ - 'prod-100' => ['product_id' => 'prod-100', 'name' => 'Desk'], - 'prod-200' => ['product_id' => 'prod-200', 'name' => 'Chair'], - ] - */ +/* + [ + 'prod-100' => ['product_id' => 'prod-100', 'name' => 'Desk'], + 'prod-200' => ['product_id' => 'prod-200', 'name' => 'Chair'], + ] +*/ +``` You may also pass a callback to the method. The callback should return the value to key the collection by: - $keyed = $collection->keyBy(function (array $item, int $key) { - return strtoupper($item['product_id']); - }); +```php +$keyed = $collection->keyBy(function (array $item, int $key) { + return strtoupper($item['product_id']); +}); - $keyed->all(); +$keyed->all(); - /* - [ - 'PROD-100' => ['product_id' => 'prod-100', 'name' => 'Desk'], - 'PROD-200' => ['product_id' => 'prod-200', 'name' => 'Chair'], - ] - */ +/* + [ + 'PROD-100' => ['product_id' => 'prod-100', 'name' => 'Desk'], + 'PROD-200' => ['product_id' => 'prod-200', 'name' => 'Chair'], + ] +*/ +``` #### `keys()` {.collection-method} The `keys` method returns all of the collection's keys: - $collection = collect([ - 'prod-100' => ['product_id' => 'prod-100', 'name' => 'Desk'], - 'prod-200' => ['product_id' => 'prod-200', 'name' => 'Chair'], - ]); +```php +$collection = collect([ + 'prod-100' => ['product_id' => 'prod-100', 'name' => 'Desk'], + 'prod-200' => ['product_id' => 'prod-200', 'name' => 'Chair'], +]); - $keys = $collection->keys(); +$keys = $collection->keys(); - $keys->all(); +$keys->all(); - // ['prod-100', 'prod-200'] +// ['prod-100', 'prod-200'] +``` #### `last()` {.collection-method} The `last` method returns the last element in the collection that passes a given truth test: - collect([1, 2, 3, 4])->last(function (int $value, int $key) { - return $value < 3; - }); +```php +collect([1, 2, 3, 4])->last(function (int $value, int $key) { + return $value < 3; +}); - // 2 +// 2 +``` You may also call the `last` method with no arguments to get the last element in the collection. If the collection is empty, `null` is returned: - collect([1, 2, 3, 4])->last(); +```php +collect([1, 2, 3, 4])->last(); - // 4 +// 4 +``` #### `lazy()` {.collection-method} The `lazy` method returns a new [`LazyCollection`](#lazy-collections) instance from the underlying array of items: - $lazyCollection = collect([1, 2, 3, 4])->lazy(); +```php +$lazyCollection = collect([1, 2, 3, 4])->lazy(); - $lazyCollection::class; +$lazyCollection::class; - // Illuminate\Support\LazyCollection +// Illuminate\Support\LazyCollection - $lazyCollection->all(); +$lazyCollection->all(); - // [1, 2, 3, 4] +// [1, 2, 3, 4] +``` This is especially useful when you need to perform transformations on a huge `Collection` that contains many items: - $count = $hugeCollection - ->lazy() - ->where('country', 'FR') - ->where('balance', '>', '100') - ->count(); +```php +$count = $hugeCollection + ->lazy() + ->where('country', 'FR') + ->where('balance', '>', '100') + ->count(); +``` By converting the collection to a `LazyCollection`, we avoid having to allocate a ton of additional memory. Though the original collection still keeps _its_ values in memory, the subsequent filters will not. Therefore, virtually no additional memory will be allocated when filtering the collection's results. @@ -1527,15 +1709,17 @@ The static `make` method creates a new collection instance. See the [Creating Co The `map` method iterates through the collection and passes each value to the given callback. The callback is free to modify the item and return it, thus forming a new collection of modified items: - $collection = collect([1, 2, 3, 4, 5]); +```php +$collection = collect([1, 2, 3, 4, 5]); - $multiplied = $collection->map(function (int $item, int $key) { - return $item * 2; - }); +$multiplied = $collection->map(function (int $item, int $key) { + return $item * 2; +}); - $multiplied->all(); +$multiplied->all(); - // [2, 4, 6, 8, 10] +// [2, 4, 6, 8, 10] +``` > [!WARNING] > Like most other collection methods, `map` returns a new collection instance; it does not modify the collection it is called on. If you want to transform the original collection, use the [`transform`](#method-transform) method. @@ -1545,217 +1729,239 @@ The `map` method iterates through the collection and passes each value to the gi The `mapInto()` method iterates over the collection, creating a new instance of the given class by passing the value into the constructor: - class Currency - { - /** - * Create a new currency instance. - */ - function __construct( - public string $code, - ) {} - } +```php +class Currency +{ + /** + * Create a new currency instance. + */ + function __construct( + public string $code, + ) {} +} - $collection = collect(['USD', 'EUR', 'GBP']); +$collection = collect(['USD', 'EUR', 'GBP']); - $currencies = $collection->mapInto(Currency::class); +$currencies = $collection->mapInto(Currency::class); - $currencies->all(); +$currencies->all(); - // [Currency('USD'), Currency('EUR'), Currency('GBP')] +// [Currency('USD'), Currency('EUR'), Currency('GBP')] +``` #### `mapSpread()` {.collection-method} The `mapSpread` method iterates over the collection's items, passing each nested item value into the given closure. The closure is free to modify the item and return it, thus forming a new collection of modified items: - $collection = collect([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]); +```php +$collection = collect([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]); - $chunks = $collection->chunk(2); +$chunks = $collection->chunk(2); - $sequence = $chunks->mapSpread(function (int $even, int $odd) { - return $even + $odd; - }); +$sequence = $chunks->mapSpread(function (int $even, int $odd) { + return $even + $odd; +}); - $sequence->all(); +$sequence->all(); - // [1, 5, 9, 13, 17] +// [1, 5, 9, 13, 17] +``` #### `mapToGroups()` {.collection-method} The `mapToGroups` method groups the collection's items by the given closure. The closure should return an associative array containing a single key / value pair, thus forming a new collection of grouped values: - $collection = collect([ - [ - 'name' => 'John Doe', - 'department' => 'Sales', - ], - [ - 'name' => 'Jane Doe', - 'department' => 'Sales', - ], - [ - 'name' => 'Johnny Doe', - 'department' => 'Marketing', - ] - ]); +```php +$collection = collect([ + [ + 'name' => 'John Doe', + 'department' => 'Sales', + ], + [ + 'name' => 'Jane Doe', + 'department' => 'Sales', + ], + [ + 'name' => 'Johnny Doe', + 'department' => 'Marketing', + ] +]); - $grouped = $collection->mapToGroups(function (array $item, int $key) { - return [$item['department'] => $item['name']]; - }); +$grouped = $collection->mapToGroups(function (array $item, int $key) { + return [$item['department'] => $item['name']]; +}); - $grouped->all(); +$grouped->all(); - /* - [ - 'Sales' => ['John Doe', 'Jane Doe'], - 'Marketing' => ['Johnny Doe'], - ] - */ +/* + [ + 'Sales' => ['John Doe', 'Jane Doe'], + 'Marketing' => ['Johnny Doe'], + ] +*/ - $grouped->get('Sales')->all(); +$grouped->get('Sales')->all(); - // ['John Doe', 'Jane Doe'] +// ['John Doe', 'Jane Doe'] +``` #### `mapWithKeys()` {.collection-method} The `mapWithKeys` method iterates through the collection and passes each value to the given callback. The callback should return an associative array containing a single key / value pair: - $collection = collect([ - [ - 'name' => 'John', - 'department' => 'Sales', - 'email' => 'john@example.com', - ], - [ - 'name' => 'Jane', - 'department' => 'Marketing', - 'email' => 'jane@example.com', - ] - ]); +```php +$collection = collect([ + [ + 'name' => 'John', + 'department' => 'Sales', + 'email' => 'john@example.com', + ], + [ + 'name' => 'Jane', + 'department' => 'Marketing', + 'email' => 'jane@example.com', + ] +]); - $keyed = $collection->mapWithKeys(function (array $item, int $key) { - return [$item['email'] => $item['name']]; - }); +$keyed = $collection->mapWithKeys(function (array $item, int $key) { + return [$item['email'] => $item['name']]; +}); - $keyed->all(); +$keyed->all(); - /* - [ - 'john@example.com' => 'John', - 'jane@example.com' => 'Jane', - ] - */ +/* + [ + 'john@example.com' => 'John', + 'jane@example.com' => 'Jane', + ] +*/ +``` #### `max()` {.collection-method} The `max` method returns the maximum value of a given key: - $max = collect([ - ['foo' => 10], - ['foo' => 20] - ])->max('foo'); +```php +$max = collect([ + ['foo' => 10], + ['foo' => 20] +])->max('foo'); - // 20 +// 20 - $max = collect([1, 2, 3, 4, 5])->max(); +$max = collect([1, 2, 3, 4, 5])->max(); - // 5 +// 5 +``` #### `median()` {.collection-method} The `median` method returns the [median value](https://en.wikipedia.org/wiki/Median) of a given key: - $median = collect([ - ['foo' => 10], - ['foo' => 10], - ['foo' => 20], - ['foo' => 40] - ])->median('foo'); +```php +$median = collect([ + ['foo' => 10], + ['foo' => 10], + ['foo' => 20], + ['foo' => 40] +])->median('foo'); - // 15 +// 15 - $median = collect([1, 1, 2, 4])->median(); +$median = collect([1, 1, 2, 4])->median(); - // 1.5 +// 1.5 +``` #### `merge()` {.collection-method} The `merge` method merges the given array or collection with the original collection. If a string key in the given items matches a string key in the original collection, the given item's value will overwrite the value in the original collection: - $collection = collect(['product_id' => 1, 'price' => 100]); +```php +$collection = collect(['product_id' => 1, 'price' => 100]); - $merged = $collection->merge(['price' => 200, 'discount' => false]); +$merged = $collection->merge(['price' => 200, 'discount' => false]); - $merged->all(); +$merged->all(); - // ['product_id' => 1, 'price' => 200, 'discount' => false] +// ['product_id' => 1, 'price' => 200, 'discount' => false] +``` If the given item's keys are numeric, the values will be appended to the end of the collection: - $collection = collect(['Desk', 'Chair']); +```php +$collection = collect(['Desk', 'Chair']); - $merged = $collection->merge(['Bookcase', 'Door']); +$merged = $collection->merge(['Bookcase', 'Door']); - $merged->all(); +$merged->all(); - // ['Desk', 'Chair', 'Bookcase', 'Door'] +// ['Desk', 'Chair', 'Bookcase', 'Door'] +``` #### `mergeRecursive()` {.collection-method} The `mergeRecursive` method merges the given array or collection recursively with the original collection. If a string key in the given items matches a string key in the original collection, then the values for these keys are merged together into an array, and this is done recursively: - $collection = collect(['product_id' => 1, 'price' => 100]); +```php +$collection = collect(['product_id' => 1, 'price' => 100]); - $merged = $collection->mergeRecursive([ - 'product_id' => 2, - 'price' => 200, - 'discount' => false - ]); +$merged = $collection->mergeRecursive([ + 'product_id' => 2, + 'price' => 200, + 'discount' => false +]); - $merged->all(); +$merged->all(); - // ['product_id' => [1, 2], 'price' => [100, 200], 'discount' => false] +// ['product_id' => [1, 2], 'price' => [100, 200], 'discount' => false] +``` #### `min()` {.collection-method} The `min` method returns the minimum value of a given key: - $min = collect([['foo' => 10], ['foo' => 20]])->min('foo'); +```php +$min = collect([['foo' => 10], ['foo' => 20]])->min('foo'); - // 10 +// 10 - $min = collect([1, 2, 3, 4, 5])->min(); +$min = collect([1, 2, 3, 4, 5])->min(); - // 1 +// 1 +``` #### `mode()` {.collection-method} The `mode` method returns the [mode value](https://en.wikipedia.org/wiki/Mode_(statistics)) of a given key: - $mode = collect([ - ['foo' => 10], - ['foo' => 10], - ['foo' => 20], - ['foo' => 40] - ])->mode('foo'); +```php +$mode = collect([ + ['foo' => 10], + ['foo' => 10], + ['foo' => 20], + ['foo' => 40] +])->mode('foo'); - // [10] +// [10] - $mode = collect([1, 1, 2, 4])->mode(); +$mode = collect([1, 1, 2, 4])->mode(); - // [1] +// [1] - $mode = collect([1, 1, 2, 2])->mode(); +$mode = collect([1, 1, 2, 2])->mode(); - // [1, 2] +// [1, 2] +``` #### `multiply()` {.collection-method} @@ -1785,35 +1991,41 @@ $users = collect([ The `nth` method creates a new collection consisting of every n-th element: - $collection = collect(['a', 'b', 'c', 'd', 'e', 'f']); +```php +$collection = collect(['a', 'b', 'c', 'd', 'e', 'f']); - $collection->nth(4); +$collection->nth(4); - // ['a', 'e'] +// ['a', 'e'] +``` You may optionally pass a starting offset as the second argument: - $collection->nth(4, 1); +```php +$collection->nth(4, 1); - // ['b', 'f'] +// ['b', 'f'] +``` #### `only()` {.collection-method} The `only` method returns the items in the collection with the specified keys: - $collection = collect([ - 'product_id' => 1, - 'name' => 'Desk', - 'price' => 100, - 'discount' => false - ]); +```php +$collection = collect([ + 'product_id' => 1, + 'name' => 'Desk', + 'price' => 100, + 'discount' => false +]); - $filtered = $collection->only(['product_id', 'name']); +$filtered = $collection->only(['product_id', 'name']); - $filtered->all(); +$filtered->all(); - // ['product_id' => 1, 'name' => 'Desk'] +// ['product_id' => 1, 'name' => 'Desk'] +``` For the inverse of `only`, see the [except](#method-except) method. @@ -1827,38 +2039,42 @@ The `pad` method will fill the array with the given value until the array reache To pad to the left, you should specify a negative size. No padding will take place if the absolute value of the given size is less than or equal to the length of the array: - $collection = collect(['A', 'B', 'C']); +```php +$collection = collect(['A', 'B', 'C']); - $filtered = $collection->pad(5, 0); +$filtered = $collection->pad(5, 0); - $filtered->all(); +$filtered->all(); - // ['A', 'B', 'C', 0, 0] +// ['A', 'B', 'C', 0, 0] - $filtered = $collection->pad(-5, 0); +$filtered = $collection->pad(-5, 0); - $filtered->all(); +$filtered->all(); - // [0, 0, 'A', 'B', 'C'] +// [0, 0, 'A', 'B', 'C'] +``` #### `partition()` {.collection-method} The `partition` method may be combined with PHP array destructuring to separate elements that pass a given truth test from those that do not: - $collection = collect([1, 2, 3, 4, 5, 6]); +```php +$collection = collect([1, 2, 3, 4, 5, 6]); - [$underThree, $equalOrAboveThree] = $collection->partition(function (int $i) { - return $i < 3; - }); +[$underThree, $equalOrAboveThree] = $collection->partition(function (int $i) { + return $i < 3; +}); - $underThree->all(); +$underThree->all(); - // [1, 2] +// [1, 2] - $equalOrAboveThree->all(); +$equalOrAboveThree->all(); - // [3, 4, 5, 6] +// [3, 4, 5, 6] +``` #### `percentage()` {.collection-method} @@ -1886,324 +2102,370 @@ $percentage = $collection->percentage(fn ($value) => $value === 1, precision: 3) The `pipe` method passes the collection to the given closure and returns the result of the executed closure: - $collection = collect([1, 2, 3]); +```php +$collection = collect([1, 2, 3]); - $piped = $collection->pipe(function (Collection $collection) { - return $collection->sum(); - }); +$piped = $collection->pipe(function (Collection $collection) { + return $collection->sum(); +}); - // 6 +// 6 +``` #### `pipeInto()` {.collection-method} The `pipeInto` method creates a new instance of the given class and passes the collection into the constructor: - class ResourceCollection - { - /** - * Create a new ResourceCollection instance. - */ - public function __construct( - public Collection $collection, - ) {} - } +```php +class ResourceCollection +{ + /** + * Create a new ResourceCollection instance. + */ + public function __construct( + public Collection $collection, + ) {} +} - $collection = collect([1, 2, 3]); +$collection = collect([1, 2, 3]); - $resource = $collection->pipeInto(ResourceCollection::class); +$resource = $collection->pipeInto(ResourceCollection::class); - $resource->collection->all(); +$resource->collection->all(); - // [1, 2, 3] +// [1, 2, 3] +``` #### `pipeThrough()` {.collection-method} The `pipeThrough` method passes the collection to the given array of closures and returns the result of the executed closures: - use Illuminate\Support\Collection; +```php +use Illuminate\Support\Collection; - $collection = collect([1, 2, 3]); +$collection = collect([1, 2, 3]); - $result = $collection->pipeThrough([ - function (Collection $collection) { - return $collection->merge([4, 5]); - }, - function (Collection $collection) { - return $collection->sum(); - }, - ]); +$result = $collection->pipeThrough([ + function (Collection $collection) { + return $collection->merge([4, 5]); + }, + function (Collection $collection) { + return $collection->sum(); + }, +]); - // 15 +// 15 +``` #### `pluck()` {.collection-method} The `pluck` method retrieves all of the values for a given key: - $collection = collect([ - ['product_id' => 'prod-100', 'name' => 'Desk'], - ['product_id' => 'prod-200', 'name' => 'Chair'], - ]); +```php +$collection = collect([ + ['product_id' => 'prod-100', 'name' => 'Desk'], + ['product_id' => 'prod-200', 'name' => 'Chair'], +]); - $plucked = $collection->pluck('name'); +$plucked = $collection->pluck('name'); - $plucked->all(); +$plucked->all(); - // ['Desk', 'Chair'] +// ['Desk', 'Chair'] +``` You may also specify how you wish the resulting collection to be keyed: - $plucked = $collection->pluck('name', 'product_id'); +```php +$plucked = $collection->pluck('name', 'product_id'); - $plucked->all(); +$plucked->all(); - // ['prod-100' => 'Desk', 'prod-200' => 'Chair'] +// ['prod-100' => 'Desk', 'prod-200' => 'Chair'] +``` The `pluck` method also supports retrieving nested values using "dot" notation: - $collection = collect([ - [ - 'name' => 'Laracon', - 'speakers' => [ - 'first_day' => ['Rosa', 'Judith'], - ], +```php +$collection = collect([ + [ + 'name' => 'Laracon', + 'speakers' => [ + 'first_day' => ['Rosa', 'Judith'], ], - [ - 'name' => 'VueConf', - 'speakers' => [ - 'first_day' => ['Abigail', 'Joey'], - ], + ], + [ + 'name' => 'VueConf', + 'speakers' => [ + 'first_day' => ['Abigail', 'Joey'], ], - ]); + ], +]); - $plucked = $collection->pluck('speakers.first_day'); +$plucked = $collection->pluck('speakers.first_day'); - $plucked->all(); +$plucked->all(); - // [['Rosa', 'Judith'], ['Abigail', 'Joey']] +// [['Rosa', 'Judith'], ['Abigail', 'Joey']] +``` If duplicate keys exist, the last matching element will be inserted into the plucked collection: - $collection = collect([ - ['brand' => 'Tesla', 'color' => 'red'], - ['brand' => 'Pagani', 'color' => 'white'], - ['brand' => 'Tesla', 'color' => 'black'], - ['brand' => 'Pagani', 'color' => 'orange'], - ]); +```php +$collection = collect([ + ['brand' => 'Tesla', 'color' => 'red'], + ['brand' => 'Pagani', 'color' => 'white'], + ['brand' => 'Tesla', 'color' => 'black'], + ['brand' => 'Pagani', 'color' => 'orange'], +]); - $plucked = $collection->pluck('color', 'brand'); +$plucked = $collection->pluck('color', 'brand'); - $plucked->all(); +$plucked->all(); - // ['Tesla' => 'black', 'Pagani' => 'orange'] +// ['Tesla' => 'black', 'Pagani' => 'orange'] +``` #### `pop()` {.collection-method} The `pop` method removes and returns the last item from the collection: - $collection = collect([1, 2, 3, 4, 5]); +```php +$collection = collect([1, 2, 3, 4, 5]); - $collection->pop(); +$collection->pop(); - // 5 +// 5 - $collection->all(); +$collection->all(); - // [1, 2, 3, 4] +// [1, 2, 3, 4] +``` You may pass an integer to the `pop` method to remove and return multiple items from the end of a collection: - $collection = collect([1, 2, 3, 4, 5]); +```php +$collection = collect([1, 2, 3, 4, 5]); - $collection->pop(3); +$collection->pop(3); - // collect([5, 4, 3]) +// collect([5, 4, 3]) - $collection->all(); +$collection->all(); - // [1, 2] +// [1, 2] +``` #### `prepend()` {.collection-method} The `prepend` method adds an item to the beginning of the collection: - $collection = collect([1, 2, 3, 4, 5]); +```php +$collection = collect([1, 2, 3, 4, 5]); - $collection->prepend(0); +$collection->prepend(0); - $collection->all(); +$collection->all(); - // [0, 1, 2, 3, 4, 5] +// [0, 1, 2, 3, 4, 5] +``` You may also pass a second argument to specify the key of the prepended item: - $collection = collect(['one' => 1, 'two' => 2]); +```php +$collection = collect(['one' => 1, 'two' => 2]); - $collection->prepend(0, 'zero'); +$collection->prepend(0, 'zero'); - $collection->all(); +$collection->all(); - // ['zero' => 0, 'one' => 1, 'two' => 2] +// ['zero' => 0, 'one' => 1, 'two' => 2] +``` #### `pull()` {.collection-method} The `pull` method removes and returns an item from the collection by its key: - $collection = collect(['product_id' => 'prod-100', 'name' => 'Desk']); +```php +$collection = collect(['product_id' => 'prod-100', 'name' => 'Desk']); - $collection->pull('name'); +$collection->pull('name'); - // 'Desk' +// 'Desk' - $collection->all(); +$collection->all(); - // ['product_id' => 'prod-100'] +// ['product_id' => 'prod-100'] +``` #### `push()` {.collection-method} The `push` method appends an item to the end of the collection: - $collection = collect([1, 2, 3, 4]); +```php +$collection = collect([1, 2, 3, 4]); - $collection->push(5); +$collection->push(5); - $collection->all(); +$collection->all(); - // [1, 2, 3, 4, 5] +// [1, 2, 3, 4, 5] +``` #### `put()` {.collection-method} The `put` method sets the given key and value in the collection: - $collection = collect(['product_id' => 1, 'name' => 'Desk']); +```php +$collection = collect(['product_id' => 1, 'name' => 'Desk']); - $collection->put('price', 100); +$collection->put('price', 100); - $collection->all(); +$collection->all(); - // ['product_id' => 1, 'name' => 'Desk', 'price' => 100] +// ['product_id' => 1, 'name' => 'Desk', 'price' => 100] +``` #### `random()` {.collection-method} The `random` method returns a random item from the collection: - $collection = collect([1, 2, 3, 4, 5]); +```php +$collection = collect([1, 2, 3, 4, 5]); - $collection->random(); +$collection->random(); - // 4 - (retrieved randomly) +// 4 - (retrieved randomly) +``` You may pass an integer to `random` to specify how many items you would like to randomly retrieve. A collection of items is always returned when explicitly passing the number of items you wish to receive: - $random = $collection->random(3); +```php +$random = $collection->random(3); - $random->all(); +$random->all(); - // [2, 4, 5] - (retrieved randomly) +// [2, 4, 5] - (retrieved randomly) +``` If the collection instance has fewer items than requested, the `random` method will throw an `InvalidArgumentException`. The `random` method also accepts a closure, which will receive the current collection instance: - use Illuminate\Support\Collection; +```php +use Illuminate\Support\Collection; - $random = $collection->random(fn (Collection $items) => min(10, count($items))); +$random = $collection->random(fn (Collection $items) => min(10, count($items))); - $random->all(); +$random->all(); - // [1, 2, 3, 4, 5] - (retrieved randomly) +// [1, 2, 3, 4, 5] - (retrieved randomly) +``` #### `range()` {.collection-method} The `range` method returns a collection containing integers between the specified range: - $collection = collect()->range(3, 6); +```php +$collection = collect()->range(3, 6); - $collection->all(); +$collection->all(); - // [3, 4, 5, 6] +// [3, 4, 5, 6] +``` #### `reduce()` {.collection-method} The `reduce` method reduces the collection to a single value, passing the result of each iteration into the subsequent iteration: - $collection = collect([1, 2, 3]); +```php +$collection = collect([1, 2, 3]); - $total = $collection->reduce(function (?int $carry, int $item) { - return $carry + $item; - }); +$total = $collection->reduce(function (?int $carry, int $item) { + return $carry + $item; +}); - // 6 +// 6 +``` The value for `$carry` on the first iteration is `null`; however, you may specify its initial value by passing a second argument to `reduce`: - $collection->reduce(function (int $carry, int $item) { - return $carry + $item; - }, 4); +```php +$collection->reduce(function (int $carry, int $item) { + return $carry + $item; +}, 4); - // 10 +// 10 +``` The `reduce` method also passes array keys in associative collections to the given callback: - $collection = collect([ - 'usd' => 1400, - 'gbp' => 1200, - 'eur' => 1000, - ]); +```php +$collection = collect([ + 'usd' => 1400, + 'gbp' => 1200, + 'eur' => 1000, +]); - $ratio = [ - 'usd' => 1, - 'gbp' => 1.37, - 'eur' => 1.22, - ]; +$ratio = [ + 'usd' => 1, + 'gbp' => 1.37, + 'eur' => 1.22, +]; - $collection->reduce(function (int $carry, int $value, int $key) use ($ratio) { - return $carry + ($value * $ratio[$key]); - }); +$collection->reduce(function (int $carry, int $value, int $key) use ($ratio) { + return $carry + ($value * $ratio[$key]); +}); - // 4264 +// 4264 +``` #### `reduceSpread()` {.collection-method} The `reduceSpread` method reduces the collection to an array of values, passing the results of each iteration into the subsequent iteration. This method is similar to the `reduce` method; however, it can accept multiple initial values: - [$creditsRemaining, $batch] = Image::where('status', 'unprocessed') - ->get() - ->reduceSpread(function (int $creditsRemaining, Collection $batch, Image $image) { - if ($creditsRemaining >= $image->creditsRequired()) { - $batch->push($image); +```php +[$creditsRemaining, $batch] = Image::where('status', 'unprocessed') + ->get() + ->reduceSpread(function (int $creditsRemaining, Collection $batch, Image $image) { + if ($creditsRemaining >= $image->creditsRequired()) { + $batch->push($image); - $creditsRemaining -= $image->creditsRequired(); - } + $creditsRemaining -= $image->creditsRequired(); + } - return [$creditsRemaining, $batch]; - }, $creditsAvailable, collect()); + return [$creditsRemaining, $batch]; + }, $creditsAvailable, collect()); +``` #### `reject()` {.collection-method} The `reject` method filters the collection using the given closure. The closure should return `true` if the item should be removed from the resulting collection: - $collection = collect([1, 2, 3, 4]); +```php +$collection = collect([1, 2, 3, 4]); - $filtered = $collection->reject(function (int $value, int $key) { - return $value > 2; - }); +$filtered = $collection->reject(function (int $value, int $key) { + return $value > 2; +}); - $filtered->all(); +$filtered->all(); - // [1, 2] +// [1, 2] +``` For the inverse of the `reject` method, see the [`filter`](#method-filter) method. @@ -2212,83 +2474,95 @@ For the inverse of the `reject` method, see the [`filter`](#method-filter) metho The `replace` method behaves similarly to `merge`; however, in addition to overwriting matching items that have string keys, the `replace` method will also overwrite items in the collection that have matching numeric keys: - $collection = collect(['Taylor', 'Abigail', 'James']); +```php +$collection = collect(['Taylor', 'Abigail', 'James']); - $replaced = $collection->replace([1 => 'Victoria', 3 => 'Finn']); +$replaced = $collection->replace([1 => 'Victoria', 3 => 'Finn']); - $replaced->all(); +$replaced->all(); - // ['Taylor', 'Victoria', 'James', 'Finn'] +// ['Taylor', 'Victoria', 'James', 'Finn'] +``` #### `replaceRecursive()` {.collection-method} This method works like `replace`, but it will recur into arrays and apply the same replacement process to the inner values: - $collection = collect([ - 'Taylor', - 'Abigail', - [ - 'James', - 'Victoria', - 'Finn' - ] - ]); +```php +$collection = collect([ + 'Taylor', + 'Abigail', + [ + 'James', + 'Victoria', + 'Finn' + ] +]); - $replaced = $collection->replaceRecursive([ - 'Charlie', - 2 => [1 => 'King'] - ]); +$replaced = $collection->replaceRecursive([ + 'Charlie', + 2 => [1 => 'King'] +]); - $replaced->all(); +$replaced->all(); - // ['Charlie', 'Abigail', ['James', 'King', 'Finn']] +// ['Charlie', 'Abigail', ['James', 'King', 'Finn']] +``` #### `reverse()` {.collection-method} The `reverse` method reverses the order of the collection's items, preserving the original keys: - $collection = collect(['a', 'b', 'c', 'd', 'e']); +```php +$collection = collect(['a', 'b', 'c', 'd', 'e']); - $reversed = $collection->reverse(); +$reversed = $collection->reverse(); - $reversed->all(); +$reversed->all(); - /* - [ - 4 => 'e', - 3 => 'd', - 2 => 'c', - 1 => 'b', - 0 => 'a', - ] - */ +/* + [ + 4 => 'e', + 3 => 'd', + 2 => 'c', + 1 => 'b', + 0 => 'a', + ] +*/ +``` #### `search()` {.collection-method} The `search` method searches the collection for the given value and returns its key if found. If the item is not found, `false` is returned: - $collection = collect([2, 4, 6, 8]); +```php +$collection = collect([2, 4, 6, 8]); - $collection->search(4); +$collection->search(4); - // 1 +// 1 +``` The search is done using a "loose" comparison, meaning a string with an integer value will be considered equal to an integer of the same value. To use "strict" comparison, pass `true` as the second argument to the method: - collect([2, 4, 6, 8])->search('4', strict: true); +```php +collect([2, 4, 6, 8])->search('4', strict: true); - // false +// false +``` Alternatively, you may provide your own closure to search for the first item that passes a given truth test: - collect([2, 4, 6, 8])->search(function (int $item, int $key) { - return $item > 5; - }); +```php +collect([2, 4, 6, 8])->search(function (int $item, int $key) { + return $item > 5; +}); - // 2 +// 2 +``` #### `select()` {.collection-method} @@ -2316,78 +2590,90 @@ $users->select(['name', 'role']); The `shift` method removes and returns the first item from the collection: - $collection = collect([1, 2, 3, 4, 5]); +```php +$collection = collect([1, 2, 3, 4, 5]); - $collection->shift(); +$collection->shift(); - // 1 +// 1 - $collection->all(); +$collection->all(); - // [2, 3, 4, 5] +// [2, 3, 4, 5] +``` You may pass an integer to the `shift` method to remove and return multiple items from the beginning of a collection: - $collection = collect([1, 2, 3, 4, 5]); +```php +$collection = collect([1, 2, 3, 4, 5]); - $collection->shift(3); +$collection->shift(3); - // collect([1, 2, 3]) +// collect([1, 2, 3]) - $collection->all(); +$collection->all(); - // [4, 5] +// [4, 5] +``` #### `shuffle()` {.collection-method} The `shuffle` method randomly shuffles the items in the collection: - $collection = collect([1, 2, 3, 4, 5]); +```php +$collection = collect([1, 2, 3, 4, 5]); - $shuffled = $collection->shuffle(); +$shuffled = $collection->shuffle(); - $shuffled->all(); +$shuffled->all(); - // [3, 2, 5, 1, 4] - (generated randomly) +// [3, 2, 5, 1, 4] - (generated randomly) +``` #### `skip()` {.collection-method} The `skip` method returns a new collection, with the given number of elements removed from the beginning of the collection: - $collection = collect([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); +```php +$collection = collect([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); - $collection = $collection->skip(4); +$collection = $collection->skip(4); - $collection->all(); +$collection->all(); - // [5, 6, 7, 8, 9, 10] +// [5, 6, 7, 8, 9, 10] +``` #### `skipUntil()` {.collection-method} The `skipUntil` method skips over items from the collection while the given callback returns `false`. Once the callback returns `true` all of the remaining items in the collection will be returned as a new collection: - $collection = collect([1, 2, 3, 4]); +```php +$collection = collect([1, 2, 3, 4]); - $subset = $collection->skipUntil(function (int $item) { - return $item >= 3; - }); +$subset = $collection->skipUntil(function (int $item) { + return $item >= 3; +}); - $subset->all(); +$subset->all(); - // [3, 4] +// [3, 4] +``` You may also pass a simple value to the `skipUntil` method to skip all items until the given value is found: - $collection = collect([1, 2, 3, 4]); +```php +$collection = collect([1, 2, 3, 4]); - $subset = $collection->skipUntil(3); +$subset = $collection->skipUntil(3); - $subset->all(); +$subset->all(); - // [3, 4] +// [3, 4] +``` > [!WARNING] > If the given value is not found or the callback never returns `true`, the `skipUntil` method will return an empty collection. @@ -2397,15 +2683,17 @@ You may also pass a simple value to the `skipUntil` method to skip all items unt The `skipWhile` method skips over items from the collection while the given callback returns `true`. Once the callback returns `false` all of the remaining items in the collection will be returned as a new collection: - $collection = collect([1, 2, 3, 4]); +```php +$collection = collect([1, 2, 3, 4]); - $subset = $collection->skipWhile(function (int $item) { - return $item <= 3; - }); +$subset = $collection->skipWhile(function (int $item) { + return $item <= 3; +}); - $subset->all(); +$subset->all(); - // [4] +// [4] +``` > [!WARNING] > If the callback never returns `false`, the `skipWhile` method will return an empty collection. @@ -2415,21 +2703,25 @@ The `skipWhile` method skips over items from the collection while the given call The `slice` method returns a slice of the collection starting at the given index: - $collection = collect([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); +```php +$collection = collect([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); - $slice = $collection->slice(4); +$slice = $collection->slice(4); - $slice->all(); +$slice->all(); - // [5, 6, 7, 8, 9, 10] +// [5, 6, 7, 8, 9, 10] +``` If you would like to limit the size of the returned slice, pass the desired size as the second argument to the method: - $slice = $collection->slice(4, 2); +```php +$slice = $collection->slice(4, 2); - $slice->all(); +$slice->all(); - // [5, 6] +// [5, 6] +``` The returned slice will preserve keys by default. If you do not wish to preserve the original keys, you can use the [`values`](#method-values) method to reindex them. @@ -2438,61 +2730,73 @@ The returned slice will preserve keys by default. If you do not wish to preserve The `sliding` method returns a new collection of chunks representing a "sliding window" view of the items in the collection: - $collection = collect([1, 2, 3, 4, 5]); +```php +$collection = collect([1, 2, 3, 4, 5]); - $chunks = $collection->sliding(2); +$chunks = $collection->sliding(2); - $chunks->toArray(); +$chunks->toArray(); - // [[1, 2], [2, 3], [3, 4], [4, 5]] +// [[1, 2], [2, 3], [3, 4], [4, 5]] +``` This is especially useful in conjunction with the [`eachSpread`](#method-eachspread) method: - $transactions->sliding(2)->eachSpread(function (Collection $previous, Collection $current) { - $current->total = $previous->total + $current->amount; - }); +```php +$transactions->sliding(2)->eachSpread(function (Collection $previous, Collection $current) { + $current->total = $previous->total + $current->amount; +}); +``` You may optionally pass a second "step" value, which determines the distance between the first item of every chunk: - $collection = collect([1, 2, 3, 4, 5]); +```php +$collection = collect([1, 2, 3, 4, 5]); - $chunks = $collection->sliding(3, step: 2); +$chunks = $collection->sliding(3, step: 2); - $chunks->toArray(); +$chunks->toArray(); - // [[1, 2, 3], [3, 4, 5]] +// [[1, 2, 3], [3, 4, 5]] +``` #### `sole()` {.collection-method} The `sole` method returns the first element in the collection that passes a given truth test, but only if the truth test matches exactly one element: - collect([1, 2, 3, 4])->sole(function (int $value, int $key) { - return $value === 2; - }); +```php +collect([1, 2, 3, 4])->sole(function (int $value, int $key) { + return $value === 2; +}); - // 2 +// 2 +``` You may also pass a key / value pair to the `sole` method, which will return the first element in the collection that matches the given pair, but only if it exactly one element matches: - $collection = collect([ - ['product' => 'Desk', 'price' => 200], - ['product' => 'Chair', 'price' => 100], - ]); +```php +$collection = collect([ + ['product' => 'Desk', 'price' => 200], + ['product' => 'Chair', 'price' => 100], +]); - $collection->sole('product', 'Chair'); +$collection->sole('product', 'Chair'); - // ['product' => 'Chair', 'price' => 100] +// ['product' => 'Chair', 'price' => 100] +``` Alternatively, you may also call the `sole` method with no argument to get the first element in the collection if there is only one element: - $collection = collect([ - ['product' => 'Desk', 'price' => 200], - ]); +```php +$collection = collect([ + ['product' => 'Desk', 'price' => 200], +]); - $collection->sole(); +$collection->sole(); - // ['product' => 'Desk', 'price' => 200] +// ['product' => 'Desk', 'price' => 200] +``` If there are no elements in the collection that should be returned by the `sole` method, an `\Illuminate\Collections\ItemNotFoundException` exception will be thrown. If there is more than one element that should be returned, an `\Illuminate\Collections\MultipleItemsFoundException` will be thrown. @@ -2506,13 +2810,15 @@ Alias for the [`contains`](#method-contains) method. The `sort` method sorts the collection. The sorted collection keeps the original array keys, so in the following example we will use the [`values`](#method-values) method to reset the keys to consecutively numbered indexes: - $collection = collect([5, 3, 1, 2, 4]); +```php +$collection = collect([5, 3, 1, 2, 4]); - $sorted = $collection->sort(); +$sorted = $collection->sort(); - $sorted->values()->all(); +$sorted->values()->all(); - // [1, 2, 3, 4, 5] +// [1, 2, 3, 4, 5] +``` If your sorting needs are more advanced, you may pass a callback to `sort` with your own algorithm. Refer to the PHP documentation on [`uasort`](https://secure.php.net/manual/en/function.uasort.php#refsect1-function.uasort-parameters), which is what the collection's `sort` method calls utilizes internally. @@ -2524,115 +2830,125 @@ If your sorting needs are more advanced, you may pass a callback to `sort` with The `sortBy` method sorts the collection by the given key. The sorted collection keeps the original array keys, so in the following example we will use the [`values`](#method-values) method to reset the keys to consecutively numbered indexes: - $collection = collect([ - ['name' => 'Desk', 'price' => 200], +```php +$collection = collect([ + ['name' => 'Desk', 'price' => 200], + ['name' => 'Chair', 'price' => 100], + ['name' => 'Bookcase', 'price' => 150], +]); + +$sorted = $collection->sortBy('price'); + +$sorted->values()->all(); + +/* + [ ['name' => 'Chair', 'price' => 100], ['name' => 'Bookcase', 'price' => 150], - ]); + ['name' => 'Desk', 'price' => 200], + ] +*/ +``` - $sorted = $collection->sortBy('price'); +The `sortBy` method accepts [sort flags](https://www.php.net/manual/en/function.sort.php) as its second argument: - $sorted->values()->all(); +```php +$collection = collect([ + ['title' => 'Item 1'], + ['title' => 'Item 12'], + ['title' => 'Item 3'], +]); - /* - [ - ['name' => 'Chair', 'price' => 100], - ['name' => 'Bookcase', 'price' => 150], - ['name' => 'Desk', 'price' => 200], - ] - */ +$sorted = $collection->sortBy('title', SORT_NATURAL); -The `sortBy` method accepts [sort flags](https://www.php.net/manual/en/function.sort.php) as its second argument: +$sorted->values()->all(); - $collection = collect([ +/* + [ ['title' => 'Item 1'], - ['title' => 'Item 12'], ['title' => 'Item 3'], - ]); + ['title' => 'Item 12'], + ] +*/ +``` - $sorted = $collection->sortBy('title', SORT_NATURAL); +Alternatively, you may pass your own closure to determine how to sort the collection's values: - $sorted->values()->all(); +```php +$collection = collect([ + ['name' => 'Desk', 'colors' => ['Black', 'Mahogany']], + ['name' => 'Chair', 'colors' => ['Black']], + ['name' => 'Bookcase', 'colors' => ['Red', 'Beige', 'Brown']], +]); - /* - [ - ['title' => 'Item 1'], - ['title' => 'Item 3'], - ['title' => 'Item 12'], - ] - */ +$sorted = $collection->sortBy(function (array $product, int $key) { + return count($product['colors']); +}); -Alternatively, you may pass your own closure to determine how to sort the collection's values: +$sorted->values()->all(); - $collection = collect([ - ['name' => 'Desk', 'colors' => ['Black', 'Mahogany']], +/* + [ ['name' => 'Chair', 'colors' => ['Black']], + ['name' => 'Desk', 'colors' => ['Black', 'Mahogany']], ['name' => 'Bookcase', 'colors' => ['Red', 'Beige', 'Brown']], - ]); + ] +*/ +``` - $sorted = $collection->sortBy(function (array $product, int $key) { - return count($product['colors']); - }); +If you would like to sort your collection by multiple attributes, you may pass an array of sort operations to the `sortBy` method. Each sort operation should be an array consisting of the attribute that you wish to sort by and the direction of the desired sort: - $sorted->values()->all(); +```php +$collection = collect([ + ['name' => 'Taylor Otwell', 'age' => 34], + ['name' => 'Abigail Otwell', 'age' => 30], + ['name' => 'Taylor Otwell', 'age' => 36], + ['name' => 'Abigail Otwell', 'age' => 32], +]); - /* - [ - ['name' => 'Chair', 'colors' => ['Black']], - ['name' => 'Desk', 'colors' => ['Black', 'Mahogany']], - ['name' => 'Bookcase', 'colors' => ['Red', 'Beige', 'Brown']], - ] - */ +$sorted = $collection->sortBy([ + ['name', 'asc'], + ['age', 'desc'], +]); -If you would like to sort your collection by multiple attributes, you may pass an array of sort operations to the `sortBy` method. Each sort operation should be an array consisting of the attribute that you wish to sort by and the direction of the desired sort: +$sorted->values()->all(); - $collection = collect([ - ['name' => 'Taylor Otwell', 'age' => 34], +/* + [ + ['name' => 'Abigail Otwell', 'age' => 32], ['name' => 'Abigail Otwell', 'age' => 30], ['name' => 'Taylor Otwell', 'age' => 36], - ['name' => 'Abigail Otwell', 'age' => 32], - ]); + ['name' => 'Taylor Otwell', 'age' => 34], + ] +*/ +``` - $sorted = $collection->sortBy([ - ['name', 'asc'], - ['age', 'desc'], - ]); +When sorting a collection by multiple attributes, you may also provide closures that define each sort operation: - $sorted->values()->all(); +```php +$collection = collect([ + ['name' => 'Taylor Otwell', 'age' => 34], + ['name' => 'Abigail Otwell', 'age' => 30], + ['name' => 'Taylor Otwell', 'age' => 36], + ['name' => 'Abigail Otwell', 'age' => 32], +]); - /* - [ - ['name' => 'Abigail Otwell', 'age' => 32], - ['name' => 'Abigail Otwell', 'age' => 30], - ['name' => 'Taylor Otwell', 'age' => 36], - ['name' => 'Taylor Otwell', 'age' => 34], - ] - */ +$sorted = $collection->sortBy([ + fn (array $a, array $b) => $a['name'] <=> $b['name'], + fn (array $a, array $b) => $b['age'] <=> $a['age'], +]); -When sorting a collection by multiple attributes, you may also provide closures that define each sort operation: +$sorted->values()->all(); - $collection = collect([ - ['name' => 'Taylor Otwell', 'age' => 34], +/* + [ + ['name' => 'Abigail Otwell', 'age' => 32], ['name' => 'Abigail Otwell', 'age' => 30], ['name' => 'Taylor Otwell', 'age' => 36], - ['name' => 'Abigail Otwell', 'age' => 32], - ]); - - $sorted = $collection->sortBy([ - fn (array $a, array $b) => $a['name'] <=> $b['name'], - fn (array $a, array $b) => $b['age'] <=> $a['age'], - ]); - - $sorted->values()->all(); - - /* - [ - ['name' => 'Abigail Otwell', 'age' => 32], - ['name' => 'Abigail Otwell', 'age' => 30], - ['name' => 'Taylor Otwell', 'age' => 36], - ['name' => 'Taylor Otwell', 'age' => 34], - ] - */ + ['name' => 'Taylor Otwell', 'age' => 34], + ] +*/ +``` #### `sortByDesc()` {.collection-method} @@ -2644,13 +2960,15 @@ This method has the same signature as the [`sortBy`](#method-sortby) method, but This method will sort the collection in the opposite order as the [`sort`](#method-sort) method: - $collection = collect([5, 3, 1, 2, 4]); +```php +$collection = collect([5, 3, 1, 2, 4]); - $sorted = $collection->sortDesc(); +$sorted = $collection->sortDesc(); - $sorted->values()->all(); +$sorted->values()->all(); - // [5, 4, 3, 2, 1] +// [5, 4, 3, 2, 1] +``` Unlike `sort`, you may not pass a closure to `sortDesc`. Instead, you should use the [`sort`](#method-sort) method and invert your comparison. @@ -2659,23 +2977,25 @@ Unlike `sort`, you may not pass a closure to `sortDesc`. Instead, you should use The `sortKeys` method sorts the collection by the keys of the underlying associative array: - $collection = collect([ - 'id' => 22345, - 'first' => 'John', - 'last' => 'Doe', - ]); +```php +$collection = collect([ + 'id' => 22345, + 'first' => 'John', + 'last' => 'Doe', +]); - $sorted = $collection->sortKeys(); +$sorted = $collection->sortKeys(); - $sorted->all(); +$sorted->all(); - /* - [ - 'first' => 'John', - 'id' => 22345, - 'last' => 'Doe', - ] - */ +/* + [ + 'first' => 'John', + 'id' => 22345, + 'last' => 'Doe', + ] +*/ +``` #### `sortKeysDesc()` {.collection-method} @@ -2687,23 +3007,25 @@ This method has the same signature as the [`sortKeys`](#method-sortkeys) method, The `sortKeysUsing` method sorts the collection by the keys of the underlying associative array using a callback: - $collection = collect([ - 'ID' => 22345, - 'first' => 'John', - 'last' => 'Doe', - ]); +```php +$collection = collect([ + 'ID' => 22345, + 'first' => 'John', + 'last' => 'Doe', +]); - $sorted = $collection->sortKeysUsing('strnatcasecmp'); +$sorted = $collection->sortKeysUsing('strnatcasecmp'); - $sorted->all(); +$sorted->all(); - /* - [ - 'first' => 'John', - 'ID' => 22345, - 'last' => 'Doe', - ] - */ +/* + [ + 'first' => 'John', + 'ID' => 22345, + 'last' => 'Doe', + ] +*/ +``` The callback must be a comparison function that returns an integer less than, equal to, or greater than zero. For more information, refer to the PHP documentation on [`uksort`](https://www.php.net/manual/en/function.uksort.php#refsect1-function.uksort-parameters), which is the PHP function that `sortKeysUsing` method utilizes internally. @@ -2712,153 +3034,177 @@ The callback must be a comparison function that returns an integer less than, eq The `splice` method removes and returns a slice of items starting at the specified index: - $collection = collect([1, 2, 3, 4, 5]); +```php +$collection = collect([1, 2, 3, 4, 5]); - $chunk = $collection->splice(2); +$chunk = $collection->splice(2); - $chunk->all(); +$chunk->all(); - // [3, 4, 5] +// [3, 4, 5] - $collection->all(); +$collection->all(); - // [1, 2] +// [1, 2] +``` You may pass a second argument to limit the size of the resulting collection: - $collection = collect([1, 2, 3, 4, 5]); +```php +$collection = collect([1, 2, 3, 4, 5]); - $chunk = $collection->splice(2, 1); +$chunk = $collection->splice(2, 1); - $chunk->all(); +$chunk->all(); - // [3] +// [3] - $collection->all(); +$collection->all(); - // [1, 2, 4, 5] +// [1, 2, 4, 5] +``` In addition, you may pass a third argument containing the new items to replace the items removed from the collection: - $collection = collect([1, 2, 3, 4, 5]); +```php +$collection = collect([1, 2, 3, 4, 5]); - $chunk = $collection->splice(2, 1, [10, 11]); +$chunk = $collection->splice(2, 1, [10, 11]); - $chunk->all(); +$chunk->all(); - // [3] +// [3] - $collection->all(); +$collection->all(); - // [1, 2, 10, 11, 4, 5] +// [1, 2, 10, 11, 4, 5] +``` #### `split()` {.collection-method} The `split` method breaks a collection into the given number of groups: - $collection = collect([1, 2, 3, 4, 5]); +```php +$collection = collect([1, 2, 3, 4, 5]); - $groups = $collection->split(3); +$groups = $collection->split(3); - $groups->all(); +$groups->all(); - // [[1, 2], [3, 4], [5]] +// [[1, 2], [3, 4], [5]] +``` #### `splitIn()` {.collection-method} The `splitIn` method breaks a collection into the given number of groups, filling non-terminal groups completely before allocating the remainder to the final group: - $collection = collect([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); +```php +$collection = collect([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); - $groups = $collection->splitIn(3); +$groups = $collection->splitIn(3); - $groups->all(); +$groups->all(); - // [[1, 2, 3, 4], [5, 6, 7, 8], [9, 10]] +// [[1, 2, 3, 4], [5, 6, 7, 8], [9, 10]] +``` #### `sum()` {.collection-method} The `sum` method returns the sum of all items in the collection: - collect([1, 2, 3, 4, 5])->sum(); +```php +collect([1, 2, 3, 4, 5])->sum(); - // 15 +// 15 +``` If the collection contains nested arrays or objects, you should pass a key that will be used to determine which values to sum: - $collection = collect([ - ['name' => 'JavaScript: The Good Parts', 'pages' => 176], - ['name' => 'JavaScript: The Definitive Guide', 'pages' => 1096], - ]); +```php +$collection = collect([ + ['name' => 'JavaScript: The Good Parts', 'pages' => 176], + ['name' => 'JavaScript: The Definitive Guide', 'pages' => 1096], +]); - $collection->sum('pages'); +$collection->sum('pages'); - // 1272 +// 1272 +``` In addition, you may pass your own closure to determine which values of the collection to sum: - $collection = collect([ - ['name' => 'Chair', 'colors' => ['Black']], - ['name' => 'Desk', 'colors' => ['Black', 'Mahogany']], - ['name' => 'Bookcase', 'colors' => ['Red', 'Beige', 'Brown']], - ]); +```php +$collection = collect([ + ['name' => 'Chair', 'colors' => ['Black']], + ['name' => 'Desk', 'colors' => ['Black', 'Mahogany']], + ['name' => 'Bookcase', 'colors' => ['Red', 'Beige', 'Brown']], +]); - $collection->sum(function (array $product) { - return count($product['colors']); - }); +$collection->sum(function (array $product) { + return count($product['colors']); +}); - // 6 +// 6 +``` #### `take()` {.collection-method} The `take` method returns a new collection with the specified number of items: - $collection = collect([0, 1, 2, 3, 4, 5]); +```php +$collection = collect([0, 1, 2, 3, 4, 5]); - $chunk = $collection->take(3); +$chunk = $collection->take(3); - $chunk->all(); +$chunk->all(); - // [0, 1, 2] +// [0, 1, 2] +``` You may also pass a negative integer to take the specified number of items from the end of the collection: - $collection = collect([0, 1, 2, 3, 4, 5]); +```php +$collection = collect([0, 1, 2, 3, 4, 5]); - $chunk = $collection->take(-2); +$chunk = $collection->take(-2); - $chunk->all(); +$chunk->all(); - // [4, 5] +// [4, 5] +``` #### `takeUntil()` {.collection-method} The `takeUntil` method returns items in the collection until the given callback returns `true`: - $collection = collect([1, 2, 3, 4]); +```php +$collection = collect([1, 2, 3, 4]); - $subset = $collection->takeUntil(function (int $item) { - return $item >= 3; - }); +$subset = $collection->takeUntil(function (int $item) { + return $item >= 3; +}); - $subset->all(); +$subset->all(); - // [1, 2] +// [1, 2] +``` You may also pass a simple value to the `takeUntil` method to get the items until the given value is found: - $collection = collect([1, 2, 3, 4]); +```php +$collection = collect([1, 2, 3, 4]); - $subset = $collection->takeUntil(3); +$subset = $collection->takeUntil(3); - $subset->all(); +$subset->all(); - // [1, 2] +// [1, 2] +``` > [!WARNING] > If the given value is not found or the callback never returns `true`, the `takeUntil` method will return all items in the collection. @@ -2868,15 +3214,17 @@ You may also pass a simple value to the `takeUntil` method to get the items unti The `takeWhile` method returns items in the collection until the given callback returns `false`: - $collection = collect([1, 2, 3, 4]); +```php +$collection = collect([1, 2, 3, 4]); - $subset = $collection->takeWhile(function (int $item) { - return $item < 3; - }); +$subset = $collection->takeWhile(function (int $item) { + return $item < 3; +}); - $subset->all(); +$subset->all(); - // [1, 2] +// [1, 2] +``` > [!WARNING] > If the callback never returns `false`, the `takeWhile` method will return all items in the collection. @@ -2886,42 +3234,48 @@ The `takeWhile` method returns items in the collection until the given callback The `tap` method passes the collection to the given callback, allowing you to "tap" into the collection at a specific point and do something with the items while not affecting the collection itself. The collection is then returned by the `tap` method: - collect([2, 4, 3, 1, 5]) - ->sort() - ->tap(function (Collection $collection) { - Log::debug('Values after sorting', $collection->values()->all()); - }) - ->shift(); - - // 1 +```php +collect([2, 4, 3, 1, 5]) + ->sort() + ->tap(function (Collection $collection) { + Log::debug('Values after sorting', $collection->values()->all()); + }) + ->shift(); + +// 1 +``` #### `times()` {.collection-method} The static `times` method creates a new collection by invoking the given closure a specified number of times: - $collection = Collection::times(10, function (int $number) { - return $number * 9; - }); +```php +$collection = Collection::times(10, function (int $number) { + return $number * 9; +}); - $collection->all(); +$collection->all(); - // [9, 18, 27, 36, 45, 54, 63, 72, 81, 90] +// [9, 18, 27, 36, 45, 54, 63, 72, 81, 90] +``` #### `toArray()` {.collection-method} The `toArray` method converts the collection into a plain PHP `array`. If the collection's values are [Eloquent](/docs/{{version}}/eloquent) models, the models will also be converted to arrays: - $collection = collect(['name' => 'Desk', 'price' => 200]); +```php +$collection = collect(['name' => 'Desk', 'price' => 200]); - $collection->toArray(); +$collection->toArray(); - /* - [ - ['name' => 'Desk', 'price' => 200], - ] - */ +/* + [ + ['name' => 'Desk', 'price' => 200], + ] +*/ +``` > [!WARNING] > `toArray` also converts all of the collection's nested objects that are an instance of `Arrayable` to an array. If you want to get the raw array underlying the collection, use the [`all`](#method-all) method instead. @@ -2931,26 +3285,30 @@ The `toArray` method converts the collection into a plain PHP `array`. If the co The `toJson` method converts the collection into a JSON serialized string: - $collection = collect(['name' => 'Desk', 'price' => 200]); +```php +$collection = collect(['name' => 'Desk', 'price' => 200]); - $collection->toJson(); +$collection->toJson(); - // '{"name":"Desk", "price":200}' +// '{"name":"Desk", "price":200}' +``` #### `transform()` {.collection-method} The `transform` method iterates over the collection and calls the given callback with each item in the collection. The items in the collection will be replaced by the values returned by the callback: - $collection = collect([1, 2, 3, 4, 5]); +```php +$collection = collect([1, 2, 3, 4, 5]); - $collection->transform(function (int $item, int $key) { - return $item * 2; - }); +$collection->transform(function (int $item, int $key) { + return $item * 2; +}); - $collection->all(); +$collection->all(); - // [2, 4, 6, 8, 10] +// [2, 4, 6, 8, 10] +``` > [!WARNING] > Unlike most other collection methods, `transform` modifies the collection itself. If you wish to create a new collection instead, use the [`map`](#method-map) method. @@ -2960,99 +3318,109 @@ The `transform` method iterates over the collection and calls the given callback The `undot` method expands a single-dimensional collection that uses "dot" notation into a multi-dimensional collection: - $person = collect([ - 'name.first_name' => 'Marie', - 'name.last_name' => 'Valentine', - 'address.line_1' => '2992 Eagle Drive', - 'address.line_2' => '', - 'address.suburb' => 'Detroit', - 'address.state' => 'MI', - 'address.postcode' => '48219' - ]); +```php +$person = collect([ + 'name.first_name' => 'Marie', + 'name.last_name' => 'Valentine', + 'address.line_1' => '2992 Eagle Drive', + 'address.line_2' => '', + 'address.suburb' => 'Detroit', + 'address.state' => 'MI', + 'address.postcode' => '48219' +]); - $person = $person->undot(); +$person = $person->undot(); - $person->toArray(); +$person->toArray(); - /* - [ - "name" => [ - "first_name" => "Marie", - "last_name" => "Valentine", - ], - "address" => [ - "line_1" => "2992 Eagle Drive", - "line_2" => "", - "suburb" => "Detroit", - "state" => "MI", - "postcode" => "48219", - ], - ] - */ +/* + [ + "name" => [ + "first_name" => "Marie", + "last_name" => "Valentine", + ], + "address" => [ + "line_1" => "2992 Eagle Drive", + "line_2" => "", + "suburb" => "Detroit", + "state" => "MI", + "postcode" => "48219", + ], + ] +*/ +``` #### `union()` {.collection-method} The `union` method adds the given array to the collection. If the given array contains keys that are already in the original collection, the original collection's values will be preferred: - $collection = collect([1 => ['a'], 2 => ['b']]); +```php +$collection = collect([1 => ['a'], 2 => ['b']]); - $union = $collection->union([3 => ['c'], 1 => ['d']]); +$union = $collection->union([3 => ['c'], 1 => ['d']]); - $union->all(); +$union->all(); - // [1 => ['a'], 2 => ['b'], 3 => ['c']] +// [1 => ['a'], 2 => ['b'], 3 => ['c']] +``` #### `unique()` {.collection-method} The `unique` method returns all of the unique items in the collection. The returned collection keeps the original array keys, so in the following example we will use the [`values`](#method-values) method to reset the keys to consecutively numbered indexes: - $collection = collect([1, 1, 2, 2, 3, 4, 2]); +```php +$collection = collect([1, 1, 2, 2, 3, 4, 2]); - $unique = $collection->unique(); +$unique = $collection->unique(); - $unique->values()->all(); +$unique->values()->all(); - // [1, 2, 3, 4] +// [1, 2, 3, 4] +``` When dealing with nested arrays or objects, you may specify the key used to determine uniqueness: - $collection = collect([ - ['name' => 'iPhone 6', 'brand' => 'Apple', 'type' => 'phone'], - ['name' => 'iPhone 5', 'brand' => 'Apple', 'type' => 'phone'], - ['name' => 'Apple Watch', 'brand' => 'Apple', 'type' => 'watch'], - ['name' => 'Galaxy S6', 'brand' => 'Samsung', 'type' => 'phone'], - ['name' => 'Galaxy Gear', 'brand' => 'Samsung', 'type' => 'watch'], - ]); +```php +$collection = collect([ + ['name' => 'iPhone 6', 'brand' => 'Apple', 'type' => 'phone'], + ['name' => 'iPhone 5', 'brand' => 'Apple', 'type' => 'phone'], + ['name' => 'Apple Watch', 'brand' => 'Apple', 'type' => 'watch'], + ['name' => 'Galaxy S6', 'brand' => 'Samsung', 'type' => 'phone'], + ['name' => 'Galaxy Gear', 'brand' => 'Samsung', 'type' => 'watch'], +]); - $unique = $collection->unique('brand'); +$unique = $collection->unique('brand'); - $unique->values()->all(); +$unique->values()->all(); - /* - [ - ['name' => 'iPhone 6', 'brand' => 'Apple', 'type' => 'phone'], - ['name' => 'Galaxy S6', 'brand' => 'Samsung', 'type' => 'phone'], - ] - */ +/* + [ + ['name' => 'iPhone 6', 'brand' => 'Apple', 'type' => 'phone'], + ['name' => 'Galaxy S6', 'brand' => 'Samsung', 'type' => 'phone'], + ] +*/ +``` Finally, you may also pass your own closure to the `unique` method to specify which value should determine an item's uniqueness: - $unique = $collection->unique(function (array $item) { - return $item['brand'].$item['type']; - }); +```php +$unique = $collection->unique(function (array $item) { + return $item['brand'].$item['type']; +}); - $unique->values()->all(); +$unique->values()->all(); - /* - [ - ['name' => 'iPhone 6', 'brand' => 'Apple', 'type' => 'phone'], - ['name' => 'Apple Watch', 'brand' => 'Apple', 'type' => 'watch'], - ['name' => 'Galaxy S6', 'brand' => 'Samsung', 'type' => 'phone'], - ['name' => 'Galaxy Gear', 'brand' => 'Samsung', 'type' => 'watch'], - ] - */ +/* + [ + ['name' => 'iPhone 6', 'brand' => 'Apple', 'type' => 'phone'], + ['name' => 'Apple Watch', 'brand' => 'Apple', 'type' => 'watch'], + ['name' => 'Galaxy S6', 'brand' => 'Samsung', 'type' => 'phone'], + ['name' => 'Galaxy Gear', 'brand' => 'Samsung', 'type' => 'watch'], + ] +*/ +``` The `unique` method uses "loose" comparisons when checking item values, meaning a string with an integer value will be considered equal to an integer of the same value. Use the [`uniqueStrict`](#method-uniquestrict) method to filter using "strict" comparisons. @@ -3069,33 +3437,37 @@ This method has the same signature as the [`unique`](#method-unique) method; how The `unless` method will execute the given callback unless the first argument given to the method evaluates to `true`: - $collection = collect([1, 2, 3]); +```php +$collection = collect([1, 2, 3]); - $collection->unless(true, function (Collection $collection) { - return $collection->push(4); - }); +$collection->unless(true, function (Collection $collection) { + return $collection->push(4); +}); - $collection->unless(false, function (Collection $collection) { - return $collection->push(5); - }); +$collection->unless(false, function (Collection $collection) { + return $collection->push(5); +}); - $collection->all(); +$collection->all(); - // [1, 2, 3, 5] +// [1, 2, 3, 5] +``` A second callback may be passed to the `unless` method. The second callback will be executed when the first argument given to the `unless` method evaluates to `true`: - $collection = collect([1, 2, 3]); +```php +$collection = collect([1, 2, 3]); - $collection->unless(true, function (Collection $collection) { - return $collection->push(4); - }, function (Collection $collection) { - return $collection->push(5); - }); +$collection->unless(true, function (Collection $collection) { + return $collection->push(4); +}, function (Collection $collection) { + return $collection->push(5); +}); - $collection->all(); +$collection->all(); - // [1, 2, 3, 5] +// [1, 2, 3, 5] +``` For the inverse of `unless`, see the [`when`](#method-when) method. @@ -3114,85 +3486,95 @@ Alias for the [`whenEmpty`](#method-whenempty) method. The static `unwrap` method returns the collection's underlying items from the given value when applicable: - Collection::unwrap(collect('John Doe')); +```php +Collection::unwrap(collect('John Doe')); - // ['John Doe'] +// ['John Doe'] - Collection::unwrap(['John Doe']); +Collection::unwrap(['John Doe']); - // ['John Doe'] +// ['John Doe'] - Collection::unwrap('John Doe'); +Collection::unwrap('John Doe'); - // 'John Doe' +// 'John Doe' +``` #### `value()` {.collection-method} The `value` method retrieves a given value from the first element of the collection: - $collection = collect([ - ['product' => 'Desk', 'price' => 200], - ['product' => 'Speaker', 'price' => 400], - ]); +```php +$collection = collect([ + ['product' => 'Desk', 'price' => 200], + ['product' => 'Speaker', 'price' => 400], +]); - $value = $collection->value('price'); +$value = $collection->value('price'); - // 200 +// 200 +``` #### `values()` {.collection-method} The `values` method returns a new collection with the keys reset to consecutive integers: - $collection = collect([ - 10 => ['product' => 'Desk', 'price' => 200], - 11 => ['product' => 'Desk', 'price' => 200], - ]); +```php +$collection = collect([ + 10 => ['product' => 'Desk', 'price' => 200], + 11 => ['product' => 'Desk', 'price' => 200], +]); - $values = $collection->values(); +$values = $collection->values(); - $values->all(); +$values->all(); - /* - [ - 0 => ['product' => 'Desk', 'price' => 200], - 1 => ['product' => 'Desk', 'price' => 200], - ] - */ +/* + [ + 0 => ['product' => 'Desk', 'price' => 200], + 1 => ['product' => 'Desk', 'price' => 200], + ] +*/ +``` #### `when()` {.collection-method} The `when` method will execute the given callback when the first argument given to the method evaluates to `true`. The collection instance and the first argument given to the `when` method will be provided to the closure: - $collection = collect([1, 2, 3]); +```php +$collection = collect([1, 2, 3]); - $collection->when(true, function (Collection $collection, int $value) { - return $collection->push(4); - }); +$collection->when(true, function (Collection $collection, int $value) { + return $collection->push(4); +}); - $collection->when(false, function (Collection $collection, int $value) { - return $collection->push(5); - }); +$collection->when(false, function (Collection $collection, int $value) { + return $collection->push(5); +}); - $collection->all(); +$collection->all(); - // [1, 2, 3, 4] +// [1, 2, 3, 4] +``` A second callback may be passed to the `when` method. The second callback will be executed when the first argument given to the `when` method evaluates to `false`: - $collection = collect([1, 2, 3]); +```php +$collection = collect([1, 2, 3]); - $collection->when(false, function (Collection $collection, int $value) { - return $collection->push(4); - }, function (Collection $collection) { - return $collection->push(5); - }); +$collection->when(false, function (Collection $collection, int $value) { + return $collection->push(4); +}, function (Collection $collection) { + return $collection->push(5); +}); - $collection->all(); +$collection->all(); - // [1, 2, 3, 5] +// [1, 2, 3, 5] +``` For the inverse of `when`, see the [`unless`](#method-unless) method. @@ -3201,40 +3583,44 @@ For the inverse of `when`, see the [`unless`](#method-unless) method. The `whenEmpty` method will execute the given callback when the collection is empty: - $collection = collect(['Michael', 'Tom']); +```php +$collection = collect(['Michael', 'Tom']); - $collection->whenEmpty(function (Collection $collection) { - return $collection->push('Adam'); - }); +$collection->whenEmpty(function (Collection $collection) { + return $collection->push('Adam'); +}); - $collection->all(); +$collection->all(); - // ['Michael', 'Tom'] +// ['Michael', 'Tom'] - $collection = collect(); +$collection = collect(); - $collection->whenEmpty(function (Collection $collection) { - return $collection->push('Adam'); - }); +$collection->whenEmpty(function (Collection $collection) { + return $collection->push('Adam'); +}); - $collection->all(); +$collection->all(); - // ['Adam'] +// ['Adam'] +``` A second closure may be passed to the `whenEmpty` method that will be executed when the collection is not empty: - $collection = collect(['Michael', 'Tom']); +```php +$collection = collect(['Michael', 'Tom']); - $collection->whenEmpty(function (Collection $collection) { - return $collection->push('Adam'); - }, function (Collection $collection) { - return $collection->push('Taylor'); - }); +$collection->whenEmpty(function (Collection $collection) { + return $collection->push('Adam'); +}, function (Collection $collection) { + return $collection->push('Taylor'); +}); - $collection->all(); +$collection->all(); - // ['Michael', 'Tom', 'Taylor'] +// ['Michael', 'Tom', 'Taylor'] +``` For the inverse of `whenEmpty`, see the [`whenNotEmpty`](#method-whennotempty) method. @@ -3243,40 +3629,44 @@ For the inverse of `whenEmpty`, see the [`whenNotEmpty`](#method-whennotempty) m The `whenNotEmpty` method will execute the given callback when the collection is not empty: - $collection = collect(['michael', 'tom']); +```php +$collection = collect(['michael', 'tom']); - $collection->whenNotEmpty(function (Collection $collection) { - return $collection->push('adam'); - }); +$collection->whenNotEmpty(function (Collection $collection) { + return $collection->push('adam'); +}); - $collection->all(); +$collection->all(); - // ['michael', 'tom', 'adam'] +// ['michael', 'tom', 'adam'] - $collection = collect(); +$collection = collect(); - $collection->whenNotEmpty(function (Collection $collection) { - return $collection->push('adam'); - }); +$collection->whenNotEmpty(function (Collection $collection) { + return $collection->push('adam'); +}); - $collection->all(); +$collection->all(); - // [] +// [] +``` A second closure may be passed to the `whenNotEmpty` method that will be executed when the collection is empty: - $collection = collect(); +```php +$collection = collect(); - $collection->whenNotEmpty(function (Collection $collection) { - return $collection->push('adam'); - }, function (Collection $collection) { - return $collection->push('taylor'); - }); +$collection->whenNotEmpty(function (Collection $collection) { + return $collection->push('adam'); +}, function (Collection $collection) { + return $collection->push('taylor'); +}); - $collection->all(); +$collection->all(); - // ['taylor'] +// ['taylor'] +``` For the inverse of `whenNotEmpty`, see the [`whenEmpty`](#method-whenempty) method. @@ -3285,44 +3675,48 @@ For the inverse of `whenNotEmpty`, see the [`whenEmpty`](#method-whenempty) meth The `where` method filters the collection by a given key / value pair: - $collection = collect([ - ['product' => 'Desk', 'price' => 200], - ['product' => 'Chair', 'price' => 100], - ['product' => 'Bookcase', 'price' => 150], - ['product' => 'Door', 'price' => 100], - ]); +```php +$collection = collect([ + ['product' => 'Desk', 'price' => 200], + ['product' => 'Chair', 'price' => 100], + ['product' => 'Bookcase', 'price' => 150], + ['product' => 'Door', 'price' => 100], +]); - $filtered = $collection->where('price', 100); +$filtered = $collection->where('price', 100); - $filtered->all(); +$filtered->all(); - /* - [ - ['product' => 'Chair', 'price' => 100], - ['product' => 'Door', 'price' => 100], - ] - */ +/* + [ + ['product' => 'Chair', 'price' => 100], + ['product' => 'Door', 'price' => 100], + ] +*/ +``` The `where` method uses "loose" comparisons when checking item values, meaning a string with an integer value will be considered equal to an integer of the same value. Use the [`whereStrict`](#method-wherestrict) method to filter using "strict" comparisons. Optionally, you may pass a comparison operator as the second parameter. Supported operators are: '===', '!==', '!=', '==', '=', '<>', '>', '<', '>=', and '<=': - $collection = collect([ - ['name' => 'Jim', 'deleted_at' => '2019-01-01 00:00:00'], - ['name' => 'Sally', 'deleted_at' => '2019-01-02 00:00:00'], - ['name' => 'Sue', 'deleted_at' => null], - ]); +```php +$collection = collect([ + ['name' => 'Jim', 'deleted_at' => '2019-01-01 00:00:00'], + ['name' => 'Sally', 'deleted_at' => '2019-01-02 00:00:00'], + ['name' => 'Sue', 'deleted_at' => null], +]); - $filtered = $collection->where('deleted_at', '!=', null); +$filtered = $collection->where('deleted_at', '!=', null); - $filtered->all(); +$filtered->all(); - /* - [ - ['name' => 'Jim', 'deleted_at' => '2019-01-01 00:00:00'], - ['name' => 'Sally', 'deleted_at' => '2019-01-02 00:00:00'], - ] - */ +/* + [ + ['name' => 'Jim', 'deleted_at' => '2019-01-01 00:00:00'], + ['name' => 'Sally', 'deleted_at' => '2019-01-02 00:00:00'], + ] +*/ +``` #### `whereStrict()` {.collection-method} @@ -3334,48 +3728,52 @@ This method has the same signature as the [`where`](#method-where) method; howev The `whereBetween` method filters the collection by determining if a specified item value is within a given range: - $collection = collect([ - ['product' => 'Desk', 'price' => 200], - ['product' => 'Chair', 'price' => 80], - ['product' => 'Bookcase', 'price' => 150], - ['product' => 'Pencil', 'price' => 30], - ['product' => 'Door', 'price' => 100], - ]); +```php +$collection = collect([ + ['product' => 'Desk', 'price' => 200], + ['product' => 'Chair', 'price' => 80], + ['product' => 'Bookcase', 'price' => 150], + ['product' => 'Pencil', 'price' => 30], + ['product' => 'Door', 'price' => 100], +]); - $filtered = $collection->whereBetween('price', [100, 200]); +$filtered = $collection->whereBetween('price', [100, 200]); - $filtered->all(); +$filtered->all(); - /* - [ - ['product' => 'Desk', 'price' => 200], - ['product' => 'Bookcase', 'price' => 150], - ['product' => 'Door', 'price' => 100], - ] - */ +/* + [ + ['product' => 'Desk', 'price' => 200], + ['product' => 'Bookcase', 'price' => 150], + ['product' => 'Door', 'price' => 100], + ] +*/ +``` #### `whereIn()` {.collection-method} The `whereIn` method removes elements from the collection that do not have a specified item value that is contained within the given array: - $collection = collect([ - ['product' => 'Desk', 'price' => 200], - ['product' => 'Chair', 'price' => 100], - ['product' => 'Bookcase', 'price' => 150], - ['product' => 'Door', 'price' => 100], - ]); +```php +$collection = collect([ + ['product' => 'Desk', 'price' => 200], + ['product' => 'Chair', 'price' => 100], + ['product' => 'Bookcase', 'price' => 150], + ['product' => 'Door', 'price' => 100], +]); - $filtered = $collection->whereIn('price', [150, 200]); +$filtered = $collection->whereIn('price', [150, 200]); - $filtered->all(); +$filtered->all(); - /* - [ - ['product' => 'Desk', 'price' => 200], - ['product' => 'Bookcase', 'price' => 150], - ] - */ +/* + [ + ['product' => 'Desk', 'price' => 200], + ['product' => 'Bookcase', 'price' => 150], + ] +*/ +``` The `whereIn` method uses "loose" comparisons when checking item values, meaning a string with an integer value will be considered equal to an integer of the same value. Use the [`whereInStrict`](#method-whereinstrict) method to filter using "strict" comparisons. @@ -3389,67 +3787,73 @@ This method has the same signature as the [`whereIn`](#method-wherein) method; h The `whereInstanceOf` method filters the collection by a given class type: - use App\Models\User; - use App\Models\Post; +```php +use App\Models\User; +use App\Models\Post; - $collection = collect([ - new User, - new User, - new Post, - ]); +$collection = collect([ + new User, + new User, + new Post, +]); - $filtered = $collection->whereInstanceOf(User::class); +$filtered = $collection->whereInstanceOf(User::class); - $filtered->all(); +$filtered->all(); - // [App\Models\User, App\Models\User] +// [App\Models\User, App\Models\User] +``` #### `whereNotBetween()` {.collection-method} The `whereNotBetween` method filters the collection by determining if a specified item value is outside of a given range: - $collection = collect([ - ['product' => 'Desk', 'price' => 200], - ['product' => 'Chair', 'price' => 80], - ['product' => 'Bookcase', 'price' => 150], - ['product' => 'Pencil', 'price' => 30], - ['product' => 'Door', 'price' => 100], - ]); +```php +$collection = collect([ + ['product' => 'Desk', 'price' => 200], + ['product' => 'Chair', 'price' => 80], + ['product' => 'Bookcase', 'price' => 150], + ['product' => 'Pencil', 'price' => 30], + ['product' => 'Door', 'price' => 100], +]); - $filtered = $collection->whereNotBetween('price', [100, 200]); +$filtered = $collection->whereNotBetween('price', [100, 200]); - $filtered->all(); +$filtered->all(); - /* - [ - ['product' => 'Chair', 'price' => 80], - ['product' => 'Pencil', 'price' => 30], - ] - */ +/* + [ + ['product' => 'Chair', 'price' => 80], + ['product' => 'Pencil', 'price' => 30], + ] +*/ +``` #### `whereNotIn()` {.collection-method} The `whereNotIn` method removes elements from the collection that have a specified item value that is contained within the given array: - $collection = collect([ - ['product' => 'Desk', 'price' => 200], - ['product' => 'Chair', 'price' => 100], - ['product' => 'Bookcase', 'price' => 150], - ['product' => 'Door', 'price' => 100], - ]); +```php +$collection = collect([ + ['product' => 'Desk', 'price' => 200], + ['product' => 'Chair', 'price' => 100], + ['product' => 'Bookcase', 'price' => 150], + ['product' => 'Door', 'price' => 100], +]); - $filtered = $collection->whereNotIn('price', [150, 200]); +$filtered = $collection->whereNotIn('price', [150, 200]); - $filtered->all(); +$filtered->all(); - /* - [ - ['product' => 'Chair', 'price' => 100], - ['product' => 'Door', 'price' => 100], - ] - */ +/* + [ + ['product' => 'Chair', 'price' => 100], + ['product' => 'Door', 'price' => 100], + ] +*/ +``` The `whereNotIn` method uses "loose" comparisons when checking item values, meaning a string with an integer value will be considered equal to an integer of the same value. Use the [`whereNotInStrict`](#method-wherenotinstrict) method to filter using "strict" comparisons. @@ -3463,81 +3867,89 @@ This method has the same signature as the [`whereNotIn`](#method-wherenotin) met The `whereNotNull` method returns items from the collection where the given key is not `null`: - $collection = collect([ - ['name' => 'Desk'], - ['name' => null], - ['name' => 'Bookcase'], - ]); +```php +$collection = collect([ + ['name' => 'Desk'], + ['name' => null], + ['name' => 'Bookcase'], +]); - $filtered = $collection->whereNotNull('name'); +$filtered = $collection->whereNotNull('name'); - $filtered->all(); +$filtered->all(); - /* - [ - ['name' => 'Desk'], - ['name' => 'Bookcase'], - ] - */ +/* + [ + ['name' => 'Desk'], + ['name' => 'Bookcase'], + ] +*/ +``` #### `whereNull()` {.collection-method} The `whereNull` method returns items from the collection where the given key is `null`: - $collection = collect([ - ['name' => 'Desk'], - ['name' => null], - ['name' => 'Bookcase'], - ]); +```php +$collection = collect([ + ['name' => 'Desk'], + ['name' => null], + ['name' => 'Bookcase'], +]); - $filtered = $collection->whereNull('name'); +$filtered = $collection->whereNull('name'); - $filtered->all(); +$filtered->all(); - /* - [ - ['name' => null], - ] - */ +/* + [ + ['name' => null], + ] +*/ +``` #### `wrap()` {.collection-method} The static `wrap` method wraps the given value in a collection when applicable: - use Illuminate\Support\Collection; +```php +use Illuminate\Support\Collection; - $collection = Collection::wrap('John Doe'); +$collection = Collection::wrap('John Doe'); - $collection->all(); +$collection->all(); - // ['John Doe'] +// ['John Doe'] - $collection = Collection::wrap(['John Doe']); +$collection = Collection::wrap(['John Doe']); - $collection->all(); +$collection->all(); - // ['John Doe'] +// ['John Doe'] - $collection = Collection::wrap(collect('John Doe')); +$collection = Collection::wrap(collect('John Doe')); - $collection->all(); +$collection->all(); - // ['John Doe'] +// ['John Doe'] +``` #### `zip()` {.collection-method} The `zip` method merges together the values of the given array with the values of the original collection at their corresponding index: - $collection = collect(['Chair', 'Desk']); +```php +$collection = collect(['Chair', 'Desk']); - $zipped = $collection->zip([100, 200]); +$zipped = $collection->zip([100, 200]); - $zipped->all(); +$zipped->all(); - // [['Chair', 100], ['Desk', 200]] +// [['Chair', 100], ['Desk', 200]] +``` ## Higher Order Messages @@ -3546,17 +3958,21 @@ Collections also provide support for "higher order messages", which are short-cu Each higher order message can be accessed as a dynamic property on a collection instance. For instance, let's use the `each` higher order message to call a method on each object within a collection: - use App\Models\User; +```php +use App\Models\User; - $users = User::where('votes', '>', 500)->get(); +$users = User::where('votes', '>', 500)->get(); - $users->each->markAsVip(); +$users->each->markAsVip(); +``` Likewise, we can use the `sum` higher order message to gather the total number of "votes" for a collection of users: - $users = User::where('group', 'Development')->get(); +```php +$users = User::where('group', 'Development')->get(); - return $users->sum->votes; +return $users->sum->votes; +``` ## Lazy Collections @@ -3571,55 +3987,63 @@ To supplement the already powerful `Collection` class, the `LazyCollection` clas For example, imagine your application needs to process a multi-gigabyte log file while taking advantage of Laravel's collection methods to parse the logs. Instead of reading the entire file into memory at once, lazy collections may be used to keep only a small part of the file in memory at a given time: - use App\Models\LogEntry; - use Illuminate\Support\LazyCollection; +```php +use App\Models\LogEntry; +use Illuminate\Support\LazyCollection; - LazyCollection::make(function () { - $handle = fopen('log.txt', 'r'); +LazyCollection::make(function () { + $handle = fopen('log.txt', 'r'); - while (($line = fgets($handle)) !== false) { - yield $line; - } - })->chunk(4)->map(function (array $lines) { - return LogEntry::fromLines($lines); - })->each(function (LogEntry $logEntry) { - // Process the log entry... - }); + while (($line = fgets($handle)) !== false) { + yield $line; + } +})->chunk(4)->map(function (array $lines) { + return LogEntry::fromLines($lines); +})->each(function (LogEntry $logEntry) { + // Process the log entry... +}); +``` Or, imagine you need to iterate through 10,000 Eloquent models. When using traditional Laravel collections, all 10,000 Eloquent models must be loaded into memory at the same time: - use App\Models\User; +```php +use App\Models\User; - $users = User::all()->filter(function (User $user) { - return $user->id > 500; - }); +$users = User::all()->filter(function (User $user) { + return $user->id > 500; +}); +``` However, the query builder's `cursor` method returns a `LazyCollection` instance. This allows you to still only run a single query against the database but also only keep one Eloquent model loaded in memory at a time. In this example, the `filter` callback is not executed until we actually iterate over each user individually, allowing for a drastic reduction in memory usage: - use App\Models\User; +```php +use App\Models\User; - $users = User::cursor()->filter(function (User $user) { - return $user->id > 500; - }); +$users = User::cursor()->filter(function (User $user) { + return $user->id > 500; +}); - foreach ($users as $user) { - echo $user->id; - } +foreach ($users as $user) { + echo $user->id; +} +``` ### Creating Lazy Collections To create a lazy collection instance, you should pass a PHP generator function to the collection's `make` method: - use Illuminate\Support\LazyCollection; +```php +use Illuminate\Support\LazyCollection; - LazyCollection::make(function () { - $handle = fopen('log.txt', 'r'); +LazyCollection::make(function () { + $handle = fopen('log.txt', 'r'); - while (($line = fgets($handle)) !== false) { - yield $line; - } - }); + while (($line = fgets($handle)) !== false) { + yield $line; + } +}); +``` ### The Enumerable Contract @@ -3768,48 +4192,54 @@ In addition to the methods defined in the `Enumerable` contract, the `LazyCollec The `takeUntilTimeout` method returns a new lazy collection that will enumerate values until the specified time. After that time, the collection will then stop enumerating: - $lazyCollection = LazyCollection::times(INF) - ->takeUntilTimeout(now()->addMinute()); +```php +$lazyCollection = LazyCollection::times(INF) + ->takeUntilTimeout(now()->addMinute()); - $lazyCollection->each(function (int $number) { - dump($number); +$lazyCollection->each(function (int $number) { + dump($number); - sleep(1); - }); + sleep(1); +}); - // 1 - // 2 - // ... - // 58 - // 59 +// 1 +// 2 +// ... +// 58 +// 59 +``` To illustrate the usage of this method, imagine an application that submits invoices from the database using a cursor. You could define a [scheduled task](/docs/{{version}}/scheduling) that runs every 15 minutes and only processes invoices for a maximum of 14 minutes: - use App\Models\Invoice; - use Illuminate\Support\Carbon; - - Invoice::pending()->cursor() - ->takeUntilTimeout( - Carbon::createFromTimestamp(LARAVEL_START)->add(14, 'minutes') - ) - ->each(fn (Invoice $invoice) => $invoice->submit()); +```php +use App\Models\Invoice; +use Illuminate\Support\Carbon; + +Invoice::pending()->cursor() + ->takeUntilTimeout( + Carbon::createFromTimestamp(LARAVEL_START)->add(14, 'minutes') + ) + ->each(fn (Invoice $invoice) => $invoice->submit()); +``` #### `tapEach()` {.collection-method} While the `each` method calls the given callback for each item in the collection right away, the `tapEach` method only calls the given callback as the items are being pulled out of the list one by one: - // Nothing has been dumped so far... - $lazyCollection = LazyCollection::times(INF)->tapEach(function (int $value) { - dump($value); - }); +```php +// Nothing has been dumped so far... +$lazyCollection = LazyCollection::times(INF)->tapEach(function (int $value) { + dump($value); +}); - // Three items are dumped... - $array = $lazyCollection->take(3)->all(); +// Three items are dumped... +$array = $lazyCollection->take(3)->all(); - // 1 - // 2 - // 3 +// 1 +// 2 +// 3 +``` #### `throttle()` {.collection-method} @@ -3832,13 +4262,15 @@ User::where('vip', true) The `remember` method returns a new lazy collection that will remember any values that have already been enumerated and will not retrieve them again on subsequent collection enumerations: - // No query has been executed yet... - $users = User::cursor()->remember(); +```php +// No query has been executed yet... +$users = User::cursor()->remember(); - // The query is executed... - // The first 5 users are hydrated from the database... - $users->take(5)->all(); +// The query is executed... +// The first 5 users are hydrated from the database... +$users->take(5)->all(); - // First 5 users come from the collection's cache... - // The rest are hydrated from the database... - $users->take(20)->all(); +// First 5 users come from the collection's cache... +// The rest are hydrated from the database... +$users->take(20)->all(); +``` diff --git a/configuration.md b/configuration.md index e5ba140778b..17a20e712b3 100644 --- a/configuration.md +++ b/configuration.md @@ -97,7 +97,9 @@ APP_NAME="My Application" All of the variables listed in the `.env` file will be loaded into the `$_ENV` PHP super-global when your application receives a request. However, you may use the `env` function to retrieve values from these variables in your configuration files. In fact, if you review the Laravel configuration files, you will notice many of the options are already using this function: - 'debug' => env('APP_DEBUG', false), +```php +'debug' => env('APP_DEBUG', false), +``` The second value passed to the `env` function is the "default value". This value will be returned if no environment variable exists for the given key. @@ -106,19 +108,23 @@ The second value passed to the `env` function is the "default value". This value The current application environment is determined via the `APP_ENV` variable from your `.env` file. You may access this value via the `environment` method on the `App` [facade](/docs/{{version}}/facades): - use Illuminate\Support\Facades\App; +```php +use Illuminate\Support\Facades\App; - $environment = App::environment(); +$environment = App::environment(); +``` You may also pass arguments to the `environment` method to determine if the environment matches a given value. The method will return `true` if the environment matches any of the given values: - if (App::environment('local')) { - // The environment is local - } +```php +if (App::environment('local')) { + // The environment is local +} - if (App::environment(['local', 'staging'])) { - // The environment is either local OR staging... - } +if (App::environment(['local', 'staging'])) { + // The environment is either local OR staging... +} +``` > [!NOTE] > The current application environment detection can be overridden by defining a server-level `APP_ENV` environment variable. @@ -192,28 +198,34 @@ php artisan env:decrypt --force You may easily access your configuration values using the `Config` facade or global `config` function from anywhere in your application. The configuration values may be accessed using "dot" syntax, which includes the name of the file and option you wish to access. A default value may also be specified and will be returned if the configuration option does not exist: - use Illuminate\Support\Facades\Config; +```php +use Illuminate\Support\Facades\Config; - $value = Config::get('app.timezone'); +$value = Config::get('app.timezone'); - $value = config('app.timezone'); +$value = config('app.timezone'); - // Retrieve a default value if the configuration value does not exist... - $value = config('app.timezone', 'Asia/Seoul'); +// Retrieve a default value if the configuration value does not exist... +$value = config('app.timezone', 'Asia/Seoul'); +``` To set configuration values at runtime, you may invoke the `Config` facade's `set` method or pass an array to the `config` function: - Config::set('app.timezone', 'America/Chicago'); +```php +Config::set('app.timezone', 'America/Chicago'); - config(['app.timezone' => 'America/Chicago']); +config(['app.timezone' => 'America/Chicago']); +``` To assist with static analysis, the `Config` facade also provides typed configuration retrieval methods. If the retrieved configuration value does not match the expected type, an exception will be thrown: - Config::string('config-key'); - Config::integer('config-key'); - Config::float('config-key'); - Config::boolean('config-key'); - Config::array('config-key'); +```php +Config::string('config-key'); +Config::integer('config-key'); +Config::float('config-key'); +Config::boolean('config-key'); +Config::array('config-key'); +``` ## Configuration Caching diff --git a/console-tests.md b/console-tests.md index 6c4bb9863ff..03b665ed7c4 100644 --- a/console-tests.md +++ b/console-tests.md @@ -33,30 +33,36 @@ public function test_console_command(): void You may use the `assertNotExitCode` method to assert that the command did not exit with a given exit code: - $this->artisan('inspire')->assertNotExitCode(1); +```php +$this->artisan('inspire')->assertNotExitCode(1); +``` Of course, all terminal commands typically exit with a status code of `0` when they are successful and a non-zero exit code when they are not successful. Therefore, for convenience, you may utilize the `assertSuccessful` and `assertFailed` assertions to assert that a given command exited with a successful exit code or not: - $this->artisan('inspire')->assertSuccessful(); +```php +$this->artisan('inspire')->assertSuccessful(); - $this->artisan('inspire')->assertFailed(); +$this->artisan('inspire')->assertFailed(); +``` ## Input / Output Expectations Laravel allows you to easily "mock" user input for your console commands using the `expectsQuestion` method. In addition, you may specify the exit code and text that you expect to be output by the console command using the `assertExitCode` and `expectsOutput` methods. For example, consider the following console command: - Artisan::command('question', function () { - $name = $this->ask('What is your name?'); +```php +Artisan::command('question', function () { + $name = $this->ask('What is your name?'); - $language = $this->choice('Which language do you prefer?', [ - 'PHP', - 'Ruby', - 'Python', - ]); + $language = $this->choice('Which language do you prefer?', [ + 'PHP', + 'Ruby', + 'Python', + ]); - $this->line('Your name is '.$name.' and you prefer '.$language.'.'); - }); + $this->line('Your name is '.$name.' and you prefer '.$language.'.'); +}); +``` You may test this command with the following test: @@ -165,23 +171,27 @@ public function test_console_command(): void When writing a command which expects confirmation in the form of a "yes" or "no" answer, you may utilize the `expectsConfirmation` method: - $this->artisan('module:import') - ->expectsConfirmation('Do you really wish to run this command?', 'no') - ->assertExitCode(1); +```php +$this->artisan('module:import') + ->expectsConfirmation('Do you really wish to run this command?', 'no') + ->assertExitCode(1); +``` #### Table Expectations If your command displays a table of information using Artisan's `table` method, it can be cumbersome to write output expectations for the entire table. Instead, you may use the `expectsTable` method. This method accepts the table's headers as its first argument and the table's data as its second argument: - $this->artisan('users:all') - ->expectsTable([ - 'ID', - 'Email', - ], [ - [1, 'taylor@example.com'], - [2, 'abigail@example.com'], - ]); +```php +$this->artisan('users:all') + ->expectsTable([ + 'ID', + 'Email', + ], [ + [1, 'taylor@example.com'], + [2, 'abigail@example.com'], + ]); +``` ## Console Events diff --git a/container.md b/container.md index 9e809664bf8..a7d0c0356bd 100644 --- a/container.md +++ b/container.md @@ -27,32 +27,34 @@ The Laravel service container is a powerful tool for managing class dependencies Let's look at a simple example: - $this->apple->findPodcast($id) - ]); - } + return view('podcasts.show', [ + 'podcast' => $this->apple->findPodcast($id) + ]); } +} +``` In this example, the `PodcastController` needs to retrieve podcasts from a data source such as Apple Music. So, we will **inject** a service that is able to retrieve podcasts. Since the service is injected, we are able to easily "mock", or create a dummy implementation of the `AppleMusic` service when testing our application. @@ -63,16 +65,18 @@ A deep understanding of the Laravel service container is essential to building a If a class has no dependencies or only depends on other concrete classes (not interfaces), the container does not need to be instructed on how to resolve that class. For example, you may place the following code in your `routes/web.php` file: - app` property. We can register a binding using the `bind` method, passing the class or interface name that we wish to register along with a closure that returns an instance of the class: - use App\Services\Transistor; - use App\Services\PodcastParser; - use Illuminate\Contracts\Foundation\Application; +```php +use App\Services\Transistor; +use App\Services\PodcastParser; +use Illuminate\Contracts\Foundation\Application; - $this->app->bind(Transistor::class, function (Application $app) { - return new Transistor($app->make(PodcastParser::class)); - }); +$this->app->bind(Transistor::class, function (Application $app) { + return new Transistor($app->make(PodcastParser::class)); +}); +``` Note that we receive the container itself as an argument to the resolver. We can then use the container to resolve sub-dependencies of the object we are building. As mentioned, you will typically be interacting with the container within service providers; however, if you would like to interact with the container outside of a service provider, you may do so via the `App` [facade](/docs/{{version}}/facades): - use App\Services\Transistor; - use Illuminate\Contracts\Foundation\Application; - use Illuminate\Support\Facades\App; +```php +use App\Services\Transistor; +use Illuminate\Contracts\Foundation\Application; +use Illuminate\Support\Facades\App; - App::bind(Transistor::class, function (Application $app) { - // ... - }); +App::bind(Transistor::class, function (Application $app) { + // ... +}); +``` You may use the `bindIf` method to register a container binding only if a binding has not already been registered for the given type: @@ -142,13 +152,15 @@ $this->app->bindIf(Transistor::class, function (Application $app) { The `singleton` method binds a class or interface into the container that should only be resolved one time. Once a singleton binding is resolved, the same object instance will be returned on subsequent calls into the container: - use App\Services\Transistor; - use App\Services\PodcastParser; - use Illuminate\Contracts\Foundation\Application; +```php +use App\Services\Transistor; +use App\Services\PodcastParser; +use Illuminate\Contracts\Foundation\Application; - $this->app->singleton(Transistor::class, function (Application $app) { - return new Transistor($app->make(PodcastParser::class)); - }); +$this->app->singleton(Transistor::class, function (Application $app) { + return new Transistor($app->make(PodcastParser::class)); +}); +``` You may use the `singletonIf` method to register a singleton container binding only if a binding has not already been registered for the given type: @@ -163,75 +175,87 @@ $this->app->singletonIf(Transistor::class, function (Application $app) { The `scoped` method binds a class or interface into the container that should only be resolved one time within a given Laravel request / job lifecycle. While this method is similar to the `singleton` method, instances registered using the `scoped` method will be flushed whenever the Laravel application starts a new "lifecycle", such as when a [Laravel Octane](/docs/{{version}}/octane) worker processes a new request or when a Laravel [queue worker](/docs/{{version}}/queues) processes a new job: - use App\Services\Transistor; - use App\Services\PodcastParser; - use Illuminate\Contracts\Foundation\Application; +```php +use App\Services\Transistor; +use App\Services\PodcastParser; +use Illuminate\Contracts\Foundation\Application; - $this->app->scoped(Transistor::class, function (Application $app) { - return new Transistor($app->make(PodcastParser::class)); - }); +$this->app->scoped(Transistor::class, function (Application $app) { + return new Transistor($app->make(PodcastParser::class)); +}); +``` You may use the `scopedIf` method to register a scoped container binding only if a binding has not already been registered for the given type: - $this->app->scopedIf(Transistor::class, function (Application $app) { - return new Transistor($app->make(PodcastParser::class)); - }); +```php +$this->app->scopedIf(Transistor::class, function (Application $app) { + return new Transistor($app->make(PodcastParser::class)); +}); +``` #### Binding Instances You may also bind an existing object instance into the container using the `instance` method. The given instance will always be returned on subsequent calls into the container: - use App\Services\Transistor; - use App\Services\PodcastParser; +```php +use App\Services\Transistor; +use App\Services\PodcastParser; - $service = new Transistor(new PodcastParser); +$service = new Transistor(new PodcastParser); - $this->app->instance(Transistor::class, $service); +$this->app->instance(Transistor::class, $service); +``` ### Binding Interfaces to Implementations A very powerful feature of the service container is its ability to bind an interface to a given implementation. For example, let's assume we have an `EventPusher` interface and a `RedisEventPusher` implementation. Once we have coded our `RedisEventPusher` implementation of this interface, we can register it with the service container like so: - use App\Contracts\EventPusher; - use App\Services\RedisEventPusher; +```php +use App\Contracts\EventPusher; +use App\Services\RedisEventPusher; - $this->app->bind(EventPusher::class, RedisEventPusher::class); +$this->app->bind(EventPusher::class, RedisEventPusher::class); +``` This statement tells the container that it should inject the `RedisEventPusher` when a class needs an implementation of `EventPusher`. Now we can type-hint the `EventPusher` interface in the constructor of a class that is resolved by the container. Remember, controllers, event listeners, middleware, and various other types of classes within Laravel applications are always resolved using the container: - use App\Contracts\EventPusher; - - /** - * Create a new class instance. - */ - public function __construct( - protected EventPusher $pusher, - ) {} +```php +use App\Contracts\EventPusher; + +/** + * Create a new class instance. + */ +public function __construct( + protected EventPusher $pusher, +) {} +``` ### Contextual Binding Sometimes you may have two classes that utilize the same interface, but you wish to inject different implementations into each class. For example, two controllers may depend on different implementations of the `Illuminate\Contracts\Filesystem\Filesystem` [contract](/docs/{{version}}/contracts). Laravel provides a simple, fluent interface for defining this behavior: - use App\Http\Controllers\PhotoController; - use App\Http\Controllers\UploadController; - use App\Http\Controllers\VideoController; - use Illuminate\Contracts\Filesystem\Filesystem; - use Illuminate\Support\Facades\Storage; +```php +use App\Http\Controllers\PhotoController; +use App\Http\Controllers\UploadController; +use App\Http\Controllers\VideoController; +use Illuminate\Contracts\Filesystem\Filesystem; +use Illuminate\Support\Facades\Storage; - $this->app->when(PhotoController::class) - ->needs(Filesystem::class) - ->give(function () { - return Storage::disk('local'); - }); +$this->app->when(PhotoController::class) + ->needs(Filesystem::class) + ->give(function () { + return Storage::disk('local'); + }); - $this->app->when([VideoController::class, UploadController::class]) - ->needs(Filesystem::class) - ->give(function () { - return Storage::disk('s3'); - }); +$this->app->when([VideoController::class, UploadController::class]) + ->needs(Filesystem::class) + ->give(function () { + return Storage::disk('s3'); + }); +``` ### Contextual Attributes @@ -312,148 +336,170 @@ Route::get('/user', function (#[CurrentUser] User $user) { You can create your own contextual attributes by implementing the `Illuminate\Contracts\Container\ContextualAttribute` contract. The container will call your attribute's `resolve` method, which should resolve the value that should be injected into the class utilizing the attribute. In the example below, we will re-implement Laravel's built-in `Config` attribute: - make('config')->get($attribute->key, $attribute->default); - } + return $container->make('config')->get($attribute->key, $attribute->default); } +} +``` ### Binding Primitives Sometimes you may have a class that receives some injected classes, but also needs an injected primitive value such as an integer. You may easily use contextual binding to inject any value your class may need: - use App\Http\Controllers\UserController; +```php +use App\Http\Controllers\UserController; - $this->app->when(UserController::class) - ->needs('$variableName') - ->give($value); +$this->app->when(UserController::class) + ->needs('$variableName') + ->give($value); +``` Sometimes a class may depend on an array of [tagged](#tagging) instances. Using the `giveTagged` method, you may easily inject all of the container bindings with that tag: - $this->app->when(ReportAggregator::class) - ->needs('$reports') - ->giveTagged('reports'); +```php +$this->app->when(ReportAggregator::class) + ->needs('$reports') + ->giveTagged('reports'); +``` If you need to inject a value from one of your application's configuration files, you may use the `giveConfig` method: - $this->app->when(ReportAggregator::class) - ->needs('$timezone') - ->giveConfig('app.timezone'); +```php +$this->app->when(ReportAggregator::class) + ->needs('$timezone') + ->giveConfig('app.timezone'); +``` ### Binding Typed Variadics Occasionally, you may have a class that receives an array of typed objects using a variadic constructor argument: - filters = $filters; - } +class Firewall +{ + /** + * The filter instances. + * + * @var array + */ + protected $filters; + + /** + * Create a new class instance. + */ + public function __construct( + protected Logger $logger, + Filter ...$filters, + ) { + $this->filters = $filters; } +} +``` Using contextual binding, you may resolve this dependency by providing the `give` method with a closure that returns an array of resolved `Filter` instances: - $this->app->when(Firewall::class) - ->needs(Filter::class) - ->give(function (Application $app) { - return [ - $app->make(NullFilter::class), - $app->make(ProfanityFilter::class), - $app->make(TooLongFilter::class), - ]; - }); +```php +$this->app->when(Firewall::class) + ->needs(Filter::class) + ->give(function (Application $app) { + return [ + $app->make(NullFilter::class), + $app->make(ProfanityFilter::class), + $app->make(TooLongFilter::class), + ]; + }); +``` For convenience, you may also just provide an array of class names to be resolved by the container whenever `Firewall` needs `Filter` instances: - $this->app->when(Firewall::class) - ->needs(Filter::class) - ->give([ - NullFilter::class, - ProfanityFilter::class, - TooLongFilter::class, - ]); +```php +$this->app->when(Firewall::class) + ->needs(Filter::class) + ->give([ + NullFilter::class, + ProfanityFilter::class, + TooLongFilter::class, + ]); +``` #### Variadic Tag Dependencies Sometimes a class may have a variadic dependency that is type-hinted as a given class (`Report ...$reports`). Using the `needs` and `giveTagged` methods, you may easily inject all of the container bindings with that [tag](#tagging) for the given dependency: - $this->app->when(ReportAggregator::class) - ->needs(Report::class) - ->giveTagged('reports'); +```php +$this->app->when(ReportAggregator::class) + ->needs(Report::class) + ->giveTagged('reports'); +``` ### Tagging Occasionally, you may need to resolve all of a certain "category" of binding. For example, perhaps you are building a report analyzer that receives an array of many different `Report` interface implementations. After registering the `Report` implementations, you can assign them a tag using the `tag` method: - $this->app->bind(CpuReport::class, function () { - // ... - }); +```php +$this->app->bind(CpuReport::class, function () { + // ... +}); - $this->app->bind(MemoryReport::class, function () { - // ... - }); +$this->app->bind(MemoryReport::class, function () { + // ... +}); - $this->app->tag([CpuReport::class, MemoryReport::class], 'reports'); +$this->app->tag([CpuReport::class, MemoryReport::class], 'reports'); +``` Once the services have been tagged, you may easily resolve them all via the container's `tagged` method: - $this->app->bind(ReportAnalyzer::class, function (Application $app) { - return new ReportAnalyzer($app->tagged('reports')); - }); +```php +$this->app->bind(ReportAnalyzer::class, function (Application $app) { + return new ReportAnalyzer($app->tagged('reports')); +}); +``` ### Extending Bindings The `extend` method allows the modification of resolved services. For example, when a service is resolved, you may run additional code to decorate or configure the service. The `extend` method accepts two arguments, the service class you're extending and a closure that should return the modified service. The closure receives the service being resolved and the container instance: - $this->app->extend(Service::class, function (Service $service, Application $app) { - return new DecoratedService($service); - }); +```php +$this->app->extend(Service::class, function (Service $service, Application $app) { + return new DecoratedService($service); +}); +``` ## Resolving @@ -463,41 +509,51 @@ The `extend` method allows the modification of resolved services. For example, w You may use the `make` method to resolve a class instance from the container. The `make` method accepts the name of the class or interface you wish to resolve: - use App\Services\Transistor; +```php +use App\Services\Transistor; - $transistor = $this->app->make(Transistor::class); +$transistor = $this->app->make(Transistor::class); +``` If some of your class's dependencies are not resolvable via the container, you may inject them by passing them as an associative array into the `makeWith` method. For example, we may manually pass the `$id` constructor argument required by the `Transistor` service: - use App\Services\Transistor; +```php +use App\Services\Transistor; - $transistor = $this->app->makeWith(Transistor::class, ['id' => 1]); +$transistor = $this->app->makeWith(Transistor::class, ['id' => 1]); +``` The `bound` method may be used to determine if a class or interface has been explicitly bound in the container: - if ($this->app->bound(Transistor::class)) { - // ... - } +```php +if ($this->app->bound(Transistor::class)) { + // ... +} +``` If you are outside of a service provider in a location of your code that does not have access to the `$app` variable, you may use the `App` [facade](/docs/{{version}}/facades) or the `app` [helper](/docs/{{version}}/helpers#method-app) to resolve a class instance from the container: - use App\Services\Transistor; - use Illuminate\Support\Facades\App; +```php +use App\Services\Transistor; +use Illuminate\Support\Facades\App; - $transistor = App::make(Transistor::class); +$transistor = App::make(Transistor::class); - $transistor = app(Transistor::class); +$transistor = app(Transistor::class); +``` If you would like to have the Laravel container instance itself injected into a class that is being resolved by the container, you may type-hint the `Illuminate\Container\Container` class on your class's constructor: - use Illuminate\Container\Container; - - /** - * Create a new class instance. - */ - public function __construct( - protected Container $container, - ) {} +```php +use Illuminate\Container\Container; + +/** + * Create a new class instance. + */ +public function __construct( + protected Container $container, +) {} +``` ### Automatic Injection @@ -506,85 +562,95 @@ Alternatively, and importantly, you may type-hint the dependency in the construc For example, you may type-hint a service defined by your application in a controller's constructor. The service will automatically be resolved and injected into the class: - apple->findPodcast($id); - } + return $this->apple->findPodcast($id); } +} +``` ## Method Invocation and Injection Sometimes you may wish to invoke a method on an object instance while allowing the container to automatically inject that method's dependencies. For example, given the following class: - ## Container Events The service container fires an event each time it resolves an object. You may listen to this event using the `resolving` method: - use App\Services\Transistor; - use Illuminate\Contracts\Foundation\Application; +```php +use App\Services\Transistor; +use Illuminate\Contracts\Foundation\Application; - $this->app->resolving(Transistor::class, function (Transistor $transistor, Application $app) { - // Called when container resolves objects of type "Transistor"... - }); +$this->app->resolving(Transistor::class, function (Transistor $transistor, Application $app) { + // Called when container resolves objects of type "Transistor"... +}); - $this->app->resolving(function (mixed $object, Application $app) { - // Called when container resolves object of any type... - }); +$this->app->resolving(function (mixed $object, Application $app) { + // Called when container resolves object of any type... +}); +``` As you can see, the object being resolved will be passed to the callback, allowing you to set any additional properties on the object before it is given to its consumer. @@ -593,35 +659,39 @@ As you can see, the object being resolved will be passed to the callback, allowi The `rebinding` method allows you to listen for when a service is re-bound to the container, meaning it is registered again or overridden after its initial binding. This can be useful when you need to update dependencies or modify behavior each time a specific binding is updated: - use App\Contracts\PodcastPublisher; - use App\Services\SpotifyPublisher; - use App\Services\TransistorPublisher; - use Illuminate\Contracts\Foundation\Application; - - $this->app->bind(PodcastPublisher::class, SpotifyPublisher::class); - - $this->app->rebinding( - PodcastPublisher::class, - function (Application $app, PodcastPublisher $newInstance) { - // - }, - ); - - // New binding will trigger rebinding closure... - $this->app->bind(PodcastPublisher::class, TransistorPublisher::class); +```php +use App\Contracts\PodcastPublisher; +use App\Services\SpotifyPublisher; +use App\Services\TransistorPublisher; +use Illuminate\Contracts\Foundation\Application; + +$this->app->bind(PodcastPublisher::class, SpotifyPublisher::class); + +$this->app->rebinding( + PodcastPublisher::class, + function (Application $app, PodcastPublisher $newInstance) { + // + }, +); + +// New binding will trigger rebinding closure... +$this->app->bind(PodcastPublisher::class, TransistorPublisher::class); +``` ## PSR-11 Laravel's service container implements the [PSR-11](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-11-container.md) interface. Therefore, you may type-hint the PSR-11 container interface to obtain an instance of the Laravel container: - use App\Services\Transistor; - use Psr\Container\ContainerInterface; +```php +use App\Services\Transistor; +use Psr\Container\ContainerInterface; - Route::get('/', function (ContainerInterface $container) { - $service = $container->get(Transistor::class); +Route::get('/', function (ContainerInterface $container) { + $service = $container->get(Transistor::class); - // ... - }); + // ... +}); +``` An exception is thrown if the given identifier can't be resolved. The exception will be an instance of `Psr\Container\NotFoundExceptionInterface` if the identifier was never bound. If the identifier was bound but was unable to be resolved, an instance of `Psr\Container\ContainerExceptionInterface` will be thrown. diff --git a/contracts.md b/contracts.md index f8c1884f74a..e157677ad57 100644 --- a/contracts.md +++ b/contracts.md @@ -38,31 +38,33 @@ Many types of classes in Laravel are resolved through the [service container](/d For example, take a look at this event listener: - - */ - public function attachments(): array - { - return [ - Attachment::fromStorage('/path/to/file'), - ]; - } +```php +/** + * Get the attachments for the message. + * + * @return array + */ +public function attachments(): array +{ + return [ + Attachment::fromStorage('/path/to/file'), + ]; +} +``` ### StyleCI diff --git a/controllers.md b/controllers.md index cab0c88c16d..6c42ab10b66 100644 --- a/controllers.md +++ b/controllers.md @@ -35,31 +35,35 @@ php artisan make:controller UserController Let's take a look at an example of a basic controller. A controller may have any number of public methods which will respond to incoming HTTP requests: - User::findOrFail($id) - ]); - } + return view('user.profile', [ + 'user' => User::findOrFail($id) + ]); } +} +``` Once you have written a controller class and method, you may define a route to the controller method like so: - use App\Http\Controllers\UserController; +```php +use App\Http\Controllers\UserController; - Route::get('/user/{id}', [UserController::class, 'show']); +Route::get('/user/{id}', [UserController::class, 'show']); +``` When an incoming request matches the specified route URI, the `show` method on the `App\Http\Controllers\UserController` class will be invoked and the route parameters will be passed to the method. @@ -71,26 +75,30 @@ When an incoming request matches the specified route URI, the `show` method on t If a controller action is particularly complex, you might find it convenient to dedicate an entire controller class to that single action. To accomplish this, you may define a single `__invoke` method within the controller: - middleware('auth'); +```php +Route::get('/profile', [UserController::class, 'show'])->middleware('auth'); +``` Or, you may find it convenient to specify middleware within your controller class. To do so, your controller should implement the `HasMiddleware` interface, which dictates that the controller should have a static `middleware` method. From this method, you may return an array of middleware that should be applied to the controller's actions: - [!WARNING] > Controllers implementing `Illuminate\Routing\Controllers\HasMiddleware` should not extend `Illuminate\Routing\Controller`. @@ -168,18 +182,22 @@ php artisan make:controller PhotoController --resource This command will generate a controller at `app/Http/Controllers/PhotoController.php`. The controller will contain a method for each of the available resource operations. Next, you may register a resource route that points to the controller: - use App\Http\Controllers\PhotoController; +```php +use App\Http\Controllers\PhotoController; - Route::resource('photos', PhotoController::class); +Route::resource('photos', PhotoController::class); +``` This single route declaration creates multiple routes to handle a variety of actions on the resource. The generated controller will already have methods stubbed for each of these actions. Remember, you can always get a quick overview of your application's routes by running the `route:list` Artisan command. You may even register many resource controllers at once by passing an array to the `resources` method: - Route::resources([ - 'photos' => PhotoController::class, - 'posts' => PostController::class, - ]); +```php +Route::resources([ + 'photos' => PhotoController::class, + 'posts' => PostController::class, +]); +``` #### Actions Handled by Resource Controllers @@ -203,27 +221,33 @@ You may even register many resource controllers at once by passing an array to t Typically, a 404 HTTP response will be generated if an implicitly bound resource model is not found. However, you may customize this behavior by calling the `missing` method when defining your resource route. The `missing` method accepts a closure that will be invoked if an implicitly bound model cannot be found for any of the resource's routes: - use App\Http\Controllers\PhotoController; - use Illuminate\Http\Request; - use Illuminate\Support\Facades\Redirect; - - Route::resource('photos', PhotoController::class) - ->missing(function (Request $request) { - return Redirect::route('photos.index'); - }); +```php +use App\Http\Controllers\PhotoController; +use Illuminate\Http\Request; +use Illuminate\Support\Facades\Redirect; + +Route::resource('photos', PhotoController::class) + ->missing(function (Request $request) { + return Redirect::route('photos.index'); + }); +``` #### Soft Deleted Models Typically, implicit model binding will not retrieve models that have been [soft deleted](/docs/{{version}}/eloquent#soft-deleting), and will instead return a 404 HTTP response. However, you can instruct the framework to allow soft deleted models by invoking the `withTrashed` method when defining your resource route: - use App\Http\Controllers\PhotoController; +```php +use App\Http\Controllers\PhotoController; - Route::resource('photos', PhotoController::class)->withTrashed(); +Route::resource('photos', PhotoController::class)->withTrashed(); +``` Calling `withTrashed` with no arguments will allow soft deleted models for the `show`, `edit`, and `update` resource routes. You may specify a subset of these routes by passing an array to the `withTrashed` method: - Route::resource('photos', PhotoController::class)->withTrashed(['show']); +```php +Route::resource('photos', PhotoController::class)->withTrashed(['show']); +``` #### Specifying the Resource Model @@ -248,34 +272,40 @@ php artisan make:controller PhotoController --model=Photo --resource --requests When declaring a resource route, you may specify a subset of actions the controller should handle instead of the full set of default actions: - use App\Http\Controllers\PhotoController; +```php +use App\Http\Controllers\PhotoController; - Route::resource('photos', PhotoController::class)->only([ - 'index', 'show' - ]); +Route::resource('photos', PhotoController::class)->only([ + 'index', 'show' +]); - Route::resource('photos', PhotoController::class)->except([ - 'create', 'store', 'update', 'destroy' - ]); +Route::resource('photos', PhotoController::class)->except([ + 'create', 'store', 'update', 'destroy' +]); +``` #### API Resource Routes When declaring resource routes that will be consumed by APIs, you will commonly want to exclude routes that present HTML templates such as `create` and `edit`. For convenience, you may use the `apiResource` method to automatically exclude these two routes: - use App\Http\Controllers\PhotoController; +```php +use App\Http\Controllers\PhotoController; - Route::apiResource('photos', PhotoController::class); +Route::apiResource('photos', PhotoController::class); +``` You may register many API resource controllers at once by passing an array to the `apiResources` method: - use App\Http\Controllers\PhotoController; - use App\Http\Controllers\PostController; +```php +use App\Http\Controllers\PhotoController; +use App\Http\Controllers\PostController; - Route::apiResources([ - 'photos' => PhotoController::class, - 'posts' => PostController::class, - ]); +Route::apiResources([ + 'photos' => PhotoController::class, + 'posts' => PostController::class, +]); +``` To quickly generate an API resource controller that does not include the `create` or `edit` methods, use the `--api` switch when executing the `make:controller` command: @@ -288,13 +318,17 @@ php artisan make:controller PhotoController --api Sometimes you may need to define routes to a nested resource. For example, a photo resource may have multiple comments that may be attached to the photo. To nest the resource controllers, you may use "dot" notation in your route declaration: - use App\Http\Controllers\PhotoCommentController; +```php +use App\Http\Controllers\PhotoCommentController; - Route::resource('photos.comments', PhotoCommentController::class); +Route::resource('photos.comments', PhotoCommentController::class); +``` This route will register a nested resource that may be accessed with URIs like the following: - /photos/{photo}/comments/{comment} +```text +/photos/{photo}/comments/{comment} +``` #### Scoping Nested Resources @@ -306,9 +340,11 @@ Laravel's [implicit model binding](/docs/{{version}}/routing#implicit-model-bind Often, it is not entirely necessary to have both the parent and the child IDs within a URI since the child ID is already a unique identifier. When using unique identifiers such as auto-incrementing primary keys to identify your models in URI segments, you may choose to use "shallow nesting": - use App\Http\Controllers\CommentController; +```php +use App\Http\Controllers\CommentController; - Route::resource('photos.comments', CommentController::class)->shallow(); +Route::resource('photos.comments', CommentController::class)->shallow(); +``` This route definition will define the following routes: @@ -331,41 +367,51 @@ This route definition will define the following routes: By default, all resource controller actions have a route name; however, you can override these names by passing a `names` array with your desired route names: - use App\Http\Controllers\PhotoController; +```php +use App\Http\Controllers\PhotoController; - Route::resource('photos', PhotoController::class)->names([ - 'create' => 'photos.build' - ]); +Route::resource('photos', PhotoController::class)->names([ + 'create' => 'photos.build' +]); +``` ### Naming Resource Route Parameters By default, `Route::resource` will create the route parameters for your resource routes based on the "singularized" version of the resource name. You can easily override this on a per resource basis using the `parameters` method. The array passed into the `parameters` method should be an associative array of resource names and parameter names: - use App\Http\Controllers\AdminUserController; +```php +use App\Http\Controllers\AdminUserController; - Route::resource('users', AdminUserController::class)->parameters([ - 'users' => 'admin_user' - ]); +Route::resource('users', AdminUserController::class)->parameters([ + 'users' => 'admin_user' +]); +``` The example above generates the following URI for the resource's `show` route: - /users/{admin_user} +```text +/users/{admin_user} +``` ### Scoping Resource Routes Laravel's [scoped implicit model binding](/docs/{{version}}/routing#implicit-model-binding-scoping) feature can automatically scope nested bindings such that the resolved child model is confirmed to belong to the parent model. By using the `scoped` method when defining your nested resource, you may enable automatic scoping as well as instruct Laravel which field the child resource should be retrieved by: - use App\Http\Controllers\PhotoCommentController; +```php +use App\Http\Controllers\PhotoCommentController; - Route::resource('photos.comments', PhotoCommentController::class)->scoped([ - 'comment' => 'slug', - ]); +Route::resource('photos.comments', PhotoCommentController::class)->scoped([ + 'comment' => 'slug', +]); +``` This route will register a scoped nested resource that may be accessed with URIs like the following: - /photos/{photo}/comments/{comment:slug} +```text +/photos/{photo}/comments/{comment:slug} +``` When using a custom keyed implicit binding as a nested route parameter, Laravel will automatically scope the query to retrieve the nested model by its parent using conventions to guess the relationship name on the parent. In this case, it will be assumed that the `Photo` model has a relationship named `comments` (the plural of the route parameter name) which can be used to retrieve the `Comment` model. @@ -374,32 +420,38 @@ When using a custom keyed implicit binding as a nested route parameter, Laravel By default, `Route::resource` will create resource URIs using English verbs and plural rules. If you need to localize the `create` and `edit` action verbs, you may use the `Route::resourceVerbs` method. This may be done at the beginning of the `boot` method within your application's `App\Providers\AppServiceProvider`: - /** - * Bootstrap any application services. - */ - public function boot(): void - { - Route::resourceVerbs([ - 'create' => 'crear', - 'edit' => 'editar', - ]); - } +```php +/** + * Bootstrap any application services. + */ +public function boot(): void +{ + Route::resourceVerbs([ + 'create' => 'crear', + 'edit' => 'editar', + ]); +} +``` Laravel's pluralizer supports [several different languages which you may configure based on your needs](/docs/{{version}}/localization#pluralization-language). Once the verbs and pluralization language have been customized, a resource route registration such as `Route::resource('publicacion', PublicacionController::class)` will produce the following URIs: - /publicacion/crear +```text +/publicacion/crear - /publicacion/{publicaciones}/editar +/publicacion/{publicaciones}/editar +``` ### Supplementing Resource Controllers If you need to add additional routes to a resource controller beyond the default set of resource routes, you should define those routes before your call to the `Route::resource` method; otherwise, the routes defined by the `resource` method may unintentionally take precedence over your supplemental routes: - use App\Http\Controller\PhotoController; +```php +use App\Http\Controller\PhotoController; - Route::get('/photos/popular', [PhotoController::class, 'popular']); - Route::resource('photos', PhotoController::class); +Route::get('/photos/popular', [PhotoController::class, 'popular']); +Route::resource('photos', PhotoController::class); +``` > [!NOTE] > Remember to keep your controllers focused. If you find yourself routinely needing methods outside of the typical set of resource actions, consider splitting your controller into two, smaller controllers. @@ -499,73 +551,81 @@ Route::apiSingleton('photos.thumbnail', ProfileController::class)->creatable(); The Laravel [service container](/docs/{{version}}/container) is used to resolve all Laravel controllers. As a result, you are able to type-hint any dependencies your controller may need in its constructor. The declared dependencies will automatically be resolved and injected into the controller instance: - #### Method Injection In addition to constructor injection, you may also type-hint dependencies on your controller's methods. A common use-case for method injection is injecting the `Illuminate\Http\Request` instance into your controller methods: - name; + $name = $request->name; - // Store the user... + // Store the user... - return redirect('/users'); - } + return redirect('/users'); } +} +``` If your controller method is also expecting input from a route parameter, list your route arguments after your other dependencies. For example, if your route is defined like so: - use App\Http\Controllers\UserController; +```php +use App\Http\Controllers\UserController; - Route::put('/user/{id}', [UserController::class, 'update']); +Route::put('/user/{id}', [UserController::class, 'update']); +``` You may still type-hint the `Illuminate\Http\Request` and access your `id` parameter by defining your controller method as follows: - session()->token(); +Route::get('/token', function (Request $request) { + $token = $request->session()->token(); - $token = csrf_token(); + $token = csrf_token(); - // ... - }); + // ... +}); +``` Anytime you define a "POST", "PUT", "PATCH", or "DELETE" HTML form in your application, you should include a hidden CSRF `_token` field in the form so that the CSRF protection middleware can validate the request. For convenience, you may use the `@csrf` Blade directive to generate the hidden token input field: @@ -74,13 +76,15 @@ Sometimes you may wish to exclude a set of URIs from CSRF protection. For exampl Typically, you should place these kinds of routes outside of the `web` middleware group that Laravel applies to all routes in the `routes/web.php` file. However, you may also exclude specific routes by providing their URIs to the `validateCsrfTokens` method in your application's `bootstrap/app.php` file: - ->withMiddleware(function (Middleware $middleware) { - $middleware->validateCsrfTokens(except: [ - 'stripe/*', - 'http://example.com/foo/bar', - 'http://example.com/foo/*', - ]); - }) +```php +->withMiddleware(function (Middleware $middleware) { + $middleware->validateCsrfTokens(except: [ + 'stripe/*', + 'http://example.com/foo/bar', + 'http://example.com/foo/*', + ]); +}) +``` > [!NOTE] > For convenience, the CSRF middleware is automatically disabled for all routes when [running tests](/docs/{{version}}/testing). diff --git a/database-testing.md b/database-testing.md index 4545a81268e..e2f7bbaaec9 100644 --- a/database-testing.md +++ b/database-testing.md @@ -157,32 +157,36 @@ class ExampleTest extends TestCase Alternatively, you may instruct Laravel to automatically seed the database before each test that uses the `RefreshDatabase` trait. You may accomplish this by defining a `$seed` property on your base test class: - ## Available Assertions @@ -194,76 +198,94 @@ Laravel provides several database assertions for your [Pest](https://pestphp.com Assert that a table in the database contains the given number of records: - $this->assertDatabaseCount('users', 5); +```php +$this->assertDatabaseCount('users', 5); +``` #### assertDatabaseEmpty Assert that a table in the database contains no records: - $this->assertDatabaseEmpty('users'); +```php +$this->assertDatabaseEmpty('users'); +``` #### assertDatabaseHas Assert that a table in the database contains records matching the given key / value query constraints: - $this->assertDatabaseHas('users', [ - 'email' => 'sally@example.com', - ]); +```php +$this->assertDatabaseHas('users', [ + 'email' => 'sally@example.com', +]); +``` #### assertDatabaseMissing Assert that a table in the database does not contain records matching the given key / value query constraints: - $this->assertDatabaseMissing('users', [ - 'email' => 'sally@example.com', - ]); +```php +$this->assertDatabaseMissing('users', [ + 'email' => 'sally@example.com', +]); +``` #### assertSoftDeleted The `assertSoftDeleted` method may be used to assert a given Eloquent model has been "soft deleted": - $this->assertSoftDeleted($user); +```php +$this->assertSoftDeleted($user); +``` #### assertNotSoftDeleted The `assertNotSoftDeleted` method may be used to assert a given Eloquent model hasn't been "soft deleted": - $this->assertNotSoftDeleted($user); +```php +$this->assertNotSoftDeleted($user); +``` #### assertModelExists Assert that a given model exists in the database: - use App\Models\User; +```php +use App\Models\User; - $user = User::factory()->create(); +$user = User::factory()->create(); - $this->assertModelExists($user); +$this->assertModelExists($user); +``` #### assertModelMissing Assert that a given model does not exist in the database: - use App\Models\User; +```php +use App\Models\User; - $user = User::factory()->create(); +$user = User::factory()->create(); - $user->delete(); +$user->delete(); - $this->assertModelMissing($user); +$this->assertModelMissing($user); +``` #### expectsDatabaseQueryCount The `expectsDatabaseQueryCount` method may be invoked at the beginning of your test to specify the total number of database queries that you expect to be run during the test. If the actual number of executed queries does not exactly match this expectation, the test will fail: - $this->expectsDatabaseQueryCount(5); +```php +$this->expectsDatabaseQueryCount(5); - // Test... +// Test... +``` diff --git a/database.md b/database.md index 089c925baf0..37a0ce161aa 100644 --- a/database.md +++ b/database.md @@ -86,34 +86,36 @@ Sometimes you may wish to use one database connection for SELECT statements, and To see how read / write connections should be configured, let's look at this example: - 'mysql' => [ - 'read' => [ - 'host' => [ - '192.168.1.1', - '196.168.1.2', - ], +```php +'mysql' => [ + 'read' => [ + 'host' => [ + '192.168.1.1', + '196.168.1.2', ], - 'write' => [ - 'host' => [ - '196.168.1.3', - ], + ], + 'write' => [ + 'host' => [ + '196.168.1.3', ], - 'sticky' => true, - - 'database' => env('DB_DATABASE', 'laravel'), - 'username' => env('DB_USERNAME', 'root'), - 'password' => env('DB_PASSWORD', ''), - 'unix_socket' => env('DB_SOCKET', ''), - 'charset' => env('DB_CHARSET', 'utf8mb4'), - 'collation' => env('DB_COLLATION', 'utf8mb4_unicode_ci'), - 'prefix' => '', - 'prefix_indexes' => true, - 'strict' => true, - 'engine' => null, - 'options' => extension_loaded('pdo_mysql') ? array_filter([ - PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'), - ]) : [], ], + 'sticky' => true, + + 'database' => env('DB_DATABASE', 'laravel'), + 'username' => env('DB_USERNAME', 'root'), + 'password' => env('DB_PASSWORD', ''), + 'unix_socket' => env('DB_SOCKET', ''), + 'charset' => env('DB_CHARSET', 'utf8mb4'), + 'collation' => env('DB_COLLATION', 'utf8mb4_unicode_ci'), + 'prefix' => '', + 'prefix_indexes' => true, + 'strict' => true, + 'engine' => null, + 'options' => extension_loaded('pdo_mysql') ? array_filter([ + PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'), + ]) : [], +], +``` Note that three keys have been added to the configuration array: `read`, `write` and `sticky`. The `read` and `write` keys have array values containing a single key: `host`. The rest of the database options for the `read` and `write` connections will be merged from the main `mysql` configuration array. @@ -134,107 +136,127 @@ Once you have configured your database connection, you may run queries using the To run a basic SELECT query, you may use the `select` method on the `DB` facade: - $users]); - } + $users = DB::select('select * from users where active = ?', [1]); + + return view('user.index', ['users' => $users]); } +} +``` The first argument passed to the `select` method is the SQL query, while the second argument is any parameter bindings that need to be bound to the query. Typically, these are the values of the `where` clause constraints. Parameter binding provides protection against SQL injection. The `select` method will always return an `array` of results. Each result within the array will be a PHP `stdClass` object representing a record from the database: - use Illuminate\Support\Facades\DB; +```php +use Illuminate\Support\Facades\DB; - $users = DB::select('select * from users'); +$users = DB::select('select * from users'); - foreach ($users as $user) { - echo $user->name; - } +foreach ($users as $user) { + echo $user->name; +} +``` #### Selecting Scalar Values Sometimes your database query may result in a single, scalar value. Instead of being required to retrieve the query's scalar result from a record object, Laravel allows you to retrieve this value directly using the `scalar` method: - $burgers = DB::scalar( - "select count(case when food = 'burger' then 1 end) as burgers from menu" - ); +```php +$burgers = DB::scalar( + "select count(case when food = 'burger' then 1 end) as burgers from menu" +); +``` #### Selecting Multiple Result Sets If your application calls stored procedures that return multiple result sets, you may use the `selectResultSets` method to retrieve all of the result sets returned by the stored procedure: - [$options, $notifications] = DB::selectResultSets( - "CALL get_user_options_and_notifications(?)", $request->user()->id - ); +```php +[$options, $notifications] = DB::selectResultSets( + "CALL get_user_options_and_notifications(?)", $request->user()->id +); +``` #### Using Named Bindings Instead of using `?` to represent your parameter bindings, you may execute a query using named bindings: - $results = DB::select('select * from users where id = :id', ['id' => 1]); +```php +$results = DB::select('select * from users where id = :id', ['id' => 1]); +``` #### Running an Insert Statement To execute an `insert` statement, you may use the `insert` method on the `DB` facade. Like `select`, this method accepts the SQL query as its first argument and bindings as its second argument: - use Illuminate\Support\Facades\DB; +```php +use Illuminate\Support\Facades\DB; - DB::insert('insert into users (id, name) values (?, ?)', [1, 'Marc']); +DB::insert('insert into users (id, name) values (?, ?)', [1, 'Marc']); +``` #### Running an Update Statement The `update` method should be used to update existing records in the database. The number of rows affected by the statement is returned by the method: - use Illuminate\Support\Facades\DB; +```php +use Illuminate\Support\Facades\DB; - $affected = DB::update( - 'update users set votes = 100 where name = ?', - ['Anita'] - ); +$affected = DB::update( + 'update users set votes = 100 where name = ?', + ['Anita'] +); +``` #### Running a Delete Statement The `delete` method should be used to delete records from the database. Like `update`, the number of rows affected will be returned by the method: - use Illuminate\Support\Facades\DB; +```php +use Illuminate\Support\Facades\DB; - $deleted = DB::delete('delete from users'); +$deleted = DB::delete('delete from users'); +``` #### Running a General Statement Some database statements do not return any value. For these types of operations, you may use the `statement` method on the `DB` facade: - DB::statement('drop table users'); +```php +DB::statement('drop table users'); +``` #### Running an Unprepared Statement Sometimes you may want to execute an SQL statement without binding any values. You may use the `DB` facade's `unprepared` method to accomplish this: - DB::unprepared('update users set votes = 100 where name = "Dries"'); +```php +DB::unprepared('update users set votes = 100 where name = "Dries"'); +``` > [!WARNING] > Since unprepared statements do not bind parameters, they may be vulnerable to SQL injection. You should never allow user controlled values within an unprepared statement. @@ -244,7 +266,9 @@ Sometimes you may want to execute an SQL statement without binding any values. Y When using the `DB` facade's `statement` and `unprepared` methods within transactions you must be careful to avoid statements that cause [implicit commits](https://dev.mysql.com/doc/refman/8.0/en/implicit-commit.html). These statements will cause the database engine to indirectly commit the entire transaction, leaving Laravel unaware of the database's transaction level. An example of such a statement is creating a database table: - DB::unprepared('create table a (col varchar(1) null)'); +```php +DB::unprepared('create table a (col varchar(1) null)'); +``` Please refer to the MySQL manual for [a list of all statements](https://dev.mysql.com/doc/refman/8.0/en/implicit-commit.html) that trigger implicit commits. @@ -253,128 +277,146 @@ Please refer to the MySQL manual for [a list of all statements](https://dev.mysq If your application defines multiple connections in your `config/database.php` configuration file, you may access each connection via the `connection` method provided by the `DB` facade. The connection name passed to the `connection` method should correspond to one of the connections listed in your `config/database.php` configuration file or configured at runtime using the `config` helper: - use Illuminate\Support\Facades\DB; +```php +use Illuminate\Support\Facades\DB; - $users = DB::connection('sqlite')->select(/* ... */); +$users = DB::connection('sqlite')->select(/* ... */); +``` You may access the raw, underlying PDO instance of a connection using the `getPdo` method on a connection instance: - $pdo = DB::connection()->getPdo(); +```php +$pdo = DB::connection()->getPdo(); +``` ### Listening for Query Events If you would like to specify a closure that is invoked for each SQL query executed by your application, you may use the `DB` facade's `listen` method. This method can be useful for logging queries or debugging. You may register your query listener closure in the `boot` method of a [service provider](/docs/{{version}}/providers): - sql; - // $query->bindings; - // $query->time; - // $query->toRawSql(); - }); - } + DB::listen(function (QueryExecuted $query) { + // $query->sql; + // $query->bindings; + // $query->time; + // $query->toRawSql(); + }); } +} +``` ### Monitoring Cumulative Query Time A common performance bottleneck of modern web applications is the amount of time they spend querying databases. Thankfully, Laravel can invoke a closure or callback of your choice when it spends too much time querying the database during a single request. To get started, provide a query time threshold (in milliseconds) and closure to the `whenQueryingForLongerThan` method. You may invoke this method in the `boot` method of a [service provider](/docs/{{version}}/providers): - ## Database Transactions You may use the `transaction` method provided by the `DB` facade to run a set of operations within a database transaction. If an exception is thrown within the transaction closure, the transaction will automatically be rolled back and the exception is re-thrown. If the closure executes successfully, the transaction will automatically be committed. You don't need to worry about manually rolling back or committing while using the `transaction` method: - use Illuminate\Support\Facades\DB; +```php +use Illuminate\Support\Facades\DB; - DB::transaction(function () { - DB::update('update users set votes = 1'); +DB::transaction(function () { + DB::update('update users set votes = 1'); - DB::delete('delete from posts'); - }); + DB::delete('delete from posts'); +}); +``` #### Handling Deadlocks The `transaction` method accepts an optional second argument which defines the number of times a transaction should be retried when a deadlock occurs. Once these attempts have been exhausted, an exception will be thrown: - use Illuminate\Support\Facades\DB; +```php +use Illuminate\Support\Facades\DB; - DB::transaction(function () { - DB::update('update users set votes = 1'); +DB::transaction(function () { + DB::update('update users set votes = 1'); - DB::delete('delete from posts'); - }, 5); + DB::delete('delete from posts'); +}, 5); +``` #### Manually Using Transactions If you would like to begin a transaction manually and have complete control over rollbacks and commits, you may use the `beginTransaction` method provided by the `DB` facade: - use Illuminate\Support\Facades\DB; +```php +use Illuminate\Support\Facades\DB; - DB::beginTransaction(); +DB::beginTransaction(); +``` You can rollback the transaction via the `rollBack` method: - DB::rollBack(); +```php +DB::rollBack(); +``` Lastly, you can commit a transaction via the `commit` method: - DB::commit(); +```php +DB::commit(); +``` > [!NOTE] > The `DB` facade's transaction methods control the transactions for both the [query builder](/docs/{{version}}/queries) and [Eloquent ORM](/docs/{{version}}/eloquent). @@ -417,17 +459,21 @@ php artisan db:show --counts --views In addition, you may use the following `Schema` methods to inspect your database: - use Illuminate\Support\Facades\Schema; +```php +use Illuminate\Support\Facades\Schema; - $tables = Schema::getTables(); - $views = Schema::getViews(); - $columns = Schema::getColumns('users'); - $indexes = Schema::getIndexes('users'); - $foreignKeys = Schema::getForeignKeys('users'); +$tables = Schema::getTables(); +$views = Schema::getViews(); +$columns = Schema::getColumns('users'); +$indexes = Schema::getIndexes('users'); +$foreignKeys = Schema::getForeignKeys('users'); +``` If you would like to inspect a database connection that is not your application's default connection, you may use the `connection` method: - $columns = Schema::connection('sqlite')->getColumns('users'); +```php +$columns = Schema::connection('sqlite')->getColumns('users'); +``` #### Table Overview diff --git a/deployment.md b/deployment.md index 11ad9a88089..66490697147 100644 --- a/deployment.md +++ b/deployment.md @@ -183,12 +183,14 @@ Laravel includes a built-in health check route that can be used to monitor the s By default, the health check route is served at `/up` and will return a 200 HTTP response if the application has booted without exceptions. Otherwise, a 500 HTTP response will be returned. You may configure the URI for this route in your application's `bootstrap/app` file: - ->withRouting( - web: __DIR__.'/../routes/web.php', - commands: __DIR__.'/../routes/console.php', - health: '/up', // [tl! remove] - health: '/status', // [tl! add] - ) +```php +->withRouting( + web: __DIR__.'/../routes/web.php', + commands: __DIR__.'/../routes/console.php', + health: '/up', // [tl! remove] + health: '/status', // [tl! add] +) +``` When HTTP requests are made to this route, Laravel will also dispatch a `Illuminate\Foundation\Events\DiagnosingHealth` event, allowing you to perform additional health checks relevant to your application. Within a [listener](/docs/{{version}}/events) for this event, you may check your application's database or cache status. If you detect a problem with your application, you may simply throw an exception from the listener. diff --git a/dusk.md b/dusk.md index 95b8e5b790d..004ad6b6414 100644 --- a/dusk.md +++ b/dusk.md @@ -107,29 +107,33 @@ By default, Dusk uses Google Chrome and a standalone [ChromeDriver](https://site To get started, open your `tests/DuskTestCase.php` file, which is the base Dusk test case for your application. Within this file, you can remove the call to the `startChromeDriver` method. This will stop Dusk from automatically starting the ChromeDriver: - /** - * Prepare for Dusk test execution. - * - * @beforeClass - */ - public static function prepare(): void - { - // static::startChromeDriver(); - } +```php +/** + * Prepare for Dusk test execution. + * + * @beforeClass + */ +public static function prepare(): void +{ + // static::startChromeDriver(); +} +``` Next, you may modify the `driver` method to connect to the URL and port of your choice. In addition, you may modify the "desired capabilities" that should be passed to the WebDriver: - use Facebook\WebDriver\Remote\RemoteWebDriver; +```php +use Facebook\WebDriver\Remote\RemoteWebDriver; - /** - * Create the RemoteWebDriver instance. - */ - protected function driver(): RemoteWebDriver - { - return RemoteWebDriver::create( - 'http://localhost:4444/wd/hub', DesiredCapabilities::phantomjs() - ); - } +/** + * Create the RemoteWebDriver instance. + */ +protected function driver(): RemoteWebDriver +{ + return RemoteWebDriver::create( + 'http://localhost:4444/wd/hub', DesiredCapabilities::phantomjs() + ); +} +``` ## Getting Started @@ -223,48 +227,56 @@ By default, this trait will truncate all tables except the `migrations` table. I > [!NOTE] > If you are using Pest, you should define properties or methods on the base `DuskTestCase` class or on any class your test file extends. - /** - * Indicates which tables should be truncated. - * - * @var array - */ - protected $tablesToTruncate = ['users']; +```php +/** + * Indicates which tables should be truncated. + * + * @var array + */ +protected $tablesToTruncate = ['users']; +``` Alternatively, you may define an `$exceptTables` property on your test class to specify which tables should be excluded from truncation: - /** - * Indicates which tables should be excluded from truncation. - * - * @var array - */ - protected $exceptTables = ['users']; +```php +/** + * Indicates which tables should be excluded from truncation. + * + * @var array + */ +protected $exceptTables = ['users']; +``` To specify the database connections that should have their tables truncated, you may define a `$connectionsToTruncate` property on your test class: - /** - * Indicates which connections should have their tables truncated. - * - * @var array - */ - protected $connectionsToTruncate = ['mysql']; +```php +/** + * Indicates which connections should have their tables truncated. + * + * @var array + */ +protected $connectionsToTruncate = ['mysql']; +``` If you would like to execute code before or after database truncation is performed, you may define `beforeTruncatingDatabase` or `afterTruncatingDatabase` methods on your test class: - /** - * Perform any work that should take place before the database has started truncating. - */ - protected function beforeTruncatingDatabase(): void - { - // - } +```php +/** + * Perform any work that should take place before the database has started truncating. + */ +protected function beforeTruncatingDatabase(): void +{ + // +} - /** - * Perform any work that should take place after the database has finished truncating. - */ - protected function afterTruncatingDatabase(): void - { - // - } +/** + * Perform any work that should take place after the database has finished truncating. + */ +protected function afterTruncatingDatabase(): void +{ + // +} +``` ### Running Tests @@ -295,29 +307,33 @@ php artisan dusk --group=foo By default, Dusk will automatically attempt to start ChromeDriver. If this does not work for your particular system, you may manually start ChromeDriver before running the `dusk` command. If you choose to start ChromeDriver manually, you should comment out the following line of your `tests/DuskTestCase.php` file: - /** - * Prepare for Dusk test execution. - * - * @beforeClass - */ - public static function prepare(): void - { - // static::startChromeDriver(); - } +```php +/** + * Prepare for Dusk test execution. + * + * @beforeClass + */ +public static function prepare(): void +{ + // static::startChromeDriver(); +} +``` In addition, if you start ChromeDriver on a port other than 9515, you should modify the `driver` method of the same class to reflect the correct port: - use Facebook\WebDriver\Remote\RemoteWebDriver; +```php +use Facebook\WebDriver\Remote\RemoteWebDriver; - /** - * Create the RemoteWebDriver instance. - */ - protected function driver(): RemoteWebDriver - { - return RemoteWebDriver::create( - 'http://localhost:9515', DesiredCapabilities::chrome() - ); - } +/** + * Create the RemoteWebDriver instance. + */ +protected function driver(): RemoteWebDriver +{ + return RemoteWebDriver::create( + 'http://localhost:9515', DesiredCapabilities::chrome() + ); +} +``` ### Environment Handling @@ -399,112 +415,138 @@ As you can see in the example above, the `browse` method accepts a closure. A br Sometimes you may need multiple browsers in order to properly carry out a test. For example, multiple browsers may be needed to test a chat screen that interacts with websockets. To create multiple browsers, simply add more browser arguments to the signature of the closure given to the `browse` method: - $this->browse(function (Browser $first, Browser $second) { - $first->loginAs(User::find(1)) - ->visit('/home') - ->waitForText('Message'); +```php +$this->browse(function (Browser $first, Browser $second) { + $first->loginAs(User::find(1)) + ->visit('/home') + ->waitForText('Message'); - $second->loginAs(User::find(2)) - ->visit('/home') - ->waitForText('Message') - ->type('message', 'Hey Taylor') - ->press('Send'); + $second->loginAs(User::find(2)) + ->visit('/home') + ->waitForText('Message') + ->type('message', 'Hey Taylor') + ->press('Send'); - $first->waitForText('Hey Taylor') - ->assertSee('Jeffrey Way'); - }); + $first->waitForText('Hey Taylor') + ->assertSee('Jeffrey Way'); +}); +``` ### Navigation The `visit` method may be used to navigate to a given URI within your application: - $browser->visit('/login'); +```php +$browser->visit('/login'); +``` You may use the `visitRoute` method to navigate to a [named route](/docs/{{version}}/routing#named-routes): - $browser->visitRoute($routeName, $parameters); +```php +$browser->visitRoute($routeName, $parameters); +``` You may navigate "back" and "forward" using the `back` and `forward` methods: - $browser->back(); +```php +$browser->back(); - $browser->forward(); +$browser->forward(); +``` You may use the `refresh` method to refresh the page: - $browser->refresh(); +```php +$browser->refresh(); +``` ### Resizing Browser Windows You may use the `resize` method to adjust the size of the browser window: - $browser->resize(1920, 1080); +```php +$browser->resize(1920, 1080); +``` The `maximize` method may be used to maximize the browser window: - $browser->maximize(); +```php +$browser->maximize(); +``` The `fitContent` method will resize the browser window to match the size of its content: - $browser->fitContent(); +```php +$browser->fitContent(); +``` When a test fails, Dusk will automatically resize the browser to fit the content prior to taking a screenshot. You may disable this feature by calling the `disableFitOnFailure` method within your test: - $browser->disableFitOnFailure(); +```php +$browser->disableFitOnFailure(); +``` You may use the `move` method to move the browser window to a different position on your screen: - $browser->move($x = 100, $y = 100); +```php +$browser->move($x = 100, $y = 100); +``` ### Browser Macros If you would like to define a custom browser method that you can re-use in a variety of your tests, you may use the `macro` method on the `Browser` class. Typically, you should call this method from a [service provider's](/docs/{{version}}/providers) `boot` method: - script("$('html, body').animate({ scrollTop: $('$element').offset().top }, 0);"); - - return $this; - }); - } + Browser::macro('scrollToElement', function (string $element = null) { + $this->script("$('html, body').animate({ scrollTop: $('$element').offset().top }, 0);"); + + return $this; + }); } +} +``` The `macro` function accepts a name as its first argument, and a closure as its second. The macro's closure will be executed when calling the macro as a method on a `Browser` instance: - $this->browse(function (Browser $browser) use ($user) { - $browser->visit('/pay') - ->scrollToElement('#credit-card-details') - ->assertSee('Enter Credit Card Details'); - }); +```php +$this->browse(function (Browser $browser) use ($user) { + $browser->visit('/pay') + ->scrollToElement('#credit-card-details') + ->assertSee('Enter Credit Card Details'); +}); +``` ### Authentication Often, you will be testing pages that require authentication. You can use Dusk's `loginAs` method in order to avoid interacting with your application's login screen during every test. The `loginAs` method accepts a primary key associated with your authenticatable model or an authenticatable model instance: - use App\Models\User; - use Laravel\Dusk\Browser; +```php +use App\Models\User; +use Laravel\Dusk\Browser; - $this->browse(function (Browser $browser) { - $browser->loginAs(User::find(1)) - ->visit('/home'); - }); +$this->browse(function (Browser $browser) { + $browser->loginAs(User::find(1)) + ->visit('/home'); +}); +``` > [!WARNING] > After using the `loginAs` method, the user session will be maintained for all tests within the file. @@ -514,62 +556,80 @@ Often, you will be testing pages that require authentication. You can use Dusk's You may use the `cookie` method to get or set an encrypted cookie's value. By default, all of the cookies created by Laravel are encrypted: - $browser->cookie('name'); +```php +$browser->cookie('name'); - $browser->cookie('name', 'Taylor'); +$browser->cookie('name', 'Taylor'); +``` You may use the `plainCookie` method to get or set an unencrypted cookie's value: - $browser->plainCookie('name'); +```php +$browser->plainCookie('name'); - $browser->plainCookie('name', 'Taylor'); +$browser->plainCookie('name', 'Taylor'); +``` You may use the `deleteCookie` method to delete the given cookie: - $browser->deleteCookie('name'); +```php +$browser->deleteCookie('name'); +``` ### Executing JavaScript You may use the `script` method to execute arbitrary JavaScript statements within the browser: - $browser->script('document.documentElement.scrollTop = 0'); +```php +$browser->script('document.documentElement.scrollTop = 0'); - $browser->script([ - 'document.body.scrollTop = 0', - 'document.documentElement.scrollTop = 0', - ]); +$browser->script([ + 'document.body.scrollTop = 0', + 'document.documentElement.scrollTop = 0', +]); - $output = $browser->script('return window.location.pathname'); +$output = $browser->script('return window.location.pathname'); +``` ### Taking a Screenshot You may use the `screenshot` method to take a screenshot and store it with the given filename. All screenshots will be stored within the `tests/Browser/screenshots` directory: - $browser->screenshot('filename'); +```php +$browser->screenshot('filename'); +``` The `responsiveScreenshots` method may be used to take a series of screenshots at various breakpoints: - $browser->responsiveScreenshots('filename'); +```php +$browser->responsiveScreenshots('filename'); +``` The `screenshotElement` method may be used to take a screenshot of a specific element on the page: - $browser->screenshotElement('#selector', 'filename'); +```php +$browser->screenshotElement('#selector', 'filename'); +``` ### Storing Console Output to Disk You may use the `storeConsoleLog` method to write the current browser's console output to disk with the given filename. Console output will be stored within the `tests/Browser/console` directory: - $browser->storeConsoleLog('filename'); +```php +$browser->storeConsoleLog('filename'); +``` ### Storing Page Source to Disk You may use the `storeSource` method to write the current page's source to disk with the given filename. The page source will be stored within the `tests/Browser/source` directory: - $browser->storeSource('filename'); +```php +$browser->storeSource('filename'); +``` ## Interacting With Elements @@ -579,29 +639,39 @@ You may use the `storeSource` method to write the current page's source to disk Choosing good CSS selectors for interacting with elements is one of the hardest parts of writing Dusk tests. Over time, frontend changes can cause CSS selectors like the following to break your tests: - // HTML... +```html +// HTML... - + +``` - // Test... +```php +// Test... - $browser->click('.login-page .container div > button'); +$browser->click('.login-page .container div > button'); +``` Dusk selectors allow you to focus on writing effective tests rather than remembering CSS selectors. To define a selector, add a `dusk` attribute to your HTML element. Then, when interacting with a Dusk browser, prefix the selector with `@` to manipulate the attached element within your test: - // HTML... +```html +// HTML... - + +``` - // Test... +```php +// Test... - $browser->click('@login-button'); +$browser->click('@login-button'); +``` If desired, you may customize the HTML attribute that the Dusk selector utilizes via the `selectorHtmlAttribute` method. Typically, this method should be called from the `boot` method of your application's `AppServiceProvider`: - use Laravel\Dusk\Dusk; +```php +use Laravel\Dusk\Dusk; - Dusk::selectorHtmlAttribute('data-dusk'); +Dusk::selectorHtmlAttribute('data-dusk'); +``` ### Text, Values, and Attributes @@ -611,29 +681,37 @@ If desired, you may customize the HTML attribute that the Dusk selector utilizes Dusk provides several methods for interacting with the current value, display text, and attributes of elements on the page. For example, to get the "value" of an element that matches a given CSS or Dusk selector, use the `value` method: - // Retrieve the value... - $value = $browser->value('selector'); +```php +// Retrieve the value... +$value = $browser->value('selector'); - // Set the value... - $browser->value('selector', 'value'); +// Set the value... +$browser->value('selector', 'value'); +``` You may use the `inputValue` method to get the "value" of an input element that has a given field name: - $value = $browser->inputValue('field'); +```php +$value = $browser->inputValue('field'); +``` #### Retrieving Text The `text` method may be used to retrieve the display text of an element that matches the given selector: - $text = $browser->text('selector'); +```php +$text = $browser->text('selector'); +``` #### Retrieving Attributes Finally, the `attribute` method may be used to retrieve the value of an attribute of an element matching the given selector: - $attribute = $browser->attribute('selector', 'value'); +```php +$attribute = $browser->attribute('selector', 'value'); +``` ### Interacting With Forms @@ -643,69 +721,95 @@ Finally, the `attribute` method may be used to retrieve the value of an attribut Dusk provides a variety of methods for interacting with forms and input elements. First, let's take a look at an example of typing text into an input field: - $browser->type('email', 'taylor@laravel.com'); +```php +$browser->type('email', 'taylor@laravel.com'); +``` Note that, although the method accepts one if necessary, we are not required to pass a CSS selector into the `type` method. If a CSS selector is not provided, Dusk will search for an `input` or `textarea` field with the given `name` attribute. To append text to a field without clearing its content, you may use the `append` method: - $browser->type('tags', 'foo') - ->append('tags', ', bar, baz'); +```php +$browser->type('tags', 'foo') + ->append('tags', ', bar, baz'); +``` You may clear the value of an input using the `clear` method: - $browser->clear('email'); +```php +$browser->clear('email'); +``` You can instruct Dusk to type slowly using the `typeSlowly` method. By default, Dusk will pause for 100 milliseconds between key presses. To customize the amount of time between key presses, you may pass the appropriate number of milliseconds as the third argument to the method: - $browser->typeSlowly('mobile', '+1 (202) 555-5555'); +```php +$browser->typeSlowly('mobile', '+1 (202) 555-5555'); - $browser->typeSlowly('mobile', '+1 (202) 555-5555', 300); +$browser->typeSlowly('mobile', '+1 (202) 555-5555', 300); +``` You may use the `appendSlowly` method to append text slowly: - $browser->type('tags', 'foo') - ->appendSlowly('tags', ', bar, baz'); +```php +$browser->type('tags', 'foo') + ->appendSlowly('tags', ', bar, baz'); +``` #### Dropdowns To select a value available on a `select` element, you may use the `select` method. Like the `type` method, the `select` method does not require a full CSS selector. When passing a value to the `select` method, you should pass the underlying option value instead of the display text: - $browser->select('size', 'Large'); +```php +$browser->select('size', 'Large'); +``` You may select a random option by omitting the second argument: - $browser->select('size'); +```php +$browser->select('size'); +``` + +By providing an array as the second```php -By providing an array as the second argument to the `select` method, you can instruct the method to select multiple options: +``` argument to the `select` method, you can instruct the method to select multiple options: - $browser->select('categories', ['Art', 'Music']); +```php +$browser->select('categories', ['Art', 'Music']); +``` #### Checkboxes To "check" a checkbox input, you may use the `check` method. Like many other input related methods, a full CSS selector is not required. If a CSS selector match can't be found, Dusk will search for a checkbox with a matching `name` attribute: - $browser->check('terms'); +```php +$browser->check('terms'); +``` The `uncheck` method may be used to "uncheck" a checkbox input: - $browser->uncheck('terms'); +```php +$browser->uncheck('terms'); +``` #### Radio Buttons To "select" a `radio` input option, you may use the `radio` method. Like many other input related methods, a full CSS selector is not required. If a CSS selector match can't be found, Dusk will search for a `radio` input with matching `name` and `value` attributes: - $browser->radio('size', 'large'); +```php +$browser->radio('size', 'large'); +``` ### Attaching Files The `attach` method may be used to attach a file to a `file` input element. Like many other input related methods, a full CSS selector is not required. If a CSS selector match can't be found, Dusk will search for a `file` input with a matching `name` attribute: - $browser->attach('photo', __DIR__.'/photos/mountains.png'); +```php +$browser->attach('photo', __DIR__.'/photos/mountains.png'); +``` > [!WARNING] > The attach function requires the `Zip` PHP extension to be installed and enabled on your server. @@ -715,28 +819,36 @@ The `attach` method may be used to attach a file to a `file` input element. Like The `press` method may be used to click a button element on the page. The argument given to the `press` method may be either the display text of the button or a CSS / Dusk selector: - $browser->press('Login'); +```php +$browser->press('Login'); +``` When submitting forms, many applications disable the form's submission button after it is pressed and then re-enable the button when the form submission's HTTP request is complete. To press a button and wait for the button to be re-enabled, you may use the `pressAndWaitFor` method: - // Press the button and wait a maximum of 5 seconds for it to be enabled... - $browser->pressAndWaitFor('Save'); +```php +// Press the button and wait a maximum of 5 seconds for it to be enabled... +$browser->pressAndWaitFor('Save'); - // Press the button and wait a maximum of 1 second for it to be enabled... - $browser->pressAndWaitFor('Save', 1); +// Press the button and wait a maximum of 1 second for it to be enabled... +$browser->pressAndWaitFor('Save', 1); +``` ### Clicking Links To click a link, you may use the `clickLink` method on the browser instance. The `clickLink` method will click the link that has the given display text: - $browser->clickLink($linkText); +```php +$browser->clickLink($linkText); +``` You may use the `seeLink` method to determine if a link with the given display text is visible on the page: - if ($browser->seeLink($linkText)) { - // ... - } +```php +if ($browser->seeLink($linkText)) { + // ... +} +``` > [!WARNING] > These methods interact with jQuery. If jQuery is not available on the page, Dusk will automatically inject it into the page so it is available for the test's duration. @@ -746,11 +858,15 @@ You may use the `seeLink` method to determine if a link with the given display t The `keys` method allows you to provide more complex input sequences to a given element than normally allowed by the `type` method. For example, you may instruct Dusk to hold modifier keys while entering values. In this example, the `shift` key will be held while `taylor` is entered into the element matching the given selector. After `taylor` is typed, `swift` will be typed without any modifier keys: - $browser->keys('selector', ['{shift}', 'taylor'], 'swift'); +```php +$browser->keys('selector', ['{shift}', 'taylor'], 'swift'); +``` Another valuable use case for the `keys` method is sending a "keyboard shortcut" combination to the primary CSS selector for your application: - $browser->keys('.app', ['{command}', 'j']); +```php +$browser->keys('.app', ['{command}', 'j']); +``` > [!NOTE] > All modifier keys such as `{command}` are wrapped in `{}` characters, and match the constants defined in the `Facebook\WebDriver\WebDriverKeys` class, which can be [found on GitHub](https://github.com/php-webdriver/php-webdriver/blob/master/lib/WebDriverKeys.php). @@ -760,60 +876,66 @@ Another valuable use case for the `keys` method is sending a "keyboard shortcut" Dusk also provides a `withKeyboard` method, allowing you to fluently perform complex keyboard interactions via the `Laravel\Dusk\Keyboard` class. The `Keyboard` class provides `press`, `release`, `type`, and `pause` methods: - use Laravel\Dusk\Keyboard; +```php +use Laravel\Dusk\Keyboard; - $browser->withKeyboard(function (Keyboard $keyboard) { - $keyboard->press('c') - ->pause(1000) - ->release('c') - ->type(['c', 'e', 'o']); - }); +$browser->withKeyboard(function (Keyboard $keyboard) { + $keyboard->press('c') + ->pause(1000) + ->release('c') + ->type(['c', 'e', 'o']); +}); +``` #### Keyboard Macros If you would like to define custom keyboard interactions that you can easily re-use throughout your test suite, you may use the `macro` method provided by the `Keyboard` class. Typically, you should call this method from a [service provider's](/docs/{{version}}/providers) `boot` method: - type([ - OperatingSystem::onMac() ? WebDriverKeys::META : WebDriverKeys::CONTROL, 'c', - ]); - - return $this; - }); + Keyboard::macro('copy', function (string $element = null) { + $this->type([ + OperatingSystem::onMac() ? WebDriverKeys::META : WebDriverKeys::CONTROL, 'c', + ]); - Keyboard::macro('paste', function (string $element = null) { - $this->type([ - OperatingSystem::onMac() ? WebDriverKeys::META : WebDriverKeys::CONTROL, 'v', - ]); + return $this; + }); - return $this; - }); - } + Keyboard::macro('paste', function (string $element = null) { + $this->type([ + OperatingSystem::onMac() ? WebDriverKeys::META : WebDriverKeys::CONTROL, 'v', + ]); + + return $this; + }); } +} +``` The `macro` function accepts a name as its first argument and a closure as its second. The macro's closure will be executed when calling the macro as a method on a `Keyboard` instance: - $browser->click('@textarea') - ->withKeyboard(fn (Keyboard $keyboard) => $keyboard->copy()) - ->click('@another-textarea') - ->withKeyboard(fn (Keyboard $keyboard) => $keyboard->paste()); +```php +$browser->click('@textarea') + ->withKeyboard(fn (Keyboard $keyboard) => $keyboard->copy()) + ->click('@another-textarea') + ->withKeyboard(fn (Keyboard $keyboard) => $keyboard->paste()); +``` ### Using the Mouse @@ -823,127 +945,165 @@ The `macro` function accepts a name as its first argument and a closure as its s The `click` method may be used to click on an element matching the given CSS or Dusk selector: - $browser->click('.selector'); +```php +$browser->click('.selector'); +``` The `clickAtXPath` method may be used to click on an element matching the given XPath expression: - $browser->clickAtXPath('//div[@class = "selector"]'); +```php +$browser->clickAtXPath('//div[@class = "selector"]'); +``` The `clickAtPoint` method may be used to click on the topmost element at a given pair of coordinates relative to the viewable area of the browser: - $browser->clickAtPoint($x = 0, $y = 0); +```php +$browser->clickAtPoint($x = 0, $y = 0); +``` The `doubleClick` method may be used to simulate the double click of a mouse: - $browser->doubleClick(); +```php +$browser->doubleClick(); - $browser->doubleClick('.selector'); +$browser->doubleClick('.selector'); +``` The `rightClick` method may be used to simulate the right click of a mouse: - $browser->rightClick(); +```php +$browser->rightClick(); - $browser->rightClick('.selector'); +$browser->rightClick('.selector'); +``` The `clickAndHold` method may be used to simulate a mouse button being clicked and held down. A subsequent call to the `releaseMouse` method will undo this behavior and release the mouse button: - $browser->clickAndHold('.selector'); +```php +$browser->clickAndHold('.selector'); - $browser->clickAndHold() - ->pause(1000) - ->releaseMouse(); +$browser->clickAndHold() + ->pause(1000) + ->releaseMouse(); +``` The `controlClick` method may be used to simulate the `ctrl+click` event within the browser: - $browser->controlClick(); +```php +$browser->controlClick(); - $browser->controlClick('.selector'); +$browser->controlClick('.selector'); +``` #### Mouseover The `mouseover` method may be used when you need to move the mouse over an element matching the given CSS or Dusk selector: - $browser->mouseover('.selector'); +```php +$browser->mouseover('.selector'); +``` #### Drag and Drop The `drag` method may be used to drag an element matching the given selector to another element: - $browser->drag('.from-selector', '.to-selector'); +```php +$browser->drag('.from-selector', '.to-selector'); +``` Or, you may drag an element in a single direction: - $browser->dragLeft('.selector', $pixels = 10); - $browser->dragRight('.selector', $pixels = 10); - $browser->dragUp('.selector', $pixels = 10); - $browser->dragDown('.selector', $pixels = 10); +```php +$browser->dragLeft('.selector', $pixels = 10); +$browser->dragRight('.selector', $pixels = 10); +$browser->dragUp('.selector', $pixels = 10); +$browser->dragDown('.selector', $pixels = 10); +``` Finally, you may drag an element by a given offset: - $browser->dragOffset('.selector', $x = 10, $y = 10); +```php +$browser->dragOffset('.selector', $x = 10, $y = 10); +``` ### JavaScript Dialogs Dusk provides various methods to interact with JavaScript Dialogs. For example, you may use the `waitForDialog` method to wait for a JavaScript dialog to appear. This method accepts an optional argument indicating how many seconds to wait for the dialog to appear: - $browser->waitForDialog($seconds = null); +```php +$browser->waitForDialog($seconds = null); +``` The `assertDialogOpened` method may be used to assert that a dialog has been displayed and contains the given message: - $browser->assertDialogOpened('Dialog message'); +```php +$browser->assertDialogOpened('Dialog message'); +``` If the JavaScript dialog contains a prompt, you may use the `typeInDialog` method to type a value into the prompt: - $browser->typeInDialog('Hello World'); +```php +$browser->typeInDialog('Hello World'); +``` To close an open JavaScript dialog by clicking the "OK" button, you may invoke the `acceptDialog` method: - $browser->acceptDialog(); +```php +$browser->acceptDialog(); +``` To close an open JavaScript dialog by clicking the "Cancel" button, you may invoke the `dismissDialog` method: - $browser->dismissDialog(); +```php +$browser->dismissDialog(); +``` ### Interacting With Inline Frames If you need to interact with elements within an iframe, you may use the `withinFrame` method. All element interactions that take place within the closure provided to the `withinFrame` method will be scoped to the context of the specified iframe: - $browser->withinFrame('#credit-card-details', function ($browser) { - $browser->type('input[name="cardnumber"]', '4242424242424242') - ->type('input[name="exp-date"]', '1224') - ->type('input[name="cvc"]', '123') - ->press('Pay'); - }); +```php +$browser->withinFrame('#credit-card-details', function ($browser) { + $browser->type('input[name="cardnumber"]', '4242424242424242') + ->type('input[name="exp-date"]', '1224') + ->type('input[name="cvc"]', '123') + ->press('Pay'); +}); +``` ### Scoping Selectors Sometimes you may wish to perform several operations while scoping all of the operations within a given selector. For example, you may wish to assert that some text exists only within a table and then click a button within that table. You may use the `with` method to accomplish this. All operations performed within the closure given to the `with` method will be scoped to the original selector: - $browser->with('.table', function (Browser $table) { - $table->assertSee('Hello World') - ->clickLink('Delete'); - }); +```php +$browser->with('.table', function (Browser $table) { + $table->assertSee('Hello World') + ->clickLink('Delete'); +}); +``` You may occasionally need to execute assertions outside of the current scope. You may use the `elsewhere` and `elsewhereWhenAvailable` methods to accomplish this: - $browser->with('.table', function (Browser $table) { - // Current scope is `body .table`... +```php +$browser->with('.table', function (Browser $table) { + // Current scope is `body .table`... - $browser->elsewhere('.page-title', function (Browser $title) { - // Current scope is `body .page-title`... - $title->assertSee('Hello World'); - }); + $browser->elsewhere('.page-title', function (Browser $title) { + // Current scope is `body .page-title`... + $title->assertSee('Hello World'); + }); - $browser->elsewhereWhenAvailable('.page-title', function (Browser $title) { - // Current scope is `body .page-title`... - $title->assertSee('Hello World'); - }); - }); + $browser->elsewhereWhenAvailable('.page-title', function (Browser $title) { + // Current scope is `body .page-title`... + $title->assertSee('Hello World'); + }); +}); +``` ### Waiting for Elements @@ -955,204 +1115,254 @@ When testing applications that use JavaScript extensively, it often becomes nece If you just need to pause the test for a given number of milliseconds, use the `pause` method: - $browser->pause(1000); +```php +$browser->pause(1000); +``` If you need to pause the test only if a given condition is `true`, use the `pauseIf` method: - $browser->pauseIf(App::environment('production'), 1000); +```php +$browser->pauseIf(App::environment('production'), 1000); +``` Likewise, if you need to pause the test unless a given condition is `true`, you may use the `pauseUnless` method: - $browser->pauseUnless(App::environment('testing'), 1000); +```php +$browser->pauseUnless(App::environment('testing'), 1000); +``` #### Waiting for Selectors The `waitFor` method may be used to pause the execution of the test until the element matching the given CSS or Dusk selector is displayed on the page. By default, this will pause the test for a maximum of five seconds before throwing an exception. If necessary, you may pass a custom timeout threshold as the second argument to the method: - // Wait a maximum of five seconds for the selector... - $browser->waitFor('.selector'); +```php +// Wait a maximum of five seconds for the selector... +$browser->waitFor('.selector'); - // Wait a maximum of one second for the selector... - $browser->waitFor('.selector', 1); +// Wait a maximum of one second for the selector... +$browser->waitFor('.selector', 1); +``` You may also wait until the element matching the given selector contains the given text: - // Wait a maximum of five seconds for the selector to contain the given text... - $browser->waitForTextIn('.selector', 'Hello World'); +```php +// Wait a maximum of five seconds for the selector to contain the given text... +$browser->waitForTextIn('.selector', 'Hello World'); - // Wait a maximum of one second for the selector to contain the given text... - $browser->waitForTextIn('.selector', 'Hello World', 1); +// Wait a maximum of one second for the selector to contain the given text... +$browser->waitForTextIn('.selector', 'Hello World', 1); +``` You may also wait until the element matching the given selector is missing from the page: - // Wait a maximum of five seconds until the selector is missing... - $browser->waitUntilMissing('.selector'); +```php +// Wait a maximum of five seconds until the selector is missing... +$browser->waitUntilMissing('.selector'); - // Wait a maximum of one second until the selector is missing... - $browser->waitUntilMissing('.selector', 1); +// Wait a maximum of one second until the selector is missing... +$browser->waitUntilMissing('.selector', 1); +``` Or, you may wait until the element matching the given selector is enabled or disabled: - // Wait a maximum of five seconds until the selector is enabled... - $browser->waitUntilEnabled('.selector'); +```php +// Wait a maximum of five seconds until the selector is enabled... +$browser->waitUntilEnabled('.selector'); - // Wait a maximum of one second until the selector is enabled... - $browser->waitUntilEnabled('.selector', 1); +// Wait a maximum of one second until the selector is enabled... +$browser->waitUntilEnabled('.selector', 1); - // Wait a maximum of five seconds until the selector is disabled... - $browser->waitUntilDisabled('.selector'); +// Wait a maximum of five seconds until the selector is disabled... +$browser->waitUntilDisabled('.selector'); - // Wait a maximum of one second until the selector is disabled... - $browser->waitUntilDisabled('.selector', 1); +// Wait a maximum of one second until the selector is disabled... +$browser->waitUntilDisabled('.selector', 1); +``` #### Scoping Selectors When Available Occasionally, you may wish to wait for an element to appear that matches a given selector and then interact with the element. For example, you may wish to wait until a modal window is available and then press the "OK" button within the modal. The `whenAvailable` method may be used to accomplish this. All element operations performed within the given closure will be scoped to the original selector: - $browser->whenAvailable('.modal', function (Browser $modal) { - $modal->assertSee('Hello World') - ->press('OK'); - }); +```php +$browser->whenAvailable('.modal', function (Browser $modal) { + $modal->assertSee('Hello World') + ->press('OK'); +}); +``` #### Waiting for Text The `waitForText` method may be used to wait until the given text is displayed on the page: - // Wait a maximum of five seconds for the text... - $browser->waitForText('Hello World'); +```php +// Wait a maximum of five seconds for the text... +$browser->waitForText('Hello World'); - // Wait a maximum of one second for the text... - $browser->waitForText('Hello World', 1); +// Wait a maximum of one second for the text... +$browser->waitForText('Hello World', 1); +``` You may use the `waitUntilMissingText` method to wait until the displayed text has been removed from the page: - // Wait a maximum of five seconds for the text to be removed... - $browser->waitUntilMissingText('Hello World'); +```php +// Wait a maximum of five seconds for the text to be removed... +$browser->waitUntilMissingText('Hello World'); - // Wait a maximum of one second for the text to be removed... - $browser->waitUntilMissingText('Hello World', 1); +// Wait a maximum of one second for the text to be removed... +$browser->waitUntilMissingText('Hello World', 1); +``` #### Waiting for Links The `waitForLink` method may be used to wait until the given link text is displayed on the page: - // Wait a maximum of five seconds for the link... - $browser->waitForLink('Create'); +```php +// Wait a maximum of five seconds for the link... +$browser->waitForLink('Create'); - // Wait a maximum of one second for the link... - $browser->waitForLink('Create', 1); +// Wait a maximum of one second for the link... +$browser->waitForLink('Create', 1); +``` #### Waiting for Inputs The `waitForInput` method may be used to wait until the given input field is visible on the page: - // Wait a maximum of five seconds for the input... - $browser->waitForInput($field); +```php +// Wait a maximum of five seconds for the input... +$browser->waitForInput($field); - // Wait a maximum of one second for the input... - $browser->waitForInput($field, 1); +// Wait a maximum of one second for the input... +$browser->waitForInput($field, 1); +``` #### Waiting on the Page Location When making a path assertion such as `$browser->assertPathIs('/home')`, the assertion can fail if `window.location.pathname` is being updated asynchronously. You may use the `waitForLocation` method to wait for the location to be a given value: - $browser->waitForLocation('/secret'); +```php +$browser->waitForLocation('/secret'); +``` The `waitForLocation` method can also be used to wait for the current window location to be a fully qualified URL: - $browser->waitForLocation('https://example.com/path'); +```php +$browser->waitForLocation('https://example.com/path'); +``` You may also wait for a [named route's](/docs/{{version}}/routing#named-routes) location: - $browser->waitForRoute($routeName, $parameters); +```php +$browser->waitForRoute($routeName, $parameters); +``` #### Waiting for Page Reloads If you need to wait for a page to reload after performing an action, use the `waitForReload` method: - use Laravel\Dusk\Browser; +```php +use Laravel\Dusk\Browser; - $browser->waitForReload(function (Browser $browser) { - $browser->press('Submit'); - }) - ->assertSee('Success!'); +$browser->waitForReload(function (Browser $browser) { + $browser->press('Submit'); +}) +->assertSee('Success!'); +``` Since the need to wait for the page to reload typically occurs after clicking a button, you may use the `clickAndWaitForReload` method for convenience: - $browser->clickAndWaitForReload('.selector') - ->assertSee('something'); +```php +$browser->clickAndWaitForReload('.selector') + ->assertSee('something'); +``` #### Waiting on JavaScript Expressions Sometimes you may wish to pause the execution of a test until a given JavaScript expression evaluates to `true`. You may easily accomplish this using the `waitUntil` method. When passing an expression to this method, you do not need to include the `return` keyword or an ending semi-colon: - // Wait a maximum of five seconds for the expression to be true... - $browser->waitUntil('App.data.servers.length > 0'); +```php +// Wait a maximum of five seconds for the expression to be true... +$browser->waitUntil('App.data.servers.length > 0'); - // Wait a maximum of one second for the expression to be true... - $browser->waitUntil('App.data.servers.length > 0', 1); +// Wait a maximum of one second for the expression to be true... +$browser->waitUntil('App.data.servers.length > 0', 1); +``` #### Waiting on Vue Expressions The `waitUntilVue` and `waitUntilVueIsNot` methods may be used to wait until a [Vue component](https://vuejs.org) attribute has a given value: - // Wait until the component attribute contains the given value... - $browser->waitUntilVue('user.name', 'Taylor', '@user'); +```php +// Wait until the component attribute contains the given value... +$browser->waitUntilVue('user.name', 'Taylor', '@user'); - // Wait until the component attribute doesn't contain the given value... - $browser->waitUntilVueIsNot('user.name', null, '@user'); +// Wait until the component attribute doesn't contain the given value... +$browser->waitUntilVueIsNot('user.name', null, '@user'); +``` #### Waiting for JavaScript Events The `waitForEvent` method can be used to pause the execution of a test until a JavaScript event occurs: - $browser->waitForEvent('load'); +```php +$browser->waitForEvent('load'); +``` The event listener is attached to the current scope, which is the `body` element by default. When using a scoped selector, the event listener will be attached to the matching element: - $browser->with('iframe', function (Browser $iframe) { - // Wait for the iframe's load event... - $iframe->waitForEvent('load'); - }); +```php +$browser->with('iframe', function (Browser $iframe) { + // Wait for the iframe's load event... + $iframe->waitForEvent('load'); +}); +``` You may also provide a selector as the second argument to the `waitForEvent` method to attach the event listener to a specific element: - $browser->waitForEvent('load', '.selector'); +```php +$browser->waitForEvent('load', '.selector'); +``` You may also wait for events on the `document` and `window` objects: - // Wait until the document is scrolled... - $browser->waitForEvent('scroll', 'document'); +```php +// Wait until the document is scrolled... +$browser->waitForEvent('scroll', 'document'); - // Wait a maximum of five seconds until the window is resized... - $browser->waitForEvent('resize', 'window', 5); +// Wait a maximum of five seconds until the window is resized... +$browser->waitForEvent('resize', 'window', 5); +``` #### Waiting With a Callback Many of the "wait" methods in Dusk rely on the underlying `waitUsing` method. You may use this method directly to wait for a given closure to return `true`. The `waitUsing` method accepts the maximum number of seconds to wait, the interval at which the closure should be evaluated, the closure, and an optional failure message: - $browser->waitUsing(10, 1, function () use ($something) { - return $something->isReady(); - }, "Something wasn't ready in time."); +```php +$browser->waitUsing(10, 1, function () use ($something) { + return $something->isReady(); +}, "Something wasn't ready in time."); +``` ### Scrolling an Element Into View Sometimes you may not be able to click on an element because it is outside of the viewable area of the browser. The `scrollIntoView` method will scroll the browser window until the element at the given selector is within the view: - $browser->scrollIntoView('.selector') - ->click('.selector'); +```php +$browser->scrollIntoView('.selector') + ->click('.selector'); +``` ## Available Assertions @@ -1260,383 +1470,493 @@ Dusk provides a variety of assertions that you may make against your application Assert that the page title matches the given text: - $browser->assertTitle($title); +```php +$browser->assertTitle($title); +``` #### assertTitleContains Assert that the page title contains the given text: - $browser->assertTitleContains($title); +```php +$browser->assertTitleContains($title); +``` #### assertUrlIs Assert that the current URL (without the query string) matches the given string: - $browser->assertUrlIs($url); +```php +$browser->assertUrlIs($url); +``` #### assertSchemeIs Assert that the current URL scheme matches the given scheme: - $browser->assertSchemeIs($scheme); +```php +$browser->assertSchemeIs($scheme); +``` #### assertSchemeIsNot Assert that the current URL scheme does not match the given scheme: - $browser->assertSchemeIsNot($scheme); +```php +$browser->assertSchemeIsNot($scheme); +``` #### assertHostIs Assert that the current URL host matches the given host: - $browser->assertHostIs($host); +```php +$browser->assertHostIs($host); +``` #### assertHostIsNot Assert that the current URL host does not match the given host: - $browser->assertHostIsNot($host); +```php +$browser->assertHostIsNot($host); +``` #### assertPortIs Assert that the current URL port matches the given port: - $browser->assertPortIs($port); +```php +$browser->assertPortIs($port); +``` #### assertPortIsNot Assert that the current URL port does not match the given port: - $browser->assertPortIsNot($port); +```php +$browser->assertPortIsNot($port); +``` #### assertPathBeginsWith Assert that the current URL path begins with the given path: - $browser->assertPathBeginsWith('/home'); +```php +$browser->assertPathBeginsWith('/home'); +``` #### assertPathEndsWith Assert that the current URL path ends with the given path: - $browser->assertPathEndsWith('/home'); +```php +$browser->assertPathEndsWith('/home'); +``` #### assertPathContains Assert that the current URL path contains the given path: - $browser->assertPathContains('/home'); +```php +$browser->assertPathContains('/home'); +``` #### assertPathIs Assert that the current path matches the given path: - $browser->assertPathIs('/home'); +```php +$browser->assertPathIs('/home'); +``` #### assertPathIsNot Assert that the current path does not match the given path: - $browser->assertPathIsNot('/home'); +```php +$browser->assertPathIsNot('/home'); +``` #### assertRouteIs Assert that the current URL matches the given [named route's](/docs/{{version}}/routing#named-routes) URL: - $browser->assertRouteIs($name, $parameters); +```php +$browser->assertRouteIs($name, $parameters); +``` #### assertQueryStringHas Assert that the given query string parameter is present: - $browser->assertQueryStringHas($name); +```php +$browser->assertQueryStringHas($name); +``` Assert that the given query string parameter is present and has a given value: - $browser->assertQueryStringHas($name, $value); +```php +$browser->assertQueryStringHas($name, $value); +``` #### assertQueryStringMissing Assert that the given query string parameter is missing: - $browser->assertQueryStringMissing($name); +```php +$browser->assertQueryStringMissing($name); +``` #### assertFragmentIs Assert that the URL's current hash fragment matches the given fragment: - $browser->assertFragmentIs('anchor'); +```php +$browser->assertFragmentIs('anchor'); +``` #### assertFragmentBeginsWith Assert that the URL's current hash fragment begins with the given fragment: - $browser->assertFragmentBeginsWith('anchor'); +```php +$browser->assertFragmentBeginsWith('anchor'); +``` #### assertFragmentIsNot Assert that the URL's current hash fragment does not match the given fragment: - $browser->assertFragmentIsNot('anchor'); +```php +$browser->assertFragmentIsNot('anchor'); +``` #### assertHasCookie Assert that the given encrypted cookie is present: - $browser->assertHasCookie($name); +```php +$browser->assertHasCookie($name); +``` #### assertHasPlainCookie Assert that the given unencrypted cookie is present: - $browser->assertHasPlainCookie($name); +```php +$browser->assertHasPlainCookie($name); +``` #### assertCookieMissing Assert that the given encrypted cookie is not present: - $browser->assertCookieMissing($name); +```php +$browser->assertCookieMissing($name); +``` #### assertPlainCookieMissing Assert that the given unencrypted cookie is not present: - $browser->assertPlainCookieMissing($name); +```php +$browser->assertPlainCookieMissing($name); +``` #### assertCookieValue Assert that an encrypted cookie has a given value: - $browser->assertCookieValue($name, $value); +```php +$browser->assertCookieValue($name, $value); +``` #### assertPlainCookieValue Assert that an unencrypted cookie has a given value: - $browser->assertPlainCookieValue($name, $value); +```php +$browser->assertPlainCookieValue($name, $value); +``` #### assertSee Assert that the given text is present on the page: - $browser->assertSee($text); +```php +$browser->assertSee($text); +``` #### assertDontSee Assert that the given text is not present on the page: - $browser->assertDontSee($text); +```php +$browser->assertDontSee($text); +``` #### assertSeeIn Assert that the given text is present within the selector: - $browser->assertSeeIn($selector, $text); +```php +$browser->assertSeeIn($selector, $text); +``` #### assertDontSeeIn Assert that the given text is not present within the selector: - $browser->assertDontSeeIn($selector, $text); +```php +$browser->assertDontSeeIn($selector, $text); +``` #### assertSeeAnythingIn Assert that any text is present within the selector: - $browser->assertSeeAnythingIn($selector); +```php +$browser->assertSeeAnythingIn($selector); +``` #### assertSeeNothingIn Assert that no text is present within the selector: - $browser->assertSeeNothingIn($selector); +```php +$browser->assertSeeNothingIn($selector); +``` #### assertScript Assert that the given JavaScript expression evaluates to the given value: - $browser->assertScript('window.isLoaded') - ->assertScript('document.readyState', 'complete'); +```php +$browser->assertScript('window.isLoaded') + ->assertScript('document.readyState', 'complete'); +``` #### assertSourceHas Assert that the given source code is present on the page: - $browser->assertSourceHas($code); +```php +$browser->assertSourceHas($code); +``` #### assertSourceMissing Assert that the given source code is not present on the page: - $browser->assertSourceMissing($code); +```php +$browser->assertSourceMissing($code); +``` #### assertSeeLink Assert that the given link is present on the page: - $browser->assertSeeLink($linkText); +```php +$browser->assertSeeLink($linkText); +``` #### assertDontSeeLink Assert that the given link is not present on the page: - $browser->assertDontSeeLink($linkText); +```php +$browser->assertDontSeeLink($linkText); +``` #### assertInputValue Assert that the given input field has the given value: - $browser->assertInputValue($field, $value); +```php +$browser->assertInputValue($field, $value); +``` #### assertInputValueIsNot Assert that the given input field does not have the given value: - $browser->assertInputValueIsNot($field, $value); +```php +$browser->assertInputValueIsNot($field, $value); +``` #### assertChecked Assert that the given checkbox is checked: - $browser->assertChecked($field); +```php +$browser->assertChecked($field); +``` #### assertNotChecked Assert that the given checkbox is not checked: - $browser->assertNotChecked($field); +```php +$browser->assertNotChecked($field); +``` #### assertIndeterminate Assert that the given checkbox is in an indeterminate state: - $browser->assertIndeterminate($field); +```php +$browser->assertIndeterminate($field); +``` #### assertRadioSelected Assert that the given radio field is selected: - $browser->assertRadioSelected($field, $value); +```php +$browser->assertRadioSelected($field, $value); +``` #### assertRadioNotSelected Assert that the given radio field is not selected: - $browser->assertRadioNotSelected($field, $value); +```php +$browser->assertRadioNotSelected($field, $value); +``` #### assertSelected Assert that the given dropdown has the given value selected: - $browser->assertSelected($field, $value); +```php +$browser->assertSelected($field, $value); +``` #### assertNotSelected Assert that the given dropdown does not have the given value selected: - $browser->assertNotSelected($field, $value); +```php +$browser->assertNotSelected($field, $value); +``` #### assertSelectHasOptions Assert that the given array of values are available to be selected: - $browser->assertSelectHasOptions($field, $values); +```php +$browser->assertSelectHasOptions($field, $values); +``` #### assertSelectMissingOptions Assert that the given array of values are not available to be selected: - $browser->assertSelectMissingOptions($field, $values); +```php +$browser->assertSelectMissingOptions($field, $values); +``` #### assertSelectHasOption Assert that the given value is available to be selected on the given field: - $browser->assertSelectHasOption($field, $value); +```php +$browser->assertSelectHasOption($field, $value); +``` #### assertSelectMissingOption Assert that the given value is not available to be selected: - $browser->assertSelectMissingOption($field, $value); +```php +$browser->assertSelectMissingOption($field, $value); +``` #### assertValue Assert that the element matching the given selector has the given value: - $browser->assertValue($selector, $value); +```php +$browser->assertValue($selector, $value); +``` #### assertValueIsNot Assert that the element matching the given selector does not have the given value: - $browser->assertValueIsNot($selector, $value); +```php +$browser->assertValueIsNot($selector, $value); +``` #### assertAttribute Assert that the element matching the given selector has the given value in the provided attribute: - $browser->assertAttribute($selector, $attribute, $value); +```php +$browser->assertAttribute($selector, $attribute, $value); +``` #### assertAttributeMissing Assert that the element matching the given selector is missing the provided attribute: - $browser->assertAttributeMissing($selector, $attribute); +```php +$browser->assertAttributeMissing($selector, $attribute); +``` @@ -1644,148 +1964,192 @@ Assert that the element matching the given selector is missing the provided attr Assert that the element matching the given selector contains the given value in the provided attribute: - $browser->assertAttributeContains($selector, $attribute, $value); +```php +$browser->assertAttributeContains($selector, $attribute, $value); +``` #### assertAttributeDoesntContain Assert that the element matching the given selector does not contain the given value in the provided attribute: - $browser->assertAttributeDoesntContain($selector, $attribute, $value); +```php +$browser->assertAttributeDoesntContain($selector, $attribute, $value); +``` #### assertAriaAttribute Assert that the element matching the given selector has the given value in the provided aria attribute: - $browser->assertAriaAttribute($selector, $attribute, $value); +```php +$browser->assertAriaAttribute($selector, $attribute, $value); +``` For example, given the markup ``, you may assert against the `aria-label` attribute like so: - $browser->assertAriaAttribute('button', 'label', 'Add') +```php +$browser->assertAriaAttribute('button', 'label', 'Add') +``` #### assertDataAttribute Assert that the element matching the given selector has the given value in the provided data attribute: - $browser->assertDataAttribute($selector, $attribute, $value); +```php +$browser->assertDataAttribute($selector, $attribute, $value); +``` For example, given the markup ``, you may assert against the `data-label` attribute like so: - $browser->assertDataAttribute('#row-1', 'content', 'attendees') +```php +$browser->assertDataAttribute('#row-1', 'content', 'attendees') +``` #### assertVisible Assert that the element matching the given selector is visible: - $browser->assertVisible($selector); +```php +$browser->assertVisible($selector); +``` #### assertPresent Assert that the element matching the given selector is present in the source: - $browser->assertPresent($selector); +```php +$browser->assertPresent($selector); +``` #### assertNotPresent Assert that the element matching the given selector is not present in the source: - $browser->assertNotPresent($selector); +```php +$browser->assertNotPresent($selector); +``` #### assertMissing Assert that the element matching the given selector is not visible: - $browser->assertMissing($selector); +```php +$browser->assertMissing($selector); +``` #### assertInputPresent Assert that an input with the given name is present: - $browser->assertInputPresent($name); +```php +$browser->assertInputPresent($name); +``` #### assertInputMissing Assert that an input with the given name is not present in the source: - $browser->assertInputMissing($name); +```php +$browser->assertInputMissing($name); +``` #### assertDialogOpened Assert that a JavaScript dialog with the given message has been opened: - $browser->assertDialogOpened($message); +```php +$browser->assertDialogOpened($message); +``` #### assertEnabled Assert that the given field is enabled: - $browser->assertEnabled($field); +```php +$browser->assertEnabled($field); +``` #### assertDisabled Assert that the given field is disabled: - $browser->assertDisabled($field); +```php +$browser->assertDisabled($field); +``` #### assertButtonEnabled Assert that the given button is enabled: - $browser->assertButtonEnabled($button); +```php +$browser->assertButtonEnabled($button); +``` #### assertButtonDisabled Assert that the given button is disabled: - $browser->assertButtonDisabled($button); +```php +$browser->assertButtonDisabled($button); +``` #### assertFocused Assert that the given field is focused: - $browser->assertFocused($field); +```php +$browser->assertFocused($field); +``` #### assertNotFocused Assert that the given field is not focused: - $browser->assertNotFocused($field); +```php +$browser->assertNotFocused($field); +``` #### assertAuthenticated Assert that the user is authenticated: - $browser->assertAuthenticated(); +```php +$browser->assertAuthenticated(); +``` #### assertGuest Assert that the user is not authenticated: - $browser->assertGuest(); +```php +$browser->assertGuest(); +``` #### assertAuthenticatedAs Assert that the user is authenticated as the given user: - $browser->assertAuthenticatedAs($user); +```php +$browser->assertAuthenticatedAs($user); +``` #### assertVue @@ -1839,21 +2203,27 @@ public function test_vue(): void Assert that a given Vue component data property does not match the given value: - $browser->assertVueIsNot($property, $value, $componentSelector = null); +```php +$browser->assertVueIsNot($property, $value, $componentSelector = null); +``` #### assertVueContains Assert that a given Vue component data property is an array and contains the given value: - $browser->assertVueContains($property, $value, $componentSelector = null); +```php +$browser->assertVueContains($property, $value, $componentSelector = null); +``` #### assertVueDoesntContain Assert that a given Vue component data property is an array and does not contain the given value: - $browser->assertVueDoesntContain($property, $value, $componentSelector = null); +```php +$browser->assertVueDoesntContain($property, $value, $componentSelector = null); +``` ## Pages @@ -1865,7 +2235,9 @@ Sometimes, tests require several complicated actions to be performed in sequence To generate a page object, execute the `dusk:page` Artisan command. All page objects will be placed in your application's `tests/Browser/Pages` directory: - php artisan dusk:page Login +```shell +php artisan dusk:page Login +``` ### Configuring Pages @@ -1877,117 +2249,135 @@ By default, pages have three methods: `url`, `assert`, and `elements`. We will d The `url` method should return the path of the URL that represents the page. Dusk will use this URL when navigating to the page in the browser: - /** - * Get the URL for the page. - */ - public function url(): string - { - return '/login'; - } +```php +/** + * Get the URL for the page. + */ +public function url(): string +{ + return '/login'; +} +``` #### The `assert` Method The `assert` method may make any assertions necessary to verify that the browser is actually on the given page. It is not actually necessary to place anything within this method; however, you are free to make these assertions if you wish. These assertions will be run automatically when navigating to the page: - /** - * Assert that the browser is on the page. - */ - public function assert(Browser $browser): void - { - $browser->assertPathIs($this->url()); - } +```php +/** + * Assert that the browser is on the page. + */ +public function assert(Browser $browser): void +{ + $browser->assertPathIs($this->url()); +} +``` ### Navigating to Pages Once a page has been defined, you may navigate to it using the `visit` method: - use Tests\Browser\Pages\Login; +```php +use Tests\Browser\Pages\Login; - $browser->visit(new Login); +$browser->visit(new Login); +``` Sometimes you may already be on a given page and need to "load" the page's selectors and methods into the current test context. This is common when pressing a button and being redirected to a given page without explicitly navigating to it. In this situation, you may use the `on` method to load the page: - use Tests\Browser\Pages\CreatePlaylist; +```php +use Tests\Browser\Pages\CreatePlaylist; - $browser->visit('/dashboard') - ->clickLink('Create Playlist') - ->on(new CreatePlaylist) - ->assertSee('@create'); +$browser->visit('/dashboard') + ->clickLink('Create Playlist') + ->on(new CreatePlaylist) + ->assertSee('@create'); +``` ### Shorthand Selectors The `elements` method within page classes allows you to define quick, easy-to-remember shortcuts for any CSS selector on your page. For example, let's define a shortcut for the "email" input field of the application's login page: - /** - * Get the element shortcuts for the page. - * - * @return array - */ - public function elements(): array - { - return [ - '@email' => 'input[name=email]', - ]; - } +```php +/** + * Get the element shortcuts for the page. + * + * @return array + */ +public function elements(): array +{ + return [ + '@email' => 'input[name=email]', + ]; +} +``` Once the shortcut has been defined, you may use the shorthand selector anywhere you would typically use a full CSS selector: - $browser->type('@email', 'taylor@laravel.com'); +```php +$browser->type('@email', 'taylor@laravel.com'); +``` #### Global Shorthand Selectors After installing Dusk, a base `Page` class will be placed in your `tests/Browser/Pages` directory. This class contains a `siteElements` method which may be used to define global shorthand selectors that should be available on every page throughout your application: - /** - * Get the global element shortcuts for the site. - * - * @return array - */ - public static function siteElements(): array - { - return [ - '@element' => '#selector', - ]; - } +```php +/** + * Get the global element shortcuts for the site. + * + * @return array + */ +public static function siteElements(): array +{ + return [ + '@element' => '#selector', + ]; +} +``` ### Page Methods In addition to the default methods defined on pages, you may define additional methods which may be used throughout your tests. For example, let's imagine we are building a music management application. A common action for one page of the application might be to create a playlist. Instead of re-writing the logic to create a playlist in each test, you may define a `createPlaylist` method on a page class: - type('name', $name) - ->check('share') - ->press('Create Playlist'); - } + $browser->type('name', $name) + ->check('share') + ->press('Create Playlist'); } +} +``` Once the method has been defined, you may use it within any test that utilizes the page. The browser instance will automatically be passed as the first argument to custom page methods: - use Tests\Browser\Pages\Dashboard; +```php +use Tests\Browser\Pages\Dashboard; - $browser->visit(new Dashboard) - ->createPlaylist('My Playlist') - ->assertSee('My Playlist'); +$browser->visit(new Dashboard) + ->createPlaylist('My Playlist') + ->assertSee('My Playlist'); +``` ## Components @@ -1999,67 +2389,71 @@ Components are similar to Dusk’s “page objects”, but are intended for piec To generate a component, execute the `dusk:component` Artisan command. New components are placed in the `tests/Browser/Components` directory: - php artisan dusk:component DatePicker +```shell +php artisan dusk:component DatePicker +``` As shown above, a "date picker" is an example of a component that might exist throughout your application on a variety of pages. It can become cumbersome to manually write the browser automation logic to select a date in dozens of tests throughout your test suite. Instead, we can define a Dusk component to represent the date picker, allowing us to encapsulate that logic within the component: - assertVisible($this->selector()); - } + /** + * Assert that the browser page contains the component. + */ + public function assert(Browser $browser): void + { + $browser->assertVisible($this->selector()); + } - /** - * Get the element shortcuts for the component. - * - * @return array - */ - public function elements(): array - { - return [ - '@date-field' => 'input.datepicker-input', - '@year-list' => 'div > div.datepicker-years', - '@month-list' => 'div > div.datepicker-months', - '@day-list' => 'div > div.datepicker-days', - ]; - } + /** + * Get the element shortcuts for the component. + * + * @return array + */ + public function elements(): array + { + return [ + '@date-field' => 'input.datepicker-input', + '@year-list' => 'div > div.datepicker-years', + '@month-list' => 'div > div.datepicker-months', + '@day-list' => 'div > div.datepicker-days', + ]; + } - /** - * Select the given date. - */ - public function selectDate(Browser $browser, int $year, int $month, int $day): void - { - $browser->click('@date-field') - ->within('@year-list', function (Browser $browser) use ($year) { - $browser->click($year); - }) - ->within('@month-list', function (Browser $browser) use ($month) { - $browser->click($month); - }) - ->within('@day-list', function (Browser $browser) use ($day) { - $browser->click($day); - }); - } + /** + * Select the given date. + */ + public function selectDate(Browser $browser, int $year, int $month, int $day): void + { + $browser->click('@date-field') + ->within('@year-list', function (Browser $browser) use ($year) { + $browser->click($year); + }) + ->within('@month-list', function (Browser $browser) use ($month) { + $browser->click($month); + }) + ->within('@day-list', function (Browser $browser) use ($day) { + $browser->click($day); + }); } +} +``` ### Using Components @@ -2125,20 +2519,22 @@ class ExampleTest extends DuskTestCase To run Dusk tests on [Heroku CI](https://www.heroku.com/continuous-integration), add the following Google Chrome buildpack and scripts to your Heroku `app.json` file: - { - "environments": { - "test": { - "buildpacks": [ - { "url": "heroku/php" }, - { "url": "https://github.com/heroku/heroku-buildpack-chrome-for-testing" } - ], - "scripts": { - "test-setup": "cp .env.testing .env", - "test": "nohup bash -c './vendor/laravel/dusk/bin/chromedriver-linux --port=9515 > /dev/null 2>&1 &' && nohup bash -c 'php artisan serve --no-reload > /dev/null 2>&1 &' && php artisan dusk" - } - } +```json +{ + "environments": { + "test": { + "buildpacks": [ + { "url": "heroku/php" }, + { "url": "https://github.com/heroku/heroku-buildpack-chrome-for-testing" } + ], + "scripts": { + "test-setup": "cp .env.testing .env", + "test": "nohup bash -c './vendor/laravel/dusk/bin/chromedriver-linux --port=9515 > /dev/null 2>&1 &' && nohup bash -c 'php artisan serve --no-reload > /dev/null 2>&1 &' && php artisan dusk" } } + } +} +``` ### Travis CI diff --git a/eloquent-collections.md b/eloquent-collections.md index 860a1f7ea8e..4790f7a04e0 100644 --- a/eloquent-collections.md +++ b/eloquent-collections.md @@ -11,21 +11,25 @@ All Eloquent methods that return more than one model result will return instance All collections also serve as iterators, allowing you to loop over them as if they were simple PHP arrays: - use App\Models\User; +```php +use App\Models\User; - $users = User::where('active', 1)->get(); +$users = User::where('active', 1)->get(); - foreach ($users as $user) { - echo $user->name; - } +foreach ($users as $user) { + echo $user->name; +} +``` However, as previously mentioned, collections are much more powerful than arrays and expose a variety of map / reduce operations that may be chained using an intuitive interface. For example, we may remove all inactive models and then gather the first name for each remaining user: - $names = User::all()->reject(function (User $user) { - return $user->active === false; - })->map(function (User $user) { - return $user->name; - }); +```php +$names = User::all()->reject(function (User $user) { + return $user->active === false; +})->map(function (User $user) { + return $user->name; +}); +``` #### Eloquent Collection Conversion @@ -88,199 +92,239 @@ In addition, the `Illuminate\Database\Eloquent\Collection` class provides a supe The `append` method may be used to indicate that an attribute should be [appended](/docs/{{version}}/eloquent-serialization#appending-values-to-json) for every model in the collection. This method accepts an array of attributes or a single attribute: - $users->append('team'); +```php +$users->append('team'); - $users->append(['team', 'is_admin']); +$users->append(['team', 'is_admin']); +``` #### `contains($key, $operator = null, $value = null)` {.collection-method} The `contains` method may be used to determine if a given model instance is contained by the collection. This method accepts a primary key or a model instance: - $users->contains(1); +```php +$users->contains(1); - $users->contains(User::find(1)); +$users->contains(User::find(1)); +``` #### `diff($items)` {.collection-method} The `diff` method returns all of the models that are not present in the given collection: - use App\Models\User; +```php +use App\Models\User; - $users = $users->diff(User::whereIn('id', [1, 2, 3])->get()); +$users = $users->diff(User::whereIn('id', [1, 2, 3])->get()); +``` #### `except($keys)` {.collection-method} The `except` method returns all of the models that do not have the given primary keys: - $users = $users->except([1, 2, 3]); +```php +$users = $users->except([1, 2, 3]); +``` #### `find($key)` {.collection-method} The `find` method returns the model that has a primary key matching the given key. If `$key` is a model instance, `find` will attempt to return a model matching the primary key. If `$key` is an array of keys, `find` will return all models which have a primary key in the given array: - $users = User::all(); +```php +$users = User::all(); - $user = $users->find(1); +$user = $users->find(1); +``` #### `findOrFail($key)` {.collection-method} The `findOrFail` method returns the model that has a primary key matching the given key or throws an `Illuminate\Database\Eloquent\ModelNotFoundException` exception if no matching model can be found in the collection: - $users = User::all(); +```php +$users = User::all(); - $user = $users->findOrFail(1); +$user = $users->findOrFail(1); +``` #### `fresh($with = [])` {.collection-method} The `fresh` method retrieves a fresh instance of each model in the collection from the database. In addition, any specified relationships will be eager loaded: - $users = $users->fresh(); +```php +$users = $users->fresh(); - $users = $users->fresh('comments'); +$users = $users->fresh('comments'); +``` #### `intersect($items)` {.collection-method} The `intersect` method returns all of the models that are also present in the given collection: - use App\Models\User; +```php +use App\Models\User; - $users = $users->intersect(User::whereIn('id', [1, 2, 3])->get()); +$users = $users->intersect(User::whereIn('id', [1, 2, 3])->get()); +``` #### `load($relations)` {.collection-method} The `load` method eager loads the given relationships for all models in the collection: - $users->load(['comments', 'posts']); +```php +$users->load(['comments', 'posts']); - $users->load('comments.author'); +$users->load('comments.author'); - $users->load(['comments', 'posts' => fn ($query) => $query->where('active', 1)]); +$users->load(['comments', 'posts' => fn ($query) => $query->where('active', 1)]); +``` #### `loadMissing($relations)` {.collection-method} The `loadMissing` method eager loads the given relationships for all models in the collection if the relationships are not already loaded: - $users->loadMissing(['comments', 'posts']); +```php +$users->loadMissing(['comments', 'posts']); - $users->loadMissing('comments.author'); +$users->loadMissing('comments.author'); - $users->loadMissing(['comments', 'posts' => fn ($query) => $query->where('active', 1)]); +$users->loadMissing(['comments', 'posts' => fn ($query) => $query->where('active', 1)]); +``` #### `modelKeys()` {.collection-method} The `modelKeys` method returns the primary keys for all models in the collection: - $users->modelKeys(); +```php +$users->modelKeys(); - // [1, 2, 3, 4, 5] +// [1, 2, 3, 4, 5] +``` #### `makeVisible($attributes)` {.collection-method} The `makeVisible` method [makes attributes visible](/docs/{{version}}/eloquent-serialization#hiding-attributes-from-json) that are typically "hidden" on each model in the collection: - $users = $users->makeVisible(['address', 'phone_number']); +```php +$users = $users->makeVisible(['address', 'phone_number']); +``` #### `makeHidden($attributes)` {.collection-method} The `makeHidden` method [hides attributes](/docs/{{version}}/eloquent-serialization#hiding-attributes-from-json) that are typically "visible" on each model in the collection: - $users = $users->makeHidden(['address', 'phone_number']); +```php +$users = $users->makeHidden(['address', 'phone_number']); +``` #### `only($keys)` {.collection-method} The `only` method returns all of the models that have the given primary keys: - $users = $users->only([1, 2, 3]); +```php +$users = $users->only([1, 2, 3]); +``` #### `setVisible($attributes)` {.collection-method} The `setVisible` method [temporarily overrides](/docs/{{version}}/eloquent-serialization#temporarily-modifying-attribute-visibility) all of the visible attributes on each model in the collection: - $users = $users->setVisible(['id', 'name']); +```php +$users = $users->setVisible(['id', 'name']); +``` #### `setHidden($attributes)` {.collection-method} The `setHidden` method [temporarily overrides](/docs/{{version}}/eloquent-serialization#temporarily-modifying-attribute-visibility) all of the hidden attributes on each model in the collection: - $users = $users->setHidden(['email', 'password', 'remember_token']); +```php +$users = $users->setHidden(['email', 'password', 'remember_token']); +``` #### `toQuery()` {.collection-method} The `toQuery` method returns an Eloquent query builder instance containing a `whereIn` constraint on the collection model's primary keys: - use App\Models\User; +```php +use App\Models\User; - $users = User::where('status', 'VIP')->get(); +$users = User::where('status', 'VIP')->get(); - $users->toQuery()->update([ - 'status' => 'Administrator', - ]); +$users->toQuery()->update([ + 'status' => 'Administrator', +]); +``` #### `unique($key = null, $strict = false)` {.collection-method} The `unique` method returns all of the unique models in the collection. Any models with the same primary key as another model in the collection are removed: - $users = $users->unique(); +```php +$users = $users->unique(); +``` ## Custom Collections If you would like to use a custom `Collection` object when interacting with a given model, you may add the `CollectedBy` attribute to your model: - $models + * @return \Illuminate\Database\Eloquent\Collection + */ + public function newCollection(array $models = []): Collection { - /** - * Create a new Eloquent Collection instance. - * - * @param array $models - * @return \Illuminate\Database\Eloquent\Collection - */ - public function newCollection(array $models = []): Collection - { - return new UserCollection($models); - } + return new UserCollection($models); } +} +``` Once you have defined a `newCollection` method or added the `CollectedBy` attribute to your model, you will receive an instance of your custom collection anytime Eloquent would normally return an `Illuminate\Database\Eloquent\Collection` instance. diff --git a/eloquent-factories.md b/eloquent-factories.md index dd315a8ff34..a95cfa08096 100644 --- a/eloquent-factories.md +++ b/eloquent-factories.md @@ -24,48 +24,50 @@ When testing your application or seeding your database, you may need to insert a To see an example of how to write a factory, take a look at the `database/factories/UserFactory.php` file in your application. This factory is included with all new Laravel applications and contains the following factory definition: - namespace Database\Factories; +```php +namespace Database\Factories; + +use Illuminate\Database\Eloquent\Factories\Factory; +use Illuminate\Support\Facades\Hash; +use Illuminate\Support\Str; + +/** + * @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\User> + */ +class UserFactory extends Factory +{ + /** + * The current password being used by the factory. + */ + protected static ?string $password; - use Illuminate\Database\Eloquent\Factories\Factory; - use Illuminate\Support\Facades\Hash; - use Illuminate\Support\Str; + /** + * Define the model's default state. + * + * @return array + */ + public function definition(): array + { + return [ + 'name' => fake()->name(), + 'email' => fake()->unique()->safeEmail(), + 'email_verified_at' => now(), + 'password' => static::$password ??= Hash::make('password'), + 'remember_token' => Str::random(10), + ]; + } /** - * @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\User> + * Indicate that the model's email address should be unverified. */ - class UserFactory extends Factory + public function unverified(): static { - /** - * The current password being used by the factory. - */ - protected static ?string $password; - - /** - * Define the model's default state. - * - * @return array - */ - public function definition(): array - { - return [ - 'name' => fake()->name(), - 'email' => fake()->unique()->safeEmail(), - 'email_verified_at' => now(), - 'password' => static::$password ??= Hash::make('password'), - 'remember_token' => Str::random(10), - ]; - } - - /** - * Indicate that the model's email address should be unverified. - */ - public function unverified(): static - { - return $this->state(fn (array $attributes) => [ - 'email_verified_at' => null, - ]); - } + return $this->state(fn (array $attributes) => [ + 'email_verified_at' => null, + ]); } +} +``` As you can see, in their most basic form, factories are classes that extend Laravel's base factory class and define a `definition` method. The `definition` method returns the default set of attribute values that should be applied when creating a model using the factory. @@ -95,30 +97,34 @@ Once you have defined your factories, you may use the static `factory` method pr The `HasFactory` trait's `factory` method will use conventions to determine the proper factory for the model the trait is assigned to. Specifically, the method will look for a factory in the `Database\Factories` namespace that has a class name matching the model name and is suffixed with `Factory`. If these conventions do not apply to your particular application or factory, you may overwrite the `newFactory` method on your model to return an instance of the model's corresponding factory directly: - use Database\Factories\Administration\FlightFactory; +```php +use Database\Factories\Administration\FlightFactory; - /** - * Create a new factory instance for the model. - */ - protected static function newFactory() - { - return FlightFactory::new(); - } +/** + * Create a new factory instance for the model. + */ +protected static function newFactory() +{ + return FlightFactory::new(); +} +``` Then, define a `model` property on the corresponding factory: - use App\Administration\Flight; - use Illuminate\Database\Eloquent\Factories\Factory; +```php +use App\Administration\Flight; +use Illuminate\Database\Eloquent\Factories\Factory; - class FlightFactory extends Factory - { - /** - * The name of the factory's corresponding model. - * - * @var class-string<\Illuminate\Database\Eloquent\Model> - */ - protected $model = Flight::class; - } +class FlightFactory extends Factory +{ + /** + * The name of the factory's corresponding model. + * + * @var class-string<\Illuminate\Database\Eloquent\Model> + */ + protected $model = Flight::class; +} +``` ### Factory States @@ -127,77 +133,85 @@ State manipulation methods allow you to define discrete modifications that can b State transformation methods typically call the `state` method provided by Laravel's base factory class. The `state` method accepts a closure which will receive the array of raw attributes defined for the factory and should return an array of attributes to modify: - use Illuminate\Database\Eloquent\Factories\Factory; +```php +use Illuminate\Database\Eloquent\Factories\Factory; - /** - * Indicate that the user is suspended. - */ - public function suspended(): Factory - { - return $this->state(function (array $attributes) { - return [ - 'account_status' => 'suspended', - ]; - }); - } +/** + * Indicate that the user is suspended. + */ +public function suspended(): Factory +{ + return $this->state(function (array $attributes) { + return [ + 'account_status' => 'suspended', + ]; + }); +} +``` #### "Trashed" State If your Eloquent model can be [soft deleted](/docs/{{version}}/eloquent#soft-deleting), you may invoke the built-in `trashed` state method to indicate that the created model should already be "soft deleted". You do not need to manually define the `trashed` state as it is automatically available to all factories: - use App\Models\User; +```php +use App\Models\User; - $user = User::factory()->trashed()->create(); +$user = User::factory()->trashed()->create(); +``` ### Factory Callbacks Factory callbacks are registered using the `afterMaking` and `afterCreating` methods and allow you to perform additional tasks after making or creating a model. You should register these callbacks by defining a `configure` method on your factory class. This method will be automatically called by Laravel when the factory is instantiated: - namespace Database\Factories; - - use App\Models\User; - use Illuminate\Database\Eloquent\Factories\Factory; - - class UserFactory extends Factory - { - /** - * Configure the model factory. - */ - public function configure(): static - { - return $this->afterMaking(function (User $user) { - // ... - })->afterCreating(function (User $user) { - // ... - }); - } +```php +namespace Database\Factories; - // ... - } - -You may also register factory callbacks within state methods to perform additional tasks that are specific to a given state: - - use App\Models\User; - use Illuminate\Database\Eloquent\Factories\Factory; +use App\Models\User; +use Illuminate\Database\Eloquent\Factories\Factory; +class UserFactory extends Factory +{ /** - * Indicate that the user is suspended. + * Configure the model factory. */ - public function suspended(): Factory + public function configure(): static { - return $this->state(function (array $attributes) { - return [ - 'account_status' => 'suspended', - ]; - })->afterMaking(function (User $user) { + return $this->afterMaking(function (User $user) { // ... })->afterCreating(function (User $user) { // ... }); } + // ... +} +``` + +You may also register factory callbacks within state methods to perform additional tasks that are specific to a given state: + +```php +use App\Models\User; +use Illuminate\Database\Eloquent\Factories\Factory; + +/** + * Indicate that the user is suspended. + */ +public function suspended(): Factory +{ + return $this->state(function (array $attributes) { + return [ + 'account_status' => 'suspended', + ]; + })->afterMaking(function (User $user) { + // ... + })->afterCreating(function (User $user) { + // ... + }); +} +``` + ## Creating Models Using Factories @@ -206,35 +220,45 @@ You may also register factory callbacks within state methods to perform addition Once you have defined your factories, you may use the static `factory` method provided to your models by the `Illuminate\Database\Eloquent\Factories\HasFactory` trait in order to instantiate a factory instance for that model. Let's take a look at a few examples of creating models. First, we'll use the `make` method to create models without persisting them to the database: - use App\Models\User; +```php +use App\Models\User; - $user = User::factory()->make(); +$user = User::factory()->make(); +``` You may create a collection of many models using the `count` method: - $users = User::factory()->count(3)->make(); +```php +$users = User::factory()->count(3)->make(); +``` #### Applying States You may also apply any of your [states](#factory-states) to the models. If you would like to apply multiple state transformations to the models, you may simply call the state transformation methods directly: - $users = User::factory()->count(5)->suspended()->make(); +```php +$users = User::factory()->count(5)->suspended()->make(); +``` #### Overriding Attributes If you would like to override some of the default values of your models, you may pass an array of values to the `make` method. Only the specified attributes will be replaced while the rest of the attributes remain set to their default values as specified by the factory: - $user = User::factory()->make([ - 'name' => 'Abigail Otwell', - ]); +```php +$user = User::factory()->make([ + 'name' => 'Abigail Otwell', +]); +``` Alternatively, the `state` method may be called directly on the factory instance to perform an inline state transformation: - $user = User::factory()->state([ - 'name' => 'Abigail Otwell', - ])->make(); +```php +$user = User::factory()->state([ + 'name' => 'Abigail Otwell', +])->make(); +``` > [!NOTE] > [Mass assignment protection](/docs/{{version}}/eloquent#mass-assignment) is automatically disabled when creating models using factories. @@ -244,65 +268,77 @@ Alternatively, the `state` method may be called directly on the factory instance The `create` method instantiates model instances and persists them to the database using Eloquent's `save` method: - use App\Models\User; +```php +use App\Models\User; - // Create a single App\Models\User instance... - $user = User::factory()->create(); +// Create a single App\Models\User instance... +$user = User::factory()->create(); - // Create three App\Models\User instances... - $users = User::factory()->count(3)->create(); +// Create three App\Models\User instances... +$users = User::factory()->count(3)->create(); +``` You may override the factory's default model attributes by passing an array of attributes to the `create` method: - $user = User::factory()->create([ - 'name' => 'Abigail', - ]); +```php +$user = User::factory()->create([ + 'name' => 'Abigail', +]); +``` ### Sequences Sometimes you may wish to alternate the value of a given model attribute for each created model. You may accomplish this by defining a state transformation as a sequence. For example, you may wish to alternate the value of an `admin` column between `Y` and `N` for each created user: - use App\Models\User; - use Illuminate\Database\Eloquent\Factories\Sequence; - - $users = User::factory() - ->count(10) - ->state(new Sequence( - ['admin' => 'Y'], - ['admin' => 'N'], - )) - ->create(); +```php +use App\Models\User; +use Illuminate\Database\Eloquent\Factories\Sequence; + +$users = User::factory() + ->count(10) + ->state(new Sequence( + ['admin' => 'Y'], + ['admin' => 'N'], + )) + ->create(); +``` In this example, five users will be created with an `admin` value of `Y` and five users will be created with an `admin` value of `N`. If necessary, you may include a closure as a sequence value. The closure will be invoked each time the sequence needs a new value: - use Illuminate\Database\Eloquent\Factories\Sequence; +```php +use Illuminate\Database\Eloquent\Factories\Sequence; - $users = User::factory() - ->count(10) - ->state(new Sequence( - fn (Sequence $sequence) => ['role' => UserRoles::all()->random()], - )) - ->create(); +$users = User::factory() + ->count(10) + ->state(new Sequence( + fn (Sequence $sequence) => ['role' => UserRoles::all()->random()], + )) + ->create(); +``` Within a sequence closure, you may access the `$index` or `$count` properties on the sequence instance that is injected into the closure. The `$index` property contains the number of iterations through the sequence that have occurred thus far, while the `$count` property contains the total number of times the sequence will be invoked: - $users = User::factory() - ->count(10) - ->sequence(fn (Sequence $sequence) => ['name' => 'Name '.$sequence->index]) - ->create(); +```php +$users = User::factory() + ->count(10) + ->sequence(fn (Sequence $sequence) => ['name' => 'Name '.$sequence->index]) + ->create(); +``` For convenience, sequences may also be applied using the `sequence` method, which simply invokes the `state` method internally. The `sequence` method accepts a closure or arrays of sequenced attributes: - $users = User::factory() - ->count(2) - ->sequence( - ['name' => 'First User'], - ['name' => 'Second User'], - ) - ->create(); +```php +$users = User::factory() + ->count(2) + ->sequence( + ['name' => 'First User'], + ['name' => 'Second User'], + ) + ->create(); +``` ## Factory Relationships @@ -312,230 +348,270 @@ For convenience, sequences may also be applied using the `sequence` method, whic Next, let's explore building Eloquent model relationships using Laravel's fluent factory methods. First, let's assume our application has an `App\Models\User` model and an `App\Models\Post` model. Also, let's assume that the `User` model defines a `hasMany` relationship with `Post`. We can create a user that has three posts using the `has` method provided by the Laravel's factories. The `has` method accepts a factory instance: - use App\Models\Post; - use App\Models\User; +```php +use App\Models\Post; +use App\Models\User; - $user = User::factory() - ->has(Post::factory()->count(3)) - ->create(); +$user = User::factory() + ->has(Post::factory()->count(3)) + ->create(); +``` By convention, when passing a `Post` model to the `has` method, Laravel will assume that the `User` model must have a `posts` method that defines the relationship. If necessary, you may explicitly specify the name of the relationship that you would like to manipulate: - $user = User::factory() - ->has(Post::factory()->count(3), 'posts') - ->create(); +```php +$user = User::factory() + ->has(Post::factory()->count(3), 'posts') + ->create(); +``` Of course, you may perform state manipulations on the related models. In addition, you may pass a closure based state transformation if your state change requires access to the parent model: - $user = User::factory() - ->has( - Post::factory() - ->count(3) - ->state(function (array $attributes, User $user) { - return ['user_type' => $user->type]; - }) - ) - ->create(); +```php +$user = User::factory() + ->has( + Post::factory() + ->count(3) + ->state(function (array $attributes, User $user) { + return ['user_type' => $user->type]; + }) + ) + ->create(); +``` #### Using Magic Methods For convenience, you may use Laravel's magic factory relationship methods to build relationships. For example, the following example will use convention to determine that the related models should be created via a `posts` relationship method on the `User` model: - $user = User::factory() - ->hasPosts(3) - ->create(); +```php +$user = User::factory() + ->hasPosts(3) + ->create(); +``` When using magic methods to create factory relationships, you may pass an array of attributes to override on the related models: - $user = User::factory() - ->hasPosts(3, [ - 'published' => false, - ]) - ->create(); +```php +$user = User::factory() + ->hasPosts(3, [ + 'published' => false, + ]) + ->create(); +``` You may provide a closure based state transformation if your state change requires access to the parent model: - $user = User::factory() - ->hasPosts(3, function (array $attributes, User $user) { - return ['user_type' => $user->type]; - }) - ->create(); +```php +$user = User::factory() + ->hasPosts(3, function (array $attributes, User $user) { + return ['user_type' => $user->type]; + }) + ->create(); +``` ### Belongs To Relationships Now that we have explored how to build "has many" relationships using factories, let's explore the inverse of the relationship. The `for` method may be used to define the parent model that factory created models belong to. For example, we can create three `App\Models\Post` model instances that belong to a single user: - use App\Models\Post; - use App\Models\User; +```php +use App\Models\Post; +use App\Models\User; - $posts = Post::factory() - ->count(3) - ->for(User::factory()->state([ - 'name' => 'Jessica Archer', - ])) - ->create(); +$posts = Post::factory() + ->count(3) + ->for(User::factory()->state([ + 'name' => 'Jessica Archer', + ])) + ->create(); +``` If you already have a parent model instance that should be associated with the models you are creating, you may pass the model instance to the `for` method: - $user = User::factory()->create(); +```php +$user = User::factory()->create(); - $posts = Post::factory() - ->count(3) - ->for($user) - ->create(); +$posts = Post::factory() + ->count(3) + ->for($user) + ->create(); +``` #### Using Magic Methods For convenience, you may use Laravel's magic factory relationship methods to define "belongs to" relationships. For example, the following example will use convention to determine that the three posts should belong to the `user` relationship on the `Post` model: - $posts = Post::factory() - ->count(3) - ->forUser([ - 'name' => 'Jessica Archer', - ]) - ->create(); +```php +$posts = Post::factory() + ->count(3) + ->forUser([ + 'name' => 'Jessica Archer', + ]) + ->create(); +``` ### Many to Many Relationships Like [has many relationships](#has-many-relationships), "many to many" relationships may be created using the `has` method: - use App\Models\Role; - use App\Models\User; +```php +use App\Models\Role; +use App\Models\User; - $user = User::factory() - ->has(Role::factory()->count(3)) - ->create(); +$user = User::factory() + ->has(Role::factory()->count(3)) + ->create(); +``` #### Pivot Table Attributes If you need to define attributes that should be set on the pivot / intermediate table linking the models, you may use the `hasAttached` method. This method accepts an array of pivot table attribute names and values as its second argument: - use App\Models\Role; - use App\Models\User; +```php +use App\Models\Role; +use App\Models\User; - $user = User::factory() - ->hasAttached( - Role::factory()->count(3), - ['active' => true] - ) - ->create(); +$user = User::factory() + ->hasAttached( + Role::factory()->count(3), + ['active' => true] + ) + ->create(); +``` You may provide a closure based state transformation if your state change requires access to the related model: - $user = User::factory() - ->hasAttached( - Role::factory() - ->count(3) - ->state(function (array $attributes, User $user) { - return ['name' => $user->name.' Role']; - }), - ['active' => true] - ) - ->create(); +```php +$user = User::factory() + ->hasAttached( + Role::factory() + ->count(3) + ->state(function (array $attributes, User $user) { + return ['name' => $user->name.' Role']; + }), + ['active' => true] + ) + ->create(); +``` If you already have model instances that you would like to be attached to the models you are creating, you may pass the model instances to the `hasAttached` method. In this example, the same three roles will be attached to all three users: - $roles = Role::factory()->count(3)->create(); +```php +$roles = Role::factory()->count(3)->create(); - $user = User::factory() - ->count(3) - ->hasAttached($roles, ['active' => true]) - ->create(); +$user = User::factory() + ->count(3) + ->hasAttached($roles, ['active' => true]) + ->create(); +``` #### Using Magic Methods For convenience, you may use Laravel's magic factory relationship methods to define many to many relationships. For example, the following example will use convention to determine that the related models should be created via a `roles` relationship method on the `User` model: - $user = User::factory() - ->hasRoles(1, [ - 'name' => 'Editor' - ]) - ->create(); +```php +$user = User::factory() + ->hasRoles(1, [ + 'name' => 'Editor' + ]) + ->create(); +``` ### Polymorphic Relationships [Polymorphic relationships](/docs/{{version}}/eloquent-relationships#polymorphic-relationships) may also be created using factories. Polymorphic "morph many" relationships are created in the same way as typical "has many" relationships. For example, if an `App\Models\Post` model has a `morphMany` relationship with an `App\Models\Comment` model: - use App\Models\Post; +```php +use App\Models\Post; - $post = Post::factory()->hasComments(3)->create(); +$post = Post::factory()->hasComments(3)->create(); +``` #### Morph To Relationships Magic methods may not be used to create `morphTo` relationships. Instead, the `for` method must be used directly and the name of the relationship must be explicitly provided. For example, imagine that the `Comment` model has a `commentable` method that defines a `morphTo` relationship. In this situation, we may create three comments that belong to a single post by using the `for` method directly: - $comments = Comment::factory()->count(3)->for( - Post::factory(), 'commentable' - )->create(); +```php +$comments = Comment::factory()->count(3)->for( + Post::factory(), 'commentable' +)->create(); +``` #### Polymorphic Many to Many Relationships Polymorphic "many to many" (`morphToMany` / `morphedByMany`) relationships may be created just like non-polymorphic "many to many" relationships: - use App\Models\Tag; - use App\Models\Video; +```php +use App\Models\Tag; +use App\Models\Video; - $videos = Video::factory() - ->hasAttached( - Tag::factory()->count(3), - ['public' => true] - ) - ->create(); +$videos = Video::factory() + ->hasAttached( + Tag::factory()->count(3), + ['public' => true] + ) + ->create(); +``` Of course, the magic `has` method may also be used to create polymorphic "many to many" relationships: - $videos = Video::factory() - ->hasTags(3, ['public' => true]) - ->create(); +```php +$videos = Video::factory() + ->hasTags(3, ['public' => true]) + ->create(); +``` ### Defining Relationships Within Factories To define a relationship within your model factory, you will typically assign a new factory instance to the foreign key of the relationship. This is normally done for the "inverse" relationships such as `belongsTo` and `morphTo` relationships. For example, if you would like to create a new user when creating a post, you may do the following: - use App\Models\User; - - /** - * Define the model's default state. - * - * @return array - */ - public function definition(): array - { - return [ - 'user_id' => User::factory(), - 'title' => fake()->title(), - 'content' => fake()->paragraph(), - ]; - } +```php +use App\Models\User; + +/** + * Define the model's default state. + * + * @return array + */ +public function definition(): array +{ + return [ + 'user_id' => User::factory(), + 'title' => fake()->title(), + 'content' => fake()->paragraph(), + ]; +} +``` If the relationship's columns depend on the factory that defines it you may assign a closure to an attribute. The closure will receive the factory's evaluated attribute array: - /** - * Define the model's default state. - * - * @return array - */ - public function definition(): array - { - return [ - 'user_id' => User::factory(), - 'user_type' => function (array $attributes) { - return User::find($attributes['user_id'])->type; - }, - 'title' => fake()->title(), - 'content' => fake()->paragraph(), - ]; - } +```php +/** + * Define the model's default state. + * + * @return array + */ +public function definition(): array +{ + return [ + 'user_id' => User::factory(), + 'user_type' => function (array $attributes) { + return User::find($attributes['user_id'])->type; + }, + 'title' => fake()->title(), + 'content' => fake()->paragraph(), + ]; +} +``` ### Recycling an Existing Model for Relationships @@ -544,14 +620,18 @@ If you have models that share a common relationship with another model, you may For example, imagine you have `Airline`, `Flight`, and `Ticket` models, where the ticket belongs to an airline and a flight, and the flight also belongs to an airline. When creating tickets, you will probably want the same airline for both the ticket and the flight, so you may pass an airline instance to the `recycle` method: - Ticket::factory() - ->recycle(Airline::factory()->create()) - ->create(); +```php +Ticket::factory() + ->recycle(Airline::factory()->create()) + ->create(); +``` You may find the `recycle` method particularly useful if you have models belonging to a common user or team. The `recycle` method also accepts a collection of existing models. When a collection is provided to the `recycle` method, a random model from the collection will be chosen when the factory needs a model of that type: - Ticket::factory() - ->recycle($airlines) - ->create(); +```php +Ticket::factory() + ->recycle($airlines) + ->create(); +``` diff --git a/eloquent-mutators.md b/eloquent-mutators.md index 60c6aefe87b..c7909cc12f5 100644 --- a/eloquent-mutators.md +++ b/eloquent-mutators.md @@ -32,35 +32,39 @@ An accessor transforms an Eloquent attribute value when it is accessed. To defin In this example, we'll define an accessor for the `first_name` attribute. The accessor will automatically be called by Eloquent when attempting to retrieve the value of the `first_name` attribute. All attribute accessor / mutator methods must declare a return type-hint of `Illuminate\Database\Eloquent\Casts\Attribute`: - ucfirst($value), - ); - } + return Attribute::make( + get: fn (string $value) => ucfirst($value), + ); } +} +``` All accessor methods return an `Attribute` instance which defines how the attribute will be accessed and, optionally, mutated. In this example, we are only defining how the attribute will be accessed. To do so, we supply the `get` argument to the `Attribute` class constructor. As you can see, the original value of the column is passed to the accessor, allowing you to manipulate and return the value. To access the value of the accessor, you may simply access the `first_name` attribute on a model instance: - use App\Models\User; +```php +use App\Models\User; - $user = User::find(1); +$user = User::find(1); - $firstName = $user->first_name; +$firstName = $user->first_name; +``` > [!NOTE] > If you would like these computed values to be added to the array / JSON representations of your model, [you will need to append them](/docs/{{version}}/eloquent-serialization#appending-values-to-json). @@ -93,14 +97,16 @@ protected function address(): Attribute When returning value objects from accessors, any changes made to the value object will automatically be synced back to the model before the model is saved. This is possible because Eloquent retains instances returned by accessors so it can return the same instance each time the accessor is invoked: - use App\Models\User; +```php +use App\Models\User; - $user = User::find(1); +$user = User::find(1); - $user->address->lineOne = 'Updated Address Line 1 Value'; - $user->address->lineTwo = 'Updated Address Line 2 Value'; +$user->address->lineOne = 'Updated Address Line 1 Value'; +$user->address->lineTwo = 'Updated Address Line 2 Value'; - $user->save(); +$user->save(); +``` However, you may sometimes wish to enable caching for primitive values like strings and booleans, particularly if they are computationally intensive. To accomplish this, you may invoke the `shouldCache` method when defining your accessor: @@ -135,34 +141,38 @@ protected function address(): Attribute A mutator transforms an Eloquent attribute value when it is set. To define a mutator, you may provide the `set` argument when defining your attribute. Let's define a mutator for the `first_name` attribute. This mutator will be automatically called when we attempt to set the value of the `first_name` attribute on the model: - ucfirst($value), - set: fn (string $value) => strtolower($value), - ); - } + return Attribute::make( + get: fn (string $value) => ucfirst($value), + set: fn (string $value) => strtolower($value), + ); } +} +``` The mutator closure will receive the value that is being set on the attribute, allowing you to manipulate the value and return the manipulated value. To use our mutator, we only need to set the `first_name` attribute on an Eloquent model: - use App\Models\User; +```php +use App\Models\User; - $user = User::find(1); +$user = User::find(1); - $user->first_name = 'Sally'; +$user->first_name = 'Sally'; +``` In this example, the `set` callback will be called with the value `Sally`. The mutator will then apply the `strtolower` function to the name and set its resulting value in the model's internal `$attributes` array. @@ -228,41 +238,47 @@ The `casts` method should return an array where the key is the name of the attri To demonstrate attribute casting, let's cast the `is_admin` attribute, which is stored in our database as an integer (`0` or `1`) to a boolean value: - + */ + protected function casts(): array { - /** - * Get the attributes that should be cast. - * - * @return array - */ - protected function casts(): array - { - return [ - 'is_admin' => 'boolean', - ]; - } + return [ + 'is_admin' => 'boolean', + ]; } +} +``` After defining the cast, the `is_admin` attribute will always be cast to a boolean when you access it, even if the underlying value is stored in the database as an integer: - $user = App\Models\User::find(1); +```php +$user = App\Models\User::find(1); - if ($user->is_admin) { - // ... - } +if ($user->is_admin) { + // ... +} +``` If you need to add a new, temporary cast at runtime, you may use the `mergeCasts` method. These cast definitions will be added to any of the casts already defined on the model: - $user->mergeCasts([ - 'is_admin' => 'integer', - 'options' => 'object', - ]); +```php +$user->mergeCasts([ + 'is_admin' => 'integer', + 'options' => 'object', +]); +``` > [!WARNING] > Attributes that are `null` will not be cast. In addition, you should never define a cast (or an attribute) that has the same name as a relationship or assign a cast to the model's primary key. @@ -272,131 +288,147 @@ If you need to add a new, temporary cast at runtime, you may use the `mergeCasts You may use the `Illuminate\Database\Eloquent\Casts\AsStringable` cast class to cast a model attribute to a [fluent `Illuminate\Support\Stringable` object](/docs/{{version}}/strings#fluent-strings-method-list): - + */ + protected function casts(): array { - /** - * Get the attributes that should be cast. - * - * @return array - */ - protected function casts(): array - { - return [ - 'directory' => AsStringable::class, - ]; - } + return [ + 'directory' => AsStringable::class, + ]; } +} +``` ### Array and JSON Casting The `array` cast is particularly useful when working with columns that are stored as serialized JSON. For example, if your database has a `JSON` or `TEXT` field type that contains serialized JSON, adding the `array` cast to that attribute will automatically deserialize the attribute to a PHP array when you access it on your Eloquent model: - + */ + protected function casts(): array { - /** - * Get the attributes that should be cast. - * - * @return array - */ - protected function casts(): array - { - return [ - 'options' => 'array', - ]; - } + return [ + 'options' => 'array', + ]; } +} +``` Once the cast is defined, you may access the `options` attribute and it will automatically be deserialized from JSON into a PHP array. When you set the value of the `options` attribute, the given array will automatically be serialized back into JSON for storage: - use App\Models\User; +```php +use App\Models\User; - $user = User::find(1); +$user = User::find(1); - $options = $user->options; +$options = $user->options; - $options['key'] = 'value'; +$options['key'] = 'value'; - $user->options = $options; +$user->options = $options; - $user->save(); +$user->save(); +``` To update a single field of a JSON attribute with a more terse syntax, you may [make the attribute mass assignable](/docs/{{version}}/eloquent#mass-assignment-json-columns) and use the `->` operator when calling the `update` method: - $user = User::find(1); +```php +$user = User::find(1); - $user->update(['options->key' => 'value']); +$user->update(['options->key' => 'value']); +``` #### Array Object and Collection Casting Although the standard `array` cast is sufficient for many applications, it does have some disadvantages. Since the `array` cast returns a primitive type, it is not possible to mutate an offset of the array directly. For example, the following code will trigger a PHP error: - $user = User::find(1); +```php +$user = User::find(1); - $user->options['key'] = $value; +$user->options['key'] = $value; +``` To solve this, Laravel offers an `AsArrayObject` cast that casts your JSON attribute to an [ArrayObject](https://www.php.net/manual/en/class.arrayobject.php) class. This feature is implemented using Laravel's [custom cast](#custom-casts) implementation, which allows Laravel to intelligently cache and transform the mutated object such that individual offsets may be modified without triggering a PHP error. To use the `AsArrayObject` cast, simply assign it to an attribute: - use Illuminate\Database\Eloquent\Casts\AsArrayObject; +```php +use Illuminate\Database\Eloquent\Casts\AsArrayObject; - /** - * Get the attributes that should be cast. - * - * @return array - */ - protected function casts(): array - { - return [ - 'options' => AsArrayObject::class, - ]; - } +/** + * Get the attributes that should be cast. + * + * @return array + */ +protected function casts(): array +{ + return [ + 'options' => AsArrayObject::class, + ]; +} +``` Similarly, Laravel offers an `AsCollection` cast that casts your JSON attribute to a Laravel [Collection](/docs/{{version}}/collections) instance: - use Illuminate\Database\Eloquent\Casts\AsCollection; +```php +use Illuminate\Database\Eloquent\Casts\AsCollection; - /** - * Get the attributes that should be cast. - * - * @return array - */ - protected function casts(): array - { - return [ - 'options' => AsCollection::class, - ]; - } +/** + * Get the attributes that should be cast. + * + * @return array + */ +protected function casts(): array +{ + return [ + 'options' => AsCollection::class, + ]; +} +``` If you would like the `AsCollection` cast to instantiate a custom collection class instead of Laravel's base collection class, you may provide the collection class name as a cast argument: - use App\Collections\OptionCollection; - use Illuminate\Database\Eloquent\Casts\AsCollection; +```php +use App\Collections\OptionCollection; +use Illuminate\Database\Eloquent\Casts\AsCollection; - /** - * Get the attributes that should be cast. - * - * @return array - */ - protected function casts(): array - { - return [ - 'options' => AsCollection::using(OptionCollection::class), - ]; - } +/** + * Get the attributes that should be cast. + * + * @return array + */ +protected function casts(): array +{ + return [ + 'options' => AsCollection::using(OptionCollection::class), + ]; +} +``` ### Date Casting @@ -405,38 +437,44 @@ By default, Eloquent will cast the `created_at` and `updated_at` columns to inst When defining a `date` or `datetime` cast, you may also specify the date's format. This format will be used when the [model is serialized to an array or JSON](/docs/{{version}}/eloquent-serialization): - /** - * Get the attributes that should be cast. - * - * @return array - */ - protected function casts(): array - { - return [ - 'created_at' => 'datetime:Y-m-d', - ]; - } +```php +/** + * Get the attributes that should be cast. + * + * @return array + */ +protected function casts(): array +{ + return [ + 'created_at' => 'datetime:Y-m-d', + ]; +} +``` When a column is cast as a date, you may set the corresponding model attribute value to a UNIX timestamp, date string (`Y-m-d`), date-time string, or a `DateTime` / `Carbon` instance. The date's value will be correctly converted and stored in your database. You may customize the default serialization format for all of your model's dates by defining a `serializeDate` method on your model. This method does not affect how your dates are formatted for storage in the database: - /** - * Prepare a date for array / JSON serialization. - */ - protected function serializeDate(DateTimeInterface $date): string - { - return $date->format('Y-m-d'); - } +```php +/** + * Prepare a date for array / JSON serialization. + */ +protected function serializeDate(DateTimeInterface $date): string +{ + return $date->format('Y-m-d'); +} +``` To specify the format that should be used when actually storing a model's dates within your database, you should define a `$dateFormat` property on your model: - /** - * The storage format of the model's date columns. - * - * @var string - */ - protected $dateFormat = 'U'; +```php +/** + * The storage format of the model's date columns. + * + * @var string + */ +protected $dateFormat = 'U'; +``` #### Date Casting, Serialization, and Timezones @@ -450,47 +488,53 @@ If a custom format is applied to the `date` or `datetime` cast, such as `datetim Eloquent also allows you to cast your attribute values to PHP [Enums](https://www.php.net/manual/en/language.enumerations.backed.php). To accomplish this, you may specify the attribute and enum you wish to cast in your model's `casts` method: - use App\Enums\ServerStatus; +```php +use App\Enums\ServerStatus; - /** - * Get the attributes that should be cast. - * - * @return array - */ - protected function casts(): array - { - return [ - 'status' => ServerStatus::class, - ]; - } +/** + * Get the attributes that should be cast. + * + * @return array + */ +protected function casts(): array +{ + return [ + 'status' => ServerStatus::class, + ]; +} +``` Once you have defined the cast on your model, the specified attribute will be automatically cast to and from an enum when you interact with the attribute: - if ($server->status == ServerStatus::Provisioned) { - $server->status = ServerStatus::Ready; +```php +if ($server->status == ServerStatus::Provisioned) { + $server->status = ServerStatus::Ready; - $server->save(); - } + $server->save(); +} +``` #### Casting Arrays of Enums Sometimes you may need your model to store an array of enum values within a single column. To accomplish this, you may utilize the `AsEnumArrayObject` or `AsEnumCollection` casts provided by Laravel: - use App\Enums\ServerStatus; - use Illuminate\Database\Eloquent\Casts\AsEnumCollection; +```php +use App\Enums\ServerStatus; +use Illuminate\Database\Eloquent\Casts\AsEnumCollection; - /** - * Get the attributes that should be cast. - * - * @return array - */ - protected function casts(): array - { - return [ - 'statuses' => AsEnumCollection::of(ServerStatus::class), - ]; - } +/** + * Get the attributes that should be cast. + * + * @return array + */ +protected function casts(): array +{ + return [ + 'statuses' => AsEnumCollection::of(ServerStatus::class), + ]; +} +``` ### Encrypted Casting @@ -509,24 +553,28 @@ As you may know, Laravel encrypts strings using the `key` configuration value sp Sometimes you may need to apply casts while executing a query, such as when selecting a raw value from a table. For example, consider the following query: - use App\Models\Post; - use App\Models\User; - - $users = User::select([ - 'users.*', - 'last_posted_at' => Post::selectRaw('MAX(created_at)') - ->whereColumn('user_id', 'users.id') - ])->get(); +```php +use App\Models\Post; +use App\Models\User; + +$users = User::select([ + 'users.*', + 'last_posted_at' => Post::selectRaw('MAX(created_at)') + ->whereColumn('user_id', 'users.id') +])->get(); +``` The `last_posted_at` attribute on the results of this query will be a simple string. It would be wonderful if we could apply a `datetime` cast to this attribute when executing the query. Thankfully, we may accomplish this using the `withCasts` method: - $users = User::select([ - 'users.*', - 'last_posted_at' => Post::selectRaw('MAX(created_at)') - ->whereColumn('user_id', 'users.id') - ])->withCasts([ - 'last_posted_at' => 'datetime' - ])->get(); +```php +$users = User::select([ + 'users.*', + 'last_posted_at' => Post::selectRaw('MAX(created_at)') + ->whereColumn('user_id', 'users.id') +])->withCasts([ + 'last_posted_at' => 'datetime' +])->get(); +``` ## Custom Casts @@ -539,60 +587,64 @@ php artisan make:cast Json All custom cast classes implement the `CastsAttributes` interface. Classes that implement this interface must define a `get` and `set` method. The `get` method is responsible for transforming a raw value from the database into a cast value, while the `set` method should transform a cast value into a raw value that can be stored in the database. As an example, we will re-implement the built-in `json` cast type as a custom cast type: - $attributes + * @return array + */ + public function get(Model $model, string $key, mixed $value, array $attributes): array { - /** - * Cast the given value. - * - * @param array $attributes - * @return array - */ - public function get(Model $model, string $key, mixed $value, array $attributes): array - { - return json_decode($value, true); - } + return json_decode($value, true); + } - /** - * Prepare the given value for storage. - * - * @param array $attributes - */ - public function set(Model $model, string $key, mixed $value, array $attributes): string - { - return json_encode($value); - } + /** + * Prepare the given value for storage. + * + * @param array $attributes + */ + public function set(Model $model, string $key, mixed $value, array $attributes): string + { + return json_encode($value); } +} +``` Once you have defined a custom cast type, you may attach it to a model attribute using its class name: - + */ + protected function casts(): array { - /** - * Get the attributes that should be cast. - * - * @return array - */ - protected function casts(): array - { - return [ - 'options' => Json::class, - ]; - } + return [ + 'options' => Json::class, + ]; } +} +``` ### Value Object Casting @@ -601,58 +653,62 @@ You are not limited to casting values to primitive types. You may also cast valu As an example, we will define a custom cast class that casts multiple model values into a single `Address` value object. We will assume the `Address` value has two public properties: `lineOne` and `lineTwo`: - $attributes + */ + public function get(Model $model, string $key, mixed $value, array $attributes): AddressValueObject { - /** - * Cast the given value. - * - * @param array $attributes - */ - public function get(Model $model, string $key, mixed $value, array $attributes): AddressValueObject - { - return new AddressValueObject( - $attributes['address_line_one'], - $attributes['address_line_two'] - ); - } - - /** - * Prepare the given value for storage. - * - * @param array $attributes - * @return array - */ - public function set(Model $model, string $key, mixed $value, array $attributes): array - { - if (! $value instanceof AddressValueObject) { - throw new InvalidArgumentException('The given value is not an Address instance.'); - } + return new AddressValueObject( + $attributes['address_line_one'], + $attributes['address_line_two'] + ); + } - return [ - 'address_line_one' => $value->lineOne, - 'address_line_two' => $value->lineTwo, - ]; + /** + * Prepare the given value for storage. + * + * @param array $attributes + * @return array + */ + public function set(Model $model, string $key, mixed $value, array $attributes): array + { + if (! $value instanceof AddressValueObject) { + throw new InvalidArgumentException('The given value is not an Address instance.'); } + + return [ + 'address_line_one' => $value->lineOne, + 'address_line_two' => $value->lineTwo, + ]; } +} +``` When casting to value objects, any changes made to the value object will automatically be synced back to the model before the model is saved: - use App\Models\User; +```php +use App\Models\User; - $user = User::find(1); +$user = User::find(1); - $user->address->lineOne = 'Updated Address Value'; +$user->address->lineOne = 'Updated Address Value'; - $user->save(); +$user->save(); +``` > [!NOTE] > If you plan to serialize your Eloquent models containing value objects to JSON or arrays, you should implement the `Illuminate\Contracts\Support\Arrayable` and `JsonSerializable` interfaces on the value object. @@ -680,15 +736,17 @@ When an Eloquent model is converted to an array or JSON using the `toArray` and Therefore, you may specify that your custom cast class will be responsible for serializing the value object. To do so, your custom cast class should implement the `Illuminate\Contracts\Database\Eloquent\SerializesCastableAttributes` interface. This interface states that your class should contain a `serialize` method which should return the serialized form of your value object: - /** - * Get the serialized representation of the value. - * - * @param array $attributes - */ - public function serialize(Model $model, string $key, mixed $value, array $attributes): string - { - return (string) $value; - } +```php +/** + * Get the serialized representation of the value. + * + * @param array $attributes + */ +public function serialize(Model $model, string $key, mixed $value, array $attributes): string +{ + return (string) $value; +} +``` ### Inbound Casting @@ -703,139 +761,151 @@ php artisan make:cast Hash --inbound A classic example of an inbound only cast is a "hashing" cast. For example, we may define a cast that hashes inbound values via a given algorithm: - $attributes + */ + public function set(Model $model, string $key, mixed $value, array $attributes): string { - /** - * Create a new cast class instance. - */ - public function __construct( - protected string|null $algorithm = null, - ) {} - - /** - * Prepare the given value for storage. - * - * @param array $attributes - */ - public function set(Model $model, string $key, mixed $value, array $attributes): string - { - return is_null($this->algorithm) - ? bcrypt($value) - : hash($this->algorithm, $value); - } + return is_null($this->algorithm) + ? bcrypt($value) + : hash($this->algorithm, $value); } +} +``` ### Cast Parameters When attaching a custom cast to a model, cast parameters may be specified by separating them from the class name using a `:` character and comma-delimiting multiple parameters. The parameters will be passed to the constructor of the cast class: - /** - * Get the attributes that should be cast. - * - * @return array - */ - protected function casts(): array - { - return [ - 'secret' => Hash::class.':sha256', - ]; - } +```php +/** + * Get the attributes that should be cast. + * + * @return array + */ +protected function casts(): array +{ + return [ + 'secret' => Hash::class.':sha256', + ]; +} +``` ### Castables You may want to allow your application's value objects to define their own custom cast classes. Instead of attaching the custom cast class to your model, you may alternatively attach a value object class that implements the `Illuminate\Contracts\Database\Eloquent\Castable` interface: - use App\ValueObjects\Address; +```php +use App\ValueObjects\Address; - protected function casts(): array - { - return [ - 'address' => Address::class, - ]; - } +protected function casts(): array +{ + return [ + 'address' => Address::class, + ]; +} +``` Objects that implement the `Castable` interface must define a `castUsing` method that returns the class name of the custom caster class that is responsible for casting to and from the `Castable` class: - $arguments + */ + public static function castUsing(array $arguments): string { - /** - * Get the name of the caster class to use when casting from / to this cast target. - * - * @param array $arguments - */ - public static function castUsing(array $arguments): string - { - return AddressCast::class; - } + return AddressCast::class; } +} +``` When using `Castable` classes, you may still provide arguments in the `casts` method definition. The arguments will be passed to the `castUsing` method: - use App\ValueObjects\Address; +```php +use App\ValueObjects\Address; - protected function casts(): array - { - return [ - 'address' => Address::class.':argument', - ]; - } +protected function casts(): array +{ + return [ + 'address' => Address::class.':argument', + ]; +} +``` #### Castables & Anonymous Cast Classes By combining "castables" with PHP's [anonymous classes](https://www.php.net/manual/en/language.oop5.anonymous.php), you may define a value object and its casting logic as a single castable object. To accomplish this, return an anonymous class from your value object's `castUsing` method. The anonymous class should implement the `CastsAttributes` interface: - $arguments + */ + public static function castUsing(array $arguments): CastsAttributes { - // ... - - /** - * Get the caster class to use when casting from / to this cast target. - * - * @param array $arguments - */ - public static function castUsing(array $arguments): CastsAttributes + return new class implements CastsAttributes { - return new class implements CastsAttributes + public function get(Model $model, string $key, mixed $value, array $attributes): Address { - public function get(Model $model, string $key, mixed $value, array $attributes): Address - { - return new Address( - $attributes['address_line_one'], - $attributes['address_line_two'] - ); - } - - public function set(Model $model, string $key, mixed $value, array $attributes): array - { - return [ - 'address_line_one' => $value->lineOne, - 'address_line_two' => $value->lineTwo, - ]; - } - }; - } + return new Address( + $attributes['address_line_one'], + $attributes['address_line_two'] + ); + } + + public function set(Model $model, string $key, mixed $value, array $attributes): array + { + return [ + 'address_line_one' => $value->lineOne, + 'address_line_two' => $value->lineTwo, + ]; + } + }; } +} +``` diff --git a/eloquent-relationships.md b/eloquent-relationships.md index 5011298daf0..d2020525d2a 100644 --- a/eloquent-relationships.md +++ b/eloquent-relationships.md @@ -64,7 +64,9 @@ Database tables are often related to one another. For example, a blog post may h Eloquent relationships are defined as methods on your Eloquent model classes. Since relationships also serve as powerful [query builders](/docs/{{version}}/queries), defining relationships as methods provides powerful method chaining and querying capabilities. For example, we may chain additional query constraints on this `posts` relationship: - $user->posts()->where('active', 1)->get(); +```php +$user->posts()->where('active', 1)->get(); +``` But, before diving too deep into using relationships, let's learn how to define each type of relationship supported by Eloquent. @@ -73,127 +75,149 @@ But, before diving too deep into using relationships, let's learn how to define A one-to-one relationship is a very basic type of database relationship. For example, a `User` model might be associated with one `Phone` model. To define this relationship, we will place a `phone` method on the `User` model. The `phone` method should call the `hasOne` method and return its result. The `hasOne` method is available to your model via the model's `Illuminate\Database\Eloquent\Model` base class: - hasOne(Phone::class); - } + return $this->hasOne(Phone::class); } +} +``` The first argument passed to the `hasOne` method is the name of the related model class. Once the relationship is defined, we may retrieve the related record using Eloquent's dynamic properties. Dynamic properties allow you to access relationship methods as if they were properties defined on the model: - $phone = User::find(1)->phone; +```php +$phone = User::find(1)->phone; +``` Eloquent determines the foreign key of the relationship based on the parent model name. In this case, the `Phone` model is automatically assumed to have a `user_id` foreign key. If you wish to override this convention, you may pass a second argument to the `hasOne` method: - return $this->hasOne(Phone::class, 'foreign_key'); +```php +return $this->hasOne(Phone::class, 'foreign_key'); +``` Additionally, Eloquent assumes that the foreign key should have a value matching the primary key column of the parent. In other words, Eloquent will look for the value of the user's `id` column in the `user_id` column of the `Phone` record. If you would like the relationship to use a primary key value other than `id` or your model's `$primaryKey` property, you may pass a third argument to the `hasOne` method: - return $this->hasOne(Phone::class, 'foreign_key', 'local_key'); +```php +return $this->hasOne(Phone::class, 'foreign_key', 'local_key'); +``` #### Defining the Inverse of the Relationship So, we can access the `Phone` model from our `User` model. Next, let's define a relationship on the `Phone` model that will let us access the user that owns the phone. We can define the inverse of a `hasOne` relationship using the `belongsTo` method: - belongsTo(User::class); - } + return $this->belongsTo(User::class); } +} +``` When invoking the `user` method, Eloquent will attempt to find a `User` model that has an `id` which matches the `user_id` column on the `Phone` model. Eloquent determines the foreign key name by examining the name of the relationship method and suffixing the method name with `_id`. So, in this case, Eloquent assumes that the `Phone` model has a `user_id` column. However, if the foreign key on the `Phone` model is not `user_id`, you may pass a custom key name as the second argument to the `belongsTo` method: - /** - * Get the user that owns the phone. - */ - public function user(): BelongsTo - { - return $this->belongsTo(User::class, 'foreign_key'); - } +```php +/** + * Get the user that owns the phone. + */ +public function user(): BelongsTo +{ + return $this->belongsTo(User::class, 'foreign_key'); +} +``` If the parent model does not use `id` as its primary key, or you wish to find the associated model using a different column, you may pass a third argument to the `belongsTo` method specifying the parent table's custom key: - /** - * Get the user that owns the phone. - */ - public function user(): BelongsTo - { - return $this->belongsTo(User::class, 'foreign_key', 'owner_key'); - } +```php +/** + * Get the user that owns the phone. + */ +public function user(): BelongsTo +{ + return $this->belongsTo(User::class, 'foreign_key', 'owner_key'); +} +``` ### One to Many / Has Many A one-to-many relationship is used to define relationships where a single model is the parent to one or more child models. For example, a blog post may have an infinite number of comments. Like all other Eloquent relationships, one-to-many relationships are defined by defining a method on your Eloquent model: - hasMany(Comment::class); - } + return $this->hasMany(Comment::class); } +} +``` Remember, Eloquent will automatically determine the proper foreign key column for the `Comment` model. By convention, Eloquent will take the "snake case" name of the parent model and suffix it with `_id`. So, in this example, Eloquent will assume the foreign key column on the `Comment` model is `post_id`. Once the relationship method has been defined, we can access the [collection](/docs/{{version}}/eloquent-collections) of related comments by accessing the `comments` property. Remember, since Eloquent provides "dynamic relationship properties", we can access relationship methods as if they were defined as properties on the model: - use App\Models\Post; +```php +use App\Models\Post; - $comments = Post::find(1)->comments; +$comments = Post::find(1)->comments; - foreach ($comments as $comment) { - // ... - } +foreach ($comments as $comment) { + // ... +} +``` Since all relationships also serve as query builders, you may add further constraints to the relationship query by calling the `comments` method and continuing to chain conditions onto the query: - $comment = Post::find(1)->comments() - ->where('title', 'foo') - ->first(); +```php +$comment = Post::find(1)->comments() + ->where('title', 'foo') + ->first(); +``` Like the `hasOne` method, you may also override the foreign and local keys by passing additional arguments to the `hasMany` method: - return $this->hasMany(Comment::class, 'foreign_key'); +```php +return $this->hasMany(Comment::class, 'foreign_key'); - return $this->hasMany(Comment::class, 'foreign_key', 'local_key'); +return $this->hasMany(Comment::class, 'foreign_key', 'local_key'); +``` #### Automatically Hydrating Parent Models on Children @@ -214,23 +238,25 @@ In the example above, an "N + 1" query problem has been introduced because, even If you would like Eloquent to automatically hydrate parent models onto their children, you may invoke the `chaperone` method when defining a `hasMany` relationship: - hasMany(Comment::class)->chaperone(); - } + return $this->hasMany(Comment::class)->chaperone(); } +} +``` Or, if you would like to opt-in to automatic parent hydration at run time, you may invoke the `chaperone` model when eager loading the relationship: @@ -247,31 +273,35 @@ $posts = Post::with([ Now that we can access all of a post's comments, let's define a relationship to allow a comment to access its parent post. To define the inverse of a `hasMany` relationship, define a relationship method on the child model which calls the `belongsTo` method: - belongsTo(Post::class); - } + return $this->belongsTo(Post::class); } +} +``` Once the relationship has been defined, we can retrieve a comment's parent post by accessing the `post` "dynamic relationship property": - use App\Models\Comment; +```php +use App\Models\Comment; - $comment = Comment::find(1); +$comment = Comment::find(1); - return $comment->post->title; +return $comment->post->title; +``` In the example above, Eloquent will attempt to find a `Post` model that has an `id` which matches the `post_id` column on the `Comment` model. @@ -279,81 +309,97 @@ Eloquent determines the default foreign key name by examining the name of the re However, if the foreign key for your relationship does not follow these conventions, you may pass a custom foreign key name as the second argument to the `belongsTo` method: - /** - * Get the post that owns the comment. - */ - public function post(): BelongsTo - { - return $this->belongsTo(Post::class, 'foreign_key'); - } +```php +/** + * Get the post that owns the comment. + */ +public function post(): BelongsTo +{ + return $this->belongsTo(Post::class, 'foreign_key'); +} +``` If your parent model does not use `id` as its primary key, or you wish to find the associated model using a different column, you may pass a third argument to the `belongsTo` method specifying your parent table's custom key: - /** - * Get the post that owns the comment. - */ - public function post(): BelongsTo - { - return $this->belongsTo(Post::class, 'foreign_key', 'owner_key'); - } +```php +/** + * Get the post that owns the comment. + */ +public function post(): BelongsTo +{ + return $this->belongsTo(Post::class, 'foreign_key', 'owner_key'); +} +``` #### Default Models The `belongsTo`, `hasOne`, `hasOneThrough`, and `morphOne` relationships allow you to define a default model that will be returned if the given relationship is `null`. This pattern is often referred to as the [Null Object pattern](https://en.wikipedia.org/wiki/Null_Object_pattern) and can help remove conditional checks in your code. In the following example, the `user` relation will return an empty `App\Models\User` model if no user is attached to the `Post` model: - /** - * Get the author of the post. - */ - public function user(): BelongsTo - { - return $this->belongsTo(User::class)->withDefault(); - } +```php +/** + * Get the author of the post. + */ +public function user(): BelongsTo +{ + return $this->belongsTo(User::class)->withDefault(); +} +``` To populate the default model with attributes, you may pass an array or closure to the `withDefault` method: - /** - * Get the author of the post. - */ - public function user(): BelongsTo - { - return $this->belongsTo(User::class)->withDefault([ - 'name' => 'Guest Author', - ]); - } +```php +/** + * Get the author of the post. + */ +public function user(): BelongsTo +{ + return $this->belongsTo(User::class)->withDefault([ + 'name' => 'Guest Author', + ]); +} - /** - * Get the author of the post. - */ - public function user(): BelongsTo - { - return $this->belongsTo(User::class)->withDefault(function (User $user, Post $post) { - $user->name = 'Guest Author'; - }); - } +/** + * Get the author of the post. + */ +public function user(): BelongsTo +{ + return $this->belongsTo(User::class)->withDefault(function (User $user, Post $post) { + $user->name = 'Guest Author'; + }); +} +``` #### Querying Belongs To Relationships When querying for the children of a "belongs to" relationship, you may manually build the `where` clause to retrieve the corresponding Eloquent models: - use App\Models\Post; +```php +use App\Models\Post; - $posts = Post::where('user_id', $user->id)->get(); +$posts = Post::where('user_id', $user->id)->get(); +``` However, you may find it more convenient to use the `whereBelongsTo` method, which will automatically determine the proper relationship and foreign key for the given model: - $posts = Post::whereBelongsTo($user)->get(); +```php +$posts = Post::whereBelongsTo($user)->get(); +``` You may also provide a [collection](/docs/{{version}}/eloquent-collections) instance to the `whereBelongsTo` method. When doing so, Laravel will retrieve models that belong to any of the parent models within the collection: - $users = User::where('vip', true)->get(); +```php +$users = User::where('vip', true)->get(); - $posts = Post::whereBelongsTo($users)->get(); +$posts = Post::whereBelongsTo($users)->get(); +``` By default, Laravel will determine the relationship associated with the given model based on the class name of the model; however, you may specify the relationship name manually by providing it as the second argument to the `whereBelongsTo` method: - $posts = Post::whereBelongsTo($user, 'author')->get(); +```php +$posts = Post::whereBelongsTo($user, 'author')->get(); +``` ### Has One of Many @@ -451,39 +497,43 @@ The "has-one-through" relationship defines a one-to-one relationship with anothe For example, in a vehicle repair shop application, each `Mechanic` model may be associated with one `Car` model, and each `Car` model may be associated with one `Owner` model. While the mechanic and the owner have no direct relationship within the database, the mechanic can access the owner _through_ the `Car` model. Let's look at the tables necessary to define this relationship: - mechanics - id - integer - name - string +```text +mechanics + id - integer + name - string - cars - id - integer - model - string - mechanic_id - integer +cars + id - integer + model - string + mechanic_id - integer - owners - id - integer - name - string - car_id - integer +owners + id - integer + name - string + car_id - integer +``` Now that we have examined the table structure for the relationship, let's define the relationship on the `Mechanic` model: - hasOneThrough(Owner::class, Car::class); - } + return $this->hasOneThrough(Owner::class, Car::class); } +} +``` The first argument passed to the `hasOneThrough` method is the name of the final model we wish to access, while the second argument is the name of the intermediate model. @@ -502,23 +552,25 @@ return $this->throughCars()->hasOwner(); Typical Eloquent foreign key conventions will be used when performing the relationship's queries. If you would like to customize the keys of the relationship, you may pass them as the third and fourth arguments to the `hasOneThrough` method. The third argument is the name of the foreign key on the intermediate model. The fourth argument is the name of the foreign key on the final model. The fifth argument is the local key, while the sixth argument is the local key of the intermediate model: - class Mechanic extends Model +```php +class Mechanic extends Model +{ + /** + * Get the car's owner. + */ + public function carOwner(): HasOneThrough { - /** - * Get the car's owner. - */ - public function carOwner(): HasOneThrough - { - return $this->hasOneThrough( - Owner::class, - Car::class, - 'mechanic_id', // Foreign key on the cars table... - 'car_id', // Foreign key on the owners table... - 'id', // Local key on the mechanics table... - 'id' // Local key on the cars table... - ); - } + return $this->hasOneThrough( + Owner::class, + Car::class, + 'mechanic_id', // Foreign key on the cars table... + 'car_id', // Foreign key on the owners table... + 'id', // Local key on the mechanics table... + 'id' // Local key on the cars table... + ); } +} +``` Or, as discussed earlier, if the relevant relationships have already been defined on all of the models involved in the relationship, you may fluently define a "has-one-through" relationship by invoking the `through` method and supplying the names of those relationships. This approach offers the advantage of reusing the key conventions already defined on the existing relationships: @@ -535,39 +587,43 @@ return $this->throughCars()->hasOwner(); The "has-many-through" relationship provides a convenient way to access distant relations via an intermediate relation. For example, let's assume we are building a deployment platform like [Laravel Vapor](https://vapor.laravel.com). A `Project` model might access many `Deployment` models through an intermediate `Environment` model. Using this example, you could easily gather all deployments for a given project. Let's look at the tables required to define this relationship: - projects - id - integer - name - string +```text +projects + id - integer + name - string - environments - id - integer - project_id - integer - name - string +environments + id - integer + project_id - integer + name - string - deployments - id - integer - environment_id - integer - commit_hash - string +deployments + id - integer + environment_id - integer + commit_hash - string +``` Now that we have examined the table structure for the relationship, let's define the relationship on the `Project` model: - hasManyThrough(Deployment::class, Environment::class); - } + return $this->hasManyThrough(Deployment::class, Environment::class); } +} +``` The first argument passed to the `hasManyThrough` method is the name of the final model we wish to access, while the second argument is the name of the intermediate model. @@ -588,20 +644,22 @@ Though the `Deployment` model's table does not contain a `project_id` column, th Typical Eloquent foreign key conventions will be used when performing the relationship's queries. If you would like to customize the keys of the relationship, you may pass them as the third and fourth arguments to the `hasManyThrough` method. The third argument is the name of the foreign key on the intermediate model. The fourth argument is the name of the foreign key on the final model. The fifth argument is the local key, while the sixth argument is the local key of the intermediate model: - class Project extends Model +```php +class Project extends Model +{ + public function deployments(): HasManyThrough { - public function deployments(): HasManyThrough - { - return $this->hasManyThrough( - Deployment::class, - Environment::class, - 'project_id', // Foreign key on the environments table... - 'environment_id', // Foreign key on the deployments table... - 'id', // Local key on the projects table... - 'id' // Local key on the environments table... - ); - } + return $this->hasManyThrough( + Deployment::class, + Environment::class, + 'project_id', // Foreign key on the environments table... + 'environment_id', // Foreign key on the deployments table... + 'id', // Local key on the projects table... + 'id' // Local key on the environments table... + ); } +} +``` Or, as discussed earlier, if the relevant relationships have already been defined on all of the models involved in the relationship, you may fluently define a "has-many-through" relationship by invoking the `through` method and supplying the names of those relationships. This approach offers the advantage of reusing the key conventions already defined on the existing relationships: @@ -618,47 +676,53 @@ return $this->throughEnvironments()->hasDeployments(); It's common to add additional methods to models that constrain relationships. For example, you might add a `featuredPosts` method to a `User` model which constrains the broader `posts` relationship with an additional `where` constraint: - hasMany(Post::class)->latest(); - } - - /** - * Get the user's featured posts. - */ - public function featuredPosts(): HasMany - { - return $this->posts()->where('featured', true); - } + return $this->hasMany(Post::class)->latest(); } -However, if you attempt to create a model via the `featuredPosts` method, its `featured` attribute would not be set to `true`. If you would like to create models via relationship methods and also specify attributes that should be added to all models created via that relationship, you may use the `withAttributes` method when building the relationship query: - /** * Get the user's featured posts. */ public function featuredPosts(): HasMany { - return $this->posts()->withAttributes(['featured' => true]); + return $this->posts()->where('featured', true); } +} +``` + +However, if you attempt to create a model via the `featuredPosts` method, its `featured` attribute would not be set to `true`. If you would like to create models via relationship methods and also specify attributes that should be added to all models created via that relationship, you may use the `withAttributes` method when building the relationship query: + +```php +/** + * Get the user's featured posts. + */ +public function featuredPosts(): HasMany +{ + return $this->posts()->withAttributes(['featured' => true]); +} +``` The `withAttributes` method will add `where` clause constraints to the query using the given attributes, and it will also add the given attributes to any models created via the relationship method: - $post = $user->featuredPosts()->create(['title' => 'Featured Post']); +```php +$post = $user->featuredPosts()->create(['title' => 'Featured Post']); - $post->featured; // true +$post->featured; // true +``` ## Many to Many Relationships @@ -672,85 +736,99 @@ To define this relationship, three database tables are needed: `users`, `roles`, Remember, since a role can belong to many users, we cannot simply place a `user_id` column on the `roles` table. This would mean that a role could only belong to a single user. In order to provide support for roles being assigned to multiple users, the `role_user` table is needed. We can summarize the relationship's table structure like so: - users - id - integer - name - string +```text +users + id - integer + name - string - roles - id - integer - name - string +roles + id - integer + name - string - role_user - user_id - integer - role_id - integer +role_user + user_id - integer + role_id - integer +``` #### Model Structure Many-to-many relationships are defined by writing a method that returns the result of the `belongsToMany` method. The `belongsToMany` method is provided by the `Illuminate\Database\Eloquent\Model` base class that is used by all of your application's Eloquent models. For example, let's define a `roles` method on our `User` model. The first argument passed to this method is the name of the related model class: - belongsToMany(Role::class); - } + return $this->belongsToMany(Role::class); } +} +``` Once the relationship is defined, you may access the user's roles using the `roles` dynamic relationship property: - use App\Models\User; +```php +use App\Models\User; - $user = User::find(1); +$user = User::find(1); - foreach ($user->roles as $role) { - // ... - } +foreach ($user->roles as $role) { + // ... +} +``` Since all relationships also serve as query builders, you may add further constraints to the relationship query by calling the `roles` method and continuing to chain conditions onto the query: - $roles = User::find(1)->roles()->orderBy('name')->get(); +```php +$roles = User::find(1)->roles()->orderBy('name')->get(); +``` To determine the table name of the relationship's intermediate table, Eloquent will join the two related model names in alphabetical order. However, you are free to override this convention. You may do so by passing a second argument to the `belongsToMany` method: - return $this->belongsToMany(Role::class, 'role_user'); +```php +return $this->belongsToMany(Role::class, 'role_user'); +``` In addition to customizing the name of the intermediate table, you may also customize the column names of the keys on the table by passing additional arguments to the `belongsToMany` method. The third argument is the foreign key name of the model on which you are defining the relationship, while the fourth argument is the foreign key name of the model that you are joining to: - return $this->belongsToMany(Role::class, 'role_user', 'user_id', 'role_id'); +```php +return $this->belongsToMany(Role::class, 'role_user', 'user_id', 'role_id'); +``` #### Defining the Inverse of the Relationship To define the "inverse" of a many-to-many relationship, you should define a method on the related model which also returns the result of the `belongsToMany` method. To complete our user / role example, let's define the `users` method on the `Role` model: - belongsToMany(User::class); - } + return $this->belongsToMany(User::class); } +} +``` As you can see, the relationship is defined exactly the same as its `User` model counterpart with the exception of referencing the `App\Models\User` model. Since we're reusing the `belongsToMany` method, all of the usual table and key customization options are available when defining the "inverse" of many-to-many relationships. @@ -759,23 +837,29 @@ As you can see, the relationship is defined exactly the same as its `User` model As you have already learned, working with many-to-many relations requires the presence of an intermediate table. Eloquent provides some very helpful ways of interacting with this table. For example, let's assume our `User` model has many `Role` models that it is related to. After accessing this relationship, we may access the intermediate table using the `pivot` attribute on the models: - use App\Models\User; +```php +use App\Models\User; - $user = User::find(1); +$user = User::find(1); - foreach ($user->roles as $role) { - echo $role->pivot->created_at; - } +foreach ($user->roles as $role) { + echo $role->pivot->created_at; +} +``` Notice that each `Role` model we retrieve is automatically assigned a `pivot` attribute. This attribute contains a model representing the intermediate table. By default, only the model keys will be present on the `pivot` model. If your intermediate table contains extra attributes, you must specify them when defining the relationship: - return $this->belongsToMany(Role::class)->withPivot('active', 'created_by'); +```php +return $this->belongsToMany(Role::class)->withPivot('active', 'created_by'); +``` If you would like your intermediate table to have `created_at` and `updated_at` timestamps that are automatically maintained by Eloquent, call the `withTimestamps` method when defining the relationship: - return $this->belongsToMany(Role::class)->withTimestamps(); +```php +return $this->belongsToMany(Role::class)->withTimestamps(); +``` > [!WARNING] > Intermediate tables that utilize Eloquent's automatically maintained timestamps are required to have both `created_at` and `updated_at` timestamp columns. @@ -787,61 +871,71 @@ As noted previously, attributes from the intermediate table may be accessed on m For example, if your application contains users that may subscribe to podcasts, you likely have a many-to-many relationship between users and podcasts. If this is the case, you may wish to rename your intermediate table attribute to `subscription` instead of `pivot`. This can be done using the `as` method when defining the relationship: - return $this->belongsToMany(Podcast::class) - ->as('subscription') - ->withTimestamps(); +```php +return $this->belongsToMany(Podcast::class) + ->as('subscription') + ->withTimestamps(); +``` Once the custom intermediate table attribute has been specified, you may access the intermediate table data using the customized name: - $users = User::with('podcasts')->get(); +```php +$users = User::with('podcasts')->get(); - foreach ($users->flatMap->podcasts as $podcast) { - echo $podcast->subscription->created_at; - } +foreach ($users->flatMap->podcasts as $podcast) { + echo $podcast->subscription->created_at; +} +``` ### Filtering Queries via Intermediate Table Columns You can also filter the results returned by `belongsToMany` relationship queries using the `wherePivot`, `wherePivotIn`, `wherePivotNotIn`, `wherePivotBetween`, `wherePivotNotBetween`, `wherePivotNull`, and `wherePivotNotNull` methods when defining the relationship: - return $this->belongsToMany(Role::class) - ->wherePivot('approved', 1); +```php +return $this->belongsToMany(Role::class) + ->wherePivot('approved', 1); - return $this->belongsToMany(Role::class) - ->wherePivotIn('priority', [1, 2]); +return $this->belongsToMany(Role::class) + ->wherePivotIn('priority', [1, 2]); - return $this->belongsToMany(Role::class) - ->wherePivotNotIn('priority', [1, 2]); +return $this->belongsToMany(Role::class) + ->wherePivotNotIn('priority', [1, 2]); - return $this->belongsToMany(Podcast::class) - ->as('subscriptions') - ->wherePivotBetween('created_at', ['2020-01-01 00:00:00', '2020-12-31 00:00:00']); +return $this->belongsToMany(Podcast::class) + ->as('subscriptions') + ->wherePivotBetween('created_at', ['2020-01-01 00:00:00', '2020-12-31 00:00:00']); - return $this->belongsToMany(Podcast::class) - ->as('subscriptions') - ->wherePivotNotBetween('created_at', ['2020-01-01 00:00:00', '2020-12-31 00:00:00']); +return $this->belongsToMany(Podcast::class) + ->as('subscriptions') + ->wherePivotNotBetween('created_at', ['2020-01-01 00:00:00', '2020-12-31 00:00:00']); - return $this->belongsToMany(Podcast::class) - ->as('subscriptions') - ->wherePivotNull('expired_at'); +return $this->belongsToMany(Podcast::class) + ->as('subscriptions') + ->wherePivotNull('expired_at'); - return $this->belongsToMany(Podcast::class) - ->as('subscriptions') - ->wherePivotNotNull('expired_at'); +return $this->belongsToMany(Podcast::class) + ->as('subscriptions') + ->wherePivotNotNull('expired_at'); +``` The `wherePivot` adds a where clause constraint to the query, but does not add the specified value when creating new models via the defined relationship. If you need to both query and create relationships with a particular pivot value, you may use the `withPivotValue` method: - return $this->belongsToMany(Role::class) - ->withPivotValue('approved', 1); +```php +return $this->belongsToMany(Role::class) + ->withPivotValue('approved', 1); +``` ### Ordering Queries via Intermediate Table Columns You can order the results returned by `belongsToMany` relationship queries using the `orderByPivot` method. In the following example, we will retrieve all of the latest badges for the user: - return $this->belongsToMany(Badge::class) - ->where('rank', 'gold') - ->orderByPivot('created_at', 'desc'); +```php +return $this->belongsToMany(Badge::class) + ->where('rank', 'gold') + ->orderByPivot('created_at', 'desc'); +``` ### Defining Custom Intermediate Table Models @@ -850,36 +944,40 @@ If you would like to define a custom model to represent the intermediate table o Custom many-to-many pivot models should extend the `Illuminate\Database\Eloquent\Relations\Pivot` class while custom polymorphic many-to-many pivot models should extend the `Illuminate\Database\Eloquent\Relations\MorphPivot` class. For example, we may define a `Role` model which uses a custom `RoleUser` pivot model: - belongsToMany(User::class)->using(RoleUser::class); - } + return $this->belongsToMany(User::class)->using(RoleUser::class); } +} +``` When defining the `RoleUser` model, you should extend the `Illuminate\Database\Eloquent\Relations\Pivot` class: - [!WARNING] > Pivot models may not use the `SoftDeletes` trait. If you need to soft delete pivot records consider converting your pivot model to an actual Eloquent model. @@ -889,12 +987,14 @@ When defining the `RoleUser` model, you should extend the `Illuminate\Database\E If you have defined a many-to-many relationship that uses a custom pivot model, and that pivot model has an auto-incrementing primary key, you should ensure your custom pivot model class defines an `incrementing` property that is set to `true`. - /** - * Indicates if the IDs are auto-incrementing. - * - * @var bool - */ - public $incrementing = true; +```php +/** + * Indicates if the IDs are auto-incrementing. + * + * @var bool + */ +public $incrementing = true; +``` ## Polymorphic Relationships @@ -909,19 +1009,21 @@ A polymorphic relationship allows the child model to belong to more than one typ A one-to-one polymorphic relation is similar to a typical one-to-one relation; however, the child model can belong to more than one type of model using a single association. For example, a blog `Post` and a `User` may share a polymorphic relation to an `Image` model. Using a one-to-one polymorphic relation allows you to have a single table of unique images that may be associated with posts and users. First, let's examine the table structure: - posts - id - integer - name - string +```text +posts + id - integer + name - string - users - id - integer - name - string +users + id - integer + name - string - images - id - integer - url - string - imageable_id - integer - imageable_type - string +images + id - integer + url - string + imageable_id - integer + imageable_type - string +``` Note the `imageable_id` and `imageable_type` columns on the `images` table. The `imageable_id` column will contain the ID value of the post or user, while the `imageable_type` column will contain the class name of the parent model. The `imageable_type` column is used by Eloquent to determine which "type" of parent model to return when accessing the `imageable` relation. In this case, the column would contain either `App\Models\Post` or `App\Models\User`. @@ -930,70 +1032,76 @@ Note the `imageable_id` and `imageable_type` columns on the `images` table. The Next, let's examine the model definitions needed to build this relationship: - morphTo(); - } + return $this->morphTo(); } +} - use Illuminate\Database\Eloquent\Model; - use Illuminate\Database\Eloquent\Relations\MorphOne; +use Illuminate\Database\Eloquent\Model; +use Illuminate\Database\Eloquent\Relations\MorphOne; - class Post extends Model +class Post extends Model +{ + /** + * Get the post's image. + */ + public function image(): MorphOne { - /** - * Get the post's image. - */ - public function image(): MorphOne - { - return $this->morphOne(Image::class, 'imageable'); - } + return $this->morphOne(Image::class, 'imageable'); } +} - use Illuminate\Database\Eloquent\Model; - use Illuminate\Database\Eloquent\Relations\MorphOne; +use Illuminate\Database\Eloquent\Model; +use Illuminate\Database\Eloquent\Relations\MorphOne; - class User extends Model +class User extends Model +{ + /** + * Get the user's image. + */ + public function image(): MorphOne { - /** - * Get the user's image. - */ - public function image(): MorphOne - { - return $this->morphOne(Image::class, 'imageable'); - } + return $this->morphOne(Image::class, 'imageable'); } +} +``` #### Retrieving the Relationship Once your database table and models are defined, you may access the relationships via your models. For example, to retrieve the image for a post, we can access the `image` dynamic relationship property: - use App\Models\Post; +```php +use App\Models\Post; - $post = Post::find(1); +$post = Post::find(1); - $image = $post->image; +$image = $post->image; +``` You may retrieve the parent of the polymorphic model by accessing the name of the method that performs the call to `morphTo`. In this case, that is the `imageable` method on the `Image` model. So, we will access that method as a dynamic relationship property: - use App\Models\Image; +```php +use App\Models\Image; - $image = Image::find(1); +$image = Image::find(1); - $imageable = $image->imageable; +$imageable = $image->imageable; +``` The `imageable` relation on the `Image` model will return either a `Post` or `User` instance, depending on which type of model owns the image. @@ -1002,13 +1110,15 @@ The `imageable` relation on the `Image` model will return either a `Post` or `Us If necessary, you may specify the name of the "id" and "type" columns utilized by your polymorphic child model. If you do so, ensure that you always pass the name of the relationship as the first argument to the `morphTo` method. Typically, this value should match the method name, so you may use PHP's `__FUNCTION__` constant: - /** - * Get the model that the image belongs to. - */ - public function imageable(): MorphTo - { - return $this->morphTo(__FUNCTION__, 'imageable_type', 'imageable_id'); - } +```php +/** + * Get the model that the image belongs to. + */ +public function imageable(): MorphTo +{ + return $this->morphTo(__FUNCTION__, 'imageable_type', 'imageable_id'); +} +``` ### One to Many (Polymorphic) @@ -1018,93 +1128,101 @@ If necessary, you may specify the name of the "id" and "type" columns utilized b A one-to-many polymorphic relation is similar to a typical one-to-many relation; however, the child model can belong to more than one type of model using a single association. For example, imagine users of your application can "comment" on posts and videos. Using polymorphic relationships, you may use a single `comments` table to contain comments for both posts and videos. First, let's examine the table structure required to build this relationship: - posts - id - integer - title - string - body - text - - videos - id - integer - title - string - url - string - - comments - id - integer - body - text - commentable_id - integer - commentable_type - string +```text +posts + id - integer + title - string + body - text + +videos + id - integer + title - string + url - string + +comments + id - integer + body - text + commentable_id - integer + commentable_type - string +``` #### Model Structure Next, let's examine the model definitions needed to build this relationship: - morphTo(); - } + return $this->morphTo(); } +} - use Illuminate\Database\Eloquent\Model; - use Illuminate\Database\Eloquent\Relations\MorphMany; +use Illuminate\Database\Eloquent\Model; +use Illuminate\Database\Eloquent\Relations\MorphMany; - class Post extends Model +class Post extends Model +{ + /** + * Get all of the post's comments. + */ + public function comments(): MorphMany { - /** - * Get all of the post's comments. - */ - public function comments(): MorphMany - { - return $this->morphMany(Comment::class, 'commentable'); - } + return $this->morphMany(Comment::class, 'commentable'); } +} - use Illuminate\Database\Eloquent\Model; - use Illuminate\Database\Eloquent\Relations\MorphMany; +use Illuminate\Database\Eloquent\Model; +use Illuminate\Database\Eloquent\Relations\MorphMany; - class Video extends Model +class Video extends Model +{ + /** + * Get all of the video's comments. + */ + public function comments(): MorphMany { - /** - * Get all of the video's comments. - */ - public function comments(): MorphMany - { - return $this->morphMany(Comment::class, 'commentable'); - } + return $this->morphMany(Comment::class, 'commentable'); } +} +``` #### Retrieving the Relationship Once your database table and models are defined, you may access the relationships via your model's dynamic relationship properties. For example, to access all of the comments for a post, we can use the `comments` dynamic property: - use App\Models\Post; +```php +use App\Models\Post; - $post = Post::find(1); +$post = Post::find(1); - foreach ($post->comments as $comment) { - // ... - } +foreach ($post->comments as $comment) { + // ... +} +``` You may also retrieve the parent of a polymorphic child model by accessing the name of the method that performs the call to `morphTo`. In this case, that is the `commentable` method on the `Comment` model. So, we will access that method as a dynamic relationship property in order to access the comment's parent model: - use App\Models\Comment; +```php +use App\Models\Comment; - $comment = Comment::find(1); +$comment = Comment::find(1); - $commentable = $comment->commentable; +$commentable = $comment->commentable; +``` The `commentable` relation on the `Comment` model will return either a `Post` or `Video` instance, depending on which type of model is the comment's parent. @@ -1127,16 +1245,18 @@ In the example above, an "N + 1" query problem has been introduced because, even If you would like Eloquent to automatically hydrate parent models onto their children, you may invoke the `chaperone` method when defining a `morphMany` relationship: - class Post extends Model +```php +class Post extends Model +{ + /** + * Get all of the post's comments. + */ + public function comments(): MorphMany { - /** - * Get all of the post's comments. - */ - public function comments(): MorphMany - { - return $this->morphMany(Comment::class, 'commentable')->chaperone(); - } + return $this->morphMany(Comment::class, 'commentable')->chaperone(); } +} +``` Or, if you would like to opt-in to automatic parent hydration at run time, you may invoke the `chaperone` model when eager loading the relationship: @@ -1200,22 +1320,24 @@ public function bestImage(): MorphOne Many-to-many polymorphic relations are slightly more complicated than "morph one" and "morph many" relationships. For example, a `Post` model and `Video` model could share a polymorphic relation to a `Tag` model. Using a many-to-many polymorphic relation in this situation would allow your application to have a single table of unique tags that may be associated with posts or videos. First, let's examine the table structure required to build this relationship: - posts - id - integer - name - string +```text +posts + id - integer + name - string - videos - id - integer - name - string +videos + id - integer + name - string - tags - id - integer - name - string +tags + id - integer + name - string - taggables - tag_id - integer - taggable_id - integer - taggable_type - string +taggables + tag_id - integer + taggable_id - integer + taggable_type - string +``` > [!NOTE] > Before diving into polymorphic many-to-many relationships, you may benefit from reading the documentation on typical [many-to-many relationships](#many-to-many). @@ -1227,23 +1349,25 @@ Next, we're ready to define the relationships on the models. The `Post` and `Vid The `morphToMany` method accepts the name of the related model as well as the "relationship name". Based on the name we assigned to our intermediate table name and the keys it contains, we will refer to the relationship as "taggable": - morphToMany(Tag::class, 'taggable'); - } + return $this->morphToMany(Tag::class, 'taggable'); } +} +``` #### Defining the Inverse of the Relationship @@ -1252,58 +1376,64 @@ Next, on the `Tag` model, you should define a method for each of its possible pa The `morphedByMany` method accepts the name of the related model as well as the "relationship name". Based on the name we assigned to our intermediate table name and the keys it contains, we will refer to the relationship as "taggable": - morphedByMany(Post::class, 'taggable'); - } - - /** - * Get all of the videos that are assigned this tag. - */ - public function videos(): MorphToMany - { - return $this->morphedByMany(Video::class, 'taggable'); - } + return $this->morphedByMany(Post::class, 'taggable'); } + /** + * Get all of the videos that are assigned this tag. + */ + public function videos(): MorphToMany + { + return $this->morphedByMany(Video::class, 'taggable'); + } +} +``` + #### Retrieving the Relationship Once your database table and models are defined, you may access the relationships via your models. For example, to access all of the tags for a post, you may use the `tags` dynamic relationship property: - use App\Models\Post; +```php +use App\Models\Post; - $post = Post::find(1); +$post = Post::find(1); - foreach ($post->tags as $tag) { - // ... - } +foreach ($post->tags as $tag) { + // ... +} +``` You may retrieve the parent of a polymorphic relation from the polymorphic child model by accessing the name of the method that performs the call to `morphedByMany`. In this case, that is the `posts` or `videos` methods on the `Tag` model: - use App\Models\Tag; +```php +use App\Models\Tag; - $tag = Tag::find(1); +$tag = Tag::find(1); - foreach ($tag->posts as $post) { - // ... - } +foreach ($tag->posts as $post) { + // ... +} - foreach ($tag->videos as $video) { - // ... - } +foreach ($tag->videos as $video) { + // ... +} +``` ### Custom Polymorphic Types @@ -1312,22 +1442,26 @@ By default, Laravel will use the fully qualified class name to store the "type" For example, instead of using the model names as the "type", we may use simple strings such as `post` and `video`. By doing so, the polymorphic "type" column values in our database will remain valid even if the models are renamed: - use Illuminate\Database\Eloquent\Relations\Relation; +```php +use Illuminate\Database\Eloquent\Relations\Relation; - Relation::enforceMorphMap([ - 'post' => 'App\Models\Post', - 'video' => 'App\Models\Video', - ]); +Relation::enforceMorphMap([ + 'post' => 'App\Models\Post', + 'video' => 'App\Models\Video', +]); +``` You may call the `enforceMorphMap` method in the `boot` method of your `App\Providers\AppServiceProvider` class or create a separate service provider if you wish. You may determine the morph alias of a given model at runtime using the model's `getMorphClass` method. Conversely, you may determine the fully-qualified class name associated with a morph alias using the `Relation::getMorphedModel` method: - use Illuminate\Database\Eloquent\Relations\Relation; +```php +use Illuminate\Database\Eloquent\Relations\Relation; - $alias = $post->getMorphClass(); +$alias = $post->getMorphClass(); - $class = Relation::getMorphedModel($alias); +$class = Relation::getMorphedModel($alias); +``` > [!WARNING] > When adding a "morph map" to your existing application, every morphable `*_type` column value in your database that still contains a fully-qualified class will need to be converted to its "map" name. @@ -1339,12 +1473,14 @@ You may use the `resolveRelationUsing` method to define relations between Eloque The `resolveRelationUsing` method accepts the desired relationship name as its first argument. The second argument passed to the method should be a closure that accepts the model instance and returns a valid Eloquent relationship definition. Typically, you should configure dynamic relationships within the boot method of a [service provider](/docs/{{version}}/providers): - use App\Models\Order; - use App\Models\Customer; +```php +use App\Models\Order; +use App\Models\Customer; - Order::resolveRelationUsing('customer', function (Order $orderModel) { - return $orderModel->belongsTo(Customer::class, 'customer_id'); - }); +Order::resolveRelationUsing('customer', function (Order $orderModel) { + return $orderModel->belongsTo(Customer::class, 'customer_id'); +}); +``` > [!WARNING] > When defining dynamic relationships, always provide explicit key name arguments to the Eloquent relationship methods. @@ -1356,31 +1492,35 @@ Since all Eloquent relationships are defined via methods, you may call those met For example, imagine a blog application in which a `User` model has many associated `Post` models: - hasMany(Post::class); - } + return $this->hasMany(Post::class); } +} +``` You may query the `posts` relationship and add additional constraints to the relationship like so: - use App\Models\User; +```php +use App\Models\User; - $user = User::find(1); +$user = User::find(1); - $user->posts()->where('active', 1)->get(); +$user->posts()->where('active', 1)->get(); +``` You are able to use any of the Laravel [query builder's](/docs/{{version}}/queries) methods on the relationship, so be sure to explore the query builder documentation to learn about all of the methods that are available to you. @@ -1389,10 +1529,12 @@ You are able to use any of the Laravel [query builder's](/docs/{{version}}/queri As demonstrated in the example above, you are free to add additional constraints to relationships when querying them. However, use caution when chaining `orWhere` clauses onto a relationship, as the `orWhere` clauses will be logically grouped at the same level as the relationship constraint: - $user->posts() - ->where('active', 1) - ->orWhere('votes', '>=', 100) - ->get(); +```php +$user->posts() + ->where('active', 1) + ->orWhere('votes', '>=', 100) + ->get(); +``` The example above will generate the following SQL. As you can see, the `or` clause instructs the query to return _any_ post with greater than 100 votes. The query is no longer constrained to a specific user: @@ -1404,14 +1546,16 @@ where user_id = ? and active = 1 or votes >= 100 In most situations, you should use [logical groups](/docs/{{version}}/queries#logical-grouping) to group the conditional checks between parentheses: - use Illuminate\Database\Eloquent\Builder; - - $user->posts() - ->where(function (Builder $query) { - return $query->where('active', 1) - ->orWhere('votes', '>=', 100); - }) - ->get(); +```php +use Illuminate\Database\Eloquent\Builder; + +$user->posts() + ->where(function (Builder $query) { + return $query->where('active', 1) + ->orWhere('votes', '>=', 100); + }) + ->get(); +``` The example above will produce the following SQL. Note that the logical grouping has properly grouped the constraints and the query remains constrained to a specific user: @@ -1426,13 +1570,15 @@ where user_id = ? and (active = 1 or votes >= 100) If you do not need to add additional constraints to an Eloquent relationship query, you may access the relationship as if it were a property. For example, continuing to use our `User` and `Post` example models, we may access all of a user's posts like so: - use App\Models\User; +```php +use App\Models\User; - $user = User::find(1); +$user = User::find(1); - foreach ($user->posts as $post) { - // ... - } +foreach ($user->posts as $post) { + // ... +} +``` Dynamic relationship properties perform "lazy loading", meaning they will only load their relationship data when you actually access them. Because of this, developers often use [eager loading](#eager-loading) to pre-load relationships they know will be accessed after loading the model. Eager loading provides a significant reduction in SQL queries that must be executed to load a model's relations. @@ -1441,34 +1587,42 @@ Dynamic relationship properties perform "lazy loading", meaning they will only l When retrieving model records, you may wish to limit your results based on the existence of a relationship. For example, imagine you want to retrieve all blog posts that have at least one comment. To do so, you may pass the name of the relationship to the `has` and `orHas` methods: - use App\Models\Post; +```php +use App\Models\Post; - // Retrieve all posts that have at least one comment... - $posts = Post::has('comments')->get(); +// Retrieve all posts that have at least one comment... +$posts = Post::has('comments')->get(); +``` You may also specify an operator and count value to further customize the query: - // Retrieve all posts that have three or more comments... - $posts = Post::has('comments', '>=', 3)->get(); +```php +// Retrieve all posts that have three or more comments... +$posts = Post::has('comments', '>=', 3)->get(); +``` Nested `has` statements may be constructed using "dot" notation. For example, you may retrieve all posts that have at least one comment that has at least one image: - // Retrieve posts that have at least one comment with images... - $posts = Post::has('comments.images')->get(); +```php +// Retrieve posts that have at least one comment with images... +$posts = Post::has('comments.images')->get(); +``` If you need even more power, you may use the `whereHas` and `orWhereHas` methods to define additional query constraints on your `has` queries, such as inspecting the content of a comment: - use Illuminate\Database\Eloquent\Builder; +```php +use Illuminate\Database\Eloquent\Builder; - // Retrieve posts with at least one comment containing words like code%... - $posts = Post::whereHas('comments', function (Builder $query) { - $query->where('content', 'like', 'code%'); - })->get(); +// Retrieve posts with at least one comment containing words like code%... +$posts = Post::whereHas('comments', function (Builder $query) { + $query->where('content', 'like', 'code%'); +})->get(); - // Retrieve posts with at least ten comments containing words like code%... - $posts = Post::whereHas('comments', function (Builder $query) { - $query->where('content', 'like', 'code%'); - }, '>=', 10)->get(); +// Retrieve posts with at least ten comments containing words like code%... +$posts = Post::whereHas('comments', function (Builder $query) { + $query->where('content', 'like', 'code%'); +}, '>=', 10)->get(); +``` > [!WARNING] > Eloquent does not currently support querying for relationship existence across databases. The relationships must exist within the same database. @@ -1478,99 +1632,117 @@ If you need even more power, you may use the `whereHas` and `orWhereHas` methods If you would like to query for a relationship's existence with a single, simple where condition attached to the relationship query, you may find it more convenient to use the `whereRelation`, `orWhereRelation`, `whereMorphRelation`, and `orWhereMorphRelation` methods. For example, we may query for all posts that have unapproved comments: - use App\Models\Post; +```php +use App\Models\Post; - $posts = Post::whereRelation('comments', 'is_approved', false)->get(); +$posts = Post::whereRelation('comments', 'is_approved', false)->get(); +``` Of course, like calls to the query builder's `where` method, you may also specify an operator: - $posts = Post::whereRelation( - 'comments', 'created_at', '>=', now()->subHour() - )->get(); +```php +$posts = Post::whereRelation( + 'comments', 'created_at', '>=', now()->subHour() +)->get(); +``` ### Querying Relationship Absence When retrieving model records, you may wish to limit your results based on the absence of a relationship. For example, imagine you want to retrieve all blog posts that **don't** have any comments. To do so, you may pass the name of the relationship to the `doesntHave` and `orDoesntHave` methods: - use App\Models\Post; +```php +use App\Models\Post; - $posts = Post::doesntHave('comments')->get(); +$posts = Post::doesntHave('comments')->get(); +``` If you need even more power, you may use the `whereDoesntHave` and `orWhereDoesntHave` methods to add additional query constraints to your `doesntHave` queries, such as inspecting the content of a comment: - use Illuminate\Database\Eloquent\Builder; +```php +use Illuminate\Database\Eloquent\Builder; - $posts = Post::whereDoesntHave('comments', function (Builder $query) { - $query->where('content', 'like', 'code%'); - })->get(); +$posts = Post::whereDoesntHave('comments', function (Builder $query) { + $query->where('content', 'like', 'code%'); +})->get(); +``` You may use "dot" notation to execute a query against a nested relationship. For example, the following query will retrieve all posts that do not have comments; however, posts that have comments from authors that are not banned will be included in the results: - use Illuminate\Database\Eloquent\Builder; +```php +use Illuminate\Database\Eloquent\Builder; - $posts = Post::whereDoesntHave('comments.author', function (Builder $query) { - $query->where('banned', 0); - })->get(); +$posts = Post::whereDoesntHave('comments.author', function (Builder $query) { + $query->where('banned', 0); +})->get(); +``` ### Querying Morph To Relationships To query the existence of "morph to" relationships, you may use the `whereHasMorph` and `whereDoesntHaveMorph` methods. These methods accept the name of the relationship as their first argument. Next, the methods accept the names of the related models that you wish to include in the query. Finally, you may provide a closure which customizes the relationship query: - use App\Models\Comment; - use App\Models\Post; - use App\Models\Video; - use Illuminate\Database\Eloquent\Builder; - - // Retrieve comments associated to posts or videos with a title like code%... - $comments = Comment::whereHasMorph( - 'commentable', - [Post::class, Video::class], - function (Builder $query) { - $query->where('title', 'like', 'code%'); - } - )->get(); - - // Retrieve comments associated to posts with a title not like code%... - $comments = Comment::whereDoesntHaveMorph( - 'commentable', - Post::class, - function (Builder $query) { - $query->where('title', 'like', 'code%'); - } - )->get(); +```php +use App\Models\Comment; +use App\Models\Post; +use App\Models\Video; +use Illuminate\Database\Eloquent\Builder; + +// Retrieve comments associated to posts or videos with a title like code%... +$comments = Comment::whereHasMorph( + 'commentable', + [Post::class, Video::class], + function (Builder $query) { + $query->where('title', 'like', 'code%'); + } +)->get(); + +// Retrieve comments associated to posts with a title not like code%... +$comments = Comment::whereDoesntHaveMorph( + 'commentable', + Post::class, + function (Builder $query) { + $query->where('title', 'like', 'code%'); + } +)->get(); +``` You may occasionally need to add query constraints based on the "type" of the related polymorphic model. The closure passed to the `whereHasMorph` method may receive a `$type` value as its second argument. This argument allows you to inspect the "type" of the query that is being built: - use Illuminate\Database\Eloquent\Builder; +```php +use Illuminate\Database\Eloquent\Builder; - $comments = Comment::whereHasMorph( - 'commentable', - [Post::class, Video::class], - function (Builder $query, string $type) { - $column = $type === Post::class ? 'content' : 'title'; +$comments = Comment::whereHasMorph( + 'commentable', + [Post::class, Video::class], + function (Builder $query, string $type) { + $column = $type === Post::class ? 'content' : 'title'; - $query->where($column, 'like', 'code%'); - } - )->get(); + $query->where($column, 'like', 'code%'); + } +)->get(); +``` Sometimes you may want to query for the children of a "morph to" relationship's parent. You may accomplish this using the `whereMorphedTo` and `whereNotMorphedTo` methods, which will automatically determine the proper morph type mapping for the given model. These methods accept the name of the `morphTo` relationship as their first argument and the related parent model as their second argument: - $comments = Comment::whereMorphedTo('commentable', $post) - ->orWhereMorphedTo('commentable', $video) - ->get(); +```php +$comments = Comment::whereMorphedTo('commentable', $post) + ->orWhereMorphedTo('commentable', $video) + ->get(); +``` #### Querying All Related Models Instead of passing an array of possible polymorphic models, you may provide `*` as a wildcard value. This will instruct Laravel to retrieve all of the possible polymorphic types from the database. Laravel will execute an additional query in order to perform this operation: - use Illuminate\Database\Eloquent\Builder; +```php +use Illuminate\Database\Eloquent\Builder; - $comments = Comment::whereHasMorph('commentable', '*', function (Builder $query) { - $query->where('title', 'like', 'foo%'); - })->get(); +$comments = Comment::whereHasMorph('commentable', '*', function (Builder $query) { + $query->where('title', 'like', 'foo%'); +})->get(); +``` ## Aggregating Related Models @@ -1580,95 +1752,115 @@ Instead of passing an array of possible polymorphic models, you may provide `*` Sometimes you may want to count the number of related models for a given relationship without actually loading the models. To accomplish this, you may use the `withCount` method. The `withCount` method will place a `{relation}_count` attribute on the resulting models: - use App\Models\Post; +```php +use App\Models\Post; - $posts = Post::withCount('comments')->get(); +$posts = Post::withCount('comments')->get(); - foreach ($posts as $post) { - echo $post->comments_count; - } +foreach ($posts as $post) { + echo $post->comments_count; +} +``` By passing an array to the `withCount` method, you may add the "counts" for multiple relations as well as add additional constraints to the queries: - use Illuminate\Database\Eloquent\Builder; +```php +use Illuminate\Database\Eloquent\Builder; - $posts = Post::withCount(['votes', 'comments' => function (Builder $query) { - $query->where('content', 'like', 'code%'); - }])->get(); +$posts = Post::withCount(['votes', 'comments' => function (Builder $query) { + $query->where('content', 'like', 'code%'); +}])->get(); - echo $posts[0]->votes_count; - echo $posts[0]->comments_count; +echo $posts[0]->votes_count; +echo $posts[0]->comments_count; +``` You may also alias the relationship count result, allowing multiple counts on the same relationship: - use Illuminate\Database\Eloquent\Builder; +```php +use Illuminate\Database\Eloquent\Builder; - $posts = Post::withCount([ - 'comments', - 'comments as pending_comments_count' => function (Builder $query) { - $query->where('approved', false); - }, - ])->get(); +$posts = Post::withCount([ + 'comments', + 'comments as pending_comments_count' => function (Builder $query) { + $query->where('approved', false); + }, +])->get(); - echo $posts[0]->comments_count; - echo $posts[0]->pending_comments_count; +echo $posts[0]->comments_count; +echo $posts[0]->pending_comments_count; +``` #### Deferred Count Loading Using the `loadCount` method, you may load a relationship count after the parent model has already been retrieved: - $book = Book::first(); +```php +$book = Book::first(); - $book->loadCount('genres'); +$book->loadCount('genres'); +``` If you need to set additional query constraints on the count query, you may pass an array keyed by the relationships you wish to count. The array values should be closures which receive the query builder instance: - $book->loadCount(['reviews' => function (Builder $query) { - $query->where('rating', 5); - }]) +```php +$book->loadCount(['reviews' => function (Builder $query) { + $query->where('rating', 5); +}]) +``` #### Relationship Counting and Custom Select Statements If you're combining `withCount` with a `select` statement, ensure that you call `withCount` after the `select` method: - $posts = Post::select(['title', 'body']) - ->withCount('comments') - ->get(); +```php +$posts = Post::select(['title', 'body']) + ->withCount('comments') + ->get(); +``` ### Other Aggregate Functions In addition to the `withCount` method, Eloquent provides `withMin`, `withMax`, `withAvg`, `withSum`, and `withExists` methods. These methods will place a `{relation}_{function}_{column}` attribute on your resulting models: - use App\Models\Post; +```php +use App\Models\Post; - $posts = Post::withSum('comments', 'votes')->get(); +$posts = Post::withSum('comments', 'votes')->get(); - foreach ($posts as $post) { - echo $post->comments_sum_votes; - } +foreach ($posts as $post) { + echo $post->comments_sum_votes; +} +``` If you wish to access the result of the aggregate function using another name, you may specify your own alias: - $posts = Post::withSum('comments as total_comments', 'votes')->get(); +```php +$posts = Post::withSum('comments as total_comments', 'votes')->get(); - foreach ($posts as $post) { - echo $post->total_comments; - } +foreach ($posts as $post) { + echo $post->total_comments; +} +``` Like the `loadCount` method, deferred versions of these methods are also available. These additional aggregate operations may be performed on Eloquent models that have already been retrieved: - $post = Post::first(); +```php +$post = Post::first(); - $post->loadSum('comments', 'votes'); +$post->loadSum('comments', 'votes'); +``` If you're combining these aggregate methods with a `select` statement, ensure that you call the aggregate methods after the `select` method: - $posts = Post::select(['title', 'body']) - ->withExists('comments') - ->get(); +```php +$posts = Post::select(['title', 'body']) + ->withExists('comments') + ->get(); +``` ### Counting Related Models on Morph To Relationships @@ -1679,70 +1871,80 @@ In this example, let's assume that `Photo` and `Post` models may create `Activit Now, let's imagine we want to retrieve `ActivityFeed` instances and eager load the `parentable` parent models for each `ActivityFeed` instance. In addition, we want to retrieve the number of tags that are associated with each parent photo and the number of comments that are associated with each parent post: - use Illuminate\Database\Eloquent\Relations\MorphTo; +```php +use Illuminate\Database\Eloquent\Relations\MorphTo; - $activities = ActivityFeed::with([ - 'parentable' => function (MorphTo $morphTo) { - $morphTo->morphWithCount([ - Photo::class => ['tags'], - Post::class => ['comments'], - ]); - }])->get(); +$activities = ActivityFeed::with([ + 'parentable' => function (MorphTo $morphTo) { + $morphTo->morphWithCount([ + Photo::class => ['tags'], + Post::class => ['comments'], + ]); + }])->get(); +``` #### Deferred Count Loading Let's assume we have already retrieved a set of `ActivityFeed` models and now we would like to load the nested relationship counts for the various `parentable` models associated with the activity feeds. You may use the `loadMorphCount` method to accomplish this: - $activities = ActivityFeed::with('parentable')->get(); +```php +$activities = ActivityFeed::with('parentable')->get(); - $activities->loadMorphCount('parentable', [ - Photo::class => ['tags'], - Post::class => ['comments'], - ]); +$activities->loadMorphCount('parentable', [ + Photo::class => ['tags'], + Post::class => ['comments'], +]); +``` ## Eager Loading When accessing Eloquent relationships as properties, the related models are "lazy loaded". This means the relationship data is not actually loaded until you first access the property. However, Eloquent can "eager load" relationships at the time you query the parent model. Eager loading alleviates the "N + 1" query problem. To illustrate the N + 1 query problem, consider a `Book` model that "belongs to" to an `Author` model: - belongsTo(Author::class); - } + return $this->belongsTo(Author::class); } +} +``` Now, let's retrieve all books and their authors: - use App\Models\Book; +```php +use App\Models\Book; - $books = Book::all(); +$books = Book::all(); - foreach ($books as $book) { - echo $book->author->name; - } +foreach ($books as $book) { + echo $book->author->name; +} +``` This loop will execute one query to retrieve all of the books within the database table, then another query for each book in order to retrieve the book's author. So, if we have 25 books, the code above would run 26 queries: one for the original book, and 25 additional queries to retrieve the author of each book. Thankfully, we can use eager loading to reduce this operation to just two queries. When building a query, you may specify which relationships should be eager loaded using the `with` method: - $books = Book::with('author')->get(); +```php +$books = Book::with('author')->get(); - foreach ($books as $book) { - echo $book->author->name; - } +foreach ($books as $book) { + echo $book->author->name; +} +``` For this operation, only two queries will be executed - one query to retrieve all of the books and one query to retrieve all of the authors for all of the books: @@ -1757,66 +1959,78 @@ select * from authors where id in (1, 2, 3, 4, 5, ...) Sometimes you may need to eager load several different relationships. To do so, just pass an array of relationships to the `with` method: - $books = Book::with(['author', 'publisher'])->get(); +```php +$books = Book::with(['author', 'publisher'])->get(); +``` #### Nested Eager Loading To eager load a relationship's relationships, you may use "dot" syntax. For example, let's eager load all of the book's authors and all of the author's personal contacts: - $books = Book::with('author.contacts')->get(); +```php +$books = Book::with('author.contacts')->get(); +``` Alternatively, you may specify nested eager loaded relationships by providing a nested array to the `with` method, which can be convenient when eager loading multiple nested relationships: - $books = Book::with([ - 'author' => [ - 'contacts', - 'publisher', - ], - ])->get(); +```php +$books = Book::with([ + 'author' => [ + 'contacts', + 'publisher', + ], +])->get(); +``` #### Nested Eager Loading `morphTo` Relationships If you would like to eager load a `morphTo` relationship, as well as nested relationships on the various entities that may be returned by that relationship, you may use the `with` method in combination with the `morphTo` relationship's `morphWith` method. To help illustrate this method, let's consider the following model: - morphTo(); - } + return $this->morphTo(); } +} +``` In this example, let's assume `Event`, `Photo`, and `Post` models may create `ActivityFeed` models. Additionally, let's assume that `Event` models belong to a `Calendar` model, `Photo` models are associated with `Tag` models, and `Post` models belong to an `Author` model. Using these model definitions and relationships, we may retrieve `ActivityFeed` model instances and eager load all `parentable` models and their respective nested relationships: - use Illuminate\Database\Eloquent\Relations\MorphTo; +```php +use Illuminate\Database\Eloquent\Relations\MorphTo; - $activities = ActivityFeed::query() - ->with(['parentable' => function (MorphTo $morphTo) { - $morphTo->morphWith([ - Event::class => ['calendar'], - Photo::class => ['tags'], - Post::class => ['author'], - ]); - }])->get(); +$activities = ActivityFeed::query() + ->with(['parentable' => function (MorphTo $morphTo) { + $morphTo->morphWith([ + Event::class => ['calendar'], + Photo::class => ['tags'], + Post::class => ['author'], + ]); + }])->get(); +``` #### Eager Loading Specific Columns You may not always need every column from the relationships you are retrieving. For this reason, Eloquent allows you to specify which columns of the relationship you would like to retrieve: - $books = Book::with('author:id,name,book_id')->get(); +```php +$books = Book::with('author:id,name,book_id')->get(); +``` > [!WARNING] > When using this feature, you should always include the `id` column and any relevant foreign key columns in the list of columns you wish to retrieve. @@ -1826,82 +2040,94 @@ You may not always need every column from the relationships you are retrieving. Sometimes you might want to always load some relationships when retrieving a model. To accomplish this, you may define a `$with` property on the model: - belongsTo(Author::class); + } - class Book extends Model + /** + * Get the genre of the book. + */ + public function genre(): BelongsTo { - /** - * The relationships that should always be loaded. - * - * @var array - */ - protected $with = ['author']; - - /** - * Get the author that wrote the book. - */ - public function author(): BelongsTo - { - return $this->belongsTo(Author::class); - } - - /** - * Get the genre of the book. - */ - public function genre(): BelongsTo - { - return $this->belongsTo(Genre::class); - } + return $this->belongsTo(Genre::class); } +} +``` If you would like to remove an item from the `$with` property for a single query, you may use the `without` method: - $books = Book::without('author')->get(); +```php +$books = Book::without('author')->get(); +``` If you would like to override all items within the `$with` property for a single query, you may use the `withOnly` method: - $books = Book::withOnly('genre')->get(); +```php +$books = Book::withOnly('genre')->get(); +``` ### Constraining Eager Loads Sometimes you may wish to eager load a relationship but also specify additional query conditions for the eager loading query. You can accomplish this by passing an array of relationships to the `with` method where the array key is a relationship name and the array value is a closure that adds additional constraints to the eager loading query: - use App\Models\User; - use Illuminate\Contracts\Database\Eloquent\Builder; +```php +use App\Models\User; +use Illuminate\Contracts\Database\Eloquent\Builder; - $users = User::with(['posts' => function (Builder $query) { - $query->where('title', 'like', '%code%'); - }])->get(); +$users = User::with(['posts' => function (Builder $query) { + $query->where('title', 'like', '%code%'); +}])->get(); +``` In this example, Eloquent will only eager load posts where the post's `title` column contains the word `code`. You may call other [query builder](/docs/{{version}}/queries) methods to further customize the eager loading operation: - $users = User::with(['posts' => function (Builder $query) { - $query->orderBy('created_at', 'desc'); - }])->get(); +```php +$users = User::with(['posts' => function (Builder $query) { + $query->orderBy('created_at', 'desc'); +}])->get(); +``` #### Constraining Eager Loading of `morphTo` Relationships If you are eager loading a `morphTo` relationship, Eloquent will run multiple queries to fetch each type of related model. You may add additional constraints to each of these queries using the `MorphTo` relation's `constrain` method: - use Illuminate\Database\Eloquent\Relations\MorphTo; +```php +use Illuminate\Database\Eloquent\Relations\MorphTo; - $comments = Comment::with(['commentable' => function (MorphTo $morphTo) { - $morphTo->constrain([ - Post::class => function ($query) { - $query->whereNull('hidden_at'); - }, - Video::class => function ($query) { - $query->where('type', 'educational'); - }, - ]); - }])->get(); +$comments = Comment::with(['commentable' => function (MorphTo $morphTo) { + $morphTo->constrain([ + Post::class => function ($query) { + $query->whereNull('hidden_at'); + }, + Video::class => function ($query) { + $query->where('type', 'educational'); + }, + ]); +}])->get(); +``` In this example, Eloquent will only eager load posts that have not been hidden and videos that have a `type` value of "educational". @@ -1910,34 +2136,42 @@ In this example, Eloquent will only eager load posts that have not been hidden a You may sometimes find yourself needing to check for the existence of a relationship while simultaneously loading the relationship based on the same conditions. For example, you may wish to only retrieve `User` models that have child `Post` models matching a given query condition while also eager loading the matching posts. You may accomplish this using the `withWhereHas` method: - use App\Models\User; +```php +use App\Models\User; - $users = User::withWhereHas('posts', function ($query) { - $query->where('featured', true); - })->get(); +$users = User::withWhereHas('posts', function ($query) { + $query->where('featured', true); +})->get(); +``` ### Lazy Eager Loading Sometimes you may need to eager load a relationship after the parent model has already been retrieved. For example, this may be useful if you need to dynamically decide whether to load related models: - use App\Models\Book; +```php +use App\Models\Book; - $books = Book::all(); +$books = Book::all(); - if ($someCondition) { - $books->load('author', 'publisher'); - } +if ($someCondition) { + $books->load('author', 'publisher'); +} +``` If you need to set additional query constraints on the eager loading query, you may pass an array keyed by the relationships you wish to load. The array values should be closure instances which receive the query instance: - $author->load(['books' => function (Builder $query) { - $query->orderBy('published_date', 'asc'); - }]); +```php +$author->load(['books' => function (Builder $query) { + $query->orderBy('published_date', 'asc'); +}]); +``` To load a relationship only when it has not already been loaded, use the `loadMissing` method: - $book->loadMissing('author'); +```php +$book->loadMissing('author'); +``` #### Nested Lazy Eager Loading and `morphTo` @@ -1946,33 +2180,37 @@ If you would like to eager load a `morphTo` relationship, as well as nested rela This method accepts the name of the `morphTo` relationship as its first argument, and an array of model / relationship pairs as its second argument. To help illustrate this method, let's consider the following model: - morphTo(); - } + return $this->morphTo(); } +} +``` In this example, let's assume `Event`, `Photo`, and `Post` models may create `ActivityFeed` models. Additionally, let's assume that `Event` models belong to a `Calendar` model, `Photo` models are associated with `Tag` models, and `Post` models belong to an `Author` model. Using these model definitions and relationships, we may retrieve `ActivityFeed` model instances and eager load all `parentable` models and their respective nested relationships: - $activities = ActivityFeed::with('parentable') - ->get() - ->loadMorph('parentable', [ - Event::class => ['calendar'], - Photo::class => ['tags'], - Post::class => ['author'], - ]); +```php +$activities = ActivityFeed::with('parentable') + ->get() + ->loadMorph('parentable', [ + Event::class => ['calendar'], + Photo::class => ['tags'], + Post::class => ['author'], + ]); +``` ### Preventing Lazy Loading @@ -2013,85 +2251,101 @@ Model::handleLazyLoadingViolationUsing(function (Model $model, string $relation) Eloquent provides convenient methods for adding new models to relationships. For example, perhaps you need to add a new comment to a post. Instead of manually setting the `post_id` attribute on the `Comment` model you may insert the comment using the relationship's `save` method: - use App\Models\Comment; - use App\Models\Post; +```php +use App\Models\Comment; +use App\Models\Post; - $comment = new Comment(['message' => 'A new comment.']); +$comment = new Comment(['message' => 'A new comment.']); - $post = Post::find(1); +$post = Post::find(1); - $post->comments()->save($comment); +$post->comments()->save($comment); +``` Note that we did not access the `comments` relationship as a dynamic property. Instead, we called the `comments` method to obtain an instance of the relationship. The `save` method will automatically add the appropriate `post_id` value to the new `Comment` model. If you need to save multiple related models, you may use the `saveMany` method: - $post = Post::find(1); +```php +$post = Post::find(1); - $post->comments()->saveMany([ - new Comment(['message' => 'A new comment.']), - new Comment(['message' => 'Another new comment.']), - ]); +$post->comments()->saveMany([ + new Comment(['message' => 'A new comment.']), + new Comment(['message' => 'Another new comment.']), +]); +``` The `save` and `saveMany` methods will persist the given model instances, but will not add the newly persisted models to any in-memory relationships that are already loaded onto the parent model. If you plan on accessing the relationship after using the `save` or `saveMany` methods, you may wish to use the `refresh` method to reload the model and its relationships: - $post->comments()->save($comment); +```php +$post->comments()->save($comment); - $post->refresh(); +$post->refresh(); - // All comments, including the newly saved comment... - $post->comments; +// All comments, including the newly saved comment... +$post->comments; +``` #### Recursively Saving Models and Relationships If you would like to `save` your model and all of its associated relationships, you may use the `push` method. In this example, the `Post` model will be saved as well as its comments and the comment's authors: - $post = Post::find(1); +```php +$post = Post::find(1); - $post->comments[0]->message = 'Message'; - $post->comments[0]->author->name = 'Author Name'; +$post->comments[0]->message = 'Message'; +$post->comments[0]->author->name = 'Author Name'; - $post->push(); +$post->push(); +``` The `pushQuietly` method may be used to save a model and its associated relationships without raising any events: - $post->pushQuietly(); +```php +$post->pushQuietly(); +``` ### The `create` Method In addition to the `save` and `saveMany` methods, you may also use the `create` method, which accepts an array of attributes, creates a model, and inserts it into the database. The difference between `save` and `create` is that `save` accepts a full Eloquent model instance while `create` accepts a plain PHP `array`. The newly created model will be returned by the `create` method: - use App\Models\Post; +```php +use App\Models\Post; - $post = Post::find(1); +$post = Post::find(1); - $comment = $post->comments()->create([ - 'message' => 'A new comment.', - ]); +$comment = $post->comments()->create([ + 'message' => 'A new comment.', +]); +``` You may use the `createMany` method to create multiple related models: - $post = Post::find(1); +```php +$post = Post::find(1); - $post->comments()->createMany([ - ['message' => 'A new comment.'], - ['message' => 'Another new comment.'], - ]); +$post->comments()->createMany([ + ['message' => 'A new comment.'], + ['message' => 'Another new comment.'], +]); +``` The `createQuietly` and `createManyQuietly` methods may be used to create a model(s) without dispatching any events: - $user = User::find(1); +```php +$user = User::find(1); - $user->posts()->createQuietly([ - 'title' => 'Post title.', - ]); +$user->posts()->createQuietly([ + 'title' => 'Post title.', +]); - $user->posts()->createManyQuietly([ - ['title' => 'First post.'], - ['title' => 'Second post.'], - ]); +$user->posts()->createManyQuietly([ + ['title' => 'First post.'], + ['title' => 'Second post.'], +]); +``` You may also use the `findOrNew`, `firstOrNew`, `firstOrCreate`, and `updateOrCreate` methods to [create and update models on relationships](/docs/{{version}}/eloquent#upserts). @@ -2103,19 +2357,23 @@ You may also use the `findOrNew`, `firstOrNew`, `firstOrCreate`, and `updateOrCr If you would like to assign a child model to a new parent model, you may use the `associate` method. In this example, the `User` model defines a `belongsTo` relationship to the `Account` model. This `associate` method will set the foreign key on the child model: - use App\Models\Account; +```php +use App\Models\Account; - $account = Account::find(10); +$account = Account::find(10); - $user->account()->associate($account); +$user->account()->associate($account); - $user->save(); +$user->save(); +``` To remove a parent model from a child model, you may use the `dissociate` method. This method will set the relationship's foreign key to `null`: - $user->account()->dissociate(); +```php +$user->account()->dissociate(); - $user->save(); +$user->save(); +``` ### Many to Many Relationships @@ -2125,78 +2383,100 @@ To remove a parent model from a child model, you may use the `dissociate` method Eloquent also provides methods to make working with many-to-many relationships more convenient. For example, let's imagine a user can have many roles and a role can have many users. You may use the `attach` method to attach a role to a user by inserting a record in the relationship's intermediate table: - use App\Models\User; +```php +use App\Models\User; - $user = User::find(1); +$user = User::find(1); - $user->roles()->attach($roleId); +$user->roles()->attach($roleId); +``` When attaching a relationship to a model, you may also pass an array of additional data to be inserted into the intermediate table: - $user->roles()->attach($roleId, ['expires' => $expires]); +```php +$user->roles()->attach($roleId, ['expires' => $expires]); +``` Sometimes it may be necessary to remove a role from a user. To remove a many-to-many relationship record, use the `detach` method. The `detach` method will delete the appropriate record out of the intermediate table; however, both models will remain in the database: - // Detach a single role from the user... - $user->roles()->detach($roleId); +```php +// Detach a single role from the user... +$user->roles()->detach($roleId); - // Detach all roles from the user... - $user->roles()->detach(); +// Detach all roles from the user... +$user->roles()->detach(); +``` For convenience, `attach` and `detach` also accept arrays of IDs as input: - $user = User::find(1); +```php +$user = User::find(1); - $user->roles()->detach([1, 2, 3]); +$user->roles()->detach([1, 2, 3]); - $user->roles()->attach([ - 1 => ['expires' => $expires], - 2 => ['expires' => $expires], - ]); +$user->roles()->attach([ + 1 => ['expires' => $expires], + 2 => ['expires' => $expires], +]); +``` #### Syncing Associations You may also use the `sync` method to construct many-to-many associations. The `sync` method accepts an array of IDs to place on the intermediate table. Any IDs that are not in the given array will be removed from the intermediate table. So, after this operation is complete, only the IDs in the given array will exist in the intermediate table: - $user->roles()->sync([1, 2, 3]); +```php +$user->roles()->sync([1, 2, 3]); +``` You may also pass additional intermediate table values with the IDs: - $user->roles()->sync([1 => ['expires' => true], 2, 3]); +```php +$user->roles()->sync([1 => ['expires' => true], 2, 3]); +``` If you would like to insert the same intermediate table values with each of the synced model IDs, you may use the `syncWithPivotValues` method: - $user->roles()->syncWithPivotValues([1, 2, 3], ['active' => true]); +```php +$user->roles()->syncWithPivotValues([1, 2, 3], ['active' => true]); +``` If you do not want to detach existing IDs that are missing from the given array, you may use the `syncWithoutDetaching` method: - $user->roles()->syncWithoutDetaching([1, 2, 3]); +```php +$user->roles()->syncWithoutDetaching([1, 2, 3]); +``` #### Toggling Associations The many-to-many relationship also provides a `toggle` method which "toggles" the attachment status of the given related model IDs. If the given ID is currently attached, it will be detached. Likewise, if it is currently detached, it will be attached: - $user->roles()->toggle([1, 2, 3]); +```php +$user->roles()->toggle([1, 2, 3]); +``` You may also pass additional intermediate table values with the IDs: - $user->roles()->toggle([ - 1 => ['expires' => true], - 2 => ['expires' => true], - ]); +```php +$user->roles()->toggle([ + 1 => ['expires' => true], + 2 => ['expires' => true], +]); +``` #### Updating a Record on the Intermediate Table If you need to update an existing row in your relationship's intermediate table, you may use the `updateExistingPivot` method. This method accepts the intermediate record foreign key and an array of attributes to update: - $user = User::find(1); +```php +$user = User::find(1); - $user->roles()->updateExistingPivot($roleId, [ - 'active' => false, - ]); +$user->roles()->updateExistingPivot($roleId, [ + 'active' => false, +]); +``` ## Touching Parent Timestamps @@ -2205,30 +2485,32 @@ When a model defines a `belongsTo` or `belongsToMany` relationship to another mo For example, when a `Comment` model is updated, you may want to automatically "touch" the `updated_at` timestamp of the owning `Post` so that it is set to the current date and time. To accomplish this, you may add a `touches` property to your child model containing the names of the relationships that should have their `updated_at` timestamps updated when the child model is updated: - belongsTo(Post::class); - } + return $this->belongsTo(Post::class); } +} +``` > [!WARNING] > Parent model timestamps will only be updated if the child model is updated using Eloquent's `save` method. diff --git a/eloquent-resources.md b/eloquent-resources.md index e57209bf81e..065ca219ecc 100644 --- a/eloquent-resources.md +++ b/eloquent-resources.md @@ -49,54 +49,60 @@ php artisan make:resource UserCollection Before diving into all of the options available to you when writing resources, let's first take a high-level look at how resources are used within Laravel. A resource class represents a single model that needs to be transformed into a JSON structure. For example, here is a simple `UserResource` resource class: - + */ + public function toArray(Request $request): array { - /** - * Transform the resource into an array. - * - * @return array - */ - public function toArray(Request $request): array - { - return [ - 'id' => $this->id, - 'name' => $this->name, - 'email' => $this->email, - 'created_at' => $this->created_at, - 'updated_at' => $this->updated_at, - ]; - } + return [ + 'id' => $this->id, + 'name' => $this->name, + 'email' => $this->email, + 'created_at' => $this->created_at, + 'updated_at' => $this->updated_at, + ]; } +} +``` Every resource class defines a `toArray` method which returns the array of attributes that should be converted to JSON when the resource is returned as a response from a route or controller method. Note that we can access model properties directly from the `$this` variable. This is because a resource class will automatically proxy property and method access down to the underlying model for convenient access. Once the resource is defined, it may be returned from a route or controller. The resource accepts the underlying model instance via its constructor: - use App\Http\Resources\UserResource; - use App\Models\User; +```php +use App\Http\Resources\UserResource; +use App\Models\User; - Route::get('/user/{id}', function (string $id) { - return new UserResource(User::findOrFail($id)); - }); +Route::get('/user/{id}', function (string $id) { + return new UserResource(User::findOrFail($id)); +}); +``` ### Resource Collections If you are returning a collection of resources or a paginated response, you should use the `collection` method provided by your resource class when creating the resource instance in your route or controller: - use App\Http\Resources\UserResource; - use App\Models\User; +```php +use App\Http\Resources\UserResource; +use App\Models\User; - Route::get('/users', function () { - return UserResource::collection(User::all()); - }); +Route::get('/users', function () { + return UserResource::collection(User::all()); +}); +``` Note that this does not allow any addition of custom meta data that may need to be returned with your collection. If you would like to customize the resource collection response, you may create a dedicated resource to represent the collection: @@ -106,69 +112,77 @@ php artisan make:resource UserCollection Once the resource collection class has been generated, you may easily define any meta data that should be included with the response: - + */ + public function toArray(Request $request): array { - /** - * Transform the resource collection into an array. - * - * @return array - */ - public function toArray(Request $request): array - { - return [ - 'data' => $this->collection, - 'links' => [ - 'self' => 'link-value', - ], - ]; - } + return [ + 'data' => $this->collection, + 'links' => [ + 'self' => 'link-value', + ], + ]; } +} +``` After defining your resource collection, it may be returned from a route or controller: - use App\Http\Resources\UserCollection; - use App\Models\User; +```php +use App\Http\Resources\UserCollection; +use App\Models\User; - Route::get('/users', function () { - return new UserCollection(User::all()); - }); +Route::get('/users', function () { + return new UserCollection(User::all()); +}); +``` #### Preserving Collection Keys When returning a resource collection from a route, Laravel resets the collection's keys so that they are in numerical order. However, you may add a `preserveKeys` property to your resource class indicating whether a collection's original keys should be preserved: - keyBy->id); - }); +Route::get('/users', function () { + return UserResource::collection(User::all()->keyBy->id); +}); +``` #### Customizing the Underlying Resource Class @@ -177,21 +191,23 @@ Typically, the `$this->collection` property of a resource collection is automati For example, `UserCollection` will attempt to map the given user instances into the `UserResource` resource. To customize this behavior, you may override the `$collects` property of your resource collection: - ## Writing Resources @@ -201,49 +217,16 @@ For example, `UserCollection` will attempt to map the given user instances into Resources only need to transform a given model into an array. So, each resource contains a `toArray` method which translates your model's attributes into an API friendly array that can be returned from your application's routes or controllers: - - */ - public function toArray(Request $request): array - { - return [ - 'id' => $this->id, - 'name' => $this->name, - 'email' => $this->email, - 'created_at' => $this->created_at, - 'updated_at' => $this->updated_at, - ]; - } - } - -Once a resource has been defined, it may be returned directly from a route or controller: - - use App\Http\Resources\UserResource; - use App\Models\User; - - Route::get('/user/{id}', function (string $id) { - return new UserResource(User::findOrFail($id)); - }); - - -#### Relationships +```php + $this->id, 'name' => $this->name, 'email' => $this->email, - 'posts' => PostResource::collection($this->posts), 'created_at' => $this->created_at, 'updated_at' => $this->updated_at, ]; } +} +``` + +Once a resource has been defined, it may be returned directly from a route or controller: + +```php +use App\Http\Resources\UserResource; +use App\Models\User; + +Route::get('/user/{id}', function (string $id) { + return new UserResource(User::findOrFail($id)); +}); +``` + + +#### Relationships + +If you would like to include related resources in your response, you may add them to the array returned by your resource's `toArray` method. In this example, we will use the `PostResource` resource's `collection` method to add the user's blog posts to the resource response: + +```php +use App\Http\Resources\PostResource; +use Illuminate\Http\Request; + +/** + * Transform the resource into an array. + * + * @return array + */ +public function toArray(Request $request): array +{ + return [ + 'id' => $this->id, + 'name' => $this->name, + 'email' => $this->email, + 'posts' => PostResource::collection($this->posts), + 'created_at' => $this->created_at, + 'updated_at' => $this->updated_at, + ]; +} +``` > [!NOTE] > If you would like to include relationships only when they have already been loaded, check out the documentation on [conditional relationships](#conditional-relationships). @@ -269,48 +291,54 @@ If you would like to include related resources in your response, you may add the While resources transform a single model into an array, resource collections transform a collection of models into an array. However, it is not absolutely necessary to define a resource collection class for each one of your models since all resources provide a `collection` method to generate an "ad-hoc" resource collection on the fly: - use App\Http\Resources\UserResource; - use App\Models\User; +```php +use App\Http\Resources\UserResource; +use App\Models\User; - Route::get('/users', function () { - return UserResource::collection(User::all()); - }); +Route::get('/users', function () { + return UserResource::collection(User::all()); +}); +``` However, if you need to customize the meta data returned with the collection, it is necessary to define your own resource collection: - + */ + public function toArray(Request $request): array { - /** - * Transform the resource collection into an array. - * - * @return array - */ - public function toArray(Request $request): array - { - return [ - 'data' => $this->collection, - 'links' => [ - 'self' => 'link-value', - ], - ]; - } + return [ + 'data' => $this->collection, + 'links' => [ + 'self' => 'link-value', + ], + ]; } +} +``` Like singular resources, resource collections may be returned directly from routes or controllers: - use App\Http\Resources\UserCollection; - use App\Models\User; +```php +use App\Http\Resources\UserCollection; +use App\Models\User; - Route::get('/users', function () { - return new UserCollection(User::all()); - }); +Route::get('/users', function () { + return new UserCollection(User::all()); +}); +``` ### Data Wrapping @@ -336,31 +364,33 @@ By default, your outermost resource is wrapped in a `data` key when the resource If you would like to disable the wrapping of the outermost resource, you should invoke the `withoutWrapping` method on the base `Illuminate\Http\Resources\Json\JsonResource` class. Typically, you should call this method from your `AppServiceProvider` or another [service provider](/docs/{{version}}/providers) that is loaded on every request to your application: - [!WARNING] > The `withoutWrapping` method only affects the outermost response and will not remove `data` keys that you manually add to your own resource collections. @@ -372,24 +402,26 @@ You have total freedom to determine how your resource's relationships are wrappe You may be wondering if this will cause your outermost resource to be wrapped in two `data` keys. Don't worry, Laravel will never let your resources be accidentally double-wrapped, so you don't have to be concerned about the nesting level of the resource collection you are transforming: - + */ + public function toArray(Request $request): array { - /** - * Transform the resource collection into an array. - * - * @return array - */ - public function toArray(Request $request): array - { - return ['data' => $this->collection]; - } + return ['data' => $this->collection]; } +} +``` #### Data Wrapping and Pagination @@ -433,12 +465,14 @@ When returning paginated collections via a resource response, Laravel will wrap You may pass a Laravel paginator instance to the `collection` method of a resource or to a custom resource collection: - use App\Http\Resources\UserCollection; - use App\Models\User; +```php +use App\Http\Resources\UserCollection; +use App\Models\User; - Route::get('/users', function () { - return new UserCollection(User::paginate()); - }); +Route::get('/users', function () { + return new UserCollection(User::paginate()); +}); +``` Paginated responses always contain `meta` and `links` keys with information about the paginator's state: @@ -479,83 +513,95 @@ Paginated responses always contain `meta` and `links` keys with information abou If you would like to customize the information included in the `links` or `meta` keys of the pagination response, you may define a `paginationInformation` method on the resource. This method will receive the `$paginated` data and the array of `$default` information, which is an array containing the `links` and `meta` keys: - /** - * Customize the pagination information for the resource. - * - * @param \Illuminate\Http\Request $request - * @param array $paginated - * @param array $default - * @return array - */ - public function paginationInformation($request, $paginated, $default) - { - $default['links']['custom'] = 'https://example.com'; +```php +/** + * Customize the pagination information for the resource. + * + * @param \Illuminate\Http\Request $request + * @param array $paginated + * @param array $default + * @return array + */ +public function paginationInformation($request, $paginated, $default) +{ + $default['links']['custom'] = 'https://example.com'; - return $default; - } + return $default; +} +``` ### Conditional Attributes Sometimes you may wish to only include an attribute in a resource response if a given condition is met. For example, you may wish to only include a value if the current user is an "administrator". Laravel provides a variety of helper methods to assist you in this situation. The `when` method may be used to conditionally add an attribute to a resource response: - /** - * Transform the resource into an array. - * - * @return array - */ - public function toArray(Request $request): array - { - return [ - 'id' => $this->id, - 'name' => $this->name, - 'email' => $this->email, - 'secret' => $this->when($request->user()->isAdmin(), 'secret-value'), - 'created_at' => $this->created_at, - 'updated_at' => $this->updated_at, - ]; - } +```php +/** + * Transform the resource into an array. + * + * @return array + */ +public function toArray(Request $request): array +{ + return [ + 'id' => $this->id, + 'name' => $this->name, + 'email' => $this->email, + 'secret' => $this->when($request->user()->isAdmin(), 'secret-value'), + 'created_at' => $this->created_at, + 'updated_at' => $this->updated_at, + ]; +} +``` In this example, the `secret` key will only be returned in the final resource response if the authenticated user's `isAdmin` method returns `true`. If the method returns `false`, the `secret` key will be removed from the resource response before it is sent to the client. The `when` method allows you to expressively define your resources without resorting to conditional statements when building the array. The `when` method also accepts a closure as its second argument, allowing you to calculate the resulting value only if the given condition is `true`: - 'secret' => $this->when($request->user()->isAdmin(), function () { - return 'secret-value'; - }), +```php +'secret' => $this->when($request->user()->isAdmin(), function () { + return 'secret-value'; +}), +``` The `whenHas` method may be used to include an attribute if it is actually present on the underlying model: - 'name' => $this->whenHas('name'), +```php +'name' => $this->whenHas('name'), +``` Additionally, the `whenNotNull` method may be used to include an attribute in the resource response if the attribute is not null: - 'name' => $this->whenNotNull($this->name), +```php +'name' => $this->whenNotNull($this->name), +``` #### Merging Conditional Attributes Sometimes you may have several attributes that should only be included in the resource response based on the same condition. In this case, you may use the `mergeWhen` method to include the attributes in the response only when the given condition is `true`: - /** - * Transform the resource into an array. - * - * @return array - */ - public function toArray(Request $request): array - { - return [ - 'id' => $this->id, - 'name' => $this->name, - 'email' => $this->email, - $this->mergeWhen($request->user()->isAdmin(), [ - 'first-secret' => 'value', - 'second-secret' => 'value', - ]), - 'created_at' => $this->created_at, - 'updated_at' => $this->updated_at, - ]; - } +```php +/** + * Transform the resource into an array. + * + * @return array + */ +public function toArray(Request $request): array +{ + return [ + 'id' => $this->id, + 'name' => $this->name, + 'email' => $this->email, + $this->mergeWhen($request->user()->isAdmin(), [ + 'first-secret' => 'value', + 'second-secret' => 'value', + ]), + 'created_at' => $this->created_at, + 'updated_at' => $this->updated_at, + ]; +} +``` Again, if the given condition is `false`, these attributes will be removed from the resource response before it is sent to the client. @@ -569,24 +615,26 @@ In addition to conditionally loading attributes, you may conditionally include r The `whenLoaded` method may be used to conditionally load a relationship. In order to avoid unnecessarily loading relationships, this method accepts the name of the relationship instead of the relationship itself: - use App\Http\Resources\PostResource; - - /** - * Transform the resource into an array. - * - * @return array - */ - public function toArray(Request $request): array - { - return [ - 'id' => $this->id, - 'name' => $this->name, - 'email' => $this->email, - 'posts' => PostResource::collection($this->whenLoaded('posts')), - 'created_at' => $this->created_at, - 'updated_at' => $this->updated_at, - ]; - } +```php +use App\Http\Resources\PostResource; + +/** + * Transform the resource into an array. + * + * @return array + */ +public function toArray(Request $request): array +{ + return [ + 'id' => $this->id, + 'name' => $this->name, + 'email' => $this->email, + 'posts' => PostResource::collection($this->whenLoaded('posts')), + 'created_at' => $this->created_at, + 'updated_at' => $this->updated_at, + ]; +} +``` In this example, if the relationship has not been loaded, the `posts` key will be removed from the resource response before it is sent to the client. @@ -595,26 +643,30 @@ In this example, if the relationship has not been loaded, the `posts` key will b In addition to conditionally including relationships, you may conditionally include relationship "counts" on your resource responses based on if the relationship's count has been loaded on the model: - new UserResource($user->loadCount('posts')); +```php +new UserResource($user->loadCount('posts')); +``` The `whenCounted` method may be used to conditionally include a relationship's count in your resource response. This method avoids unnecessarily including the attribute if the relationships' count is not present: - /** - * Transform the resource into an array. - * - * @return array - */ - public function toArray(Request $request): array - { - return [ - 'id' => $this->id, - 'name' => $this->name, - 'email' => $this->email, - 'posts_count' => $this->whenCounted('posts'), - 'created_at' => $this->created_at, - 'updated_at' => $this->updated_at, - ]; - } +```php +/** + * Transform the resource into an array. + * + * @return array + */ +public function toArray(Request $request): array +{ + return [ + 'id' => $this->id, + 'name' => $this->name, + 'email' => $this->email, + 'posts_count' => $this->whenCounted('posts'), + 'created_at' => $this->created_at, + 'updated_at' => $this->updated_at, + ]; +} +``` In this example, if the `posts` relationship's count has not been loaded, the `posts_count` key will be removed from the resource response before it is sent to the client. @@ -632,65 +684,73 @@ Other types of aggregates, such as `avg`, `sum`, `min`, and `max` may also be co In addition to conditionally including relationship information in your resource responses, you may conditionally include data from the intermediate tables of many-to-many relationships using the `whenPivotLoaded` method. The `whenPivotLoaded` method accepts the name of the pivot table as its first argument. The second argument should be a closure that returns the value to be returned if the pivot information is available on the model: - /** - * Transform the resource into an array. - * - * @return array - */ - public function toArray(Request $request): array - { - return [ - 'id' => $this->id, - 'name' => $this->name, - 'expires_at' => $this->whenPivotLoaded('role_user', function () { - return $this->pivot->expires_at; - }), - ]; - } +```php +/** + * Transform the resource into an array. + * + * @return array + */ +public function toArray(Request $request): array +{ + return [ + 'id' => $this->id, + 'name' => $this->name, + 'expires_at' => $this->whenPivotLoaded('role_user', function () { + return $this->pivot->expires_at; + }), + ]; +} +``` If your relationship is using a [custom intermediate table model](/docs/{{version}}/eloquent-relationships#defining-custom-intermediate-table-models), you may pass an instance of the intermediate table model as the first argument to the `whenPivotLoaded` method: - 'expires_at' => $this->whenPivotLoaded(new Membership, function () { - return $this->pivot->expires_at; - }), +```php +'expires_at' => $this->whenPivotLoaded(new Membership, function () { + return $this->pivot->expires_at; +}), +``` If your intermediate table is using an accessor other than `pivot`, you may use the `whenPivotLoadedAs` method: - /** - * Transform the resource into an array. - * - * @return array - */ - public function toArray(Request $request): array - { - return [ - 'id' => $this->id, - 'name' => $this->name, - 'expires_at' => $this->whenPivotLoadedAs('subscription', 'role_user', function () { - return $this->subscription->expires_at; - }), - ]; - } +```php +/** + * Transform the resource into an array. + * + * @return array + */ +public function toArray(Request $request): array +{ + return [ + 'id' => $this->id, + 'name' => $this->name, + 'expires_at' => $this->whenPivotLoadedAs('subscription', 'role_user', function () { + return $this->subscription->expires_at; + }), + ]; +} +``` ### Adding Meta Data Some JSON API standards require the addition of meta data to your resource and resource collections responses. This often includes things like `links` to the resource or related resources, or meta data about the resource itself. If you need to return additional meta data about a resource, include it in your `toArray` method. For example, you might include `links` information when transforming a resource collection: - /** - * Transform the resource into an array. - * - * @return array - */ - public function toArray(Request $request): array - { - return [ - 'data' => $this->collection, - 'links' => [ - 'self' => 'link-value', - ], - ]; - } +```php +/** + * Transform the resource into an array. + * + * @return array + */ +public function toArray(Request $request): array +{ + return [ + 'data' => $this->collection, + 'links' => [ + 'self' => 'link-value', + ], + ]; +} +``` When returning additional meta data from your resources, you never have to worry about accidentally overriding the `links` or `meta` keys that are automatically added by Laravel when returning paginated responses. Any additional `links` you define will be merged with the links provided by the paginator. @@ -699,101 +759,111 @@ When returning additional meta data from your resources, you never have to worry Sometimes you may wish to only include certain meta data with a resource response if the resource is the outermost resource being returned. Typically, this includes meta information about the response as a whole. To define this meta data, add a `with` method to your resource class. This method should return an array of meta data to be included with the resource response only when the resource is the outermost resource being transformed: - + */ + public function toArray(Request $request): array { - /** - * Transform the resource collection into an array. - * - * @return array - */ - public function toArray(Request $request): array - { - return parent::toArray($request); - } + return parent::toArray($request); + } - /** - * Get additional data that should be returned with the resource array. - * - * @return array - */ - public function with(Request $request): array - { - return [ - 'meta' => [ - 'key' => 'value', - ], - ]; - } + /** + * Get additional data that should be returned with the resource array. + * + * @return array + */ + public function with(Request $request): array + { + return [ + 'meta' => [ + 'key' => 'value', + ], + ]; } +} +``` #### Adding Meta Data When Constructing Resources You may also add top-level data when constructing resource instances in your route or controller. The `additional` method, which is available on all resources, accepts an array of data that should be added to the resource response: - return (new UserCollection(User::all()->load('roles'))) - ->additional(['meta' => [ - 'key' => 'value', - ]]); +```php +return (new UserCollection(User::all()->load('roles'))) + ->additional(['meta' => [ + 'key' => 'value', + ]]); +``` ## Resource Responses As you have already read, resources may be returned directly from routes and controllers: - use App\Http\Resources\UserResource; - use App\Models\User; +```php +use App\Http\Resources\UserResource; +use App\Models\User; - Route::get('/user/{id}', function (string $id) { - return new UserResource(User::findOrFail($id)); - }); +Route::get('/user/{id}', function (string $id) { + return new UserResource(User::findOrFail($id)); +}); +``` However, sometimes you may need to customize the outgoing HTTP response before it is sent to the client. There are two ways to accomplish this. First, you may chain the `response` method onto the resource. This method will return an `Illuminate\Http\JsonResponse` instance, giving you full control over the response's headers: - use App\Http\Resources\UserResource; - use App\Models\User; - - Route::get('/user', function () { - return (new UserResource(User::find(1))) - ->response() - ->header('X-Value', 'True'); - }); +```php +use App\Http\Resources\UserResource; +use App\Models\User; + +Route::get('/user', function () { + return (new UserResource(User::find(1))) + ->response() + ->header('X-Value', 'True'); +}); +``` Alternatively, you may define a `withResponse` method within the resource itself. This method will be called when the resource is returned as the outermost resource in a response: - + */ + public function toArray(Request $request): array { - /** - * Transform the resource into an array. - * - * @return array - */ - public function toArray(Request $request): array - { - return [ - 'id' => $this->id, - ]; - } + return [ + 'id' => $this->id, + ]; + } - /** - * Customize the outgoing response for the resource. - */ - public function withResponse(Request $request, JsonResponse $response): void - { - $response->header('X-Value', 'True'); - } + /** + * Customize the outgoing response for the resource. + */ + public function withResponse(Request $request, JsonResponse $response): void + { + $response->header('X-Value', 'True'); } +} +``` diff --git a/eloquent-serialization.md b/eloquent-serialization.md index 45f4df9cce7..5488da39372 100644 --- a/eloquent-serialization.md +++ b/eloquent-serialization.md @@ -24,46 +24,58 @@ When building APIs using Laravel, you will often need to convert your models and To convert a model and its loaded [relationships](/docs/{{version}}/eloquent-relationships) to an array, you should use the `toArray` method. This method is recursive, so all attributes and all relations (including the relations of relations) will be converted to arrays: - use App\Models\User; +```php +use App\Models\User; - $user = User::with('roles')->first(); +$user = User::with('roles')->first(); - return $user->toArray(); +return $user->toArray(); +``` The `attributesToArray` method may be used to convert a model's attributes to an array but not its relationships: - $user = User::first(); +```php +$user = User::first(); - return $user->attributesToArray(); +return $user->attributesToArray(); +``` You may also convert entire [collections](/docs/{{version}}/eloquent-collections) of models to arrays by calling the `toArray` method on the collection instance: - $users = User::all(); +```php +$users = User::all(); - return $users->toArray(); +return $users->toArray(); +``` ### Serializing to JSON To convert a model to JSON, you should use the `toJson` method. Like `toArray`, the `toJson` method is recursive, so all attributes and relations will be converted to JSON. You may also specify any JSON encoding options that are [supported by PHP](https://secure.php.net/manual/en/function.json-encode.php): - use App\Models\User; +```php +use App\Models\User; - $user = User::find(1); +$user = User::find(1); - return $user->toJson(); +return $user->toJson(); - return $user->toJson(JSON_PRETTY_PRINT); +return $user->toJson(JSON_PRETTY_PRINT); +``` Alternatively, you may cast a model or collection to a string, which will automatically call the `toJson` method on the model or collection: - return (string) User::find(1); +```php +return (string) User::find(1); +``` Since models and collections are converted to JSON when cast to a string, you can return Eloquent objects directly from your application's routes or controllers. Laravel will automatically serialize your Eloquent models and collections to JSON when they are returned from routes or controllers: - Route::get('/users', function () { - return User::all(); - }); +```php +Route::get('/users', function () { + return User::all(); +}); +``` #### Relationships @@ -75,102 +87,116 @@ When an Eloquent model is converted to JSON, its loaded relationships will autom Sometimes you may wish to limit the attributes, such as passwords, that are included in your model's array or JSON representation. To do so, add a `$hidden` property to your model. Attributes that are listed in the `$hidden` property's array will not be included in the serialized representation of your model: - - */ - protected $hidden = ['password']; - } +class User extends Model +{ + /** + * The attributes that should be hidden for serialization. + * + * @var array + */ + protected $hidden = ['password']; +} +``` > [!NOTE] > To hide relationships, add the relationship's method name to your Eloquent model's `$hidden` property. Alternatively, you may use the `visible` property to define an "allow list" of attributes that should be included in your model's array and JSON representation. All attributes that are not present in the `$visible` array will be hidden when the model is converted to an array or JSON: - #### Temporarily Modifying Attribute Visibility If you would like to make some typically hidden attributes visible on a given model instance, you may use the `makeVisible` method. The `makeVisible` method returns the model instance: - return $user->makeVisible('attribute')->toArray(); +```php +return $user->makeVisible('attribute')->toArray(); +``` Likewise, if you would like to hide some attributes that are typically visible, you may use the `makeHidden` method. - return $user->makeHidden('attribute')->toArray(); +```php +return $user->makeHidden('attribute')->toArray(); +``` If you wish to temporarily override all of the visible or hidden attributes, you may use the `setVisible` and `setHidden` methods respectively: - return $user->setVisible(['id', 'name'])->toArray(); +```php +return $user->setVisible(['id', 'name'])->toArray(); - return $user->setHidden(['email', 'password', 'remember_token'])->toArray(); +return $user->setHidden(['email', 'password', 'remember_token'])->toArray(); +``` ## Appending Values to JSON Occasionally, when converting models to arrays or JSON, you may wish to add attributes that do not have a corresponding column in your database. To do so, first define an [accessor](/docs/{{version}}/eloquent-mutators) for the value: - 'yes', - ); - } + return new Attribute( + get: fn () => 'yes', + ); } +} +``` If you would like the accessor to always be appended to your model's array and JSON representations, you may add the attribute name to the `appends` property of your model. Note that attribute names are typically referenced using their "snake case" serialized representation, even though the accessor's PHP method is defined using "camel case": - append('is_admin')->toArray(); +```php +return $user->append('is_admin')->toArray(); - return $user->setAppends(['is_admin'])->toArray(); +return $user->setAppends(['is_admin'])->toArray(); +``` ## Date Serialization @@ -191,23 +219,27 @@ At runtime, you may instruct a model instance to append additional attributes us You may customize the default serialization format by overriding the `serializeDate` method. This method does not affect how your dates are formatted for storage in the database: - /** - * Prepare a date for array / JSON serialization. - */ - protected function serializeDate(DateTimeInterface $date): string - { - return $date->format('Y-m-d'); - } +```php +/** + * Prepare a date for array / JSON serialization. + */ +protected function serializeDate(DateTimeInterface $date): string +{ + return $date->format('Y-m-d'); +} +``` #### Customizing the Date Format per Attribute You may customize the serialization format of individual Eloquent date attributes by specifying the date format in the model's [cast declarations](/docs/{{version}}/eloquent-mutators#attribute-casting): - protected function casts(): array - { - return [ - 'birthday' => 'date:Y-m-d', - 'joined_at' => 'datetime:Y-m-d H:00', - ]; - } +```php +protected function casts(): array +{ + return [ + 'birthday' => 'date:Y-m-d', + 'joined_at' => 'datetime:Y-m-d H:00', + ]; +} +``` diff --git a/eloquent.md b/eloquent.md index 74504364dda..ab6c8ff076d 100644 --- a/eloquent.md +++ b/eloquent.md @@ -114,16 +114,18 @@ php artisan model:show Flight Models generated by the `make:model` command will be placed in the `app/Models` directory. Let's examine a basic model class and discuss some of Eloquent's key conventions: - ### Table Names @@ -132,70 +134,78 @@ After glancing at the example above, you may have noticed that we did not tell E If your model's corresponding database table does not fit this convention, you may manually specify the model's table name by defining a `table` property on the model: - ### Primary Keys Eloquent will also assume that each model's corresponding database table has a primary key column named `id`. If necessary, you may define a protected `$primaryKey` property on your model to specify a different column that serves as your model's primary key: - #### "Composite" Primary Keys @@ -209,157 +219,175 @@ Instead of using auto-incrementing integers as your Eloquent model's primary key If you would like a model to use a UUID key instead of an auto-incrementing integer key, you may use the `Illuminate\Database\Eloquent\Concerns\HasUuids` trait on the model. Of course, you should ensure that the model has a [UUID equivalent primary key column](/docs/{{version}}/migrations#column-method-uuid): - use Illuminate\Database\Eloquent\Concerns\HasUuids; - use Illuminate\Database\Eloquent\Model; +```php +use Illuminate\Database\Eloquent\Concerns\HasUuids; +use Illuminate\Database\Eloquent\Model; - class Article extends Model - { - use HasUuids; +class Article extends Model +{ + use HasUuids; - // ... - } + // ... +} - $article = Article::create(['title' => 'Traveling to Europe']); +$article = Article::create(['title' => 'Traveling to Europe']); - $article->id; // "8f8e8478-9035-4d23-b9a7-62f4d2612ce5" +$article->id; // "8f8e8478-9035-4d23-b9a7-62f4d2612ce5" +``` By default, The `HasUuids` trait will generate ["ordered" UUIDs](/docs/{{version}}/strings#method-str-ordered-uuid) for your models. These UUIDs are more efficient for indexed database storage because they can be sorted lexicographically. You can override the UUID generation process for a given model by defining a `newUniqueId` method on the model. In addition, you may specify which columns should receive UUIDs by defining a `uniqueIds` method on the model: - use Ramsey\Uuid\Uuid; +```php +use Ramsey\Uuid\Uuid; - /** - * Generate a new UUID for the model. - */ - public function newUniqueId(): string - { - return (string) Uuid::uuid4(); - } +/** + * Generate a new UUID for the model. + */ +public function newUniqueId(): string +{ + return (string) Uuid::uuid4(); +} - /** - * Get the columns that should receive a unique identifier. - * - * @return array - */ - public function uniqueIds(): array - { - return ['id', 'discount_code']; - } +/** + * Get the columns that should receive a unique identifier. + * + * @return array + */ +public function uniqueIds(): array +{ + return ['id', 'discount_code']; +} +``` If you wish, you may choose to utilize "ULIDs" instead of UUIDs. ULIDs are similar to UUIDs; however, they are only 26 characters in length. Like ordered UUIDs, ULIDs are lexicographically sortable for efficient database indexing. To utilize ULIDs, you should use the `Illuminate\Database\Eloquent\Concerns\HasUlids` trait on your model. You should also ensure that the model has a [ULID equivalent primary key column](/docs/{{version}}/migrations#column-method-ulid): - use Illuminate\Database\Eloquent\Concerns\HasUlids; - use Illuminate\Database\Eloquent\Model; +```php +use Illuminate\Database\Eloquent\Concerns\HasUlids; +use Illuminate\Database\Eloquent\Model; - class Article extends Model - { - use HasUlids; +class Article extends Model +{ + use HasUlids; - // ... - } + // ... +} - $article = Article::create(['title' => 'Traveling to Asia']); +$article = Article::create(['title' => 'Traveling to Asia']); - $article->id; // "01gd4d3tgrrfqeda94gdbtdk5c" +$article->id; // "01gd4d3tgrrfqeda94gdbtdk5c" +``` ### Timestamps By default, Eloquent expects `created_at` and `updated_at` columns to exist on your model's corresponding database table. Eloquent will automatically set these column's values when models are created or updated. If you do not want these columns to be automatically managed by Eloquent, you should define a `$timestamps` property on your model with a value of `false`: - $post->increment('reads')); +```php +Model::withoutTimestamps(fn () => $post->increment('reads')); +``` ### Database Connections By default, all Eloquent models will use the default database connection that is configured for your application. If you would like to specify a different connection that should be used when interacting with a particular model, you should define a `$connection` property on the model: - ### Default Attribute Values By default, a newly instantiated model instance will not contain any attribute values. If you would like to define the default values for some of your model's attributes, you may define an `$attributes` property on your model. Attribute values placed in the `$attributes` array should be in their raw, "storable" format as if they were just read from the database: - '[]', - 'delayed' => false, - ]; - } +class Flight extends Model +{ + /** + * The model's default values for attributes. + * + * @var array + */ + protected $attributes = [ + 'options' => '[]', + 'delayed' => false, + ]; +} +``` ### Configuring Eloquent Strictness @@ -391,21 +419,25 @@ Model::preventSilentlyDiscardingAttributes(! $this->app->isProduction()); Once you have created a model and [its associated database table](/docs/{{version}}/migrations#generating-migrations), you are ready to start retrieving data from your database. You can think of each Eloquent model as a powerful [query builder](/docs/{{version}}/queries) allowing you to fluently query the database table associated with the model. The model's `all` method will retrieve all of the records from the model's associated database table: - use App\Models\Flight; +```php +use App\Models\Flight; - foreach (Flight::all() as $flight) { - echo $flight->name; - } +foreach (Flight::all() as $flight) { + echo $flight->name; +} +``` #### Building Queries The Eloquent `all` method will return all of the results in the model's table. However, since each Eloquent model serves as a [query builder](/docs/{{version}}/queries), you may add additional constraints to queries and then invoke the `get` method to retrieve the results: - $flights = Flight::where('active', 1) - ->orderBy('name') - ->take(10) - ->get(); +```php +$flights = Flight::where('active', 1) + ->orderBy('name') + ->take(10) + ->get(); +``` > [!NOTE] > Since Eloquent models are query builders, you should review all of the methods provided by Laravel's [query builder](/docs/{{version}}/queries). You may use any of these methods when writing your Eloquent queries. @@ -415,19 +447,23 @@ The Eloquent `all` method will return all of the results in the model's table. H If you already have an instance of an Eloquent model that was retrieved from the database, you can "refresh" the model using the `fresh` and `refresh` methods. The `fresh` method will re-retrieve the model from the database. The existing model instance will not be affected: - $flight = Flight::where('number', 'FR 900')->first(); +```php +$flight = Flight::where('number', 'FR 900')->first(); - $freshFlight = $flight->fresh(); +$freshFlight = $flight->fresh(); +``` The `refresh` method will re-hydrate the existing model using fresh data from the database. In addition, all of its loaded relationships will be refreshed as well: - $flight = Flight::where('number', 'FR 900')->first(); +```php +$flight = Flight::where('number', 'FR 900')->first(); - $flight->number = 'FR 456'; +$flight->number = 'FR 456'; - $flight->refresh(); +$flight->refresh(); - $flight->number; // "FR 900" +$flight->number; // "FR 900" +``` ### Collections @@ -565,69 +601,81 @@ Eloquent also offers advanced subquery support, which allows you to pull informa Using the subquery functionality available to the query builder's `select` and `addSelect` methods, we can select all of the `destinations` and the name of the flight that most recently arrived at that destination using a single query: - use App\Models\Destination; - use App\Models\Flight; +```php +use App\Models\Destination; +use App\Models\Flight; - return Destination::addSelect(['last_flight' => Flight::select('name') - ->whereColumn('destination_id', 'destinations.id') - ->orderByDesc('arrived_at') - ->limit(1) - ])->get(); +return Destination::addSelect(['last_flight' => Flight::select('name') + ->whereColumn('destination_id', 'destinations.id') + ->orderByDesc('arrived_at') + ->limit(1) +])->get(); +``` #### Subquery Ordering In addition, the query builder's `orderBy` function supports subqueries. Continuing to use our flight example, we may use this functionality to sort all destinations based on when the last flight arrived at that destination. Again, this may be done while executing a single database query: - return Destination::orderByDesc( - Flight::select('arrived_at') - ->whereColumn('destination_id', 'destinations.id') - ->orderByDesc('arrived_at') - ->limit(1) - )->get(); +```php +return Destination::orderByDesc( + Flight::select('arrived_at') + ->whereColumn('destination_id', 'destinations.id') + ->orderByDesc('arrived_at') + ->limit(1) +)->get(); +``` ## Retrieving Single Models / Aggregates In addition to retrieving all of the records matching a given query, you may also retrieve single records using the `find`, `first`, or `firstWhere` methods. Instead of returning a collection of models, these methods return a single model instance: - use App\Models\Flight; +```php +use App\Models\Flight; - // Retrieve a model by its primary key... - $flight = Flight::find(1); +// Retrieve a model by its primary key... +$flight = Flight::find(1); - // Retrieve the first model matching the query constraints... - $flight = Flight::where('active', 1)->first(); +// Retrieve the first model matching the query constraints... +$flight = Flight::where('active', 1)->first(); - // Alternative to retrieving the first model matching the query constraints... - $flight = Flight::firstWhere('active', 1); +// Alternative to retrieving the first model matching the query constraints... +$flight = Flight::firstWhere('active', 1); +``` Sometimes you may wish to perform some other action if no results are found. The `findOr` and `firstOr` methods will return a single model instance or, if no results are found, execute the given closure. The value returned by the closure will be considered the result of the method: - $flight = Flight::findOr(1, function () { - // ... - }); +```php +$flight = Flight::findOr(1, function () { + // ... +}); - $flight = Flight::where('legs', '>', 3)->firstOr(function () { - // ... - }); +$flight = Flight::where('legs', '>', 3)->firstOr(function () { + // ... +}); +``` #### Not Found Exceptions Sometimes you may wish to throw an exception if a model is not found. This is particularly useful in routes or controllers. The `findOrFail` and `firstOrFail` methods will retrieve the first result of the query; however, if no result is found, an `Illuminate\Database\Eloquent\ModelNotFoundException` will be thrown: - $flight = Flight::findOrFail(1); +```php +$flight = Flight::findOrFail(1); - $flight = Flight::where('legs', '>', 3)->firstOrFail(); +$flight = Flight::where('legs', '>', 3)->firstOrFail(); +``` If the `ModelNotFoundException` is not caught, a 404 HTTP response is automatically sent back to the client: - use App\Models\Flight; +```php +use App\Models\Flight; - Route::get('/api/flights/{id}', function (string $id) { - return Flight::findOrFail($id); - }); +Route::get('/api/flights/{id}', function (string $id) { + return Flight::findOrFail($id); +}); +``` ### Retrieving or Creating Models @@ -636,38 +684,42 @@ The `firstOrCreate` method will attempt to locate a database record using the gi The `firstOrNew` method, like `firstOrCreate`, will attempt to locate a record in the database matching the given attributes. However, if a model is not found, a new model instance will be returned. Note that the model returned by `firstOrNew` has not yet been persisted to the database. You will need to manually call the `save` method to persist it: - use App\Models\Flight; - - // Retrieve flight by name or create it if it doesn't exist... - $flight = Flight::firstOrCreate([ - 'name' => 'London to Paris' - ]); - - // Retrieve flight by name or create it with the name, delayed, and arrival_time attributes... - $flight = Flight::firstOrCreate( - ['name' => 'London to Paris'], - ['delayed' => 1, 'arrival_time' => '11:30'] - ); - - // Retrieve flight by name or instantiate a new Flight instance... - $flight = Flight::firstOrNew([ - 'name' => 'London to Paris' - ]); +```php +use App\Models\Flight; - // Retrieve flight by name or instantiate with the name, delayed, and arrival_time attributes... - $flight = Flight::firstOrNew( - ['name' => 'Tokyo to Sydney'], - ['delayed' => 1, 'arrival_time' => '11:30'] - ); +// Retrieve flight by name or create it if it doesn't exist... +$flight = Flight::firstOrCreate([ + 'name' => 'London to Paris' +]); + +// Retrieve flight by name or create it with the name, delayed, and arrival_time attributes... +$flight = Flight::firstOrCreate( + ['name' => 'London to Paris'], + ['delayed' => 1, 'arrival_time' => '11:30'] +); + +// Retrieve flight by name or instantiate a new Flight instance... +$flight = Flight::firstOrNew([ + 'name' => 'London to Paris' +]); + +// Retrieve flight by name or instantiate with the name, delayed, and arrival_time attributes... +$flight = Flight::firstOrNew( + ['name' => 'Tokyo to Sydney'], + ['delayed' => 1, 'arrival_time' => '11:30'] +); +``` ### Retrieving Aggregates When interacting with Eloquent models, you may also use the `count`, `sum`, `max`, and other [aggregate methods](/docs/{{version}}/queries#aggregates) provided by the Laravel [query builder](/docs/{{version}}/queries). As you might expect, these methods return a scalar value instead of an Eloquent model instance: - $count = Flight::where('active', 1)->count(); +```php +$count = Flight::where('active', 1)->count(); - $max = Flight::where('active', 1)->max('price'); +$max = Flight::where('active', 1)->max('price'); +``` ## Inserting and Updating Models @@ -677,43 +729,47 @@ When interacting with Eloquent models, you may also use the `count`, `sum`, `max Of course, when using Eloquent, we don't only need to retrieve models from the database. We also need to insert new records. Thankfully, Eloquent makes it simple. To insert a new record into the database, you should instantiate a new model instance and set attributes on the model. Then, call the `save` method on the model instance: - name = $request->name; + $flight->name = $request->name; - $flight->save(); + $flight->save(); - return redirect('/flights'); - } + return redirect('/flights'); } +} +``` In this example, we assign the `name` field from the incoming HTTP request to the `name` attribute of the `App\Models\Flight` model instance. When we call the `save` method, a record will be inserted into the database. The model's `created_at` and `updated_at` timestamps will automatically be set when the `save` method is called, so there is no need to set them manually. Alternatively, you may use the `create` method to "save" a new model using a single PHP statement. The inserted model instance will be returned to you by the `create` method: - use App\Models\Flight; +```php +use App\Models\Flight; - $flight = Flight::create([ - 'name' => 'London to Paris', - ]); +$flight = Flight::create([ + 'name' => 'London to Paris', +]); +``` However, before using the `create` method, you will need to specify either a `fillable` or `guarded` property on your model class. These properties are required because all Eloquent models are protected against mass assignment vulnerabilities by default. To learn more about mass assignment, please consult the [mass assignment documentation](#mass-assignment). @@ -722,31 +778,37 @@ However, before using the `create` method, you will need to specify either a `fi The `save` method may also be used to update models that already exist in the database. To update a model, you should retrieve it and set any attributes you wish to update. Then, you should call the model's `save` method. Again, the `updated_at` timestamp will automatically be updated, so there is no need to manually set its value: - use App\Models\Flight; +```php +use App\Models\Flight; - $flight = Flight::find(1); +$flight = Flight::find(1); - $flight->name = 'Paris to London'; +$flight->name = 'Paris to London'; - $flight->save(); +$flight->save(); +``` Occasionally, you may need to update an existing model or create a new model if no matching model exists. Like the `firstOrCreate` method, the `updateOrCreate` method persists the model, so there's no need to manually call the `save` method. In the example below, if a flight exists with a `departure` location of `Oakland` and a `destination` location of `San Diego`, its `price` and `discounted` columns will be updated. If no such flight exists, a new flight will be created which has the attributes resulting from merging the first argument array with the second argument array: - $flight = Flight::updateOrCreate( - ['departure' => 'Oakland', 'destination' => 'San Diego'], - ['price' => 99, 'discounted' => 1] - ); +```php +$flight = Flight::updateOrCreate( + ['departure' => 'Oakland', 'destination' => 'San Diego'], + ['price' => 99, 'discounted' => 1] +); +``` #### Mass Updates Updates can also be performed against models that match a given query. In this example, all flights that are `active` and have a `destination` of `San Diego` will be marked as delayed: - Flight::where('active', 1) - ->where('destination', 'San Diego') - ->update(['delayed' => 1]); +```php +Flight::where('active', 1) + ->where('destination', 'San Diego') + ->update(['delayed' => 1]); +``` The `update` method expects an array of column and value pairs representing the columns that should be updated. The `update` method returns the number of affected rows. @@ -760,72 +822,80 @@ Eloquent provides the `isDirty`, `isClean`, and `wasChanged` methods to examine The `isDirty` method determines if any of the model's attributes have been changed since the model was retrieved. You may pass a specific attribute name or an array of attributes to the `isDirty` method to determine if any of the attributes are "dirty". The `isClean` method will determine if an attribute has remained unchanged since the model was retrieved. This method also accepts an optional attribute argument: - use App\Models\User; +```php +use App\Models\User; - $user = User::create([ - 'first_name' => 'Taylor', - 'last_name' => 'Otwell', - 'title' => 'Developer', - ]); +$user = User::create([ + 'first_name' => 'Taylor', + 'last_name' => 'Otwell', + 'title' => 'Developer', +]); - $user->title = 'Painter'; +$user->title = 'Painter'; - $user->isDirty(); // true - $user->isDirty('title'); // true - $user->isDirty('first_name'); // false - $user->isDirty(['first_name', 'title']); // true +$user->isDirty(); // true +$user->isDirty('title'); // true +$user->isDirty('first_name'); // false +$user->isDirty(['first_name', 'title']); // true - $user->isClean(); // false - $user->isClean('title'); // false - $user->isClean('first_name'); // true - $user->isClean(['first_name', 'title']); // false +$user->isClean(); // false +$user->isClean('title'); // false +$user->isClean('first_name'); // true +$user->isClean(['first_name', 'title']); // false - $user->save(); +$user->save(); - $user->isDirty(); // false - $user->isClean(); // true +$user->isDirty(); // false +$user->isClean(); // true +``` The `wasChanged` method determines if any attributes were changed when the model was last saved within the current request cycle. If needed, you may pass an attribute name to see if a particular attribute was changed: - $user = User::create([ - 'first_name' => 'Taylor', - 'last_name' => 'Otwell', - 'title' => 'Developer', - ]); +```php +$user = User::create([ + 'first_name' => 'Taylor', + 'last_name' => 'Otwell', + 'title' => 'Developer', +]); - $user->title = 'Painter'; +$user->title = 'Painter'; - $user->save(); +$user->save(); - $user->wasChanged(); // true - $user->wasChanged('title'); // true - $user->wasChanged(['title', 'slug']); // true - $user->wasChanged('first_name'); // false - $user->wasChanged(['first_name', 'title']); // true +$user->wasChanged(); // true +$user->wasChanged('title'); // true +$user->wasChanged(['title', 'slug']); // true +$user->wasChanged('first_name'); // false +$user->wasChanged(['first_name', 'title']); // true +``` The `getOriginal` method returns an array containing the original attributes of the model regardless of any changes to the model since it was retrieved. If needed, you may pass a specific attribute name to get the original value of a particular attribute: - $user = User::find(1); +```php +$user = User::find(1); - $user->name; // John - $user->email; // john@example.com +$user->name; // John +$user->email; // john@example.com - $user->name = "Jack"; - $user->name; // Jack +$user->name = "Jack"; +$user->name; // Jack - $user->getOriginal('name'); // John - $user->getOriginal(); // Array of original attributes... +$user->getOriginal('name'); // John +$user->getOriginal(); // Array of original attributes... +``` ### Mass Assignment You may use the `create` method to "save" a new model using a single PHP statement. The inserted model instance will be returned to you by the method: - use App\Models\Flight; +```php +use App\Models\Flight; - $flight = Flight::create([ - 'name' => 'London to Paris', - ]); +$flight = Flight::create([ + 'name' => 'London to Paris', +]); +``` However, before using the `create` method, you will need to specify either a `fillable` or `guarded` property on your model class. These properties are required because all Eloquent models are protected against mass assignment vulnerabilities by default. @@ -833,55 +903,65 @@ A mass assignment vulnerability occurs when a user passes an unexpected HTTP req So, to get started, you should define which model attributes you want to make mass assignable. You may do this using the `$fillable` property on the model. For example, let's make the `name` attribute of our `Flight` model mass assignable: - - */ - protected $fillable = ['name']; - } +class Flight extends Model +{ + /** + * The attributes that are mass assignable. + * + * @var array + */ + protected $fillable = ['name']; +} +``` Once you have specified which attributes are mass assignable, you may use the `create` method to insert a new record in the database. The `create` method returns the newly created model instance: - $flight = Flight::create(['name' => 'London to Paris']); +```php +$flight = Flight::create(['name' => 'London to Paris']); +``` If you already have a model instance, you may use the `fill` method to populate it with an array of attributes: - $flight->fill(['name' => 'Amsterdam to Frankfurt']); +```php +$flight->fill(['name' => 'Amsterdam to Frankfurt']); +``` #### Mass Assignment and JSON Columns When assigning JSON columns, each column's mass assignable key must be specified in your model's `$fillable` array. For security, Laravel does not support updating nested JSON attributes when using the `guarded` property: - /** - * The attributes that are mass assignable. - * - * @var array - */ - protected $fillable = [ - 'options->enabled', - ]; +```php +/** + * The attributes that are mass assignable. + * + * @var array + */ +protected $fillable = [ + 'options->enabled', +]; +``` #### Allowing Mass Assignment If you would like to make all of your attributes mass assignable, you may define your model's `$guarded` property as an empty array. If you choose to unguard your model, you should take special care to always hand-craft the arrays passed to Eloquent's `fill`, `create`, and `update` methods: - /** - * The attributes that aren't mass assignable. - * - * @var array|bool - */ - protected $guarded = []; +```php +/** + * The attributes that aren't mass assignable. + * + * @var array|bool + */ +protected $guarded = []; +``` #### Mass Assignment Exceptions @@ -890,25 +970,29 @@ By default, attributes that are not included in the `$fillable` array are silent If you wish, you may instruct Laravel to throw an exception when attempting to fill an unfillable attribute by invoking the `preventSilentlyDiscardingAttributes` method. Typically, this method should be invoked in the `boot` method of your application's `AppServiceProvider` class: - use Illuminate\Database\Eloquent\Model; - - /** - * Bootstrap any application services. - */ - public function boot(): void - { - Model::preventSilentlyDiscardingAttributes($this->app->isLocal()); - } +```php +use Illuminate\Database\Eloquent\Model; - -### Upserts +/** + * Bootstrap any application services. + */ +public function boot(): void +{ + Model::preventSilentlyDiscardingAttributes($this->app->isLocal()); +} +``` + + +### Upserts Eloquent's `upsert` method may be used to update or create records in a single, atomic operation. The method's first argument consists of the values to insert or update, while the second argument lists the column(s) that uniquely identify records within the associated table. The method's third and final argument is an array of the columns that should be updated if a matching record already exists in the database. The `upsert` method will automatically set the `created_at` and `updated_at` timestamps if timestamps are enabled on the model: - Flight::upsert([ - ['departure' => 'Oakland', 'destination' => 'San Diego', 'price' => 99], - ['departure' => 'Chicago', 'destination' => 'New York', 'price' => 150] - ], uniqueBy: ['departure', 'destination'], update: ['price']); +```php +Flight::upsert([ + ['departure' => 'Oakland', 'destination' => 'San Diego', 'price' => 99], + ['departure' => 'Chicago', 'destination' => 'New York', 'price' => 150] +], uniqueBy: ['departure', 'destination'], update: ['price']); +``` > [!WARNING] > All databases except SQL Server require the columns in the second argument of the `upsert` method to have a "primary" or "unique" index. In addition, the MariaDB and MySQL database drivers ignore the second argument of the `upsert` method and always use the "primary" and "unique" indexes of the table to detect existing records. @@ -918,28 +1002,34 @@ Eloquent's `upsert` method may be used to update or create records in a single, To delete a model, you may call the `delete` method on the model instance: - use App\Models\Flight; +```php +use App\Models\Flight; - $flight = Flight::find(1); +$flight = Flight::find(1); - $flight->delete(); +$flight->delete(); +``` #### Deleting an Existing Model by its Primary Key In the example above, we are retrieving the model from the database before calling the `delete` method. However, if you know the primary key of the model, you may delete the model without explicitly retrieving it by calling the `destroy` method. In addition to accepting the single primary key, the `destroy` method will accept multiple primary keys, an array of primary keys, or a [collection](/docs/{{version}}/collections) of primary keys: - Flight::destroy(1); +```php +Flight::destroy(1); - Flight::destroy(1, 2, 3); +Flight::destroy(1, 2, 3); - Flight::destroy([1, 2, 3]); +Flight::destroy([1, 2, 3]); - Flight::destroy(collect([1, 2, 3])); +Flight::destroy(collect([1, 2, 3])); +``` If you are utilizing [soft deleting models](#soft-deleting), you may permanently delete models via the `forceDestroy` method: - Flight::forceDestroy(1); +```php +Flight::forceDestroy(1); +``` > [!WARNING] > The `destroy` method loads each model individually and calls the `delete` method so that the `deleting` and `deleted` events are properly dispatched for each model. @@ -949,11 +1039,15 @@ If you are utilizing [soft deleting models](#soft-deleting), you may permanently Of course, you may build an Eloquent query to delete all models matching your query's criteria. In this example, we will delete all flights that are marked as inactive. Like mass updates, mass deletes will not dispatch model events for the models that are deleted: - $deleted = Flight::where('active', 0)->delete(); +```php +$deleted = Flight::where('active', 0)->delete(); +``` To delete all models in a table, you should execute a query without adding any conditions: - $deleted = Flight::query()->delete(); +```php +$deleted = Flight::query()->delete(); +``` > [!WARNING] > When executing a mass delete statement via Eloquent, the `deleting` and `deleted` model events will not be dispatched for the deleted models. This is because the models are never actually retrieved when executing the delete statement. @@ -963,69 +1057,85 @@ To delete all models in a table, you should execute a query without adding any c In addition to actually removing records from your database, Eloquent can also "soft delete" models. When models are soft deleted, they are not actually removed from your database. Instead, a `deleted_at` attribute is set on the model indicating the date and time at which the model was "deleted". To enable soft deletes for a model, add the `Illuminate\Database\Eloquent\SoftDeletes` trait to the model: - [!NOTE] > The `SoftDeletes` trait will automatically cast the `deleted_at` attribute to a `DateTime` / `Carbon` instance for you. You should also add the `deleted_at` column to your database table. The Laravel [schema builder](/docs/{{version}}/migrations) contains a helper method to create this column: - use Illuminate\Database\Schema\Blueprint; - use Illuminate\Support\Facades\Schema; +```php +use Illuminate\Database\Schema\Blueprint; +use Illuminate\Support\Facades\Schema; - Schema::table('flights', function (Blueprint $table) { - $table->softDeletes(); - }); +Schema::table('flights', function (Blueprint $table) { + $table->softDeletes(); +}); - Schema::table('flights', function (Blueprint $table) { - $table->dropSoftDeletes(); - }); +Schema::table('flights', function (Blueprint $table) { + $table->dropSoftDeletes(); +}); +``` Now, when you call the `delete` method on the model, the `deleted_at` column will be set to the current date and time. However, the model's database record will be left in the table. When querying a model that uses soft deletes, the soft deleted models will automatically be excluded from all query results. To determine if a given model instance has been soft deleted, you may use the `trashed` method: - if ($flight->trashed()) { - // ... - } +```php +if ($flight->trashed()) { + // ... +} +``` #### Restoring Soft Deleted Models Sometimes you may wish to "un-delete" a soft deleted model. To restore a soft deleted model, you may call the `restore` method on a model instance. The `restore` method will set the model's `deleted_at` column to `null`: - $flight->restore(); +```php +$flight->restore(); +``` You may also use the `restore` method in a query to restore multiple models. Again, like other "mass" operations, this will not dispatch any model events for the models that are restored: - Flight::withTrashed() - ->where('airline_id', 1) - ->restore(); +```php +Flight::withTrashed() + ->where('airline_id', 1) + ->restore(); +``` The `restore` method may also be used when building [relationship](/docs/{{version}}/eloquent-relationships) queries: - $flight->history()->restore(); +```php +$flight->history()->restore(); +``` #### Permanently Deleting Models Sometimes you may need to truly remove a model from your database. You may use the `forceDelete` method to permanently remove a soft deleted model from the database table: - $flight->forceDelete(); +```php +$flight->forceDelete(); +``` You may also use the `forceDelete` method when building Eloquent relationship queries: - $flight->history()->forceDelete(); +```php +$flight->history()->forceDelete(); +``` ### Querying Soft Deleted Models @@ -1035,78 +1145,94 @@ You may also use the `forceDelete` method when building Eloquent relationship qu As noted above, soft deleted models will automatically be excluded from query results. However, you may force soft deleted models to be included in a query's results by calling the `withTrashed` method on the query: - use App\Models\Flight; +```php +use App\Models\Flight; - $flights = Flight::withTrashed() - ->where('account_id', 1) - ->get(); +$flights = Flight::withTrashed() + ->where('account_id', 1) + ->get(); +``` The `withTrashed` method may also be called when building a [relationship](/docs/{{version}}/eloquent-relationships) query: - $flight->history()->withTrashed()->get(); +```php +$flight->history()->withTrashed()->get(); +``` #### Retrieving Only Soft Deleted Models The `onlyTrashed` method will retrieve **only** soft deleted models: - $flights = Flight::onlyTrashed() - ->where('airline_id', 1) - ->get(); +```php +$flights = Flight::onlyTrashed() + ->where('airline_id', 1) + ->get(); +``` ## Pruning Models Sometimes you may want to periodically delete models that are no longer needed. To accomplish this, you may add the `Illuminate\Database\Eloquent\Prunable` or `Illuminate\Database\Eloquent\MassPrunable` trait to the models you would like to periodically prune. After adding one of the traits to the model, implement a `prunable` method which returns an Eloquent query builder that resolves the models that are no longer needed: - subMonth()); - } - } +use Illuminate\Database\Eloquent\Builder; +use Illuminate\Database\Eloquent\Model; +use Illuminate\Database\Eloquent\Prunable; -When marking models as `Prunable`, you may also define a `pruning` method on the model. This method will be called before the model is deleted. This method can be useful for deleting any additional resources associated with the model, such as stored files, before the model is permanently removed from the database: +class Flight extends Model +{ + use Prunable; /** - * Prepare the model for pruning. + * Get the prunable model query. */ - protected function pruning(): void + public function prunable(): Builder { - // ... + return static::where('created_at', '<=', now()->subMonth()); } +} +``` + +When marking models as `Prunable`, you may also define a `pruning` method on the model. This method will be called before the model is deleted. This method can be useful for deleting any additional resources associated with the model, such as stored files, before the model is permanently removed from the database: + +```php +/** + * Prepare the model for pruning. + */ +protected function pruning(): void +{ + // ... +} +``` After configuring your prunable model, you should schedule the `model:prune` Artisan command in your application's `routes/console.php` file. You are free to choose the appropriate interval at which this command should be run: - use Illuminate\Support\Facades\Schedule; +```php +use Illuminate\Support\Facades\Schedule; - Schedule::command('model:prune')->daily(); +Schedule::command('model:prune')->daily(); +``` Behind the scenes, the `model:prune` command will automatically detect "Prunable" models within your application's `app/Models` directory. If your models are in a different location, you may use the `--model` option to specify the model class names: - Schedule::command('model:prune', [ - '--model' => [Address::class, Flight::class], - ])->daily(); +```php +Schedule::command('model:prune', [ + '--model' => [Address::class, Flight::class], +])->daily(); +``` If you wish to exclude certain models from being pruned while pruning all other detected models, you may use the `--except` option: - Schedule::command('model:prune', [ - '--except' => [Address::class, Flight::class], - ])->daily(); +```php +Schedule::command('model:prune', [ + '--except' => [Address::class, Flight::class], +])->daily(); +``` You may test your `prunable` query by executing the `model:prune` command with the `--pretend` option. When pretending, the `model:prune` command will simply report how many records would be pruned if the command were to actually run: @@ -1122,61 +1248,67 @@ php artisan model:prune --pretend When models are marked with the `Illuminate\Database\Eloquent\MassPrunable` trait, models are deleted from the database using mass-deletion queries. Therefore, the `pruning` method will not be invoked, nor will the `deleting` and `deleted` model events be dispatched. This is because the models are never actually retrieved before deletion, thus making the pruning process much more efficient: - subMonth()); - } + return static::where('created_at', '<=', now()->subMonth()); } +} +``` ## Replicating Models You may create an unsaved copy of an existing model instance using the `replicate` method. This method is particularly useful when you have model instances that share many of the same attributes: - use App\Models\Address; +```php +use App\Models\Address; - $shipping = Address::create([ - 'type' => 'shipping', - 'line_1' => '123 Example Street', - 'city' => 'Victorville', - 'state' => 'CA', - 'postcode' => '90001', - ]); +$shipping = Address::create([ + 'type' => 'shipping', + 'line_1' => '123 Example Street', + 'city' => 'Victorville', + 'state' => 'CA', + 'postcode' => '90001', +]); - $billing = $shipping->replicate()->fill([ - 'type' => 'billing' - ]); +$billing = $shipping->replicate()->fill([ + 'type' => 'billing' +]); - $billing->save(); +$billing->save(); +``` To exclude one or more attributes from being replicated to the new model, you may pass an array to the `replicate` method: - $flight = Flight::create([ - 'destination' => 'LAX', - 'origin' => 'LHR', - 'last_flown' => '2020-03-04 11:00:00', - 'last_pilot_id' => 747, - ]); - - $flight = $flight->replicate([ - 'last_flown', - 'last_pilot_id' - ]); +```php +$flight = Flight::create([ + 'destination' => 'LAX', + 'origin' => 'LHR', + 'last_flown' => '2020-03-04 11:00:00', + 'last_pilot_id' => 747, +]); + +$flight = $flight->replicate([ + 'last_flown', + 'last_pilot_id' +]); +``` ## Query Scopes @@ -1200,24 +1332,26 @@ php artisan make:scope AncientScope Writing a global scope is simple. First, use the `make:scope` command to generate a class that implements the `Illuminate\Database\Eloquent\Scope` interface. The `Scope` interface requires you to implement one method: `apply`. The `apply` method may add `where` constraints or other types of clauses to the query as needed: - where('created_at', '<', now()->subYears(2000)); - } + $builder->where('created_at', '<', now()->subYears(2000)); } +} +``` > [!NOTE] > If your global scope is adding columns to the select clause of the query, you should use the `addSelect` method instead of `select`. This will prevent the unintentional replacement of the query's existing select clause. @@ -1227,38 +1361,42 @@ Writing a global scope is simple. First, use the `make:scope` command to generat To assign a global scope to a model, you may simply place the `ScopedBy` attribute on the model: - where('created_at', '<', now()->subYears(2000)); - }); - } + static::addGlobalScope('ancient', function (Builder $builder) { + $builder->where('created_at', '<', now()->subYears(2000)); + }); } +} +``` #### Removing Global Scopes If you would like to remove a global scope for a given query, you may use the `withoutGlobalScope` method. This method accepts the class name of the global scope as its only argument: - User::withoutGlobalScope(AncientScope::class)->get(); +```php +User::withoutGlobalScope(AncientScope::class)->get(); +``` Or, if you defined the global scope using a closure, you should pass the string name that you assigned to the global scope: - User::withoutGlobalScope('ancient')->get(); +```php +User::withoutGlobalScope('ancient')->get(); +``` If you would like to remove several or even all of the query's global scopes, you may use the `withoutGlobalScopes` method: - // Remove all of the global scopes... - User::withoutGlobalScopes()->get(); +```php +// Remove all of the global scopes... +User::withoutGlobalScopes()->get(); - // Remove some of the global scopes... - User::withoutGlobalScopes([ - FirstScope::class, SecondScope::class - ])->get(); +// Remove some of the global scopes... +User::withoutGlobalScopes([ + FirstScope::class, SecondScope::class +])->get(); +``` ### Local Scopes @@ -1319,127 +1465,147 @@ Local scopes allow you to define common sets of query constraints that you may e Scopes should always return the same query builder instance or `void`: - where('votes', '>', 100); + } - class User extends Model + /** + * Scope a query to only include active users. + */ + public function scopeActive(Builder $query): void { - /** - * Scope a query to only include popular users. - */ - public function scopePopular(Builder $query): void - { - $query->where('votes', '>', 100); - } - - /** - * Scope a query to only include active users. - */ - public function scopeActive(Builder $query): void - { - $query->where('active', 1); - } + $query->where('active', 1); } +} +``` #### Utilizing a Local Scope Once the scope has been defined, you may call the scope methods when querying the model. However, you should not include the `scope` prefix when calling the method. You can even chain calls to various scopes: - use App\Models\User; +```php +use App\Models\User; - $users = User::popular()->active()->orderBy('created_at')->get(); +$users = User::popular()->active()->orderBy('created_at')->get(); +``` Combining multiple Eloquent model scopes via an `or` query operator may require the use of closures to achieve the correct [logical grouping](/docs/{{version}}/queries#logical-grouping): - $users = User::popular()->orWhere(function (Builder $query) { - $query->active(); - })->get(); +```php +$users = User::popular()->orWhere(function (Builder $query) { + $query->active(); +})->get(); +``` However, since this can be cumbersome, Laravel provides a "higher order" `orWhere` method that allows you to fluently chain scopes together without the use of closures: - $users = User::popular()->orWhere->active()->get(); +```php +$users = User::popular()->orWhere->active()->get(); +``` #### Dynamic Scopes Sometimes you may wish to define a scope that accepts parameters. To get started, just add your additional parameters to your scope method's signature. Scope parameters should be defined after the `$query` parameter: - where('type', $type); - } + $query->where('type', $type); } +} +``` Once the expected arguments have been added to your scope method's signature, you may pass the arguments when calling the scope: - $users = User::ofType('admin')->get(); +```php +$users = User::ofType('admin')->get(); +``` ### Pending Attributes If you would like to use scopes to create models that have the same attributes as those used to constrain the scope, you may use the `withAttributes` method when building the scope query: - withAttributes([ - 'hidden' => true, - ]); - } + $query->withAttributes([ + 'hidden' => true, + ]); } +} +``` The `withAttributes` method will add `where` clause constraints to the query using the given attributes, and it will also add the given attributes to any models created via the scope: - $draft = Post::draft()->create(['title' => 'In Progress']); +```php +$draft = Post::draft()->create(['title' => 'In Progress']); - $draft->hidden; // true +$draft->hidden; // true +``` ## Comparing Models Sometimes you may need to determine if two models are the "same" or not. The `is` and `isNot` methods may be used to quickly verify two models have the same primary key, table, and database connection or not: - if ($post->is($anotherPost)) { - // ... - } +```php +if ($post->is($anotherPost)) { + // ... +} - if ($post->isNot($anotherPost)) { - // ... - } +if ($post->isNot($anotherPost)) { + // ... +} +``` The `is` and `isNot` methods are also available when using the `belongsTo`, `hasOne`, `morphTo`, and `morphOne` [relationships](/docs/{{version}}/eloquent-relationships). This method is particularly helpful when you would like to compare a related model without issuing a query to retrieve that model: - if ($post->author()->is($user)) { - // ... - } +```php +if ($post->author()->is($user)) { + // ... +} +``` ## Events @@ -1453,29 +1619,31 @@ The `retrieved` event will dispatch when an existing model is retrieved from the To start listening to model events, define a `$dispatchesEvents` property on your Eloquent model. This property maps various points of the Eloquent model's lifecycle to your own [event classes](/docs/{{version}}/events). Each model event class should expect to receive an instance of the affected model via its constructor: - - */ - protected $dispatchesEvents = [ - 'saved' => UserSaved::class, - 'deleted' => UserDeleted::class, - ]; - } +class User extends Authenticatable +{ + use Notifiable; + + /** + * The event map for the model. + * + * @var array + */ + protected $dispatchesEvents = [ + 'saved' => UserSaved::class, + 'deleted' => UserDeleted::class, + ]; +} +``` After defining and mapping your Eloquent events, you may use [event listeners](/docs/{{version}}/events#defining-listeners) to handle the events. @@ -1487,32 +1655,36 @@ After defining and mapping your Eloquent events, you may use [event listeners](/ Instead of using custom event classes, you may register closures that execute when various model events are dispatched. Typically, you should register these closures in the `booted` method of your model: - ### Observers @@ -1528,78 +1700,84 @@ php artisan make:observer UserObserver --model=User This command will place the new observer in your `app/Observers` directory. If this directory does not exist, Artisan will create it for you. Your fresh observer will look like the following: - [!NOTE] > There are additional events an observer can listen to, such as `saving` and `retrieved`. These events are described within the [events](#events) documentation. @@ -1609,50 +1787,58 @@ Or, you may manually register an observer by invoking the `observe` method on th When models are being created within a database transaction, you may want to instruct an observer to only execute its event handlers after the database transaction is committed. You may accomplish this by implementing the `ShouldHandleEventsAfterCommit` interface on your observer. If a database transaction is not in progress, the event handlers will execute immediately: - ### Muting Events You may occasionally need to temporarily "mute" all events fired by a model. You may achieve this using the `withoutEvents` method. The `withoutEvents` method accepts a closure as its only argument. Any code executed within this closure will not dispatch model events, and any value returned by the closure will be returned by the `withoutEvents` method: - use App\Models\User; +```php +use App\Models\User; - $user = User::withoutEvents(function () { - User::findOrFail(1)->delete(); +$user = User::withoutEvents(function () { + User::findOrFail(1)->delete(); - return User::find(2); - }); + return User::find(2); +}); +``` #### Saving a Single Model Without Events Sometimes you may wish to "save" a given model without dispatching any events. You may accomplish this using the `saveQuietly` method: - $user = User::findOrFail(1); +```php +$user = User::findOrFail(1); - $user->name = 'Victoria Faith'; +$user->name = 'Victoria Faith'; - $user->saveQuietly(); +$user->saveQuietly(); +``` You may also "update", "delete", "soft delete", "restore", and "replicate" a given model without dispatching any events: - $user->deleteQuietly(); - $user->forceDeleteQuietly(); - $user->restoreQuietly(); +```php +$user->deleteQuietly(); +$user->forceDeleteQuietly(); +$user->restoreQuietly(); +``` diff --git a/encryption.md b/encryption.md index 49beb8cd179..6123a6ce6bc 100644 --- a/encryption.md +++ b/encryption.md @@ -39,39 +39,43 @@ This approach to graceful decryption allows users to keep using your application You may encrypt a value using the `encryptString` method provided by the `Crypt` facade. All encrypted values are encrypted using OpenSSL and the AES-256-CBC cipher. Furthermore, all encrypted values are signed with a message authentication code (MAC). The integrated message authentication code will prevent the decryption of any values that have been tampered with by malicious users: - user()->fill([ - 'token' => Crypt::encryptString($request->token), - ])->save(); - - return redirect('/secrets'); - } + $request->user()->fill([ + 'token' => Crypt::encryptString($request->token), + ])->save(); + + return redirect('/secrets'); } +} +``` #### Decrypting a Value You may decrypt values using the `decryptString` method provided by the `Crypt` facade. If the value cannot be properly decrypted, such as when the message authentication code is invalid, an `Illuminate\Contracts\Encryption\DecryptException` will be thrown: - use Illuminate\Contracts\Encryption\DecryptException; - use Illuminate\Support\Facades\Crypt; +```php +use Illuminate\Contracts\Encryption\DecryptException; +use Illuminate\Support\Facades\Crypt; - try { - $decrypted = Crypt::decryptString($encryptedValue); - } catch (DecryptException $e) { - // ... - } +try { + $decrypted = Crypt::decryptString($encryptedValue); +} catch (DecryptException $e) { + // ... +} +``` diff --git a/errors.md b/errors.md index 15c743d1fc6..c55d258cf15 100644 --- a/errors.md +++ b/errors.md @@ -36,23 +36,27 @@ In Laravel, exception reporting is used to log exceptions or send them to an ext If you need to report different types of exceptions in different ways, you may use the `report` exception method in your application's `bootstrap/app.php` to register a closure that should be executed when an exception of a given type needs to be reported. Laravel will determine what type of exception the closure reports by examining the type-hint of the closure: - ->withExceptions(function (Exceptions $exceptions) { - $exceptions->report(function (InvalidOrderException $e) { - // ... - }); - }) +```php +->withExceptions(function (Exceptions $exceptions) { + $exceptions->report(function (InvalidOrderException $e) { + // ... + }); +}) +``` When you register a custom exception reporting callback using the `report` method, Laravel will still log the exception using the default logging configuration for the application. If you wish to stop the propagation of the exception to the default logging stack, you may use the `stop` method when defining your reporting callback or return `false` from the callback: - ->withExceptions(function (Exceptions $exceptions) { - $exceptions->report(function (InvalidOrderException $e) { - // ... - })->stop(); +```php +->withExceptions(function (Exceptions $exceptions) { + $exceptions->report(function (InvalidOrderException $e) { + // ... + })->stop(); - $exceptions->report(function (InvalidOrderException $e) { - return false; - }); - }) + $exceptions->report(function (InvalidOrderException $e) { + return false; + }); +}) +``` > [!NOTE] > To customize the exception reporting for a given exception, you may also utilize [reportable exceptions](/docs/{{version}}/errors#renderable-exceptions). @@ -62,53 +66,59 @@ When you register a custom exception reporting callback using the `report` metho If available, Laravel automatically adds the current user's ID to every exception's log message as contextual data. You may define your own global contextual data using the `context` exception method in your application's `bootstrap/app.php` file. This information will be included in every exception's log message written by your application: - ->withExceptions(function (Exceptions $exceptions) { - $exceptions->context(fn () => [ - 'foo' => 'bar', - ]); - }) +```php +->withExceptions(function (Exceptions $exceptions) { + $exceptions->context(fn () => [ + 'foo' => 'bar', + ]); +}) +``` #### Exception Log Context While adding context to every log message can be useful, sometimes a particular exception may have unique context that you would like to include in your logs. By defining a `context` method on one of your application's exceptions, you may specify any data relevant to that exception that should be added to the exception's log entry: - - */ - public function context(): array - { - return ['order_id' => $this->orderId]; - } + /** + * Get the exception's context information. + * + * @return array + */ + public function context(): array + { + return ['order_id' => $this->orderId]; } +} +``` #### The `report` Helper Sometimes you may need to report an exception but continue handling the current request. The `report` helper function allows you to quickly report an exception without rendering an error page to the user: - public function isValid(string $value): bool - { - try { - // Validate the value... - } catch (Throwable $e) { - report($e); +```php +public function isValid(string $value): bool +{ + try { + // Validate the value... + } catch (Throwable $e) { + report($e); - return false; - } + return false; } +} +``` #### Deduplicating Reported Exceptions @@ -117,9 +127,11 @@ If you are using the `report` function throughout your application, you may occa If you would like to ensure that a single instance of an exception is only ever reported once, you may invoke the `dontReportDuplicates` exception method in your application's `bootstrap/app.php` file: - ->withExceptions(function (Exceptions $exceptions) { - $exceptions->dontReportDuplicates(); - }) +```php +->withExceptions(function (Exceptions $exceptions) { + $exceptions->dontReportDuplicates(); +}) +``` Now, when the `report` helper is called with the same instance of an exception, only the first call will be reported: @@ -147,25 +159,29 @@ As noted above, even when you register a custom exception reporting callback usi To accomplish this, you may use the `level` exception method in your application's `bootstrap/app.php` file. This method receives the exception type as its first argument and the log level as its second argument: - use PDOException; - use Psr\Log\LogLevel; +```php +use PDOException; +use Psr\Log\LogLevel; - ->withExceptions(function (Exceptions $exceptions) { - $exceptions->level(PDOException::class, LogLevel::CRITICAL); - }) +->withExceptions(function (Exceptions $exceptions) { + $exceptions->level(PDOException::class, LogLevel::CRITICAL); +}) +``` ### Ignoring Exceptions by Type When building your application, there will be some types of exceptions you never want to report. To ignore these exceptions, you may use the `dontReport` exception method in your application's `bootstrap/app.php` file. Any class provided to this method will never be reported; however, they may still have custom rendering logic: - use App\Exceptions\InvalidOrderException; +```php +use App\Exceptions\InvalidOrderException; - ->withExceptions(function (Exceptions $exceptions) { - $exceptions->dontReport([ - InvalidOrderException::class, - ]); - }) +->withExceptions(function (Exceptions $exceptions) { + $exceptions->dontReport([ + InvalidOrderException::class, + ]); +}) +``` Alternatively, you may simply "mark" an exception class with the `Illuminate\Contracts\Debug\ShouldntReport` interface. When an exception is marked with this interface, it will never be reported by Laravel's exception handler: @@ -185,11 +201,13 @@ class PodcastProcessingException extends Exception implements ShouldntReport Internally, Laravel already ignores some types of errors for you, such as exceptions resulting from 404 HTTP errors or 419 HTTP responses generated by invalid CSRF tokens. If you would like to instruct Laravel to stop ignoring a given type of exception, you may use the `stopIgnoring` exception method in your application's `bootstrap/app.php` file: - use Symfony\Component\HttpKernel\Exception\HttpException; +```php +use Symfony\Component\HttpKernel\Exception\HttpException; - ->withExceptions(function (Exceptions $exceptions) { - $exceptions->stopIgnoring(HttpException::class); - }) +->withExceptions(function (Exceptions $exceptions) { + $exceptions->stopIgnoring(HttpException::class); +}) +``` ### Rendering Exceptions @@ -198,131 +216,145 @@ By default, the Laravel exception handler will convert exceptions into an HTTP r The closure passed to the `render` method should return an instance of `Illuminate\Http\Response`, which may be generated via the `response` helper. Laravel will determine what type of exception the closure renders by examining the type-hint of the closure: - use App\Exceptions\InvalidOrderException; - use Illuminate\Http\Request; - - ->withExceptions(function (Exceptions $exceptions) { - $exceptions->render(function (InvalidOrderException $e, Request $request) { - return response()->view('errors.invalid-order', status: 500); - }); - }) +```php +use App\Exceptions\InvalidOrderException; +use Illuminate\Http\Request; + +->withExceptions(function (Exceptions $exceptions) { + $exceptions->render(function (InvalidOrderException $e, Request $request) { + return response()->view('errors.invalid-order', status: 500); + }); +}) +``` You may also use the `render` method to override the rendering behavior for built-in Laravel or Symfony exceptions such as `NotFoundHttpException`. If the closure given to the `render` method does not return a value, Laravel's default exception rendering will be utilized: - use Illuminate\Http\Request; - use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; - - ->withExceptions(function (Exceptions $exceptions) { - $exceptions->render(function (NotFoundHttpException $e, Request $request) { - if ($request->is('api/*')) { - return response()->json([ - 'message' => 'Record not found.' - ], 404); - } - }); - }) +```php +use Illuminate\Http\Request; +use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; + +->withExceptions(function (Exceptions $exceptions) { + $exceptions->render(function (NotFoundHttpException $e, Request $request) { + if ($request->is('api/*')) { + return response()->json([ + 'message' => 'Record not found.' + ], 404); + } + }); +}) +``` #### Rendering Exceptions as JSON When rendering an exception, Laravel will automatically determine if the exception should be rendered as an HTML or JSON response based on the `Accept` header of the request. If you would like to customize how Laravel determines whether to render HTML or JSON exception responses, you may utilize the `shouldRenderJsonWhen` method: - use Illuminate\Http\Request; - use Throwable; +```php +use Illuminate\Http\Request; +use Throwable; - ->withExceptions(function (Exceptions $exceptions) { - $exceptions->shouldRenderJsonWhen(function (Request $request, Throwable $e) { - if ($request->is('admin/*')) { - return true; - } +->withExceptions(function (Exceptions $exceptions) { + $exceptions->shouldRenderJsonWhen(function (Request $request, Throwable $e) { + if ($request->is('admin/*')) { + return true; + } - return $request->expectsJson(); - }); - }) + return $request->expectsJson(); + }); +}) +``` #### Customizing the Exception Response Rarely, you may need to customize the entire HTTP response rendered by Laravel's exception handler. To accomplish this, you may register a response customization closure using the `respond` method: - use Symfony\Component\HttpFoundation\Response; - - ->withExceptions(function (Exceptions $exceptions) { - $exceptions->respond(function (Response $response) { - if ($response->getStatusCode() === 419) { - return back()->with([ - 'message' => 'The page expired, please try again.', - ]); - } +```php +use Symfony\Component\HttpFoundation\Response; + +->withExceptions(function (Exceptions $exceptions) { + $exceptions->respond(function (Response $response) { + if ($response->getStatusCode() === 419) { + return back()->with([ + 'message' => 'The page expired, please try again.', + ]); + } - return $response; - }); - }) + return $response; + }); +}) +``` ### Reportable and Renderable Exceptions Instead of defining custom reporting and rendering behavior in your application's `bootstrap/app.php` file, you may define `report` and `render` methods directly on your application's exceptions. When these methods exist, they will automatically be called by the framework: - [!NOTE] > You may type-hint any required dependencies of the `report` method and they will automatically be injected into the method by Laravel's [service container](/docs/{{version}}/container). @@ -333,88 +365,102 @@ If your application reports a very large number of exceptions, you may want to t To take a random sample rate of exceptions, you may use the `throttle` exception method in your application's `bootstrap/app.php` file. The `throttle` method receives a closure that should return a `Lottery` instance: - use Illuminate\Support\Lottery; - use Throwable; - - ->withExceptions(function (Exceptions $exceptions) { - $exceptions->throttle(function (Throwable $e) { - return Lottery::odds(1, 1000); - }); - }) +```php +use Illuminate\Support\Lottery; +use Throwable; + +->withExceptions(function (Exceptions $exceptions) { + $exceptions->throttle(function (Throwable $e) { + return Lottery::odds(1, 1000); + }); +}) +``` It is also possible to conditionally sample based on the exception type. If you would like to only sample instances of a specific exception class, you may return a `Lottery` instance only for that class: - use App\Exceptions\ApiMonitoringException; - use Illuminate\Support\Lottery; - use Throwable; +```php +use App\Exceptions\ApiMonitoringException; +use Illuminate\Support\Lottery; +use Throwable; - ->withExceptions(function (Exceptions $exceptions) { - $exceptions->throttle(function (Throwable $e) { - if ($e instanceof ApiMonitoringException) { - return Lottery::odds(1, 1000); - } - }); - }) +->withExceptions(function (Exceptions $exceptions) { + $exceptions->throttle(function (Throwable $e) { + if ($e instanceof ApiMonitoringException) { + return Lottery::odds(1, 1000); + } + }); +}) +``` You may also rate limit exceptions logged or sent to an external error tracking service by returning a `Limit` instance instead of a `Lottery`. This is useful if you want to protect against sudden bursts of exceptions flooding your logs, for example, when a third-party service used by your application is down: - use Illuminate\Broadcasting\BroadcastException; - use Illuminate\Cache\RateLimiting\Limit; - use Throwable; - - ->withExceptions(function (Exceptions $exceptions) { - $exceptions->throttle(function (Throwable $e) { - if ($e instanceof BroadcastException) { - return Limit::perMinute(300); - } - }); - }) +```php +use Illuminate\Broadcasting\BroadcastException; +use Illuminate\Cache\RateLimiting\Limit; +use Throwable; + +->withExceptions(function (Exceptions $exceptions) { + $exceptions->throttle(function (Throwable $e) { + if ($e instanceof BroadcastException) { + return Limit::perMinute(300); + } + }); +}) +``` By default, limits will use the exception's class as the rate limit key. You can customize this by specifying your own key using the `by` method on the `Limit`: - use Illuminate\Broadcasting\BroadcastException; - use Illuminate\Cache\RateLimiting\Limit; - use Throwable; - - ->withExceptions(function (Exceptions $exceptions) { - $exceptions->throttle(function (Throwable $e) { - if ($e instanceof BroadcastException) { - return Limit::perMinute(300)->by($e->getMessage()); - } - }); - }) +```php +use Illuminate\Broadcasting\BroadcastException; +use Illuminate\Cache\RateLimiting\Limit; +use Throwable; + +->withExceptions(function (Exceptions $exceptions) { + $exceptions->throttle(function (Throwable $e) { + if ($e instanceof BroadcastException) { + return Limit::perMinute(300)->by($e->getMessage()); + } + }); +}) +``` Of course, you may return a mixture of `Lottery` and `Limit` instances for different exceptions: - use App\Exceptions\ApiMonitoringException; - use Illuminate\Broadcasting\BroadcastException; - use Illuminate\Cache\RateLimiting\Limit; - use Illuminate\Support\Lottery; - use Throwable; - - ->withExceptions(function (Exceptions $exceptions) { - $exceptions->throttle(function (Throwable $e) { - return match (true) { - $e instanceof BroadcastException => Limit::perMinute(300), - $e instanceof ApiMonitoringException => Lottery::odds(1, 1000), - default => Limit::none(), - }; - }); - }) +```php +use App\Exceptions\ApiMonitoringException; +use Illuminate\Broadcasting\BroadcastException; +use Illuminate\Cache\RateLimiting\Limit; +use Illuminate\Support\Lottery; +use Throwable; + +->withExceptions(function (Exceptions $exceptions) { + $exceptions->throttle(function (Throwable $e) { + return match (true) { + $e instanceof BroadcastException => Limit::perMinute(300), + $e instanceof ApiMonitoringException => Lottery::odds(1, 1000), + default => Limit::none(), + }; + }); +}) +``` ## HTTP Exceptions Some exceptions describe HTTP error codes from the server. For example, this may be a "page not found" error (404), an "unauthorized error" (401), or even a developer generated 500 error. In order to generate such a response from anywhere in your application, you may use the `abort` helper: - abort(404); +```php +abort(404); +``` ### Custom HTTP Error Pages Laravel makes it easy to display custom error pages for various HTTP status codes. For example, to customize the error page for 404 HTTP status codes, create a `resources/views/errors/404.blade.php` view template. This view will be rendered for all 404 errors generated by your application. The views within this directory should be named to match the HTTP status code they correspond to. The `Symfony\Component\HttpKernel\Exception\HttpException` instance raised by the `abort` function will be passed to the view as an `$exception` variable: -

{{ $exception->getMessage() }}

+```blade +

{{ $exception->getMessage() }}

+``` You may publish Laravel's default error page templates using the `vendor:publish` Artisan command. Once the templates have been published, you may customize them to your liking: diff --git a/events.md b/events.md index b0379bb4bfb..4f98b892cef 100644 --- a/events.md +++ b/events.md @@ -55,40 +55,48 @@ php artisan make:listener By default, Laravel will automatically find and register your event listeners by scanning your application's `Listeners` directory. When Laravel finds any listener class method that begins with `handle` or `__invoke`, Laravel will register those methods as event listeners for the event that is type-hinted in the method's signature: - use App\Events\PodcastProcessed; - - class SendPodcastNotification - { - /** - * Handle the given event. - */ - public function handle(PodcastProcessed $event): void - { - // ... - } - } - -You may listen to multiple events using PHP's union types: +```php +use App\Events\PodcastProcessed; +class SendPodcastNotification +{ /** * Handle the given event. */ - public function handle(PodcastProcessed|PodcastPublished $event): void + public function handle(PodcastProcessed $event): void { // ... } +} +``` + +You may listen to multiple events using PHP's union types: + +```php +/** + * Handle the given event. + */ +public function handle(PodcastProcessed|PodcastPublished $event): void +{ + // ... +} +``` If you plan to store your listeners in a different directory or within multiple directories, you may instruct Laravel to scan those directories using the `withEvents` method in your application's `bootstrap/app.php` file: - ->withEvents(discover: [ - __DIR__.'/../app/Domain/Orders/Listeners', - ]) +```php +->withEvents(discover: [ + __DIR__.'/../app/Domain/Orders/Listeners', +]) +``` You may scan for listeners in multiple similar directories using the `*` character as a wildcard: - ->withEvents(discover: [ - __DIR__.'/../app/Domain/*/Listeners', - ]) +```php +->withEvents(discover: [ + __DIR__.'/../app/Domain/*/Listeners', +]) +``` The `event:list` command may be used to list all of the listeners registered within your application: @@ -106,20 +114,22 @@ To give your application a speed boost, you should cache a manifest of all of yo Using the `Event` facade, you may manually register events and their corresponding listeners within the `boot` method of your application's `AppServiceProvider`: - use App\Domain\Orders\Events\PodcastProcessed; - use App\Domain\Orders\Listeners\SendPodcastNotification; - use Illuminate\Support\Facades\Event; +```php +use App\Domain\Orders\Events\PodcastProcessed; +use App\Domain\Orders\Listeners\SendPodcastNotification; +use Illuminate\Support\Facades\Event; - /** - * Bootstrap any application services. - */ - public function boot(): void - { - Event::listen( - PodcastProcessed::class, - SendPodcastNotification::class, - ); - } +/** + * Bootstrap any application services. + */ +public function boot(): void +{ + Event::listen( + PodcastProcessed::class, + SendPodcastNotification::class, + ); +} +``` The `event:list` command may be used to list all of the listeners registered within your application: @@ -132,91 +142,103 @@ php artisan event:list Typically, listeners are defined as classes; however, you may also manually register closure-based event listeners in the `boot` method of your application's `AppServiceProvider`: - use App\Events\PodcastProcessed; - use Illuminate\Support\Facades\Event; +```php +use App\Events\PodcastProcessed; +use Illuminate\Support\Facades\Event; - /** - * Bootstrap any application services. - */ - public function boot(): void - { - Event::listen(function (PodcastProcessed $event) { - // ... - }); - } +/** + * Bootstrap any application services. + */ +public function boot(): void +{ + Event::listen(function (PodcastProcessed $event) { + // ... + }); +} +``` #### Queueable Anonymous Event Listeners When registering closure based event listeners, you may wrap the listener closure within the `Illuminate\Events\queueable` function to instruct Laravel to execute the listener using the [queue](/docs/{{version}}/queues): - use App\Events\PodcastProcessed; - use function Illuminate\Events\queueable; - use Illuminate\Support\Facades\Event; +```php +use App\Events\PodcastProcessed; +use function Illuminate\Events\queueable; +use Illuminate\Support\Facades\Event; - /** - * Bootstrap any application services. - */ - public function boot(): void - { - Event::listen(queueable(function (PodcastProcessed $event) { - // ... - })); - } +/** + * Bootstrap any application services. + */ +public function boot(): void +{ + Event::listen(queueable(function (PodcastProcessed $event) { + // ... + })); +} +``` Like queued jobs, you may use the `onConnection`, `onQueue`, and `delay` methods to customize the execution of the queued listener: - Event::listen(queueable(function (PodcastProcessed $event) { - // ... - })->onConnection('redis')->onQueue('podcasts')->delay(now()->addSeconds(10))); +```php +Event::listen(queueable(function (PodcastProcessed $event) { + // ... +})->onConnection('redis')->onQueue('podcasts')->delay(now()->addSeconds(10))); +``` If you would like to handle anonymous queued listener failures, you may provide a closure to the `catch` method while defining the `queueable` listener. This closure will receive the event instance and the `Throwable` instance that caused the listener's failure: - use App\Events\PodcastProcessed; - use function Illuminate\Events\queueable; - use Illuminate\Support\Facades\Event; - use Throwable; +```php +use App\Events\PodcastProcessed; +use function Illuminate\Events\queueable; +use Illuminate\Support\Facades\Event; +use Throwable; - Event::listen(queueable(function (PodcastProcessed $event) { - // ... - })->catch(function (PodcastProcessed $event, Throwable $e) { - // The queued listener failed... - })); +Event::listen(queueable(function (PodcastProcessed $event) { + // ... +})->catch(function (PodcastProcessed $event, Throwable $e) { + // The queued listener failed... +})); +``` #### Wildcard Event Listeners You may also register listeners using the `*` character as a wildcard parameter, allowing you to catch multiple events on the same listener. Wildcard listeners receive the event name as their first argument and the entire event data array as their second argument: - Event::listen('event.*', function (string $eventName, array $data) { - // ... - }); +```php +Event::listen('event.*', function (string $eventName, array $data) { + // ... +}); +``` ## Defining Events An event class is essentially a data container which holds the information related to the event. For example, let's assume an `App\Events\OrderShipped` event receives an [Eloquent ORM](/docs/{{version}}/eloquent) object: - order... - } + // Access the order using $event->order... } +} +``` > [!NOTE] > Your event listeners may also type-hint any dependencies they need on their constructors. All event listeners are resolved via the Laravel [service container](/docs/{{version}}/container), so dependencies will be injected automatically. @@ -262,17 +286,19 @@ Queueing listeners can be beneficial if your listener is going to perform a slow To specify that a listener should be queued, add the `ShouldQueue` interface to the listener class. Listeners generated by the `make:listener` Artisan commands already have this interface imported into the current namespace so you can use it immediately: - highPriority ? 0 : 60; - } + public $delay = 60; +} +``` + +If you would like to define the listener's queue connection, queue name, or delay at runtime, you may define `viaConnection`, `viaQueue`, or `withDelay` methods on the listener: + +```php +/** + * Get the name of the listener's queue connection. + */ +public function viaConnection(): string +{ + return 'sqs'; +} + +/** + * Get the name of the listener's queue. + */ +public function viaQueue(): string +{ + return 'listeners'; +} + +/** + * Get the number of seconds before the job should be processed. + */ +public function withDelay(OrderShipped $event): int +{ + return $event->highPriority ? 0 : 60; +} +``` #### Conditionally Queueing Listeners Sometimes, you may need to determine whether a listener should be queued based on some data that are only available at runtime. To accomplish this, a `shouldQueue` method may be added to a listener to determine whether the listener should be queued. If the `shouldQueue` method returns `false`, the listener will not be queued: - order->subtotal >= 5000; - } + /** + * Determine whether the listener should be queued. + */ + public function shouldQueue(OrderCreated $event): bool + { + return $event->order->subtotal >= 5000; } +} +``` ### Manually Interacting With the Queue If you need to manually access the listener's underlying queue job's `delete` and `release` methods, you may do so using the `Illuminate\Queue\InteractsWithQueue` trait. This trait is imported by default on generated listeners and provides access to these methods: - release(30); - } + if (true) { + $this->release(30); } } +} +``` ### Queued Event Listeners and Database Transactions @@ -404,17 +438,19 @@ When queued listeners are dispatched within database transactions, they may be p If your queue connection's `after_commit` configuration option is set to `false`, you may still indicate that a particular queued listener should be dispatched after all open database transactions have been committed by implementing the `ShouldQueueAfterCommit` interface on the listener class: - [!NOTE] > To learn more about working around these issues, please review the documentation regarding [queued jobs and database transactions](/docs/{{version}}/queues#jobs-and-database-transactions). @@ -424,35 +460,37 @@ If your queue connection's `after_commit` configuration option is set to `false` Sometimes your queued event listeners may fail. If the queued listener exceeds the maximum number of attempts as defined by your queue worker, the `failed` method will be called on your listener. The `failed` method receives the event instance and the `Throwable` that caused the failure: - #### Specifying Queued Listener Maximum Attempts @@ -461,109 +499,123 @@ If one of your queued listeners is encountering an error, you likely do not want You may define a `$tries` property on your listener class to specify how many times the listener may be attempted before it is considered to have failed: - addMinutes(5); - } +/** + * Determine the time at which the listener should timeout. + */ +public function retryUntil(): DateTime +{ + return now()->addMinutes(5); +} +``` #### Specifying Queued Listener Backoff If you would like to configure how many seconds Laravel should wait before retrying a listener that has encountered an exception, you may do so by defining a `backoff` property on your listener class: - /** - * The number of seconds to wait before retrying the queued listener. - * - * @var int - */ - public $backoff = 3; +```php +/** + * The number of seconds to wait before retrying the queued listener. + * + * @var int + */ +public $backoff = 3; +``` If you require more complex logic for determining the listeners's backoff time, you may define a `backoff` method on your listener class: - /** - * Calculate the number of seconds to wait before retrying the queued listener. - */ - public function backoff(): int - { - return 3; - } +```php +/** + * Calculate the number of seconds to wait before retrying the queued listener. + */ +public function backoff(): int +{ + return 3; +} +``` You may easily configure "exponential" backoffs by returning an array of backoff values from the `backoff` method. In this example, the retry delay will be 1 second for the first retry, 5 seconds for the second retry, 10 seconds for the third retry, and 10 seconds for every subsequent retry if there are more attempts remaining: - /** - * Calculate the number of seconds to wait before retrying the queued listener. - * - * @return array - */ - public function backoff(): array - { - return [1, 5, 10]; - } +```php +/** + * Calculate the number of seconds to wait before retrying the queued listener. + * + * @return array + */ +public function backoff(): array +{ + return [1, 5, 10]; +} +``` ## Dispatching Events To dispatch an event, you may call the static `dispatch` method on the event. This method is made available on the event by the `Illuminate\Foundation\Events\Dispatchable` trait. Any arguments passed to the `dispatch` method will be passed to the event's constructor: - order_id); + $order = Order::findOrFail($request->order_id); - // Order shipment logic... + // Order shipment logic... - OrderShipped::dispatch($order); + OrderShipped::dispatch($order); - return redirect('/orders'); - } + return redirect('/orders'); } +} +``` If you would like to conditionally dispatch an event, you may use the `dispatchIf` and `dispatchUnless` methods: - OrderShipped::dispatchIf($condition, $order); +```php +OrderShipped::dispatchIf($condition, $order); - OrderShipped::dispatchUnless($condition, $order); +OrderShipped::dispatchUnless($condition, $order); +``` > [!NOTE] > When testing, it can be helpful to assert that certain events were dispatched without actually triggering their listeners. Laravel's [built-in testing helpers](#testing) make it a cinch. @@ -575,27 +627,29 @@ Sometimes, you may want to instruct Laravel to only dispatch an event after the This interface instructs Laravel to not dispatch the event until the current database transaction is committed. If the transaction fails, the event will be discarded. If no database transaction is in progress when the event is dispatched, the event will be dispatched immediately: - ## Event Subscribers @@ -605,102 +659,108 @@ This interface instructs Laravel to not dispatch the event until the current dat Event subscribers are classes that may subscribe to multiple events from within the subscriber class itself, allowing you to define several event handlers within a single class. Subscribers should define a `subscribe` method, which will be passed an event dispatcher instance. You may call the `listen` method on the given dispatcher to register event listeners: - listen( - Login::class, - [UserEventSubscriber::class, 'handleUserLogin'] - ); - - $events->listen( - Logout::class, - [UserEventSubscriber::class, 'handleUserLogout'] - ); - } + $events->listen( + Login::class, + [UserEventSubscriber::class, 'handleUserLogin'] + ); + + $events->listen( + Logout::class, + [UserEventSubscriber::class, 'handleUserLogout'] + ); } +} +``` If your event listener methods are defined within the subscriber itself, you may find it more convenient to return an array of events and method names from the subscriber's `subscribe` method. Laravel will automatically determine the subscriber's class name when registering the event listeners: - + */ + public function subscribe(Dispatcher $events): array { - /** - * Handle user login events. - */ - public function handleUserLogin(Login $event): void {} - - /** - * Handle user logout events. - */ - public function handleUserLogout(Logout $event): void {} - - /** - * Register the listeners for the subscriber. - * - * @return array - */ - public function subscribe(Dispatcher $events): array - { - return [ - Login::class => 'handleUserLogin', - Logout::class => 'handleUserLogout', - ]; - } + return [ + Login::class => 'handleUserLogin', + Logout::class => 'handleUserLogout', + ]; } +} +``` ### Registering Event Subscribers After writing the subscriber, Laravel will automatically register handler methods within the subscriber if they follow Laravel's [event discovery conventions](#event-discovery). Otherwise, you may manually register your subscriber using the `subscribe` method of the `Event` facade. Typically, this should be done within the `boot` method of your application's `AppServiceProvider`: - ## Testing @@ -773,16 +833,20 @@ class ExampleTest extends TestCase You may pass a closure to the `assertDispatched` or `assertNotDispatched` methods in order to assert that an event was dispatched that passes a given "truth test". If at least one event was dispatched that passes the given truth test then the assertion will be successful: - Event::assertDispatched(function (OrderShipped $event) use ($order) { - return $event->order->id === $order->id; - }); +```php +Event::assertDispatched(function (OrderShipped $event) use ($order) { + return $event->order->id === $order->id; +}); +``` If you would simply like to assert that an event listener is listening to a given event, you may use the `assertListening` method: - Event::assertListening( - OrderShipped::class, - SendShipmentNotification::class - ); +```php +Event::assertListening( + OrderShipped::class, + SendShipmentNotification::class +); +``` > [!WARNING] > After calling `Event::fake()`, no event listeners will be executed. So, if your tests use model factories that rely on events, such as creating a UUID during a model's `creating` event, you should call `Event::fake()` **after** using your factories. @@ -828,9 +892,11 @@ public function test_orders_can_be_processed(): void You may fake all events except for a set of specified events using the `except` method: - Event::fake()->except([ - OrderCreated::class, - ]); +```php +Event::fake()->except([ + OrderCreated::class, +]); +``` ### Scoped Event Fakes diff --git a/facades.md b/facades.md index a8621e52502..d48b3188f26 100644 --- a/facades.md +++ b/facades.md @@ -17,12 +17,14 @@ Laravel facades serve as "static proxies" to underlying classes in the service c All of Laravel's facades are defined in the `Illuminate\Support\Facades` namespace. So, we can easily access a facade like so: - use Illuminate\Support\Facades\Cache; - use Illuminate\Support\Facades\Route; +```php +use Illuminate\Support\Facades\Cache; +use Illuminate\Support\Facades\Route; - Route::get('/cache', function () { - return Cache::get('key'); - }); +Route::get('/cache', function () { + return Cache::get('key'); +}); +``` Throughout the Laravel documentation, many of the examples will use facades to demonstrate various features of the framework. @@ -33,19 +35,21 @@ To complement facades, Laravel offers a variety of global "helper functions" tha For example, instead of using the `Illuminate\Support\Facades\Response` facade to generate a JSON response, we may simply use the `response` function. Because helper functions are globally available, you do not need to import any classes in order to use them: - use Illuminate\Support\Facades\Response; +```php +use Illuminate\Support\Facades\Response; - Route::get('/users', function () { - return Response::json([ - // ... - ]); - }); +Route::get('/users', function () { + return Response::json([ + // ... + ]); +}); - Route::get('/users', function () { - return response()->json([ - // ... - ]); - }); +Route::get('/users', function () { + return response()->json([ + // ... + ]); +}); +``` ## When to Utilize Facades @@ -61,11 +65,13 @@ One of the primary benefits of dependency injection is the ability to swap imple Typically, it would not be possible to mock or stub a truly static class method. However, since facades use dynamic methods to proxy method calls to objects resolved from the service container, we actually can test facades just as we would test an injected class instance. For example, given the following route: - use Illuminate\Support\Facades\Cache; +```php +use Illuminate\Support\Facades\Cache; - Route::get('/cache', function () { - return Cache::get('key'); - }); +Route::get('/cache', function () { + return Cache::get('key'); +}); +``` Using Laravel's facade testing methods, we can write the following test to verify that the `Cache::get` method was called with the argument we expected: @@ -106,33 +112,39 @@ public function test_basic_example(): void In addition to facades, Laravel includes a variety of "helper" functions which can perform common tasks like generating views, firing events, dispatching jobs, or sending HTTP responses. Many of these helper functions perform the same function as a corresponding facade. For example, this facade call and helper call are equivalent: - return Illuminate\Support\Facades\View::make('profile'); +```php +return Illuminate\Support\Facades\View::make('profile'); - return view('profile'); +return view('profile'); +``` There is absolutely no practical difference between facades and helper functions. When using helper functions, you may still test them exactly as you would the corresponding facade. For example, given the following route: - Route::get('/cache', function () { - return cache('key'); - }); +```php +Route::get('/cache', function () { + return cache('key'); +}); +``` The `cache` helper is going to call the `get` method on the class underlying the `Cache` facade. So, even though we are using the helper function, we can write the following test to verify that the method was called with the argument we expected: - use Illuminate\Support\Facades\Cache; +```php +use Illuminate\Support\Facades\Cache; - /** - * A basic functional test example. - */ - public function test_basic_example(): void - { - Cache::shouldReceive('get') - ->with('key') - ->andReturn('value'); +/** + * A basic functional test example. + */ +public function test_basic_example(): void +{ + Cache::shouldReceive('get') + ->with('key') + ->andReturn('value'); - $response = $this->get('/cache'); + $response = $this->get('/cache'); - $response->assertSee('value'); - } + $response->assertSee('value'); +} +``` ## How Facades Work @@ -141,41 +153,45 @@ In a Laravel application, a facade is a class that provides access to an object The `Facade` base class makes use of the `__callStatic()` magic-method to defer calls from your facade to an object resolved from the container. In the example below, a call is made to the Laravel cache system. By glancing at this code, one might assume that the static `get` method is being called on the `Cache` class: - $user]); - } + $user = Cache::get('user:'.$id); + + return view('profile', ['user' => $user]); } +} +``` Notice that near the top of the file we are "importing" the `Cache` facade. This facade serves as a proxy for accessing the underlying implementation of the `Illuminate\Contracts\Cache\Factory` interface. Any calls we make using the facade will be passed to the underlying instance of Laravel's cache service. If we look at that `Illuminate\Support\Facades\Cache` class, you'll see that there is no static method `get`: - class Cache extends Facade +```php +class Cache extends Facade +{ + /** + * Get the registered name of the component. + */ + protected static function getFacadeAccessor(): string { - /** - * Get the registered name of the component. - */ - protected static function getFacadeAccessor(): string - { - return 'cache'; - } + return 'cache'; } +} +``` Instead, the `Cache` facade extends the base `Facade` class and defines the method `getFacadeAccessor()`. This method's job is to return the name of a service container binding. When a user references any static method on the `Cache` facade, Laravel resolves the `cache` binding from the [service container](/docs/{{version}}/container) and runs the requested method (in this case, `get`) against that object. @@ -184,50 +200,54 @@ Instead, the `Cache` facade extends the base `Facade` class and defines the meth Using real-time facades, you may treat any class in your application as if it was a facade. To illustrate how this can be used, let's first examine some code that does not use real-time facades. For example, let's assume our `Podcast` model has a `publish` method. However, in order to publish the podcast, we need to inject a `Publisher` instance: - update(['publishing' => now()]); - - $publisher->publish($this); - } + $this->update(['publishing' => now()]); + + $publisher->publish($this); } +} +``` Injecting a publisher implementation into the method allows us to easily test the method in isolation since we can mock the injected publisher. However, it requires us to always pass a publisher instance each time we call the `publish` method. Using real-time facades, we can maintain the same testability while not being required to explicitly pass a `Publisher` instance. To generate a real-time facade, prefix the namespace of the imported class with `Facades`: - update(['publishing' => now()]); - - $publisher->publish($this); // [tl! remove] - Publisher::publish($this); // [tl! add] - } + $this->update(['publishing' => now()]); + + $publisher->publish($this); // [tl! remove] + Publisher::publish($this); // [tl! add] } +} +``` When the real-time facade is used, the publisher implementation will be resolved out of the service container using the portion of the interface or class name that appears after the `Facades` prefix. When testing, we can use Laravel's built-in facade testing helpers to mock this method call: diff --git a/filesystem.md b/filesystem.md index dd302ead5e5..d50402e30f8 100644 --- a/filesystem.md +++ b/filesystem.md @@ -45,9 +45,11 @@ The `local` driver interacts with files stored locally on the server running the When using the `local` driver, all file operations are relative to the `root` directory defined in your `filesystems` configuration file. By default, this value is set to the `storage/app/private` directory. Therefore, the following method would write to `storage/app/private/example.txt`: - use Illuminate\Support\Facades\Storage; +```php +use Illuminate\Support\Facades\Storage; - Storage::disk('local')->put('example.txt', 'Contents'); +Storage::disk('local')->put('example.txt', 'Contents'); +``` ### The Public Disk @@ -64,14 +66,18 @@ php artisan storage:link Once a file has been stored and the symbolic link has been created, you can create a URL to the files using the `asset` helper: - echo asset('storage/file.txt'); +```php +echo asset('storage/file.txt'); +``` You may configure additional symbolic links in your `filesystems` configuration file. Each of the configured links will be created when you run the `storage:link` command: - 'links' => [ - public_path('storage') => storage_path('app/public'), - public_path('images') => storage_path('app/images'), - ], +```php +'links' => [ + public_path('storage') => storage_path('app/public'), + public_path('images') => storage_path('app/images'), +], +``` The `storage:unlink` command may be used to destroy your configured symbolic links: @@ -114,19 +120,21 @@ composer require league/flysystem-ftp "^3.0" Laravel's Flysystem integrations work great with FTP; however, a sample configuration is not included with the framework's default `config/filesystems.php` configuration file. If you need to configure an FTP filesystem, you may use the configuration example below: - 'ftp' => [ - 'driver' => 'ftp', - 'host' => env('FTP_HOST'), - 'username' => env('FTP_USERNAME'), - 'password' => env('FTP_PASSWORD'), - - // Optional FTP Settings... - // 'port' => env('FTP_PORT', 21), - // 'root' => env('FTP_ROOT'), - // 'passive' => true, - // 'ssl' => true, - // 'timeout' => 30, - ], +```php +'ftp' => [ + 'driver' => 'ftp', + 'host' => env('FTP_HOST'), + 'username' => env('FTP_USERNAME'), + 'password' => env('FTP_PASSWORD'), + + // Optional FTP Settings... + // 'port' => env('FTP_PORT', 21), + // 'root' => env('FTP_ROOT'), + // 'passive' => true, + // 'ssl' => true, + // 'timeout' => 30, +], +``` #### SFTP Driver Configuration @@ -139,31 +147,33 @@ composer require league/flysystem-sftp-v3 "^3.0" Laravel's Flysystem integrations work great with SFTP; however, a sample configuration is not included with the framework's default `config/filesystems.php` configuration file. If you need to configure an SFTP filesystem, you may use the configuration example below: - 'sftp' => [ - 'driver' => 'sftp', - 'host' => env('SFTP_HOST'), - - // Settings for basic authentication... - 'username' => env('SFTP_USERNAME'), - 'password' => env('SFTP_PASSWORD'), - - // Settings for SSH key based authentication with encryption password... - 'privateKey' => env('SFTP_PRIVATE_KEY'), - 'passphrase' => env('SFTP_PASSPHRASE'), - - // Settings for file / directory permissions... - 'visibility' => 'private', // `private` = 0600, `public` = 0644 - 'directory_visibility' => 'private', // `private` = 0700, `public` = 0755 - - // Optional SFTP Settings... - // 'hostFingerprint' => env('SFTP_HOST_FINGERPRINT'), - // 'maxTries' => 4, - // 'passphrase' => env('SFTP_PASSPHRASE'), - // 'port' => env('SFTP_PORT', 22), - // 'root' => env('SFTP_ROOT', ''), - // 'timeout' => 30, - // 'useAgent' => true, - ], +```php +'sftp' => [ + 'driver' => 'sftp', + 'host' => env('SFTP_HOST'), + + // Settings for basic authentication... + 'username' => env('SFTP_USERNAME'), + 'password' => env('SFTP_PASSWORD'), + + // Settings for SSH key based authentication with encryption password... + 'privateKey' => env('SFTP_PRIVATE_KEY'), + 'passphrase' => env('SFTP_PASSPHRASE'), + + // Settings for file / directory permissions... + 'visibility' => 'private', // `private` = 0600, `public` = 0644 + 'directory_visibility' => 'private', // `private` = 0700, `public` = 0755 + + // Optional SFTP Settings... + // 'hostFingerprint' => env('SFTP_HOST_FINGERPRINT'), + // 'maxTries' => 4, + // 'passphrase' => env('SFTP_PASSPHRASE'), + // 'port' => env('SFTP_PORT', 22), + // 'root' => env('SFTP_ROOT', ''), + // 'timeout' => 30, + // 'useAgent' => true, +], +``` ### Scoped and Read-Only Filesystems @@ -207,7 +217,9 @@ By default, your application's `filesystems` configuration file contains a disk Typically, after updating the disk's credentials to match the credentials of the service you are planning to use, you only need to update the value of the `endpoint` configuration option. This option's value is typically defined via the `AWS_ENDPOINT` environment variable: - 'endpoint' => env('AWS_ENDPOINT', 'https://minio:9000'), +```php +'endpoint' => env('AWS_ENDPOINT', 'https://minio:9000'), +``` #### MinIO @@ -226,13 +238,17 @@ AWS_URL=http://localhost:9000/local The `Storage` facade may be used to interact with any of your configured disks. For example, you may use the `put` method on the facade to store an avatar on the default disk. If you call methods on the `Storage` facade without first calling the `disk` method, the method will automatically be passed to the default disk: - use Illuminate\Support\Facades\Storage; +```php +use Illuminate\Support\Facades\Storage; - Storage::put('avatars/1', $content); +Storage::put('avatars/1', $content); +``` If your application interacts with multiple disks, you may use the `disk` method on the `Storage` facade to work with files on a particular disk: - Storage::disk('s3')->put('avatars/1', $content); +```php +Storage::disk('s3')->put('avatars/1', $content); +``` ### On-Demand Disks @@ -255,41 +271,53 @@ $disk->put('image.jpg', $content); The `get` method may be used to retrieve the contents of a file. The raw string contents of the file will be returned by the method. Remember, all file paths should be specified relative to the disk's "root" location: - $contents = Storage::get('file.jpg'); +```php +$contents = Storage::get('file.jpg'); +``` If the file you are retrieving contains JSON, you may use the `json` method to retrieve the file and decode its contents: - $orders = Storage::json('orders.json'); +```php +$orders = Storage::json('orders.json'); +``` The `exists` method may be used to determine if a file exists on the disk: - if (Storage::disk('s3')->exists('file.jpg')) { - // ... - } +```php +if (Storage::disk('s3')->exists('file.jpg')) { + // ... +} +``` The `missing` method may be used to determine if a file is missing from the disk: - if (Storage::disk('s3')->missing('file.jpg')) { - // ... - } +```php +if (Storage::disk('s3')->missing('file.jpg')) { + // ... +} +``` ### Downloading Files The `download` method may be used to generate a response that forces the user's browser to download the file at the given path. The `download` method accepts a filename as the second argument to the method, which will determine the filename that is seen by the user downloading the file. Finally, you may pass an array of HTTP headers as the third argument to the method: - return Storage::download('file.jpg'); +```php +return Storage::download('file.jpg'); - return Storage::download('file.jpg', $name, $headers); +return Storage::download('file.jpg', $name, $headers); +``` ### File URLs You may use the `url` method to get the URL for a given file. If you are using the `local` driver, this will typically just prepend `/storage` to the given path and return a relative URL to the file. If you are using the `s3` driver, the fully qualified remote URL will be returned: - use Illuminate\Support\Facades\Storage; +```php +use Illuminate\Support\Facades\Storage; - $url = Storage::url('file.jpg'); +$url = Storage::url('file.jpg'); +``` When using the `local` driver, all files that should be publicly accessible should be placed in the `storage/app/public` directory. Furthermore, you should [create a symbolic link](#the-public-disk) at `public/storage` which points to the `storage/app/public` directory. @@ -301,24 +329,28 @@ When using the `local` driver, all files that should be publicly accessible shou If you would like to modify the host for URLs generated using the `Storage` facade, you may add or change the `url` option in the disk's configuration array: - 'public' => [ - 'driver' => 'local', - 'root' => storage_path('app/public'), - 'url' => env('APP_URL').'/storage', - 'visibility' => 'public', - 'throw' => false, - ], +```php +'public' => [ + 'driver' => 'local', + 'root' => storage_path('app/public'), + 'url' => env('APP_URL').'/storage', + 'visibility' => 'public', + 'throw' => false, +], +``` ### Temporary URLs Using the `temporaryUrl` method, you may create temporary URLs to files stored using the `local` and `s3` drivers. This method accepts a path and a `DateTime` instance specifying when the URL should expire: - use Illuminate\Support\Facades\Storage; +```php +use Illuminate\Support\Facades\Storage; - $url = Storage::temporaryUrl( - 'file.jpg', now()->addMinutes(5) - ); +$url = Storage::temporaryUrl( + 'file.jpg', now()->addMinutes(5) +); +``` #### Enabling Local Temporary URLs @@ -339,47 +371,51 @@ If you started developing your application before support for temporary URLs was If you need to specify additional [S3 request parameters](https://docs.aws.amazon.com/AmazonS3/latest/API/RESTObjectGET.html#RESTObjectGET-requests), you may pass the array of request parameters as the third argument to the `temporaryUrl` method: - $url = Storage::temporaryUrl( - 'file.jpg', - now()->addMinutes(5), - [ - 'ResponseContentType' => 'application/octet-stream', - 'ResponseContentDisposition' => 'attachment; filename=file2.jpg', - ] - ); +```php +$url = Storage::temporaryUrl( + 'file.jpg', + now()->addMinutes(5), + [ + 'ResponseContentType' => 'application/octet-stream', + 'ResponseContentDisposition' => 'attachment; filename=file2.jpg', + ] +); +``` #### Customizing Temporary URLs If you need to customize how temporary URLs are created for a specific storage disk, you can use the `buildTemporaryUrlsUsing` method. For example, this can be useful if you have a controller that allows you to download files stored via a disk that doesn't typically support temporary URLs. Usually, this method should be called from the `boot` method of a service provider: - buildTemporaryUrlsUsing( - function (string $path, DateTime $expiration, array $options) { - return URL::temporarySignedRoute( - 'files.download', - $expiration, - array_merge($options, ['path' => $path]) - ); - } - ); - } + Storage::disk('local')->buildTemporaryUrlsUsing( + function (string $path, DateTime $expiration, array $options) { + return URL::temporarySignedRoute( + 'files.download', + $expiration, + array_merge($options, ['path' => $path]) + ); + } + ); } +} +``` #### Temporary Upload URLs @@ -389,11 +425,13 @@ If you need to customize how temporary URLs are created for a specific storage d If you need to generate a temporary URL that can be used to upload a file directly from your client-side application, you may use the `temporaryUploadUrl` method. This method accepts a path and a `DateTime` instance specifying when the URL should expire. The `temporaryUploadUrl` method returns an associative array which may be destructured into the upload URL and the headers that should be included with the upload request: - use Illuminate\Support\Facades\Storage; +```php +use Illuminate\Support\Facades\Storage; - ['url' => $url, 'headers' => $headers] = Storage::temporaryUploadUrl( - 'file.jpg', now()->addMinutes(5) - ); +['url' => $url, 'headers' => $headers] = Storage::temporaryUploadUrl( + 'file.jpg', now()->addMinutes(5) +); +``` This method is primarily useful in serverless environments that require the client-side application to directly upload files to a cloud storage system such as Amazon S3. @@ -402,138 +440,168 @@ This method is primarily useful in serverless environments that require the clie In addition to reading and writing files, Laravel can also provide information about the files themselves. For example, the `size` method may be used to get the size of a file in bytes: - use Illuminate\Support\Facades\Storage; +```php +use Illuminate\Support\Facades\Storage; - $size = Storage::size('file.jpg'); +$size = Storage::size('file.jpg'); +``` The `lastModified` method returns the UNIX timestamp of the last time the file was modified: - $time = Storage::lastModified('file.jpg'); +```php +$time = Storage::lastModified('file.jpg'); +``` The MIME type of a given file may be obtained via the `mimeType` method: - $mime = Storage::mimeType('file.jpg'); +```php +$mime = Storage::mimeType('file.jpg'); +``` #### File Paths You may use the `path` method to get the path for a given file. If you are using the `local` driver, this will return the absolute path to the file. If you are using the `s3` driver, this method will return the relative path to the file in the S3 bucket: - use Illuminate\Support\Facades\Storage; +```php +use Illuminate\Support\Facades\Storage; - $path = Storage::path('file.jpg'); +$path = Storage::path('file.jpg'); +``` ## Storing Files The `put` method may be used to store file contents on a disk. You may also pass a PHP `resource` to the `put` method, which will use Flysystem's underlying stream support. Remember, all file paths should be specified relative to the "root" location configured for the disk: - use Illuminate\Support\Facades\Storage; +```php +use Illuminate\Support\Facades\Storage; - Storage::put('file.jpg', $contents); +Storage::put('file.jpg', $contents); - Storage::put('file.jpg', $resource); +Storage::put('file.jpg', $resource); +``` #### Failed Writes If the `put` method (or other "write" operations) is unable to write the file to disk, `false` will be returned: - if (! Storage::put('file.jpg', $contents)) { - // The file could not be written to disk... - } +```php +if (! Storage::put('file.jpg', $contents)) { + // The file could not be written to disk... +} +``` If you wish, you may define the `throw` option within your filesystem disk's configuration array. When this option is defined as `true`, "write" methods such as `put` will throw an instance of `League\Flysystem\UnableToWriteFile` when write operations fail: - 'public' => [ - 'driver' => 'local', - // ... - 'throw' => true, - ], +```php +'public' => [ + 'driver' => 'local', + // ... + 'throw' => true, +], +``` ### Prepending and Appending To Files The `prepend` and `append` methods allow you to write to the beginning or end of a file: - Storage::prepend('file.log', 'Prepended Text'); +```php +Storage::prepend('file.log', 'Prepended Text'); - Storage::append('file.log', 'Appended Text'); +Storage::append('file.log', 'Appended Text'); +``` ### Copying and Moving Files The `copy` method may be used to copy an existing file to a new location on the disk, while the `move` method may be used to rename or move an existing file to a new location: - Storage::copy('old/file.jpg', 'new/file.jpg'); +```php +Storage::copy('old/file.jpg', 'new/file.jpg'); - Storage::move('old/file.jpg', 'new/file.jpg'); +Storage::move('old/file.jpg', 'new/file.jpg'); +``` ### Automatic Streaming Streaming files to storage offers significantly reduced memory usage. If you would like Laravel to automatically manage streaming a given file to your storage location, you may use the `putFile` or `putFileAs` method. This method accepts either an `Illuminate\Http\File` or `Illuminate\Http\UploadedFile` instance and will automatically stream the file to your desired location: - use Illuminate\Http\File; - use Illuminate\Support\Facades\Storage; +```php +use Illuminate\Http\File; +use Illuminate\Support\Facades\Storage; - // Automatically generate a unique ID for filename... - $path = Storage::putFile('photos', new File('/path/to/photo')); +// Automatically generate a unique ID for filename... +$path = Storage::putFile('photos', new File('/path/to/photo')); - // Manually specify a filename... - $path = Storage::putFileAs('photos', new File('/path/to/photo'), 'photo.jpg'); +// Manually specify a filename... +$path = Storage::putFileAs('photos', new File('/path/to/photo'), 'photo.jpg'); +``` There are a few important things to note about the `putFile` method. Note that we only specified a directory name and not a filename. By default, the `putFile` method will generate a unique ID to serve as the filename. The file's extension will be determined by examining the file's MIME type. The path to the file will be returned by the `putFile` method so you can store the path, including the generated filename, in your database. The `putFile` and `putFileAs` methods also accept an argument to specify the "visibility" of the stored file. This is particularly useful if you are storing the file on a cloud disk such as Amazon S3 and would like the file to be publicly accessible via generated URLs: - Storage::putFile('photos', new File('/path/to/photo'), 'public'); +```php +Storage::putFile('photos', new File('/path/to/photo'), 'public'); +``` ### File Uploads In web applications, one of the most common use-cases for storing files is storing user uploaded files such as photos and documents. Laravel makes it very easy to store uploaded files using the `store` method on an uploaded file instance. Call the `store` method with the path at which you wish to store the uploaded file: - file('avatar')->store('avatars'); - - return $path; - } + $path = $request->file('avatar')->store('avatars'); + + return $path; } +} +``` There are a few important things to note about this example. Note that we only specified a directory name, not a filename. By default, the `store` method will generate a unique ID to serve as the filename. The file's extension will be determined by examining the file's MIME type. The path to the file will be returned by the `store` method so you can store the path, including the generated filename, in your database. You may also call the `putFile` method on the `Storage` facade to perform the same file storage operation as the example above: - $path = Storage::putFile('avatars', $request->file('avatar')); +```php +$path = Storage::putFile('avatars', $request->file('avatar')); +``` #### Specifying a File Name If you do not want a filename to be automatically assigned to your stored file, you may use the `storeAs` method, which receives the path, the filename, and the (optional) disk as its arguments: - $path = $request->file('avatar')->storeAs( - 'avatars', $request->user()->id - ); +```php +$path = $request->file('avatar')->storeAs( + 'avatars', $request->user()->id +); +``` You may also use the `putFileAs` method on the `Storage` facade, which will perform the same file storage operation as the example above: - $path = Storage::putFileAs( - 'avatars', $request->file('avatar'), $request->user()->id - ); +```php +$path = Storage::putFileAs( + 'avatars', $request->file('avatar'), $request->user()->id +); +``` > [!WARNING] > Unprintable and invalid unicode characters will automatically be removed from file paths. Therefore, you may wish to sanitize your file paths before passing them to Laravel's file storage methods. File paths are normalized using the `League\Flysystem\WhitespacePathNormalizer::normalizePath` method. @@ -543,34 +611,42 @@ You may also use the `putFileAs` method on the `Storage` facade, which will perf By default, this uploaded file's `store` method will use your default disk. If you would like to specify another disk, pass the disk name as the second argument to the `store` method: - $path = $request->file('avatar')->store( - 'avatars/'.$request->user()->id, 's3' - ); +```php +$path = $request->file('avatar')->store( + 'avatars/'.$request->user()->id, 's3' +); +``` If you are using the `storeAs` method, you may pass the disk name as the third argument to the method: - $path = $request->file('avatar')->storeAs( - 'avatars', - $request->user()->id, - 's3' - ); +```php +$path = $request->file('avatar')->storeAs( + 'avatars', + $request->user()->id, + 's3' +); +``` #### Other Uploaded File Information If you would like to get the original name and extension of the uploaded file, you may do so using the `getClientOriginalName` and `getClientOriginalExtension` methods: - $file = $request->file('avatar'); +```php +$file = $request->file('avatar'); - $name = $file->getClientOriginalName(); - $extension = $file->getClientOriginalExtension(); +$name = $file->getClientOriginalName(); +$extension = $file->getClientOriginalExtension(); +``` However, keep in mind that the `getClientOriginalName` and `getClientOriginalExtension` methods are considered unsafe, as the file name and extension may be tampered with by a malicious user. For this reason, you should typically prefer the `hashName` and `extension` methods to get a name and an extension for the given file upload: - $file = $request->file('avatar'); +```php +$file = $request->file('avatar'); - $name = $file->hashName(); // Generate a unique, random name... - $extension = $file->extension(); // Determine the file's extension based on the file's MIME type... +$name = $file->hashName(); // Generate a unique, random name... +$extension = $file->extension(); // Determine the file's extension based on the file's MIME type... +``` ### File Visibility @@ -579,63 +655,81 @@ In Laravel's Flysystem integration, "visibility" is an abstraction of file permi You can set the visibility when writing the file via the `put` method: - use Illuminate\Support\Facades\Storage; +```php +use Illuminate\Support\Facades\Storage; - Storage::put('file.jpg', $contents, 'public'); +Storage::put('file.jpg', $contents, 'public'); +``` If the file has already been stored, its visibility can be retrieved and set via the `getVisibility` and `setVisibility` methods: - $visibility = Storage::getVisibility('file.jpg'); +```php +$visibility = Storage::getVisibility('file.jpg'); - Storage::setVisibility('file.jpg', 'public'); +Storage::setVisibility('file.jpg', 'public'); +``` When interacting with uploaded files, you may use the `storePublicly` and `storePubliclyAs` methods to store the uploaded file with `public` visibility: - $path = $request->file('avatar')->storePublicly('avatars', 's3'); +```php +$path = $request->file('avatar')->storePublicly('avatars', 's3'); - $path = $request->file('avatar')->storePubliclyAs( - 'avatars', - $request->user()->id, - 's3' - ); +$path = $request->file('avatar')->storePubliclyAs( + 'avatars', + $request->user()->id, + 's3' +); +``` #### Local Files and Visibility When using the `local` driver, `public` [visibility](#file-visibility) translates to `0755` permissions for directories and `0644` permissions for files. You can modify the permissions mappings in your application's `filesystems` configuration file: - 'local' => [ - 'driver' => 'local', - 'root' => storage_path('app'), - 'permissions' => [ - 'file' => [ - 'public' => 0644, - 'private' => 0600, - ], - 'dir' => [ - 'public' => 0755, - 'private' => 0700, - ], +```php + +``````php +'local' => [ + 'driver' => 'local', + 'root' => storage_path('app'), + 'permissions' => [ + 'file' => [ + 'public' => 0644, + 'private' => 0600, + ], + 'dir' => [ + 'public' => 0755, + 'private' => 0700, ], - 'throw' => false, ], + 'throw' => false, +], +``` ## Deleting Files The `delete` method accepts a single filename or an array of files to delete: - use Illuminate\Support\Facades\Storage; +```php - Storage::delete('file.jpg'); +``````php +use Illuminate\Support\Facades\Storage; - Storage::delete(['file.jpg', 'file2.jpg']); +Storage::delete('file.jpg'); + +Storage::delete(['file.jpg', 'file2.jpg']); +``` If necessary, you may specify the disk that the file should be deleted from: - use Illuminate\Support\Facades\Storage; +```php - Storage::disk('s3')->delete('path/file.jpg'); +``````php +use Illuminate\Support\Facades\Storage; + +Storage::disk('s3')->delete('path/file.jpg'); +``` ## Directories @@ -645,34 +739,42 @@ If necessary, you may specify the disk that the file should be deleted from: The `files` method returns an array of all of the files in a given directory. If you would like to retrieve a list of all files within a given directory including all subdirectories, you may use the `allFiles` method: - use Illuminate\Support\Facades\Storage; +```php +use Illuminate\Support\Facades\Storage; - $files = Storage::files($directory); +$files = Storage::files($directory); - $files = Storage::allFiles($directory); +$files = Storage::allFiles($directory); +``` #### Get All Directories Within a Directory The `directories` method returns an array of all the directories within a given directory. Additionally, you may use the `allDirectories` method to get a list of all directories within a given directory and all of its subdirectories: - $directories = Storage::directories($directory); +```php +$directories = Storage::directories($directory); - $directories = Storage::allDirectories($directory); +$directories = Storage::allDirectories($directory); +``` #### Create a Directory The `makeDirectory` method will create the given directory, including any needed subdirectories: - Storage::makeDirectory($directory); +```php +Storage::makeDirectory($directory); +``` #### Delete a Directory Finally, the `deleteDirectory` method may be used to remove a directory and all of its files: - Storage::deleteDirectory($directory); +```php +Storage::deleteDirectory($directory); +``` ## Testing @@ -764,46 +866,48 @@ composer require spatie/flysystem-dropbox Next, you can register the driver within the `boot` method of one of your application's [service providers](/docs/{{version}}/providers). To accomplish this, you should use the `extend` method of the `Storage` facade: - user()->fill([ - 'password' => Hash::make($request->newPassword) - ])->save(); - - return redirect('/profile'); - } + // Validate the new password length... + + $request->user()->fill([ + 'password' => Hash::make($request->newPassword) + ])->save(); + + return redirect('/profile'); } +} +``` #### Adjusting The Bcrypt Work Factor If you are using the Bcrypt algorithm, the `make` method allows you to manage the work factor of the algorithm using the `rounds` option; however, the default work factor managed by Laravel is acceptable for most applications: - $hashed = Hash::make('password', [ - 'rounds' => 12, - ]); +```php +$hashed = Hash::make('password', [ + 'rounds' => 12, +]); +``` #### Adjusting The Argon2 Work Factor If you are using the Argon2 algorithm, the `make` method allows you to manage the work factor of the algorithm using the `memory`, `time`, and `threads` options; however, the default values managed by Laravel are acceptable for most applications: - $hashed = Hash::make('password', [ - 'memory' => 1024, - 'time' => 2, - 'threads' => 2, - ]); +```php +$hashed = Hash::make('password', [ + 'memory' => 1024, + 'time' => 2, + 'threads' => 2, +]); +``` > [!NOTE] > For more information on these options, please refer to the [official PHP documentation regarding Argon hashing](https://secure.php.net/manual/en/function.password-hash.php). @@ -87,18 +93,22 @@ If you are using the Argon2 algorithm, the `make` method allows you to manage th The `check` method provided by the `Hash` facade allows you to verify that a given plain-text string corresponds to a given hash: - if (Hash::check('plain-text', $hashedPassword)) { - // The passwords match... - } +```php +if (Hash::check('plain-text', $hashedPassword)) { + // The passwords match... +} +``` ### Determining if a Password Needs to be Rehashed The `needsRehash` method provided by the `Hash` facade allows you to determine if the work factor used by the hasher has changed since the password was hashed. Some applications choose to perform this check during the application's authentication process: - if (Hash::needsRehash($hashed)) { - $hashed = Hash::make('plain-text'); - } +```php +if (Hash::needsRehash($hashed)) { + $hashed = Hash::make('plain-text'); +} +``` ## Hash Algorithm Verification diff --git a/helpers.md b/helpers.md index 40a271b0ded..df7d42e52f4 100644 --- a/helpers.md +++ b/helpers.md @@ -216,705 +216,799 @@ Laravel includes a variety of global "helper" PHP functions. Many of these funct The `Arr::accessible` method determines if the given value is array accessible: - use Illuminate\Support\Arr; - use Illuminate\Support\Collection; +```php +use Illuminate\Support\Arr; +use Illuminate\Support\Collection; - $isAccessible = Arr::accessible(['a' => 1, 'b' => 2]); +$isAccessible = Arr::accessible(['a' => 1, 'b' => 2]); - // true +// true - $isAccessible = Arr::accessible(new Collection); +$isAccessible = Arr::accessible(new Collection); - // true +// true - $isAccessible = Arr::accessible('abc'); +$isAccessible = Arr::accessible('abc'); - // false +// false - $isAccessible = Arr::accessible(new stdClass); +$isAccessible = Arr::accessible(new stdClass); - // false +// false +``` #### `Arr::add()` {.collection-method} The `Arr::add` method adds a given key / value pair to an array if the given key doesn't already exist in the array or is set to `null`: - use Illuminate\Support\Arr; +```php +use Illuminate\Support\Arr; - $array = Arr::add(['name' => 'Desk'], 'price', 100); +$array = Arr::add(['name' => 'Desk'], 'price', 100); - // ['name' => 'Desk', 'price' => 100] +// ['name' => 'Desk', 'price' => 100] - $array = Arr::add(['name' => 'Desk', 'price' => null], 'price', 100); +$array = Arr::add(['name' => 'Desk', 'price' => null], 'price', 100); - // ['name' => 'Desk', 'price' => 100] +// ['name' => 'Desk', 'price' => 100] +``` #### `Arr::collapse()` {.collection-method} The `Arr::collapse` method collapses an array of arrays into a single array: - use Illuminate\Support\Arr; +```php +use Illuminate\Support\Arr; - $array = Arr::collapse([[1, 2, 3], [4, 5, 6], [7, 8, 9]]); +$array = Arr::collapse([[1, 2, 3], [4, 5, 6], [7, 8, 9]]); - // [1, 2, 3, 4, 5, 6, 7, 8, 9] +// [1, 2, 3, 4, 5, 6, 7, 8, 9] +``` #### `Arr::crossJoin()` {.collection-method} The `Arr::crossJoin` method cross joins the given arrays, returning a Cartesian product with all possible permutations: - use Illuminate\Support\Arr; - - $matrix = Arr::crossJoin([1, 2], ['a', 'b']); - - /* - [ - [1, 'a'], - [1, 'b'], - [2, 'a'], - [2, 'b'], - ] - */ - - $matrix = Arr::crossJoin([1, 2], ['a', 'b'], ['I', 'II']); - - /* - [ - [1, 'a', 'I'], - [1, 'a', 'II'], - [1, 'b', 'I'], - [1, 'b', 'II'], - [2, 'a', 'I'], - [2, 'a', 'II'], - [2, 'b', 'I'], - [2, 'b', 'II'], - ] - */ +```php +use Illuminate\Support\Arr; + +$matrix = Arr::crossJoin([1, 2], ['a', 'b']); + +/* + [ + [1, 'a'], + [1, 'b'], + [2, 'a'], + [2, 'b'], + ] +*/ + +$matrix = Arr::crossJoin([1, 2], ['a', 'b'], ['I', 'II']); + +/* + [ + [1, 'a', 'I'], + [1, 'a', 'II'], + [1, 'b', 'I'], + [1, 'b', 'II'], + [2, 'a', 'I'], + [2, 'a', 'II'], + [2, 'b', 'I'], + [2, 'b', 'II'], + ] +*/ +``` #### `Arr::divide()` {.collection-method} The `Arr::divide` method returns two arrays: one containing the keys and the other containing the values of the given array: - use Illuminate\Support\Arr; +```php +use Illuminate\Support\Arr; - [$keys, $values] = Arr::divide(['name' => 'Desk']); +[$keys, $values] = Arr::divide(['name' => 'Desk']); - // $keys: ['name'] +// $keys: ['name'] - // $values: ['Desk'] +// $values: ['Desk'] +``` #### `Arr::dot()` {.collection-method} The `Arr::dot` method flattens a multi-dimensional array into a single level array that uses "dot" notation to indicate depth: - use Illuminate\Support\Arr; +```php +use Illuminate\Support\Arr; - $array = ['products' => ['desk' => ['price' => 100]]]; +$array = ['products' => ['desk' => ['price' => 100]]]; - $flattened = Arr::dot($array); +$flattened = Arr::dot($array); - // ['products.desk.price' => 100] +// ['products.desk.price' => 100] +``` #### `Arr::except()` {.collection-method} The `Arr::except` method removes the given key / value pairs from an array: - use Illuminate\Support\Arr; +```php +use Illuminate\Support\Arr; - $array = ['name' => 'Desk', 'price' => 100]; +$array = ['name' => 'Desk', 'price' => 100]; - $filtered = Arr::except($array, ['price']); +$filtered = Arr::except($array, ['price']); - // ['name' => 'Desk'] +// ['name' => 'Desk'] +``` #### `Arr::exists()` {.collection-method} The `Arr::exists` method checks that the given key exists in the provided array: - use Illuminate\Support\Arr; +```php +use Illuminate\Support\Arr; - $array = ['name' => 'John Doe', 'age' => 17]; +$array = ['name' => 'John Doe', 'age' => 17]; - $exists = Arr::exists($array, 'name'); +$exists = Arr::exists($array, 'name'); - // true +// true - $exists = Arr::exists($array, 'salary'); +$exists = Arr::exists($array, 'salary'); - // false +// false +``` #### `Arr::first()` {.collection-method} The `Arr::first` method returns the first element of an array passing a given truth test: - use Illuminate\Support\Arr; +```php +use Illuminate\Support\Arr; - $array = [100, 200, 300]; +$array = [100, 200, 300]; - $first = Arr::first($array, function (int $value, int $key) { - return $value >= 150; - }); +$first = Arr::first($array, function (int $value, int $key) { + return $value >= 150; +}); - // 200 +// 200 +``` A default value may also be passed as the third parameter to the method. This value will be returned if no value passes the truth test: - use Illuminate\Support\Arr; +```php +use Illuminate\Support\Arr; - $first = Arr::first($array, $callback, $default); +$first = Arr::first($array, $callback, $default); +``` #### `Arr::flatten()` {.collection-method} The `Arr::flatten` method flattens a multi-dimensional array into a single level array: - use Illuminate\Support\Arr; +```php +use Illuminate\Support\Arr; - $array = ['name' => 'Joe', 'languages' => ['PHP', 'Ruby']]; +$array = ['name' => 'Joe', 'languages' => ['PHP', 'Ruby']]; - $flattened = Arr::flatten($array); +$flattened = Arr::flatten($array); - // ['Joe', 'PHP', 'Ruby'] +// ['Joe', 'PHP', 'Ruby'] +``` #### `Arr::forget()` {.collection-method} The `Arr::forget` method removes a given key / value pair from a deeply nested array using "dot" notation: - use Illuminate\Support\Arr; +```php +use Illuminate\Support\Arr; - $array = ['products' => ['desk' => ['price' => 100]]]; +$array = ['products' => ['desk' => ['price' => 100]]]; - Arr::forget($array, 'products.desk'); +Arr::forget($array, 'products.desk'); - // ['products' => []] +// ['products' => []] +``` #### `Arr::get()` {.collection-method} The `Arr::get` method retrieves a value from a deeply nested array using "dot" notation: - use Illuminate\Support\Arr; +```php +use Illuminate\Support\Arr; - $array = ['products' => ['desk' => ['price' => 100]]]; +$array = ['products' => ['desk' => ['price' => 100]]]; - $price = Arr::get($array, 'products.desk.price'); +$price = Arr::get($array, 'products.desk.price'); - // 100 +// 100 +``` The `Arr::get` method also accepts a default value, which will be returned if the specified key is not present in the array: - use Illuminate\Support\Arr; +```php +use Illuminate\Support\Arr; - $discount = Arr::get($array, 'products.desk.discount', 0); +$discount = Arr::get($array, 'products.desk.discount', 0); - // 0 +// 0 +``` #### `Arr::has()` {.collection-method} The `Arr::has` method checks whether a given item or items exists in an array using "dot" notation: - use Illuminate\Support\Arr; +```php +use Illuminate\Support\Arr; - $array = ['product' => ['name' => 'Desk', 'price' => 100]]; +$array = ['product' => ['name' => 'Desk', 'price' => 100]]; - $contains = Arr::has($array, 'product.name'); +$contains = Arr::has($array, 'product.name'); - // true +// true - $contains = Arr::has($array, ['product.price', 'product.discount']); +$contains = Arr::has($array, ['product.price', 'product.discount']); - // false +// false +``` #### `Arr::hasAny()` {.collection-method} The `Arr::hasAny` method checks whether any item in a given set exists in an array using "dot" notation: - use Illuminate\Support\Arr; +```php +use Illuminate\Support\Arr; - $array = ['product' => ['name' => 'Desk', 'price' => 100]]; +$array = ['product' => ['name' => 'Desk', 'price' => 100]]; - $contains = Arr::hasAny($array, 'product.name'); +$contains = Arr::hasAny($array, 'product.name'); - // true +// true - $contains = Arr::hasAny($array, ['product.name', 'product.discount']); +$contains = Arr::hasAny($array, ['product.name', 'product.discount']); - // true +// true - $contains = Arr::hasAny($array, ['category', 'product.discount']); +$contains = Arr::hasAny($array, ['category', 'product.discount']); - // false +// false +``` #### `Arr::isAssoc()` {.collection-method} The `Arr::isAssoc` method returns `true` if the given array is an associative array. An array is considered "associative" if it doesn't have sequential numerical keys beginning with zero: - use Illuminate\Support\Arr; +```php +use Illuminate\Support\Arr; - $isAssoc = Arr::isAssoc(['product' => ['name' => 'Desk', 'price' => 100]]); +$isAssoc = Arr::isAssoc(['product' => ['name' => 'Desk', 'price' => 100]]); - // true +// true - $isAssoc = Arr::isAssoc([1, 2, 3]); +$isAssoc = Arr::isAssoc([1, 2, 3]); - // false +// false +``` #### `Arr::isList()` {.collection-method} The `Arr::isList` method returns `true` if the given array's keys are sequential integers beginning from zero: - use Illuminate\Support\Arr; +```php +use Illuminate\Support\Arr; - $isList = Arr::isList(['foo', 'bar', 'baz']); +$isList = Arr::isList(['foo', 'bar', 'baz']); - // true +// true - $isList = Arr::isList(['product' => ['name' => 'Desk', 'price' => 100]]); +$isList = Arr::isList(['product' => ['name' => 'Desk', 'price' => 100]]); - // false +// false +``` #### `Arr::join()` {.collection-method} The `Arr::join` method joins array elements with a string. Using this method's second argument, you may also specify the joining string for the final element of the array: - use Illuminate\Support\Arr; +```php +use Illuminate\Support\Arr; - $array = ['Tailwind', 'Alpine', 'Laravel', 'Livewire']; +$array = ['Tailwind', 'Alpine', 'Laravel', 'Livewire']; - $joined = Arr::join($array, ', '); +$joined = Arr::join($array, ', '); - // Tailwind, Alpine, Laravel, Livewire +// Tailwind, Alpine, Laravel, Livewire - $joined = Arr::join($array, ', ', ' and '); +$joined = Arr::join($array, ', ', ' and '); - // Tailwind, Alpine, Laravel and Livewire +// Tailwind, Alpine, Laravel and Livewire +``` #### `Arr::keyBy()` {.collection-method} The `Arr::keyBy` method keys the array by the given key. If multiple items have the same key, only the last one will appear in the new array: - use Illuminate\Support\Arr; +```php +use Illuminate\Support\Arr; - $array = [ - ['product_id' => 'prod-100', 'name' => 'Desk'], - ['product_id' => 'prod-200', 'name' => 'Chair'], - ]; +$array = [ + ['product_id' => 'prod-100', 'name' => 'Desk'], + ['product_id' => 'prod-200', 'name' => 'Chair'], +]; - $keyed = Arr::keyBy($array, 'product_id'); +$keyed = Arr::keyBy($array, 'product_id'); - /* - [ - 'prod-100' => ['product_id' => 'prod-100', 'name' => 'Desk'], - 'prod-200' => ['product_id' => 'prod-200', 'name' => 'Chair'], - ] - */ +/* + [ + 'prod-100' => ['product_id' => 'prod-100', 'name' => 'Desk'], + 'prod-200' => ['product_id' => 'prod-200', 'name' => 'Chair'], + ] +*/ +``` #### `Arr::last()` {.collection-method} The `Arr::last` method returns the last element of an array passing a given truth test: - use Illuminate\Support\Arr; +```php +use Illuminate\Support\Arr; - $array = [100, 200, 300, 110]; +$array = [100, 200, 300, 110]; - $last = Arr::last($array, function (int $value, int $key) { - return $value >= 150; - }); +$last = Arr::last($array, function (int $value, int $key) { + return $value >= 150; +}); - // 300 +// 300 +``` A default value may be passed as the third argument to the method. This value will be returned if no value passes the truth test: - use Illuminate\Support\Arr; +```php +use Illuminate\Support\Arr; - $last = Arr::last($array, $callback, $default); +$last = Arr::last($array, $callback, $default); +``` #### `Arr::map()` {.collection-method} The `Arr::map` method iterates through the array and passes each value and key to the given callback. The array value is replaced by the value returned by the callback: - use Illuminate\Support\Arr; +```php +use Illuminate\Support\Arr; - $array = ['first' => 'james', 'last' => 'kirk']; +$array = ['first' => 'james', 'last' => 'kirk']; - $mapped = Arr::map($array, function (string $value, string $key) { - return ucfirst($value); - }); +$mapped = Arr::map($array, function (string $value, string $key) { + return ucfirst($value); +}); - // ['first' => 'James', 'last' => 'Kirk'] +// ['first' => 'James', 'last' => 'Kirk'] +``` #### `Arr::mapSpread()` {.collection-method} The `Arr::mapSpread` method iterates over the array, passing each nested item value into the given closure. The closure is free to modify the item and return it, thus forming a new array of modified items: - use Illuminate\Support\Arr; +```php +use Illuminate\Support\Arr; - $array = [ - [0, 1], - [2, 3], - [4, 5], - [6, 7], - [8, 9], - ]; +$array = [ + [0, 1], + [2, 3], + [4, 5], + [6, 7], + [8, 9], +]; - $mapped = Arr::mapSpread($array, function (int $even, int $odd) { - return $even + $odd; - }); +$mapped = Arr::mapSpread($array, function (int $even, int $odd) { + return $even + $odd; +}); - /* - [1, 5, 9, 13, 17] - */ +/* + [1, 5, 9, 13, 17] +*/ +``` #### `Arr::mapWithKeys()` {.collection-method} The `Arr::mapWithKeys` method iterates through the array and passes each value to the given callback. The callback should return an associative array containing a single key / value pair: - use Illuminate\Support\Arr; +```php +use Illuminate\Support\Arr; - $array = [ - [ - 'name' => 'John', - 'department' => 'Sales', - 'email' => 'john@example.com', - ], - [ - 'name' => 'Jane', - 'department' => 'Marketing', - 'email' => 'jane@example.com', - ] - ]; - - $mapped = Arr::mapWithKeys($array, function (array $item, int $key) { - return [$item['email'] => $item['name']]; - }); +$array = [ + [ + 'name' => 'John', + 'department' => 'Sales', + 'email' => 'john@example.com', + ], + [ + 'name' => 'Jane', + 'department' => 'Marketing', + 'email' => 'jane@example.com', + ] +]; - /* - [ - 'john@example.com' => 'John', - 'jane@example.com' => 'Jane', - ] - */ +$mapped = Arr::mapWithKeys($array, function (array $item, int $key) { + return [$item['email'] => $item['name']]; +}); + +/* + [ + 'john@example.com' => 'John', + 'jane@example.com' => 'Jane', + ] +*/ +``` #### `Arr::only()` {.collection-method} The `Arr::only` method returns only the specified key / value pairs from the given array: - use Illuminate\Support\Arr; +```php +use Illuminate\Support\Arr; - $array = ['name' => 'Desk', 'price' => 100, 'orders' => 10]; +$array = ['name' => 'Desk', 'price' => 100, 'orders' => 10]; - $slice = Arr::only($array, ['name', 'price']); +$slice = Arr::only($array, ['name', 'price']); - // ['name' => 'Desk', 'price' => 100] +// ['name' => 'Desk', 'price' => 100] +``` #### `Arr::pluck()` {.collection-method} The `Arr::pluck` method retrieves all of the values for a given key from an array: - use Illuminate\Support\Arr; +```php +use Illuminate\Support\Arr; - $array = [ - ['developer' => ['id' => 1, 'name' => 'Taylor']], - ['developer' => ['id' => 2, 'name' => 'Abigail']], - ]; +$array = [ + ['developer' => ['id' => 1, 'name' => 'Taylor']], + ['developer' => ['id' => 2, 'name' => 'Abigail']], +]; - $names = Arr::pluck($array, 'developer.name'); +$names = Arr::pluck($array, 'developer.name'); - // ['Taylor', 'Abigail'] +// ['Taylor', 'Abigail'] +``` You may also specify how you wish the resulting list to be keyed: - use Illuminate\Support\Arr; +```php +use Illuminate\Support\Arr; - $names = Arr::pluck($array, 'developer.name', 'developer.id'); +$names = Arr::pluck($array, 'developer.name', 'developer.id'); - // [1 => 'Taylor', 2 => 'Abigail'] +// [1 => 'Taylor', 2 => 'Abigail'] +``` #### `Arr::prepend()` {.collection-method} The `Arr::prepend` method will push an item onto the beginning of an array: - use Illuminate\Support\Arr; +```php +use Illuminate\Support\Arr; - $array = ['one', 'two', 'three', 'four']; +$array = ['one', 'two', 'three', 'four']; - $array = Arr::prepend($array, 'zero'); +$array = Arr::prepend($array, 'zero'); - // ['zero', 'one', 'two', 'three', 'four'] +// ['zero', 'one', 'two', 'three', 'four'] +``` If needed, you may specify the key that should be used for the value: - use Illuminate\Support\Arr; +```php +use Illuminate\Support\Arr; - $array = ['price' => 100]; +$array = ['price' => 100]; - $array = Arr::prepend($array, 'Desk', 'name'); +$array = Arr::prepend($array, 'Desk', 'name'); - // ['name' => 'Desk', 'price' => 100] +// ['name' => 'Desk', 'price' => 100] +``` #### `Arr::prependKeysWith()` {.collection-method} The `Arr::prependKeysWith` prepends all key names of an associative array with the given prefix: - use Illuminate\Support\Arr; +```php +use Illuminate\Support\Arr; - $array = [ - 'name' => 'Desk', - 'price' => 100, - ]; +$array = [ + 'name' => 'Desk', + 'price' => 100, +]; - $keyed = Arr::prependKeysWith($array, 'product.'); +$keyed = Arr::prependKeysWith($array, 'product.'); - /* - [ - 'product.name' => 'Desk', - 'product.price' => 100, - ] - */ +/* + [ + 'product.name' => 'Desk', + 'product.price' => 100, + ] +*/ +``` #### `Arr::pull()` {.collection-method} The `Arr::pull` method returns and removes a key / value pair from an array: - use Illuminate\Support\Arr; +```php +use Illuminate\Support\Arr; - $array = ['name' => 'Desk', 'price' => 100]; +$array = ['name' => 'Desk', 'price' => 100]; - $name = Arr::pull($array, 'name'); +$name = Arr::pull($array, 'name'); - // $name: Desk +// $name: Desk - // $array: ['price' => 100] +// $array: ['price' => 100] +``` A default value may be passed as the third argument to the method. This value will be returned if the key doesn't exist: - use Illuminate\Support\Arr; +```php +use Illuminate\Support\Arr; - $value = Arr::pull($array, $key, $default); +$value = Arr::pull($array, $key, $default); +``` #### `Arr::query()` {.collection-method} The `Arr::query` method converts the array into a query string: - use Illuminate\Support\Arr; +```php +use Illuminate\Support\Arr; - $array = [ - 'name' => 'Taylor', - 'order' => [ - 'column' => 'created_at', - 'direction' => 'desc' - ] - ]; +$array = [ + 'name' => 'Taylor', + 'order' => [ + 'column' => 'created_at', + 'direction' => 'desc' + ] +]; - Arr::query($array); +Arr::query($array); - // name=Taylor&order[column]=created_at&order[direction]=desc +// name=Taylor&order[column]=created_at&order[direction]=desc +``` #### `Arr::random()` {.collection-method} The `Arr::random` method returns a random value from an array: - use Illuminate\Support\Arr; +```php +use Illuminate\Support\Arr; - $array = [1, 2, 3, 4, 5]; +$array = [1, 2, 3, 4, 5]; - $random = Arr::random($array); +$random = Arr::random($array); - // 4 - (retrieved randomly) +// 4 - (retrieved randomly) +``` You may also specify the number of items to return as an optional second argument. Note that providing this argument will return an array even if only one item is desired: - use Illuminate\Support\Arr; +```php +use Illuminate\Support\Arr; - $items = Arr::random($array, 2); +$items = Arr::random($array, 2); - // [2, 5] - (retrieved randomly) +// [2, 5] - (retrieved randomly) +``` #### `Arr::set()` {.collection-method} The `Arr::set` method sets a value within a deeply nested array using "dot" notation: - use Illuminate\Support\Arr; +```php +use Illuminate\Support\Arr; - $array = ['products' => ['desk' => ['price' => 100]]]; +$array = ['products' => ['desk' => ['price' => 100]]]; - Arr::set($array, 'products.desk.price', 200); +Arr::set($array, 'products.desk.price', 200); - // ['products' => ['desk' => ['price' => 200]]] +// ['products' => ['desk' => ['price' => 200]]] +``` #### `Arr::shuffle()` {.collection-method} The `Arr::shuffle` method randomly shuffles the items in the array: - use Illuminate\Support\Arr; +```php +use Illuminate\Support\Arr; - $array = Arr::shuffle([1, 2, 3, 4, 5]); +$array = Arr::shuffle([1, 2, 3, 4, 5]); - // [3, 2, 5, 1, 4] - (generated randomly) +// [3, 2, 5, 1, 4] - (generated randomly) +``` #### `Arr::sort()` {.collection-method} The `Arr::sort` method sorts an array by its values: - use Illuminate\Support\Arr; +```php +use Illuminate\Support\Arr; - $array = ['Desk', 'Table', 'Chair']; +$array = ['Desk', 'Table', 'Chair']; - $sorted = Arr::sort($array); +$sorted = Arr::sort($array); - // ['Chair', 'Desk', 'Table'] +// ['Chair', 'Desk', 'Table'] +``` You may also sort the array by the results of a given closure: - use Illuminate\Support\Arr; +```php +use Illuminate\Support\Arr; - $array = [ - ['name' => 'Desk'], - ['name' => 'Table'], - ['name' => 'Chair'], - ]; +$array = [ + ['name' => 'Desk'], + ['name' => 'Table'], + ['name' => 'Chair'], +]; - $sorted = array_values(Arr::sort($array, function (array $value) { - return $value['name']; - })); +$sorted = array_values(Arr::sort($array, function (array $value) { + return $value['name']; +})); - /* - [ - ['name' => 'Chair'], - ['name' => 'Desk'], - ['name' => 'Table'], - ] - */ +/* + [ + ['name' => 'Chair'], + ['name' => 'Desk'], + ['name' => 'Table'], + ] +*/ +``` #### `Arr::sortDesc()` {.collection-method} The `Arr::sortDesc` method sorts an array in descending order by its values: - use Illuminate\Support\Arr; +```php +use Illuminate\Support\Arr; - $array = ['Desk', 'Table', 'Chair']; +$array = ['Desk', 'Table', 'Chair']; - $sorted = Arr::sortDesc($array); +$sorted = Arr::sortDesc($array); - // ['Table', 'Desk', 'Chair'] +// ['Table', 'Desk', 'Chair'] +``` You may also sort the array by the results of a given closure: - use Illuminate\Support\Arr; +```php +use Illuminate\Support\Arr; - $array = [ - ['name' => 'Desk'], - ['name' => 'Table'], - ['name' => 'Chair'], - ]; +$array = [ + ['name' => 'Desk'], + ['name' => 'Table'], + ['name' => 'Chair'], +]; - $sorted = array_values(Arr::sortDesc($array, function (array $value) { - return $value['name']; - })); +$sorted = array_values(Arr::sortDesc($array, function (array $value) { + return $value['name']; +})); - /* - [ - ['name' => 'Table'], - ['name' => 'Desk'], - ['name' => 'Chair'], - ] - */ +/* + [ + ['name' => 'Table'], + ['name' => 'Desk'], + ['name' => 'Chair'], + ] +*/ +``` #### `Arr::sortRecursive()` {.collection-method} The `Arr::sortRecursive` method recursively sorts an array using the `sort` function for numerically indexed sub-arrays and the `ksort` function for associative sub-arrays: - use Illuminate\Support\Arr; +```php +use Illuminate\Support\Arr; - $array = [ - ['Roman', 'Taylor', 'Li'], - ['PHP', 'Ruby', 'JavaScript'], - ['one' => 1, 'two' => 2, 'three' => 3], - ]; +$array = [ + ['Roman', 'Taylor', 'Li'], + ['PHP', 'Ruby', 'JavaScript'], + ['one' => 1, 'two' => 2, 'three' => 3], +]; - $sorted = Arr::sortRecursive($array); +$sorted = Arr::sortRecursive($array); - /* - [ - ['JavaScript', 'PHP', 'Ruby'], - ['one' => 1, 'three' => 3, 'two' => 2], - ['Li', 'Roman', 'Taylor'], - ] - */ +/* + [ + ['JavaScript', 'PHP', 'Ruby'], + ['one' => 1, 'three' => 3, 'two' => 2], + ['Li', 'Roman', 'Taylor'], + ] +*/ +``` If you would like the results sorted in descending order, you may use the `Arr::sortRecursiveDesc` method. - $sorted = Arr::sortRecursiveDesc($array); +```php +$sorted = Arr::sortRecursiveDesc($array); +``` #### `Arr::take()` {.collection-method} The `Arr::take` method returns a new array with the specified number of items: - use Illuminate\Support\Arr; +```php +use Illuminate\Support\Arr; - $array = [0, 1, 2, 3, 4, 5]; +$array = [0, 1, 2, 3, 4, 5]; - $chunk = Arr::take($array, 3); +$chunk = Arr::take($array, 3); - // [0, 1, 2] +// [0, 1, 2] +``` You may also pass a negative integer to take the specified number of items from the end of the array: - $array = [0, 1, 2, 3, 4, 5]; +```php +$array = [0, 1, 2, 3, 4, 5]; - $chunk = Arr::take($array, -2); +$chunk = Arr::take($array, -2); - // [4, 5] +// [4, 5] +``` #### `Arr::toCssClasses()` {.collection-method} The `Arr::toCssClasses` method conditionally compiles a CSS class string. The method accepts an array of classes where the array key contains the class or classes you wish to add, while the value is a boolean expression. If the array element has a numeric key, it will always be included in the rendered class list: - use Illuminate\Support\Arr; +```php +use Illuminate\Support\Arr; - $isActive = false; - $hasError = true; +$isActive = false; +$hasError = true; - $array = ['p-4', 'font-bold' => $isActive, 'bg-red' => $hasError]; +$array = ['p-4', 'font-bold' => $isActive, 'bg-red' => $hasError]; - $classes = Arr::toCssClasses($array); +$classes = Arr::toCssClasses($array); - /* - 'p-4 bg-red' - */ +/* + 'p-4 bg-red' +*/ +``` #### `Arr::toCssStyles()` {.collection-method} @@ -942,233 +1036,269 @@ This method powers Laravel's functionality allowing [merging classes with a Blad The `Arr::undot` method expands a single-dimensional array that uses "dot" notation into a multi-dimensional array: - use Illuminate\Support\Arr; +```php +use Illuminate\Support\Arr; - $array = [ - 'user.name' => 'Kevin Malone', - 'user.occupation' => 'Accountant', - ]; +$array = [ + 'user.name' => 'Kevin Malone', + 'user.occupation' => 'Accountant', +]; - $array = Arr::undot($array); +$array = Arr::undot($array); - // ['user' => ['name' => 'Kevin Malone', 'occupation' => 'Accountant']] +// ['user' => ['name' => 'Kevin Malone', 'occupation' => 'Accountant']] +``` #### `Arr::where()` {.collection-method} The `Arr::where` method filters an array using the given closure: - use Illuminate\Support\Arr; +```php +use Illuminate\Support\Arr; - $array = [100, '200', 300, '400', 500]; +$array = [100, '200', 300, '400', 500]; - $filtered = Arr::where($array, function (string|int $value, int $key) { - return is_string($value); - }); +$filtered = Arr::where($array, function (string|int $value, int $key) { + return is_string($value); +}); - // [1 => '200', 3 => '400'] +// [1 => '200', 3 => '400'] +``` #### `Arr::whereNotNull()` {.collection-method} The `Arr::whereNotNull` method removes all `null` values from the given array: - use Illuminate\Support\Arr; +```php +use Illuminate\Support\Arr; - $array = [0, null]; +$array = [0, null]; - $filtered = Arr::whereNotNull($array); +$filtered = Arr::whereNotNull($array); - // [0 => 0] +// [0 => 0] +``` #### `Arr::wrap()` {.collection-method} The `Arr::wrap` method wraps the given value in an array. If the given value is already an array it will be returned without modification: - use Illuminate\Support\Arr; +```php +use Illuminate\Support\Arr; - $string = 'Laravel'; +$string = 'Laravel'; - $array = Arr::wrap($string); +$array = Arr::wrap($string); - // ['Laravel'] +// ['Laravel'] +``` If the given value is `null`, an empty array will be returned: - use Illuminate\Support\Arr; +```php +use Illuminate\Support\Arr; - $array = Arr::wrap(null); +$array = Arr::wrap(null); - // [] +// [] +``` #### `data_fill()` {.collection-method} The `data_fill` function sets a missing value within a nested array or object using "dot" notation: - $data = ['products' => ['desk' => ['price' => 100]]]; +```php +$data = ['products' => ['desk' => ['price' => 100]]]; - data_fill($data, 'products.desk.price', 200); +data_fill($data, 'products.desk.price', 200); - // ['products' => ['desk' => ['price' => 100]]] +// ['products' => ['desk' => ['price' => 100]]] - data_fill($data, 'products.desk.discount', 10); +data_fill($data, 'products.desk.discount', 10); - // ['products' => ['desk' => ['price' => 100, 'discount' => 10]]] +// ['products' => ['desk' => ['price' => 100, 'discount' => 10]]] +``` This function also accepts asterisks as wildcards and will fill the target accordingly: - $data = [ +```php +$data = [ + 'products' => [ + ['name' => 'Desk 1', 'price' => 100], + ['name' => 'Desk 2'], + ], +]; + +data_fill($data, 'products.*.price', 200); + +/* + [ 'products' => [ ['name' => 'Desk 1', 'price' => 100], - ['name' => 'Desk 2'], + ['name' => 'Desk 2', 'price' => 200], ], - ]; - - data_fill($data, 'products.*.price', 200); - - /* - [ - 'products' => [ - ['name' => 'Desk 1', 'price' => 100], - ['name' => 'Desk 2', 'price' => 200], - ], - ] - */ + ] +*/ +``` #### `data_get()` {.collection-method} The `data_get` function retrieves a value from a nested array or object using "dot" notation: - $data = ['products' => ['desk' => ['price' => 100]]]; +```php +$data = ['products' => ['desk' => ['price' => 100]]]; - $price = data_get($data, 'products.desk.price'); +$price = data_get($data, 'products.desk.price'); - // 100 +// 100 +``` The `data_get` function also accepts a default value, which will be returned if the specified key is not found: - $discount = data_get($data, 'products.desk.discount', 0); +```php +$discount = data_get($data, 'products.desk.discount', 0); - // 0 +// 0 +``` The function also accepts wildcards using asterisks, which may target any key of the array or object: - $data = [ - 'product-one' => ['name' => 'Desk 1', 'price' => 100], - 'product-two' => ['name' => 'Desk 2', 'price' => 150], - ]; +```php +$data = [ + 'product-one' => ['name' => 'Desk 1', 'price' => 100], + 'product-two' => ['name' => 'Desk 2', 'price' => 150], +]; - data_get($data, '*.name'); +data_get($data, '*.name'); - // ['Desk 1', 'Desk 2']; +// ['Desk 1', 'Desk 2']; +``` The `{first}` and `{last}` placeholders may be used to retrieve the first or last items in an array: - $flight = [ - 'segments' => [ - ['from' => 'LHR', 'departure' => '9:00', 'to' => 'IST', 'arrival' => '15:00'], - ['from' => 'IST', 'departure' => '16:00', 'to' => 'PKX', 'arrival' => '20:00'], - ], - ]; +```php +$flight = [ + 'segments' => [ + ['from' => 'LHR', 'departure' => '9:00', 'to' => 'IST', 'arrival' => '15:00'], + ['from' => 'IST', 'departure' => '16:00', 'to' => 'PKX', 'arrival' => '20:00'], + ], +]; - data_get($flight, 'segments.{first}.arrival'); +data_get($flight, 'segments.{first}.arrival'); - // 15:00 +// 15:00 +``` #### `data_set()` {.collection-method} The `data_set` function sets a value within a nested array or object using "dot" notation: - $data = ['products' => ['desk' => ['price' => 100]]]; +```php +$data = ['products' => ['desk' => ['price' => 100]]]; - data_set($data, 'products.desk.price', 200); +data_set($data, 'products.desk.price', 200); - // ['products' => ['desk' => ['price' => 200]]] +// ['products' => ['desk' => ['price' => 200]]] +``` This function also accepts wildcards using asterisks and will set values on the target accordingly: - $data = [ - 'products' => [ - ['name' => 'Desk 1', 'price' => 100], - ['name' => 'Desk 2', 'price' => 150], - ], - ]; +```php +$data = [ + 'products' => [ + ['name' => 'Desk 1', 'price' => 100], + ['name' => 'Desk 2', 'price' => 150], + ], +]; - data_set($data, 'products.*.price', 200); +data_set($data, 'products.*.price', 200); - /* - [ - 'products' => [ - ['name' => 'Desk 1', 'price' => 200], - ['name' => 'Desk 2', 'price' => 200], - ], - ] - */ +/* + [ + 'products' => [ + ['name' => 'Desk 1', 'price' => 200], + ['name' => 'Desk 2', 'price' => 200], + ], + ] +*/ +``` By default, any existing values are overwritten. If you wish to only set a value if it doesn't exist, you may pass `false` as the fourth argument to the function: - $data = ['products' => ['desk' => ['price' => 100]]]; +```php +$data = ['products' => ['desk' => ['price' => 100]]]; - data_set($data, 'products.desk.price', 200, overwrite: false); +data_set($data, 'products.desk.price', 200, overwrite: false); - // ['products' => ['desk' => ['price' => 100]]] +// ['products' => ['desk' => ['price' => 100]]] +``` #### `data_forget()` {.collection-method} The `data_forget` function removes a value within a nested array or object using "dot" notation: - $data = ['products' => ['desk' => ['price' => 100]]]; +```php +$data = ['products' => ['desk' => ['price' => 100]]]; - data_forget($data, 'products.desk.price'); +data_forget($data, 'products.desk.price'); - // ['products' => ['desk' => []]] +// ['products' => ['desk' => []]] +``` This function also accepts wildcards using asterisks and will remove values on the target accordingly: - $data = [ - 'products' => [ - ['name' => 'Desk 1', 'price' => 100], - ['name' => 'Desk 2', 'price' => 150], - ], - ]; +```php +$data = [ + 'products' => [ + ['name' => 'Desk 1', 'price' => 100], + ['name' => 'Desk 2', 'price' => 150], + ], +]; - data_forget($data, 'products.*.price'); +data_forget($data, 'products.*.price'); - /* - [ - 'products' => [ - ['name' => 'Desk 1'], - ['name' => 'Desk 2'], - ], - ] - */ +/* + [ + 'products' => [ + ['name' => 'Desk 1'], + ['name' => 'Desk 2'], + ], + ] +*/ +``` #### `head()` {.collection-method} The `head` function returns the first element in the given array: - $array = [100, 200, 300]; +```php +$array = [100, 200, 300]; - $first = head($array); +$first = head($array); - // 100 +// 100 +``` #### `last()` {.collection-method} The `last` function returns the last element in the given array: - $array = [100, 200, 300]; +```php +$array = [100, 200, 300]; - $last = last($array); +$last = last($array); - // 300 +// 300 +``` ## Numbers @@ -1178,163 +1308,181 @@ The `last` function returns the last element in the given array: The `Number::abbreviate` method returns the human-readable format of the provided numerical value, with an abbreviation for the units: - use Illuminate\Support\Number; +```php +use Illuminate\Support\Number; - $number = Number::abbreviate(1000); +$number = Number::abbreviate(1000); - // 1K +// 1K - $number = Number::abbreviate(489939); +$number = Number::abbreviate(489939); - // 490K +// 490K - $number = Number::abbreviate(1230000, precision: 2); +$number = Number::abbreviate(1230000, precision: 2); - // 1.23M +// 1.23M +``` #### `Number::clamp()` {.collection-method} The `Number::clamp` method ensures a given number stays within a specified range. If the number is lower than the minimum, the minimum value is returned. If the number is higher than the maximum, the maximum value is returned: - use Illuminate\Support\Number; +```php +use Illuminate\Support\Number; - $number = Number::clamp(105, min: 10, max: 100); +$number = Number::clamp(105, min: 10, max: 100); - // 100 +// 100 - $number = Number::clamp(5, min: 10, max: 100); +$number = Number::clamp(5, min: 10, max: 100); - // 10 +// 10 - $number = Number::clamp(10, min: 10, max: 100); +$number = Number::clamp(10, min: 10, max: 100); - // 10 +// 10 - $number = Number::clamp(20, min: 10, max: 100); +$number = Number::clamp(20, min: 10, max: 100); - // 20 +// 20 +``` #### `Number::currency()` {.collection-method} The `Number::currency` method returns the currency representation of the given value as a string: - use Illuminate\Support\Number; +```php +use Illuminate\Support\Number; - $currency = Number::currency(1000); +$currency = Number::currency(1000); - // $1,000.00 +// $1,000.00 - $currency = Number::currency(1000, in: 'EUR'); +$currency = Number::currency(1000, in: 'EUR'); - // €1,000.00 +// €1,000.00 - $currency = Number::currency(1000, in: 'EUR', locale: 'de'); +$currency = Number::currency(1000, in: 'EUR', locale: 'de'); - // 1.000,00 € +// 1.000,00 € +``` #### `Number::defaultCurrency()` {.collection-method} The `Number::defaultCurrency` method returns the default currency being used by the `Number` class: - use Illuminate\Support\Number; +```php +use Illuminate\Support\Number; - $currency = Number::defaultCurrency(); +$currency = Number::defaultCurrency(); - // USD +// USD +``` #### `Number::defaultLocale()` {.collection-method} The `Number::defaultLocale` method returns the default locale being used by the `Number` class: - use Illuminate\Support\Number; +```php +use Illuminate\Support\Number; - $locale = Number::defaultLocale(); +$locale = Number::defaultLocale(); - // en +// en +``` #### `Number::fileSize()` {.collection-method} The `Number::fileSize` method returns the file size representation of the given byte value as a string: - use Illuminate\Support\Number; +```php +use Illuminate\Support\Number; - $size = Number::fileSize(1024); +$size = Number::fileSize(1024); - // 1 KB +// 1 KB - $size = Number::fileSize(1024 * 1024); +$size = Number::fileSize(1024 * 1024); - // 1 MB +// 1 MB - $size = Number::fileSize(1024, precision: 2); +$size = Number::fileSize(1024, precision: 2); - // 1.00 KB +// 1.00 KB +``` #### `Number::forHumans()` {.collection-method} The `Number::forHumans` method returns the human-readable format of the provided numerical value: - use Illuminate\Support\Number; +```php +use Illuminate\Support\Number; - $number = Number::forHumans(1000); +$number = Number::forHumans(1000); - // 1 thousand +// 1 thousand - $number = Number::forHumans(489939); +$number = Number::forHumans(489939); - // 490 thousand +// 490 thousand - $number = Number::forHumans(1230000, precision: 2); +$number = Number::forHumans(1230000, precision: 2); - // 1.23 million +// 1.23 million +``` #### `Number::format()` {.collection-method} The `Number::format` method formats the given number into a locale specific string: - use Illuminate\Support\Number; +```php +use Illuminate\Support\Number; - $number = Number::format(100000); +$number = Number::format(100000); - // 100,000 +// 100,000 - $number = Number::format(100000, precision: 2); +$number = Number::format(100000, precision: 2); - // 100,000.00 +// 100,000.00 - $number = Number::format(100000.123, maxPrecision: 2); +$number = Number::format(100000.123, maxPrecision: 2); - // 100,000.12 +// 100,000.12 - $number = Number::format(100000, locale: 'de'); +$number = Number::format(100000, locale: 'de'); - // 100.000 +// 100.000 +``` #### `Number::ordinal()` {.collection-method} The `Number::ordinal` method returns a number's ordinal representation: - use Illuminate\Support\Number; +```php +use Illuminate\Support\Number; - $number = Number::ordinal(1); +$number = Number::ordinal(1); - // 1st +// 1st - $number = Number::ordinal(2); +$number = Number::ordinal(2); - // 2nd +// 2nd - $number = Number::ordinal(21); +$number = Number::ordinal(21); - // 21st +// 21st +``` #### `Number::pairs()` {.collection-method} @@ -1358,125 +1506,143 @@ $result = Number::pairs(25, 10, offset: 0); The `Number::percentage` method returns the percentage representation of the given value as a string: - use Illuminate\Support\Number; +```php +use Illuminate\Support\Number; - $percentage = Number::percentage(10); +$percentage = Number::percentage(10); - // 10% +// 10% - $percentage = Number::percentage(10, precision: 2); +$percentage = Number::percentage(10, precision: 2); - // 10.00% +// 10.00% - $percentage = Number::percentage(10.123, maxPrecision: 2); +$percentage = Number::percentage(10.123, maxPrecision: 2); - // 10.12% +// 10.12% - $percentage = Number::percentage(10, precision: 2, locale: 'de'); +$percentage = Number::percentage(10, precision: 2, locale: 'de'); - // 10,00% +// 10,00% +``` #### `Number::spell()` {.collection-method} The `Number::spell` method transforms the given number into a string of words: - use Illuminate\Support\Number; +```php +use Illuminate\Support\Number; - $number = Number::spell(102); +$number = Number::spell(102); - // one hundred and two +// one hundred and two - $number = Number::spell(88, locale: 'fr'); +$number = Number::spell(88, locale: 'fr'); - // quatre-vingt-huit +// quatre-vingt-huit +``` The `after` argument allows you to specify a value after which all numbers should be spelled out: - $number = Number::spell(10, after: 10); +```php +$number = Number::spell(10, after: 10); - // 10 +// 10 - $number = Number::spell(11, after: 10); +$number = Number::spell(11, after: 10); - // eleven +// eleven +``` The `until` argument allows you to specify a value before which all numbers should be spelled out: - $number = Number::spell(5, until: 10); +```php +$number = Number::spell(5, until: 10); - // five +// five - $number = Number::spell(10, until: 10); +$number = Number::spell(10, until: 10); - // 10 +// 10 +``` #### `Number::trim()` {.collection-method} The `Number::trim` method removes any trailing zero digits after the decimal point of the given number: - use Illuminate\Support\Number; +```php +use Illuminate\Support\Number; - $number = Number::trim(12.0); +$number = Number::trim(12.0); - // 12 +// 12 - $number = Number::trim(12.30); +$number = Number::trim(12.30); - // 12.3 +// 12.3 +``` #### `Number::useLocale()` {.collection-method} The `Number::useLocale` method sets the default number locale globally, which affects how numbers and currency are formatted by subsequent invocations to the `Number` class's methods: - use Illuminate\Support\Number; +```php +use Illuminate\Support\Number; - /** - * Bootstrap any application services. - */ - public function boot(): void - { - Number::useLocale('de'); - } +/** + * Bootstrap any application services. + */ +public function boot(): void +{ + Number::useLocale('de'); +} +``` #### `Number::withLocale()` {.collection-method} The `Number::withLocale` method executes the given closure using the specified locale and then restores the original locale after the callback has executed: - use Illuminate\Support\Number; +```php +use Illuminate\Support\Number; - $number = Number::withLocale('de', function () { - return Number::format(1500); - }); +$number = Number::withLocale('de', function () { + return Number::format(1500); +}); +``` #### `Number::useCurrency()` {.collection-method} The `Number::useCurrency` method sets the default number currency globally, which affects how the currency is formatted by subsequent invocations to the `Number` class's methods: - use Illuminate\Support\Number; +```php +use Illuminate\Support\Number; - /** - * Bootstrap any application services. - */ - public function boot(): void - { - Number::useCurrency('GBP'); - } +/** + * Bootstrap any application services. + */ +public function boot(): void +{ + Number::useCurrency('GBP'); +} +``` #### `Number::withCurrency()` {.collection-method} The `Number::withCurrency` method executes the given closure using the specified currency and then restores the original currency after the callback has executed: - use Illuminate\Support\Number; +```php +use Illuminate\Support\Number; - $number = Number::withCurrency('GBP', function () { - // ... - }); +$number = Number::withCurrency('GBP', function () { + // ... +}); +``` ## Paths @@ -1486,45 +1652,55 @@ The `Number::withCurrency` method executes the given closure using the specified The `app_path` function returns the fully qualified path to your application's `app` directory. You may also use the `app_path` function to generate a fully qualified path to a file relative to the application directory: - $path = app_path(); +```php +$path = app_path(); - $path = app_path('Http/Controllers/Controller.php'); +$path = app_path('Http/Controllers/Controller.php'); +``` #### `base_path()` {.collection-method} The `base_path` function returns the fully qualified path to your application's root directory. You may also use the `base_path` function to generate a fully qualified path to a given file relative to the project root directory: - $path = base_path(); +```php +$path = base_path(); - $path = base_path('vendor/bin'); +$path = base_path('vendor/bin'); +``` #### `config_path()` {.collection-method} The `config_path` function returns the fully qualified path to your application's `config` directory. You may also use the `config_path` function to generate a fully qualified path to a given file within the application's configuration directory: - $path = config_path(); +```php +$path = config_path(); - $path = config_path('app.php'); +$path = config_path('app.php'); +``` #### `database_path()` {.collection-method} The `database_path` function returns the fully qualified path to your application's `database` directory. You may also use the `database_path` function to generate a fully qualified path to a given file within the database directory: - $path = database_path(); +```php +$path = database_path(); - $path = database_path('factories/UserFactory.php'); +$path = database_path('factories/UserFactory.php'); +``` #### `lang_path()` {.collection-method} The `lang_path` function returns the fully qualified path to your application's `lang` directory. You may also use the `lang_path` function to generate a fully qualified path to a given file within the directory: - $path = lang_path(); +```php +$path = lang_path(); - $path = lang_path('en/messages.php'); +$path = lang_path('en/messages.php'); +``` > [!NOTE] > By default, the Laravel application skeleton does not include the `lang` directory. If you would like to customize Laravel's language files, you may publish them via the `lang:publish` Artisan command. @@ -1534,34 +1710,42 @@ The `lang_path` function returns the fully qualified path to your application's The `mix` function returns the path to a [versioned Mix file](/docs/{{version}}/mix): - $path = mix('css/app.css'); +```php +$path = mix('css/app.css'); +``` #### `public_path()` {.collection-method} The `public_path` function returns the fully qualified path to your application's `public` directory. You may also use the `public_path` function to generate a fully qualified path to a given file within the public directory: - $path = public_path(); +```php +$path = public_path(); - $path = public_path('css/app.css'); +$path = public_path('css/app.css'); +``` #### `resource_path()` {.collection-method} The `resource_path` function returns the fully qualified path to your application's `resources` directory. You may also use the `resource_path` function to generate a fully qualified path to a given file within the resources directory: - $path = resource_path(); +```php +$path = resource_path(); - $path = resource_path('sass/app.scss'); +$path = resource_path('sass/app.scss'); +``` #### `storage_path()` {.collection-method} The `storage_path` function returns the fully qualified path to your application's `storage` directory. You may also use the `storage_path` function to generate a fully qualified path to a given file within the storage directory: - $path = storage_path(); +```php +$path = storage_path(); - $path = storage_path('app/file.txt'); +$path = storage_path('app/file.txt'); +``` ## URLs @@ -1571,85 +1755,111 @@ The `storage_path` function returns the fully qualified path to your application The `action` function generates a URL for the given controller action: - use App\Http\Controllers\HomeController; +```php +use App\Http\Controllers\HomeController; - $url = action([HomeController::class, 'index']); +$url = action([HomeController::class, 'index']); +``` If the method accepts route parameters, you may pass them as the second argument to the method: - $url = action([UserController::class, 'profile'], ['id' => 1]); +```php +$url = action([UserController::class, 'profile'], ['id' => 1]); +``` #### `asset()` {.collection-method} The `asset` function generates a URL for an asset using the current scheme of the request (HTTP or HTTPS): - $url = asset('img/photo.jpg'); +```php +$url = asset('img/photo.jpg'); +``` You can configure the asset URL host by setting the `ASSET_URL` variable in your `.env` file. This can be useful if you host your assets on an external service like Amazon S3 or another CDN: - // ASSET_URL=http://example.com/assets +```php +// ASSET_URL=http://example.com/assets - $url = asset('img/photo.jpg'); // http://example.com/assets/img/photo.jpg +$url = asset('img/photo.jpg'); // http://example.com/assets/img/photo.jpg +``` #### `route()` {.collection-method} The `route` function generates a URL for a given [named route](/docs/{{version}}/routing#named-routes): - $url = route('route.name'); +```php +$url = route('route.name'); +``` If the route accepts parameters, you may pass them as the second argument to the function: - $url = route('route.name', ['id' => 1]); +```php +$url = route('route.name', ['id' => 1]); +``` By default, the `route` function generates an absolute URL. If you wish to generate a relative URL, you may pass `false` as the third argument to the function: - $url = route('route.name', ['id' => 1], false); +```php +$url = route('route.name', ['id' => 1], false); +``` #### `secure_asset()` {.collection-method} The `secure_asset` function generates a URL for an asset using HTTPS: - $url = secure_asset('img/photo.jpg'); +```php +$url = secure_asset('img/photo.jpg'); +``` #### `secure_url()` {.collection-method} The `secure_url` function generates a fully qualified HTTPS URL to the given path. Additional URL segments may be passed in the function's second argument: - $url = secure_url('user/profile'); +```php +$url = secure_url('user/profile'); - $url = secure_url('user/profile', [1]); +$url = secure_url('user/profile', [1]); +``` #### `to_route()` {.collection-method} The `to_route` function generates a [redirect HTTP response](/docs/{{version}}/responses#redirects) for a given [named route](/docs/{{version}}/routing#named-routes): - return to_route('users.show', ['user' => 1]); +```php +return to_route('users.show', ['user' => 1]); +``` If necessary, you may pass the HTTP status code that should be assigned to the redirect and any additional response headers as the third and fourth arguments to the `to_route` method: - return to_route('users.show', ['user' => 1], 302, ['X-Framework' => 'Laravel']); +```php +return to_route('users.show', ['user' => 1], 302, ['X-Framework' => 'Laravel']); +``` #### `url()` {.collection-method} The `url` function generates a fully qualified URL to the given path: - $url = url('user/profile'); +```php +$url = url('user/profile'); - $url = url('user/profile', [1]); +$url = url('user/profile', [1]); +``` If no path is provided, an `Illuminate\Routing\UrlGenerator` instance is returned: - $current = url()->current(); +```php +$current = url()->current(); - $full = url()->full(); +$full = url()->full(); - $previous = url()->previous(); +$previous = url()->previous(); +``` ## Miscellaneous @@ -1659,18 +1869,24 @@ If no path is provided, an `Illuminate\Routing\UrlGenerator` instance is returne The `abort` function throws [an HTTP exception](/docs/{{version}}/errors#http-exceptions) which will be rendered by the [exception handler](/docs/{{version}}/errors#handling-exceptions): - abort(403); +```php +abort(403); +``` You may also provide the exception's message and custom HTTP response headers that should be sent to the browser: - abort(403, 'Unauthorized.', $headers); +```php +abort(403, 'Unauthorized.', $headers); +``` #### `abort_if()` {.collection-method} The `abort_if` function throws an HTTP exception if a given boolean expression evaluates to `true`: - abort_if(! Auth::user()->isAdmin(), 403); +```php +abort_if(! Auth::user()->isAdmin(), 403); +``` Like the `abort` method, you may also provide the exception's response text as the third argument and an array of custom response headers as the fourth argument to the function. @@ -1679,7 +1895,9 @@ Like the `abort` method, you may also provide the exception's response text as t The `abort_unless` function throws an HTTP exception if a given boolean expression evaluates to `false`: - abort_unless(Auth::user()->isAdmin(), 403); +```php +abort_unless(Auth::user()->isAdmin(), 403); +``` Like the `abort` method, you may also provide the exception's response text as the third argument and an array of custom response headers as the fourth argument to the function. @@ -1688,56 +1906,70 @@ Like the `abort` method, you may also provide the exception's response text as t The `app` function returns the [service container](/docs/{{version}}/container) instance: - $container = app(); +```php +$container = app(); +``` You may pass a class or interface name to resolve it from the container: - $api = app('HelpSpot\API'); +```php +$api = app('HelpSpot\API'); +``` #### `auth()` {.collection-method} The `auth` function returns an [authenticator](/docs/{{version}}/authentication) instance. You may use it as an alternative to the `Auth` facade: - $user = auth()->user(); +```php +$user = auth()->user(); +``` If needed, you may specify which guard instance you would like to access: - $user = auth('admin')->user(); +```php +$user = auth('admin')->user(); +``` #### `back()` {.collection-method} The `back` function generates a [redirect HTTP response](/docs/{{version}}/responses#redirects) to the user's previous location: - return back($status = 302, $headers = [], $fallback = '/'); +```php +return back($status = 302, $headers = [], $fallback = '/'); - return back(); +return back(); +``` #### `bcrypt()` {.collection-method} The `bcrypt` function [hashes](/docs/{{version}}/hashing) the given value using Bcrypt. You may use this function as an alternative to the `Hash` facade: - $password = bcrypt('my-secret-password'); +```php +$password = bcrypt('my-secret-password'); +``` #### `blank()` {.collection-method} The `blank` function determines whether the given value is "blank": - blank(''); - blank(' '); - blank(null); - blank(collect()); +```php +blank(''); +blank(' '); +blank(null); +blank(collect()); - // true +// true - blank(0); - blank(true); - blank(false); +blank(0); +blank(true); +blank(false); - // false +// false +``` For the inverse of `blank`, see the [`filled`](#method-filled) method. @@ -1746,103 +1978,131 @@ For the inverse of `blank`, see the [`filled`](#method-filled) method. The `broadcast` function [broadcasts](/docs/{{version}}/broadcasting) the given [event](/docs/{{version}}/events) to its listeners: - broadcast(new UserRegistered($user)); +```php +broadcast(new UserRegistered($user)); - broadcast(new UserRegistered($user))->toOthers(); +broadcast(new UserRegistered($user))->toOthers(); +``` #### `cache()` {.collection-method} The `cache` function may be used to get values from the [cache](/docs/{{version}}/cache). If the given key does not exist in the cache, an optional default value will be returned: - $value = cache('key'); +```php +$value = cache('key'); - $value = cache('key', 'default'); +$value = cache('key', 'default'); +``` You may add items to the cache by passing an array of key / value pairs to the function. You should also pass the number of seconds or duration the cached value should be considered valid: - cache(['key' => 'value'], 300); +```php +cache(['key' => 'value'], 300); - cache(['key' => 'value'], now()->addSeconds(10)); +cache(['key' => 'value'], now()->addSeconds(10)); +``` #### `class_uses_recursive()` {.collection-method} The `class_uses_recursive` function returns all traits used by a class, including traits used by all of its parent classes: - $traits = class_uses_recursive(App\Models\User::class); +```php +$traits = class_uses_recursive(App\Models\User::class); +``` #### `collect()` {.collection-method} The `collect` function creates a [collection](/docs/{{version}}/collections) instance from the given value: - $collection = collect(['taylor', 'abigail']); +```php +$collection = collect(['taylor', 'abigail']); +``` #### `config()` {.collection-method} The `config` function gets the value of a [configuration](/docs/{{version}}/configuration) variable. The configuration values may be accessed using "dot" syntax, which includes the name of the file and the option you wish to access. A default value may be specified and is returned if the configuration option does not exist: - $value = config('app.timezone'); +```php +$value = config('app.timezone'); - $value = config('app.timezone', $default); +$value = config('app.timezone', $default); +``` You may set configuration variables at runtime by passing an array of key / value pairs. However, note that this function only affects the configuration value for the current request and does not update your actual configuration values: - config(['app.debug' => true]); +```php +config(['app.debug' => true]); +``` #### `context()` {.collection-method} The `context` function gets the value from the [current context](/docs/{{version}}/context). A default value may be specified and is returned if the context key does not exist: - $value = context('trace_id'); +```php +$value = context('trace_id'); - $value = context('trace_id', $default); +$value = context('trace_id', $default); +``` You may set context values by passing an array of key / value pairs: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - context(['trace_id' => Str::uuid()->toString()]); +context(['trace_id' => Str::uuid()->toString()]); +``` #### `cookie()` {.collection-method} The `cookie` function creates a new [cookie](/docs/{{version}}/requests#cookies) instance: - $cookie = cookie('name', 'value', $minutes); +```php +$cookie = cookie('name', 'value', $minutes); +``` #### `csrf_field()` {.collection-method} The `csrf_field` function generates an HTML `hidden` input field containing the value of the CSRF token. For example, using [Blade syntax](/docs/{{version}}/blade): - {{ csrf_field() }} +```blade +{{ csrf_field() }} +``` #### `csrf_token()` {.collection-method} The `csrf_token` function retrieves the value of the current CSRF token: - $token = csrf_token(); +```php +$token = csrf_token(); +``` #### `decrypt()` {.collection-method} The `decrypt` function [decrypts](/docs/{{version}}/encryption) the given value. You may use this function as an alternative to the `Crypt` facade: - $password = decrypt($value); +```php +$password = decrypt($value); +``` #### `dd()` {.collection-method} The `dd` function dumps the given variables and ends the execution of the script: - dd($value); +```php +dd($value); - dd($value1, $value2, $value3, ...); +dd($value1, $value2, $value3, ...); +``` If you do not want to halt the execution of your script, use the [`dump`](#method-dump) function instead. @@ -1851,23 +2111,29 @@ If you do not want to halt the execution of your script, use the [`dump`](#metho The `dispatch` function pushes the given [job](/docs/{{version}}/queues#creating-jobs) onto the Laravel [job queue](/docs/{{version}}/queues): - dispatch(new App\Jobs\SendEmails); +```php +dispatch(new App\Jobs\SendEmails); +``` #### `dispatch_sync()` {.collection-method} The `dispatch_sync` function pushes the given job to the [sync](/docs/{{version}}/queues#synchronous-dispatching) queue so that it is processed immediately: - dispatch_sync(new App\Jobs\SendEmails); +```php +dispatch_sync(new App\Jobs\SendEmails); +``` #### `dump()` {.collection-method} The `dump` function dumps the given variables: - dump($value); +```php +dump($value); - dump($value1, $value2, $value3, ...); +dump($value1, $value2, $value3, ...); +``` If you want to stop executing the script after dumping the variables, use the [`dd`](#method-dd) function instead. @@ -1876,16 +2142,20 @@ If you want to stop executing the script after dumping the variables, use the [` The `encrypt` function [encrypts](/docs/{{version}}/encryption) the given value. You may use this function as an alternative to the `Crypt` facade: - $secret = encrypt('my-secret-value'); +```php +$secret = encrypt('my-secret-value'); +``` #### `env()` {.collection-method} The `env` function retrieves the value of an [environment variable](/docs/{{version}}/configuration#environment-configuration) or returns a default value: - $env = env('APP_ENV'); +```php +$env = env('APP_ENV'); - $env = env('APP_ENV', 'production'); +$env = env('APP_ENV', 'production'); +``` > [!WARNING] > If you execute the `config:cache` command during your deployment process, you should be sure that you are only calling the `env` function from within your configuration files. Once the configuration has been cached, the `.env` file will not be loaded and all calls to the `env` function will return `null`. @@ -1895,7 +2165,9 @@ The `env` function retrieves the value of an [environment variable](/docs/{{vers The `event` function dispatches the given [event](/docs/{{version}}/events) to its listeners: - event(new UserRegistered($user)); +```php +event(new UserRegistered($user)); +``` #### `fake()` {.collection-method} @@ -1916,25 +2188,29 @@ The `fake` function resolves a [Faker](https://github.com/FakerPHP/Faker) single By default, the `fake` function will utilize the `app.faker_locale` configuration option in your `config/app.php` configuration. Typically, this configuration option is set via the `APP_FAKER_LOCALE` environment variable. You may also specify the locale by passing it to the `fake` function. Each locale will resolve an individual singleton: - fake('nl_NL')->name() +```php +fake('nl_NL')->name() +``` #### `filled()` {.collection-method} The `filled` function determines whether the given value is not "blank": - filled(0); - filled(true); - filled(false); +```php +filled(0); +filled(true); +filled(false); - // true +// true - filled(''); - filled(' '); - filled(null); - filled(collect()); +filled(''); +filled(' '); +filled(null); +filled(collect()); - // false +// false +``` For the inverse of `filled`, see the [`blank`](#method-blank) method. @@ -1943,88 +2219,110 @@ For the inverse of `filled`, see the [`blank`](#method-blank) method. The `info` function will write information to your application's [log](/docs/{{version}}/logging): - info('Some helpful information!'); +```php +info('Some helpful information!'); +``` An array of contextual data may also be passed to the function: - info('User login attempt failed.', ['id' => $user->id]); +```php +info('User login attempt failed.', ['id' => $user->id]); +``` #### `literal()` {.collection-method} The `literal` function creates a new [stdClass](https://www.php.net/manual/en/class.stdclass.php) instance with the given named arguments as properties: - $obj = literal( - name: 'Joe', - languages: ['PHP', 'Ruby'], - ); +```php +$obj = literal( + name: 'Joe', + languages: ['PHP', 'Ruby'], +); - $obj->name; // 'Joe' - $obj->languages; // ['PHP', 'Ruby'] +$obj->name; // 'Joe' +$obj->languages; // ['PHP', 'Ruby'] +``` #### `logger()` {.collection-method} The `logger` function can be used to write a `debug` level message to the [log](/docs/{{version}}/logging): - logger('Debug message'); +```php +logger('Debug message'); +``` An array of contextual data may also be passed to the function: - logger('User has logged in.', ['id' => $user->id]); +```php +logger('User has logged in.', ['id' => $user->id]); +``` A [logger](/docs/{{version}}/logging) instance will be returned if no value is passed to the function: - logger()->error('You are not allowed here.'); +```php +logger()->error('You are not allowed here.'); +``` #### `method_field()` {.collection-method} The `method_field` function generates an HTML `hidden` input field containing the spoofed value of the form's HTTP verb. For example, using [Blade syntax](/docs/{{version}}/blade): -
- {{ method_field('DELETE') }} -
+```blade +
+ {{ method_field('DELETE') }} +
+``` #### `now()` {.collection-method} The `now` function creates a new `Illuminate\Support\Carbon` instance for the current time: - $now = now(); +```php +$now = now(); +``` #### `old()` {.collection-method} The `old` function [retrieves](/docs/{{version}}/requests#retrieving-input) an [old input](/docs/{{version}}/requests#old-input) value flashed into the session: - $value = old('value'); +```php +$value = old('value'); - $value = old('value', 'default'); +$value = old('value', 'default'); +``` Since the "default value" provided as the second argument to the `old` function is often an attribute of an Eloquent model, Laravel allows you to simply pass the entire Eloquent model as the second argument to the `old` function. When doing so, Laravel will assume the first argument provided to the `old` function is the name of the Eloquent attribute that should be considered the "default value": - {{ old('name', $user->name) }} +```blade +{{ old('name', $user->name) }} - // Is equivalent to... +// Is equivalent to... - {{ old('name', $user) }} +{{ old('name', $user) }} +``` #### `once()` {.collection-method} The `once` function executes the given callback and caches the result in memory for the duration of the request. Any subsequent calls to the `once` function with the same callback will return the previously cached result: - function random(): int - { - return once(function () { - return random_int(1, 1000); - }); - } +```php +function random(): int +{ + return once(function () { + return random_int(1, 1000); + }); +} - random(); // 123 - random(); // 123 (cached result) - random(); // 123 (cached result) +random(); // 123 +random(); // 123 (cached result) +random(); // 123 (cached result) +``` When the `once` function is executed from within an object instance, the cached result will be unique to that object instance: @@ -2054,318 +2352,390 @@ $secondService->all(); // (cached result) The `optional` function accepts any argument and allows you to access properties or call methods on that object. If the given object is `null`, properties and methods will return `null` instead of causing an error: - return optional($user->address)->street; +```php +return optional($user->address)->street; - {!! old('name', optional($user)->name) !!} +{!! old('name', optional($user)->name) !!} +``` The `optional` function also accepts a closure as its second argument. The closure will be invoked if the value provided as the first argument is not null: - return optional(User::find($id), function (User $user) { - return $user->name; - }); +```php +return optional(User::find($id), function (User $user) { + return $user->name; +}); +``` #### `policy()` {.collection-method} The `policy` method retrieves a [policy](/docs/{{version}}/authorization#creating-policies) instance for a given class: - $policy = policy(App\Models\User::class); +```php +$policy = policy(App\Models\User::class); +``` #### `redirect()` {.collection-method} The `redirect` function returns a [redirect HTTP response](/docs/{{version}}/responses#redirects), or returns the redirector instance if called with no arguments: - return redirect($to = null, $status = 302, $headers = [], $https = null); +```php +return redirect($to = null, $status = 302, $headers = [], $https = null); - return redirect('/home'); +return redirect('/home'); - return redirect()->route('route.name'); +return redirect()->route('route.name'); +``` #### `report()` {.collection-method} The `report` function will report an exception using your [exception handler](/docs/{{version}}/errors#handling-exceptions): - report($e); +```php +report($e); +``` The `report` function also accepts a string as an argument. When a string is given to the function, the function will create an exception with the given string as its message: - report('Something went wrong.'); +```php +report('Something went wrong.'); +``` #### `report_if()` {.collection-method} The `report_if` function will report an exception using your [exception handler](/docs/{{version}}/errors#handling-exceptions) if the given condition is `true`: - report_if($shouldReport, $e); +```php +report_if($shouldReport, $e); - report_if($shouldReport, 'Something went wrong.'); +report_if($shouldReport, 'Something went wrong.'); +``` #### `report_unless()` {.collection-method} The `report_unless` function will report an exception using your [exception handler](/docs/{{version}}/errors#handling-exceptions) if the given condition is `false`: - report_unless($reportingDisabled, $e); +```php +report_unless($reportingDisabled, $e); - report_unless($reportingDisabled, 'Something went wrong.'); +report_unless($reportingDisabled, 'Something went wrong.'); +``` #### `request()` {.collection-method} The `request` function returns the current [request](/docs/{{version}}/requests) instance or obtains an input field's value from the current request: - $request = request(); +```php +$request = request(); - $value = request('key', $default); +$value = request('key', $default); +``` #### `rescue()` {.collection-method} The `rescue` function executes the given closure and catches any exceptions that occur during its execution. All exceptions that are caught will be sent to your [exception handler](/docs/{{version}}/errors#handling-exceptions); however, the request will continue processing: - return rescue(function () { - return $this->method(); - }); +```php +return rescue(function () { + return $this->method(); +}); +``` You may also pass a second argument to the `rescue` function. This argument will be the "default" value that should be returned if an exception occurs while executing the closure: - return rescue(function () { - return $this->method(); - }, false); - - return rescue(function () { - return $this->method(); - }, function () { - return $this->failure(); - }); +```php +return rescue(function () { + return $this->method(); +}, false); + +return rescue(function () { + return $this->method(); +}, function () { + return $this->failure(); +}); +``` A `report` argument may be provided to the `rescue` function to determine if the exception should be reported via the `report` function: - return rescue(function () { - return $this->method(); - }, report: function (Throwable $throwable) { - return $throwable instanceof InvalidArgumentException; - }); +```php +return rescue(function () { + return $this->method(); +}, report: function (Throwable $throwable) { + return $throwable instanceof InvalidArgumentException; +}); +``` #### `resolve()` {.collection-method} The `resolve` function resolves a given class or interface name to an instance using the [service container](/docs/{{version}}/container): - $api = resolve('HelpSpot\API'); +```php +$api = resolve('HelpSpot\API'); +``` #### `response()` {.collection-method} The `response` function creates a [response](/docs/{{version}}/responses) instance or obtains an instance of the response factory: - return response('Hello World', 200, $headers); +```php +return response('Hello World', 200, $headers); - return response()->json(['foo' => 'bar'], 200, $headers); +return response()->json(['foo' => 'bar'], 200, $headers); +``` #### `retry()` {.collection-method} The `retry` function attempts to execute the given callback until the given maximum attempt threshold is met. If the callback does not throw an exception, its return value will be returned. If the callback throws an exception, it will automatically be retried. If the maximum attempt count is exceeded, the exception will be thrown: - return retry(5, function () { - // Attempt 5 times while resting 100ms between attempts... - }, 100); +```php +return retry(5, function () { + // Attempt 5 times while resting 100ms between attempts... +}, 100); +``` If you would like to manually calculate the number of milliseconds to sleep between attempts, you may pass a closure as the third argument to the `retry` function: - use Exception; +```php +use Exception; - return retry(5, function () { - // ... - }, function (int $attempt, Exception $exception) { - return $attempt * 100; - }); +return retry(5, function () { + // ... +}, function (int $attempt, Exception $exception) { + return $attempt * 100; +}); +``` For convenience, you may provide an array as the first argument to the `retry` function. This array will be used to determine how many milliseconds to sleep between subsequent attempts: - return retry([100, 200], function () { - // Sleep for 100ms on first retry, 200ms on second retry... - }); +```php +return retry([100, 200], function () { + // Sleep for 100ms on first retry, 200ms on second retry... +}); +``` To only retry under specific conditions, you may pass a closure as the fourth argument to the `retry` function: - use Exception; +```php +use Exception; - return retry(5, function () { - // ... - }, 100, function (Exception $exception) { - return $exception instanceof RetryException; - }); +return retry(5, function () { + // ... +}, 100, function (Exception $exception) { + return $exception instanceof RetryException; +}); +``` #### `session()` {.collection-method} The `session` function may be used to get or set [session](/docs/{{version}}/session) values: - $value = session('key'); +```php +$value = session('key'); +``` You may set values by passing an array of key / value pairs to the function: - session(['chairs' => 7, 'instruments' => 3]); +```php +session(['chairs' => 7, 'instruments' => 3]); +``` The session store will be returned if no value is passed to the function: - $value = session()->get('key'); +```php +$value = session()->get('key'); - session()->put('key', $value); +session()->put('key', $value); +``` #### `tap()` {.collection-method} The `tap` function accepts two arguments: an arbitrary `$value` and a closure. The `$value` will be passed to the closure and then be returned by the `tap` function. The return value of the closure is irrelevant: - $user = tap(User::first(), function (User $user) { - $user->name = 'taylor'; +```php +$user = tap(User::first(), function (User $user) { + $user->name = 'taylor'; - $user->save(); - }); + $user->save(); +}); +``` If no closure is passed to the `tap` function, you may call any method on the given `$value`. The return value of the method you call will always be `$value`, regardless of what the method actually returns in its definition. For example, the Eloquent `update` method typically returns an integer. However, we can force the method to return the model itself by chaining the `update` method call through the `tap` function: - $user = tap($user)->update([ - 'name' => $name, - 'email' => $email, - ]); +```php +$user = tap($user)->update([ + 'name' => $name, + 'email' => $email, +]); +``` To add a `tap` method to a class, you may add the `Illuminate\Support\Traits\Tappable` trait to the class. The `tap` method of this trait accepts a Closure as its only argument. The object instance itself will be passed to the Closure and then be returned by the `tap` method: - return $user->tap(function (User $user) { - // ... - }); +```php +return $user->tap(function (User $user) { + // ... +}); +``` #### `throw_if()` {.collection-method} The `throw_if` function throws the given exception if a given boolean expression evaluates to `true`: - throw_if(! Auth::user()->isAdmin(), AuthorizationException::class); +```php +throw_if(! Auth::user()->isAdmin(), AuthorizationException::class); - throw_if( - ! Auth::user()->isAdmin(), - AuthorizationException::class, - 'You are not allowed to access this page.' - ); +throw_if( + ! Auth::user()->isAdmin(), + AuthorizationException::class, + 'You are not allowed to access this page.' +); +``` #### `throw_unless()` {.collection-method} The `throw_unless` function throws the given exception if a given boolean expression evaluates to `false`: - throw_unless(Auth::user()->isAdmin(), AuthorizationException::class); +```php +throw_unless(Auth::user()->isAdmin(), AuthorizationException::class); - throw_unless( - Auth::user()->isAdmin(), - AuthorizationException::class, - 'You are not allowed to access this page.' - ); +throw_unless( + Auth::user()->isAdmin(), + AuthorizationException::class, + 'You are not allowed to access this page.' +); +``` #### `today()` {.collection-method} The `today` function creates a new `Illuminate\Support\Carbon` instance for the current date: - $today = today(); +```php +$today = today(); +``` #### `trait_uses_recursive()` {.collection-method} The `trait_uses_recursive` function returns all traits used by a trait: - $traits = trait_uses_recursive(\Illuminate\Notifications\Notifiable::class); +```php +$traits = trait_uses_recursive(\Illuminate\Notifications\Notifiable::class); +``` #### `transform()` {.collection-method} The `transform` function executes a closure on a given value if the value is not [blank](#method-blank) and then returns the return value of the closure: - $callback = function (int $value) { - return $value * 2; - }; +```php +$callback = function (int $value) { + return $value * 2; +}; - $result = transform(5, $callback); +$result = transform(5, $callback); - // 10 +// 10 +``` A default value or closure may be passed as the third argument to the function. This value will be returned if the given value is blank: - $result = transform(null, $callback, 'The value is blank'); +```php +$result = transform(null, $callback, 'The value is blank'); - // The value is blank +// The value is blank +``` #### `validator()` {.collection-method} The `validator` function creates a new [validator](/docs/{{version}}/validation) instance with the given arguments. You may use it as an alternative to the `Validator` facade: - $validator = validator($data, $rules, $messages); +```php +$validator = validator($data, $rules, $messages); +``` #### `value()` {.collection-method} The `value` function returns the value it is given. However, if you pass a closure to the function, the closure will be executed and its returned value will be returned: - $result = value(true); +```php +$result = value(true); - // true +// true - $result = value(function () { - return false; - }); +$result = value(function () { + return false; +}); - // false +// false +``` Additional arguments may be passed to the `value` function. If the first argument is a closure then the additional parameters will be passed to the closure as arguments, otherwise they will be ignored: - $result = value(function (string $name) { - return $name; - }, 'Taylor'); +```php +$result = value(function (string $name) { + return $name; +}, 'Taylor'); - // 'Taylor' +// 'Taylor' +``` #### `view()` {.collection-method} The `view` function retrieves a [view](/docs/{{version}}/views) instance: - return view('auth.login'); +```php +return view('auth.login'); +``` #### `with()` {.collection-method} The `with` function returns the value it is given. If a closure is passed as the second argument to the function, the closure will be executed and its returned value will be returned: - $callback = function (mixed $value) { - return is_numeric($value) ? $value * 2 : 0; - }; +```php +$callback = function (mixed $value) { + return is_numeric($value) ? $value * 2 : 0; +}; - $result = with(5, $callback); +$result = with(5, $callback); - // 10 +// 10 - $result = with(null, $callback); +$result = with(null, $callback); - // 0 +// 0 - $result = with(5, null); +$result = with(5, null); - // 5 +// 5 +``` #### `when()` {.collection-method} The `when` function returns the value it is given if a given condition evaluates to `true`. Otherwise, `null` is returned. If a closure is passed as the second argument to the function, the closure will be executed and its returned value will be returned: - $value = when(true, 'Hello World'); +```php +$value = when(true, 'Hello World'); - $value = when(true, fn () => 'Hello World'); +$value = when(true, fn () => 'Hello World'); +``` The `when` function is primarily useful for conditionally rendering HTML attributes: @@ -2383,27 +2753,33 @@ The `when` function is primarily useful for conditionally rendering HTML attribu Sometimes you may wish to quickly test the performance of certain parts of your application. On those occasions, you may utilize the `Benchmark` support class to measure the number of milliseconds it takes for the given callbacks to complete: - User::find(1)); // 0.1 ms +Benchmark::dd(fn () => User::find(1)); // 0.1 ms - Benchmark::dd([ - 'Scenario 1' => fn () => User::count(), // 0.5 ms - 'Scenario 2' => fn () => User::all()->count(), // 20.0 ms - ]); +Benchmark::dd([ + 'Scenario 1' => fn () => User::count(), // 0.5 ms + 'Scenario 2' => fn () => User::all()->count(), // 20.0 ms +]); +``` By default, the given callbacks will be executed once (one iteration), and their duration will be displayed in the browser / console. To invoke a callback more than once, you may specify the number of iterations that the callback should be invoked as the second argument to the method. When executing a callback more than once, the `Benchmark` class will return the average amount of milliseconds it took to execute the callback across all iterations: - Benchmark::dd(fn () => User::count(), iterations: 10); // 0.5 ms +```php +Benchmark::dd(fn () => User::count(), iterations: 10); // 0.5 ms +``` Sometimes, you may want to benchmark the execution of a callback while still obtaining the value returned by the callback. The `value` method will return a tuple containing the value returned by the callback and the amount of milliseconds it took to execute the callback: - [$count, $duration] = Benchmark::value(fn () => User::count()); +```php +[$count, $duration] = Benchmark::value(fn () => User::count()); +``` ### Dates @@ -2531,40 +2907,46 @@ abstract class TestCase extends BaseTestCase Laravel's lottery class may be used to execute callbacks based on a set of given odds. This can be particularly useful when you only want to execute code for a percentage of your incoming requests: - use Illuminate\Support\Lottery; +```php +use Illuminate\Support\Lottery; - Lottery::odds(1, 20) - ->winner(fn () => $user->won()) - ->loser(fn () => $user->lost()) - ->choose(); +Lottery::odds(1, 20) + ->winner(fn () => $user->won()) + ->loser(fn () => $user->lost()) + ->choose(); +``` You may combine Laravel's lottery class with other Laravel features. For example, you may wish to only report a small percentage of slow queries to your exception handler. And, since the lottery class is callable, we may pass an instance of the class into any method that accepts callables: - use Carbon\CarbonInterval; - use Illuminate\Support\Facades\DB; - use Illuminate\Support\Lottery; - - DB::whenQueryingForLongerThan( - CarbonInterval::seconds(2), - Lottery::odds(1, 100)->winner(fn () => report('Querying > 2 seconds.')), - ); +```php +use Carbon\CarbonInterval; +use Illuminate\Support\Facades\DB; +use Illuminate\Support\Lottery; + +DB::whenQueryingForLongerThan( + CarbonInterval::seconds(2), + Lottery::odds(1, 100)->winner(fn () => report('Querying > 2 seconds.')), +); +``` #### Testing Lotteries Laravel provides some simple methods to allow you to easily test your application's lottery invocations: - // Lottery will always win... - Lottery::alwaysWin(); +```php +// Lottery will always win... +Lottery::alwaysWin(); - // Lottery will always lose... - Lottery::alwaysLose(); +// Lottery will always lose... +Lottery::alwaysLose(); - // Lottery will win then lose, and finally return to normal behavior... - Lottery::fix([true, false]); +// Lottery will win then lose, and finally return to normal behavior... +Lottery::fix([true, false]); - // Lottery will return to normal behavior... - Lottery::determineResultsNormally(); +// Lottery will return to normal behavior... +Lottery::determineResultsNormally(); +``` ### Pipeline @@ -2613,63 +2995,71 @@ $user = Pipeline::send($user) Laravel's `Sleep` class is a light-weight wrapper around PHP's native `sleep` and `usleep` functions, offering greater testability while also exposing a developer friendly API for working with time: - use Illuminate\Support\Sleep; +```php +use Illuminate\Support\Sleep; - $waiting = true; +$waiting = true; - while ($waiting) { - Sleep::for(1)->second(); +while ($waiting) { + Sleep::for(1)->second(); - $waiting = /* ... */; - } + $waiting = /* ... */; +} +``` The `Sleep` class offers a variety of methods that allow you to work with different units of time: - // Return a value after sleeping... - $result = Sleep::for(1)->second()->then(fn () => 1 + 1); +```php +// Return a value after sleeping... +$result = Sleep::for(1)->second()->then(fn () => 1 + 1); - // Sleep while a given value is true... - Sleep::for(1)->second()->while(fn () => shouldKeepSleeping()); +// Sleep while a given value is true... +Sleep::for(1)->second()->while(fn () => shouldKeepSleeping()); - // Pause execution for 90 seconds... - Sleep::for(1.5)->minutes(); +// Pause execution for 90 seconds... +Sleep::for(1.5)->minutes(); - // Pause execution for 2 seconds... - Sleep::for(2)->seconds(); +// Pause execution for 2 seconds... +Sleep::for(2)->seconds(); - // Pause execution for 500 milliseconds... - Sleep::for(500)->milliseconds(); +// Pause execution for 500 milliseconds... +Sleep::for(500)->milliseconds(); - // Pause execution for 5,000 microseconds... - Sleep::for(5000)->microseconds(); +// Pause execution for 5,000 microseconds... +Sleep::for(5000)->microseconds(); - // Pause execution until a given time... - Sleep::until(now()->addMinute()); +// Pause execution until a given time... +Sleep::until(now()->addMinute()); - // Alias of PHP's native "sleep" function... - Sleep::sleep(2); +// Alias of PHP's native "sleep" function... +Sleep::sleep(2); - // Alias of PHP's native "usleep" function... - Sleep::usleep(5000); +// Alias of PHP's native "usleep" function... +Sleep::usleep(5000); +``` To easily combine units of time, you may use the `and` method: - Sleep::for(1)->second()->and(10)->milliseconds(); +```php +Sleep::for(1)->second()->and(10)->milliseconds(); +``` #### Testing Sleep When testing code that utilizes the `Sleep` class or PHP's native sleep functions, your test will pause execution. As you might expect, this makes your test suite significantly slower. For example, imagine you are testing the following code: - $waiting = /* ... */; +```php +$waiting = /* ... */; - $seconds = 1; +$seconds = 1; - while ($waiting) { - Sleep::for($seconds++)->seconds(); +while ($waiting) { + Sleep::for($seconds++)->seconds(); - $waiting = /* ... */; - } + $waiting = /* ... */; +} +``` Typically, testing this code would take _at least_ one second. Luckily, the `Sleep` class allows us to "fake" sleeping so that our test suite stays fast: @@ -2725,22 +3115,24 @@ public function test_it_checks_if_ready_three_times() Of course, the `Sleep` class offers a variety of other assertions you may use when testing: - use Carbon\CarbonInterval as Duration; - use Illuminate\Support\Sleep; +```php +use Carbon\CarbonInterval as Duration; +use Illuminate\Support\Sleep; - // Assert that sleep was called 3 times... - Sleep::assertSleptTimes(3); +// Assert that sleep was called 3 times... +Sleep::assertSleptTimes(3); - // Assert against the duration of sleep... - Sleep::assertSlept(function (Duration $duration): bool { - return /* ... */; - }, times: 1); +// Assert against the duration of sleep... +Sleep::assertSlept(function (Duration $duration): bool { + return /* ... */; +}, times: 1); - // Assert that the Sleep class was never invoked... - Sleep::assertNeverSlept(); +// Assert that the Sleep class was never invoked... +Sleep::assertNeverSlept(); - // Assert that, even if Sleep was called, no execution paused occurred... - Sleep::assertInsomniac(); +// Assert that, even if Sleep was called, no execution paused occurred... +Sleep::assertInsomniac(); +``` Sometimes it may be useful to perform an action whenever a fake sleep occurs in your application code. To achieve this, you may provide a callback to the `whenFakingSleep` method. In the following example, we use Laravel's [time manipulation helpers](/docs/{{version}}/mocking#interacting-with-time) to instantly progress time by the duration of each sleep: diff --git a/homestead.md b/homestead.md index 05d9448955e..aeea06fd25d 100644 --- a/homestead.md +++ b/homestead.md @@ -266,7 +266,9 @@ Homestead publishes hostnames using `mDNS` for automatic host resolution. If you Using automatic hostnames works best for [per project installations](#per-project-installation) of Homestead. If you host multiple sites on a single Homestead instance, you may add the "domains" for your web sites to the `hosts` file on your machine. The `hosts` file will redirect requests for your Homestead sites into your Homestead virtual machine. On macOS and Linux, this file is located at `/etc/hosts`. On Windows, it is located at `C:\Windows\System32\drivers\etc\hosts`. The lines you add to this file will look like the following: - 192.168.56.56 homestead.test +```text +192.168.56.56 homestead.test +``` Make sure the IP address listed is the one set in your `Homestead.yaml` file. Once you have added the domain to your `hosts` file and launched the Vagrant box you will be able to access the site via your web browser: @@ -473,8 +475,10 @@ sites: If Vagrant is not automatically managing your "hosts" file, you may need to add the new site to that file as well. On macOS and Linux, this file is located at `/etc/hosts`. On Windows, it is located at `C:\Windows\System32\drivers\etc\hosts`: - 192.168.56.56 homestead.test - 192.168.56.56 another.test +```text +192.168.56.56 homestead.test +192.168.56.56 another.test +``` Once the site has been added, execute the `vagrant reload --provision` terminal command from your Homestead directory. @@ -616,7 +620,9 @@ A `homestead` database is configured for both MySQL and PostgreSQL out of the bo Homestead can automatically backup your database when your Homestead virtual machine is destroyed. To utilize this feature, you must be using Vagrant 2.1.0 or greater. Or, if you are using an older version of Vagrant, you must install the `vagrant-triggers` plug-in. To enable automatic database backups, add the following line to your `Homestead.yaml` file: - backup: true +```yaml +backup: true +``` Once configured, Homestead will export your databases to `.backup/mysql_backup` and `.backup/postgres_backup` directories when the `vagrant destroy` command is executed. These directories can be found in the folder where you installed Homestead or in the root of your project if you are using the [per project installation](#per-project-installation) method. @@ -747,7 +753,9 @@ xdebug.start_with_request = yes To debug a PHP CLI application, use the `xphp` shell alias inside your Homestead virtual machine: - xphp /path/to/script +```shell +xphp /path/to/script +``` ### Profiling Applications With Blackfire diff --git a/horizon.md b/horizon.md index fc2a090c627..6bec738bc63 100644 --- a/horizon.md +++ b/horizon.md @@ -58,33 +58,37 @@ After publishing Horizon's assets, its primary configuration file will be locate After installation, the primary Horizon configuration option that you should familiarize yourself with is the `environments` configuration option. This configuration option is an array of environments that your application runs on and defines the worker process options for each environment. By default, this entry contains a `production` and `local` environment. However, you are free to add more environments as needed: - 'environments' => [ - 'production' => [ - 'supervisor-1' => [ - 'maxProcesses' => 10, - 'balanceMaxShift' => 1, - 'balanceCooldown' => 3, - ], +```php +'environments' => [ + 'production' => [ + 'supervisor-1' => [ + 'maxProcesses' => 10, + 'balanceMaxShift' => 1, + 'balanceCooldown' => 3, ], + ], - 'local' => [ - 'supervisor-1' => [ - 'maxProcesses' => 3, - ], + 'local' => [ + 'supervisor-1' => [ + 'maxProcesses' => 3, ], ], +], +``` You may also define a wildcard environment (`*`) which will be used when no other matching environment is found: - 'environments' => [ - // ... +```php +'environments' => [ + // ... - '*' => [ - 'supervisor-1' => [ - 'maxProcesses' => 3, - ], + '*' => [ + 'supervisor-1' => [ + 'maxProcesses' => 3, ], ], +], +``` When you start Horizon, it will use the worker process configuration options for the environment that your application is running on. Typically, the environment is determined by the value of the `APP_ENV` [environment variable](/docs/{{version}}/configuration#determining-the-current-environment). For example, the default `local` Horizon environment is configured to start three worker processes and automatically balance the number of worker processes assigned to each queue. The default `production` environment is configured to start a maximum of 10 worker processes and automatically balance the number of worker processes assigned to each queue. @@ -103,14 +107,16 @@ You may add additional supervisors to a given environment if you would like to d While your application is in [maintenance mode](/docs/{{version}}/configuration#maintenance-mode), queued jobs will not be processed by Horizon unless the supervisor's `force` option is defined as `true` within the Horizon configuration file: - 'environments' => [ - 'production' => [ - 'supervisor-1' => [ - // ... - 'force' => true, - ], +```php +'environments' => [ + 'production' => [ + 'supervisor-1' => [ + // ... + 'force' => true, ], ], +], +``` #### Default Values @@ -128,21 +134,23 @@ The `auto` strategy, which is the configuration file's default, adjusts the numb When using the `auto` strategy, you may define the `minProcesses` and `maxProcesses` configuration options to control the minimum number of processes per queue and the maximum number of worker processes in total Horizon should scale up and down to: - 'environments' => [ - 'production' => [ - 'supervisor-1' => [ - 'connection' => 'redis', - 'queue' => ['default'], - 'balance' => 'auto', - 'autoScalingStrategy' => 'time', - 'minProcesses' => 1, - 'maxProcesses' => 10, - 'balanceMaxShift' => 1, - 'balanceCooldown' => 3, - 'tries' => 3, - ], +```php +'environments' => [ + 'production' => [ + 'supervisor-1' => [ + 'connection' => 'redis', + 'queue' => ['default'], + 'balance' => 'auto', + 'autoScalingStrategy' => 'time', + 'minProcesses' => 1, + 'maxProcesses' => 10, + 'balanceMaxShift' => 1, + 'balanceCooldown' => 3, + 'tries' => 3, ], ], +], +``` The `autoScalingStrategy` configuration value determines if Horizon will assign more worker processes to queues based on the total amount of time it will take to clear the queue (`time` strategy) or by the total number of jobs on the queue (`size` strategy). @@ -155,19 +163,21 @@ When the `balance` option is set to `false`, the default Laravel behavior will b The Horizon dashboard may be accessed via the `/horizon` route. By default, you will only be able to access this dashboard in the `local` environment. However, within your `app/Providers/HorizonServiceProvider.php` file, there is an [authorization gate](/docs/{{version}}/authorization#gates) definition. This authorization gate controls access to Horizon in **non-local** environments. You are free to modify this gate as needed to restrict access to your Horizon installation: - /** - * Register the Horizon gate. - * - * This gate determines who can access Horizon in non-local environments. - */ - protected function gate(): void - { - Gate::define('viewHorizon', function (User $user) { - return in_array($user->email, [ - 'taylor@laravel.com', - ]); - }); - } +```php +/** + * Register the Horizon gate. + * + * This gate determines who can access Horizon in non-local environments. + */ +protected function gate(): void +{ + Gate::define('viewHorizon', function (User $user) { + return in_array($user->email, [ + 'taylor@laravel.com', + ]); + }); +} +``` #### Alternative Authentication Strategies @@ -179,20 +189,24 @@ Remember that Laravel automatically injects the authenticated user into the gate Sometimes, you may not be interested in viewing certain jobs dispatched by your application or third-party packages. Instead of these jobs taking up space in your "Completed Jobs" list, you can silence them. To get started, add the job's class name to the `silenced` configuration option in your application's `horizon` configuration file: - 'silenced' => [ - App\Jobs\ProcessPodcast::class, - ], +```php +'silenced' => [ + App\Jobs\ProcessPodcast::class, +], +``` Alternatively, the job you wish to silence can implement the `Laravel\Horizon\Contracts\Silenced` interface. If a job implements this interface, it will automatically be silenced, even if it is not present in the `silenced` configuration array: - use Laravel\Horizon\Contracts\Silenced; +```php +use Laravel\Horizon\Contracts\Silenced; - class ProcessPodcast implements ShouldQueue, Silenced - { - use Queueable; +class ProcessPodcast implements ShouldQueue, Silenced +{ + use Queueable; - // ... - } + // ... +} +``` ## Upgrading Horizon @@ -308,78 +322,86 @@ sudo supervisorctl start horizon Horizon allows you to assign “tags” to jobs, including mailables, broadcast events, notifications, and queued event listeners. In fact, Horizon will intelligently and automatically tag most jobs depending on the Eloquent models that are attached to the job. For example, take a look at the following job: - #### Manually Tagging Jobs If you would like to manually define the tags for one of your queueable objects, you may define a `tags` method on the class: - class RenderVideo implements ShouldQueue +```php +class RenderVideo implements ShouldQueue +{ + /** + * Get the tags that should be assigned to the job. + * + * @return array + */ + public function tags(): array { - /** - * Get the tags that should be assigned to the job. - * - * @return array - */ - public function tags(): array - { - return ['render', 'video:'.$this->video->id]; - } + return ['render', 'video:'.$this->video->id]; } +} +``` #### Manually Tagging Event Listeners When retrieving the tags for a queued event listener, Horizon will automatically pass the event instance to the `tags` method, allowing you to add event data to the tags: - class SendRenderNotifications implements ShouldQueue +```php +class SendRenderNotifications implements ShouldQueue +{ + /** + * Get the tags that should be assigned to the listener. + * + * @return array + */ + public function tags(VideoRendered $event): array { - /** - * Get the tags that should be assigned to the listener. - * - * @return array - */ - public function tags(VideoRendered $event): array - { - return ['video:'.$event->video->id]; - } + return ['video:'.$event->video->id]; } +} +``` ## Notifications @@ -389,37 +411,43 @@ When retrieving the tags for a queued event listener, Horizon will automatically If you would like to be notified when one of your queues has a long wait time, you may use the `Horizon::routeMailNotificationsTo`, `Horizon::routeSlackNotificationsTo`, and `Horizon::routeSmsNotificationsTo` methods. You may call these methods from the `boot` method of your application's `App\Providers\HorizonServiceProvider`: - /** - * Bootstrap any application services. - */ - public function boot(): void - { - parent::boot(); - - Horizon::routeSmsNotificationsTo('15556667777'); - Horizon::routeMailNotificationsTo('example@example.com'); - Horizon::routeSlackNotificationsTo('slack-webhook-url', '#channel'); - } +```php +/** + * Bootstrap any application services. + */ +public function boot(): void +{ + parent::boot(); + + Horizon::routeSmsNotificationsTo('15556667777'); + Horizon::routeMailNotificationsTo('example@example.com'); + Horizon::routeSlackNotificationsTo('slack-webhook-url', '#channel'); +} +``` #### Configuring Notification Wait Time Thresholds You may configure how many seconds are considered a "long wait" within your application's `config/horizon.php` configuration file. The `waits` configuration option within this file allows you to control the long wait threshold for each connection / queue combination. Any undefined connection / queue combinations will default to a long wait threshold of 60 seconds: - 'waits' => [ - 'redis:critical' => 30, - 'redis:default' => 60, - 'redis:batch' => 120, - ], +```php +'waits' => [ + 'redis:critical' => 30, + 'redis:default' => 60, + 'redis:batch' => 120, +], +``` ## Metrics Horizon includes a metrics dashboard which provides information regarding your job and queue wait times and throughput. In order to populate this dashboard, you should configure Horizon's `snapshot` Artisan command to run every five minutes in your application's `routes/console.php` file: - use Illuminate\Support\Facades\Schedule; +```php +use Illuminate\Support\Facades\Schedule; - Schedule::command('horizon:snapshot')->everyFiveMinutes(); +Schedule::command('horizon:snapshot')->everyFiveMinutes(); +``` ## Deleting Failed Jobs diff --git a/http-client.md b/http-client.md index 9970fa34c88..312c6362713 100644 --- a/http-client.md +++ b/http-client.md @@ -28,47 +28,55 @@ Laravel provides an expressive, minimal API around the [Guzzle HTTP client](http To make requests, you may use the `head`, `get`, `post`, `put`, `patch`, and `delete` methods provided by the `Http` facade. First, let's examine how to make a basic `GET` request to another URL: - use Illuminate\Support\Facades\Http; +```php +use Illuminate\Support\Facades\Http; - $response = Http::get('http://example.com'); +$response = Http::get('http://example.com'); +``` The `get` method returns an instance of `Illuminate\Http\Client\Response`, which provides a variety of methods that may be used to inspect the response: - $response->body() : string; - $response->json($key = null, $default = null) : mixed; - $response->object() : object; - $response->collect($key = null) : Illuminate\Support\Collection; - $response->resource() : resource; - $response->status() : int; - $response->successful() : bool; - $response->redirect(): bool; - $response->failed() : bool; - $response->clientError() : bool; - $response->header($header) : string; - $response->headers() : array; +```php +$response->body() : string; +$response->json($key = null, $default = null) : mixed; +$response->object() : object; +$response->collect($key = null) : Illuminate\Support\Collection; +$response->resource() : resource; +$response->status() : int; +$response->successful() : bool; +$response->redirect(): bool; +$response->failed() : bool; +$response->clientError() : bool; +$response->header($header) : string; +$response->headers() : array; +``` The `Illuminate\Http\Client\Response` object also implements the PHP `ArrayAccess` interface, allowing you to access JSON response data directly on the response: - return Http::get('http://example.com/users/1')['name']; +```php +return Http::get('http://example.com/users/1')['name']; +``` In addition to the response methods listed above, the following methods may be used to determine if the response has a given status code: - $response->ok() : bool; // 200 OK - $response->created() : bool; // 201 Created - $response->accepted() : bool; // 202 Accepted - $response->noContent() : bool; // 204 No Content - $response->movedPermanently() : bool; // 301 Moved Permanently - $response->found() : bool; // 302 Found - $response->badRequest() : bool; // 400 Bad Request - $response->unauthorized() : bool; // 401 Unauthorized - $response->paymentRequired() : bool; // 402 Payment Required - $response->forbidden() : bool; // 403 Forbidden - $response->notFound() : bool; // 404 Not Found - $response->requestTimeout() : bool; // 408 Request Timeout - $response->conflict() : bool; // 409 Conflict - $response->unprocessableEntity() : bool; // 422 Unprocessable Entity - $response->tooManyRequests() : bool; // 429 Too Many Requests - $response->serverError() : bool; // 500 Internal Server Error +```php +$response->ok() : bool; // 200 OK +$response->created() : bool; // 201 Created +$response->accepted() : bool; // 202 Accepted +$response->noContent() : bool; // 204 No Content +$response->movedPermanently() : bool; // 301 Moved Permanently +$response->found() : bool; // 302 Found +$response->badRequest() : bool; // 400 Bad Request +$response->unauthorized() : bool; // 401 Unauthorized +$response->paymentRequired() : bool; // 402 Payment Required +$response->forbidden() : bool; // 403 Forbidden +$response->notFound() : bool; // 404 Not Found +$response->requestTimeout() : bool; // 408 Request Timeout +$response->conflict() : bool; // 409 Conflict +$response->unprocessableEntity() : bool; // 422 Unprocessable Entity +$response->tooManyRequests() : bool; // 429 Too Many Requests +$response->serverError() : bool; // 500 Internal Server Error +``` #### URI Templates @@ -89,92 +97,114 @@ Http::withUrlParameters([ If you would like to dump the outgoing request instance before it is sent and terminate the script's execution, you may add the `dd` method to the beginning of your request definition: - return Http::dd()->get('http://example.com'); +```php +return Http::dd()->get('http://example.com'); +``` ### Request Data Of course, it is common when making `POST`, `PUT`, and `PATCH` requests to send additional data with your request, so these methods accept an array of data as their second argument. By default, data will be sent using the `application/json` content type: - use Illuminate\Support\Facades\Http; +```php +use Illuminate\Support\Facades\Http; - $response = Http::post('http://example.com/users', [ - 'name' => 'Steve', - 'role' => 'Network Administrator', - ]); +$response = Http::post('http://example.com/users', [ + 'name' => 'Steve', + 'role' => 'Network Administrator', +]); +``` #### GET Request Query Parameters When making `GET` requests, you may either append a query string to the URL directly or pass an array of key / value pairs as the second argument to the `get` method: - $response = Http::get('http://example.com/users', [ - 'name' => 'Taylor', - 'page' => 1, - ]); +```php +$response = Http::get('http://example.com/users', [ + 'name' => 'Taylor', + 'page' => 1, +]); +``` Alternatively, the `withQueryParameters` method may be used: - Http::retry(3, 100)->withQueryParameters([ - 'name' => 'Taylor', - 'page' => 1, - ])->get('http://example.com/users') +```php +Http::retry(3, 100)->withQueryParameters([ + 'name' => 'Taylor', + 'page' => 1, +])->get('http://example.com/users') +``` #### Sending Form URL Encoded Requests If you would like to send data using the `application/x-www-form-urlencoded` content type, you should call the `asForm` method before making your request: - $response = Http::asForm()->post('http://example.com/users', [ - 'name' => 'Sara', - 'role' => 'Privacy Consultant', - ]); +```php +$response = Http::asForm()->post('http://example.com/users', [ + 'name' => 'Sara', + 'role' => 'Privacy Consultant', +]); +``` #### Sending a Raw Request Body You may use the `withBody` method if you would like to provide a raw request body when making a request. The content type may be provided via the method's second argument: - $response = Http::withBody( - base64_encode($photo), 'image/jpeg' - )->post('http://example.com/photo'); +```php +$response = Http::withBody( + base64_encode($photo), 'image/jpeg' +)->post('http://example.com/photo'); +``` #### Multi-Part Requests If you would like to send files as multi-part requests, you should call the `attach` method before making your request. This method accepts the name of the file and its contents. If needed, you may provide a third argument which will be considered the file's filename, while a fourth argument may be used to provide headers associated with the file: - $response = Http::attach( - 'attachment', file_get_contents('photo.jpg'), 'photo.jpg', ['Content-Type' => 'image/jpeg'] - )->post('http://example.com/attachments'); +```php +$response = Http::attach( + 'attachment', file_get_contents('photo.jpg'), 'photo.jpg', ['Content-Type' => 'image/jpeg'] +)->post('http://example.com/attachments'); +``` Instead of passing the raw contents of a file, you may pass a stream resource: - $photo = fopen('photo.jpg', 'r'); +```php +$photo = fopen('photo.jpg', 'r'); - $response = Http::attach( - 'attachment', $photo, 'photo.jpg' - )->post('http://example.com/attachments'); +$response = Http::attach( + 'attachment', $photo, 'photo.jpg' +)->post('http://example.com/attachments'); +``` ### Headers Headers may be added to requests using the `withHeaders` method. This `withHeaders` method accepts an array of key / value pairs: - $response = Http::withHeaders([ - 'X-First' => 'foo', - 'X-Second' => 'bar' - ])->post('http://example.com/users', [ - 'name' => 'Taylor', - ]); +```php +$response = Http::withHeaders([ + 'X-First' => 'foo', + 'X-Second' => 'bar' +])->post('http://example.com/users', [ + 'name' => 'Taylor', +]); +``` You may use the `accept` method to specify the content type that your application is expecting in response to your request: - $response = Http::accept('application/json')->get('http://example.com/users'); +```php +$response = Http::accept('application/json')->get('http://example.com/users'); +``` For convenience, you may use the `acceptJson` method to quickly specify that your application expects the `application/json` content type in response to your request: - $response = Http::acceptJson()->get('http://example.com/users'); +```php +$response = Http::acceptJson()->get('http://example.com/users'); +``` The `withHeaders` method merges new headers into the request's existing headers. If needed, you may replace all of the headers entirely using the `replaceHeaders` method: @@ -193,79 +223,99 @@ $response = Http::withHeaders([ You may specify basic and digest authentication credentials using the `withBasicAuth` and `withDigestAuth` methods, respectively: - // Basic authentication... - $response = Http::withBasicAuth('taylor@laravel.com', 'secret')->post(/* ... */); +```php +// Basic authentication... +$response = Http::withBasicAuth('taylor@laravel.com', 'secret')->post(/* ... */); - // Digest authentication... - $response = Http::withDigestAuth('taylor@laravel.com', 'secret')->post(/* ... */); +// Digest authentication... +$response = Http::withDigestAuth('taylor@laravel.com', 'secret')->post(/* ... */); +``` #### Bearer Tokens If you would like to quickly add a bearer token to the request's `Authorization` header, you may use the `withToken` method: - $response = Http::withToken('token')->post(/* ... */); +```php +$response = Http::withToken('token')->post(/* ... */); +``` ### Timeout The `timeout` method may be used to specify the maximum number of seconds to wait for a response. By default, the HTTP client will timeout after 30 seconds: - $response = Http::timeout(3)->get(/* ... */); +```php +$response = Http::timeout(3)->get(/* ... */); +``` If the given timeout is exceeded, an instance of `Illuminate\Http\Client\ConnectionException` will be thrown. You may specify the maximum number of seconds to wait while trying to connect to a server using the `connectTimeout` method: - $response = Http::connectTimeout(3)->get(/* ... */); +```php +$response = Http::connectTimeout(3)->get(/* ... */); +``` ### Retries If you would like the HTTP client to automatically retry the request if a client or server error occurs, you may use the `retry` method. The `retry` method accepts the maximum number of times the request should be attempted and the number of milliseconds that Laravel should wait in between attempts: - $response = Http::retry(3, 100)->post(/* ... */); +```php +$response = Http::retry(3, 100)->post(/* ... */); +``` If you would like to manually calculate the number of milliseconds to sleep between attempts, you may pass a closure as the second argument to the `retry` method: - use Exception; +```php +use Exception; - $response = Http::retry(3, function (int $attempt, Exception $exception) { - return $attempt * 100; - })->post(/* ... */); +$response = Http::retry(3, function (int $attempt, Exception $exception) { + return $attempt * 100; +})->post(/* ... */); +``` For convenience, you may also provide an array as the first argument to the `retry` method. This array will be used to determine how many milliseconds to sleep between subsequent attempts: - $response = Http::retry([100, 200])->post(/* ... */); +```php +$response = Http::retry([100, 200])->post(/* ... */); +``` If needed, you may pass a third argument to the `retry` method. The third argument should be a callable that determines if the retries should actually be attempted. For example, you may wish to only retry the request if the initial request encounters an `ConnectionException`: - use Exception; - use Illuminate\Http\Client\PendingRequest; +```php +use Exception; +use Illuminate\Http\Client\PendingRequest; - $response = Http::retry(3, 100, function (Exception $exception, PendingRequest $request) { - return $exception instanceof ConnectionException; - })->post(/* ... */); +$response = Http::retry(3, 100, function (Exception $exception, PendingRequest $request) { + return $exception instanceof ConnectionException; +})->post(/* ... */); +``` If a request attempt fails, you may wish to make a change to the request before a new attempt is made. You can achieve this by modifying the request argument provided to the callable you provided to the `retry` method. For example, you might want to retry the request with a new authorization token if the first attempt returned an authentication error: - use Exception; - use Illuminate\Http\Client\PendingRequest; - use Illuminate\Http\Client\RequestException; +```php +use Exception; +use Illuminate\Http\Client\PendingRequest; +use Illuminate\Http\Client\RequestException; - $response = Http::withToken($this->getToken())->retry(2, 0, function (Exception $exception, PendingRequest $request) { - if (! $exception instanceof RequestException || $exception->response->status() !== 401) { - return false; - } +$response = Http::withToken($this->getToken())->retry(2, 0, function (Exception $exception, PendingRequest $request) { + if (! $exception instanceof RequestException || $exception->response->status() !== 401) { + return false; + } - $request->withToken($this->getNewToken()); + $request->withToken($this->getNewToken()); - return true; - })->post(/* ... */); + return true; +})->post(/* ... */); +``` If all of the requests fail, an instance of `Illuminate\Http\Client\RequestException` will be thrown. If you would like to disable this behavior, you may provide a `throw` argument with a value of `false`. When disabled, the last response received by the client will be returned after all retries have been attempted: - $response = Http::retry(3, 100, throw: false)->post(/* ... */); +```php +$response = Http::retry(3, 100, throw: false)->post(/* ... */); +``` > [!WARNING] > If all of the requests fail because of a connection issue, a `Illuminate\Http\Client\ConnectionException` will still be thrown even when the `throw` argument is set to `false`. @@ -275,106 +325,120 @@ If all of the requests fail, an instance of `Illuminate\Http\Client\RequestExcep Unlike Guzzle's default behavior, Laravel's HTTP client wrapper does not throw exceptions on client or server errors (`400` and `500` level responses from servers). You may determine if one of these errors was returned using the `successful`, `clientError`, or `serverError` methods: - // Determine if the status code is >= 200 and < 300... - $response->successful(); +```php +// Determine if the status code is >= 200 and < 300... +$response->successful(); - // Determine if the status code is >= 400... - $response->failed(); +// Determine if the status code is >= 400... +$response->failed(); - // Determine if the response has a 400 level status code... - $response->clientError(); +// Determine if the response has a 400 level status code... +$response->clientError(); - // Determine if the response has a 500 level status code... - $response->serverError(); +// Determine if the response has a 500 level status code... +$response->serverError(); - // Immediately execute the given callback if there was a client or server error... - $response->onError(callable $callback); +// Immediately execute the given callback if there was a client or server error... +$response->onError(callable $callback); +``` #### Throwing Exceptions If you have a response instance and would like to throw an instance of `Illuminate\Http\Client\RequestException` if the response status code indicates a client or server error, you may use the `throw` or `throwIf` methods: - use Illuminate\Http\Client\Response; +```php +use Illuminate\Http\Client\Response; - $response = Http::post(/* ... */); +$response = Http::post(/* ... */); - // Throw an exception if a client or server error occurred... - $response->throw(); +// Throw an exception if a client or server error occurred... +$response->throw(); - // Throw an exception if an error occurred and the given condition is true... - $response->throwIf($condition); +// Throw an exception if an error occurred and the given condition is true... +$response->throwIf($condition); - // Throw an exception if an error occurred and the given closure resolves to true... - $response->throwIf(fn (Response $response) => true); +// Throw an exception if an error occurred and the given closure resolves to true... +$response->throwIf(fn (Response $response) => true); - // Throw an exception if an error occurred and the given condition is false... - $response->throwUnless($condition); +// Throw an exception if an error occurred and the given condition is false... +$response->throwUnless($condition); - // Throw an exception if an error occurred and the given closure resolves to false... - $response->throwUnless(fn (Response $response) => false); +// Throw an exception if an error occurred and the given closure resolves to false... +$response->throwUnless(fn (Response $response) => false); - // Throw an exception if the response has a specific status code... - $response->throwIfStatus(403); +// Throw an exception if the response has a specific status code... +$response->throwIfStatus(403); - // Throw an exception unless the response has a specific status code... - $response->throwUnlessStatus(200); +// Throw an exception unless the response has a specific status code... +$response->throwUnlessStatus(200); - return $response['user']['id']; +return $response['user']['id']; +``` The `Illuminate\Http\Client\RequestException` instance has a public `$response` property which will allow you to inspect the returned response. The `throw` method returns the response instance if no error occurred, allowing you to chain other operations onto the `throw` method: - return Http::post(/* ... */)->throw()->json(); +```php +return Http::post(/* ... */)->throw()->json(); +``` If you would like to perform some additional logic before the exception is thrown, you may pass a closure to the `throw` method. The exception will be thrown automatically after the closure is invoked, so you do not need to re-throw the exception from within the closure: - use Illuminate\Http\Client\Response; - use Illuminate\Http\Client\RequestException; +```php +use Illuminate\Http\Client\Response; +use Illuminate\Http\Client\RequestException; - return Http::post(/* ... */)->throw(function (Response $response, RequestException $e) { - // ... - })->json(); +return Http::post(/* ... */)->throw(function (Response $response, RequestException $e) { + // ... +})->json(); +``` By default, `RequestException` messages are truncated to 120 characters when logged or reported. To customize or disable this behavior, you may utilize the `truncateRequestExceptionsAt` and `dontTruncateRequestExceptions` methods when configuring your application's exception handling behavior in your `bootstrap/app.php` file: - ->withExceptions(function (Exceptions $exceptions) { - // Truncate request exception messages to 240 characters... - $exceptions->truncateRequestExceptionsAt(240); +```php +->withExceptions(function (Exceptions $exceptions) { + // Truncate request exception messages to 240 characters... + $exceptions->truncateRequestExceptionsAt(240); - // Disable request exception message truncation... - $exceptions->dontTruncateRequestExceptions(); - }) + // Disable request exception message truncation... + $exceptions->dontTruncateRequestExceptions(); +}) +``` ### Guzzle Middleware Since Laravel's HTTP client is powered by Guzzle, you may take advantage of [Guzzle Middleware](https://docs.guzzlephp.org/en/stable/handlers-and-middleware.html) to manipulate the outgoing request or inspect the incoming response. To manipulate the outgoing request, register a Guzzle middleware via the `withRequestMiddleware` method: - use Illuminate\Support\Facades\Http; - use Psr\Http\Message\RequestInterface; +```php +use Illuminate\Support\Facades\Http; +use Psr\Http\Message\RequestInterface; - $response = Http::withRequestMiddleware( - function (RequestInterface $request) { - return $request->withHeader('X-Example', 'Value'); - } - )->get('http://example.com'); +$response = Http::withRequestMiddleware( + function (RequestInterface $request) { + return $request->withHeader('X-Example', 'Value'); + } +)->get('http://example.com'); +``` Likewise, you can inspect the incoming HTTP response by registering a middleware via the `withResponseMiddleware` method: - use Illuminate\Support\Facades\Http; - use Psr\Http\Message\ResponseInterface; +```php +use Illuminate\Support\Facades\Http; +use Psr\Http\Message\ResponseInterface; - $response = Http::withResponseMiddleware( - function (ResponseInterface $response) { - $header = $response->getHeader('X-Example'); +$response = Http::withResponseMiddleware( + function (ResponseInterface $response) { + $header = $response->getHeader('X-Example'); - // ... + // ... - return $response; - } - )->get('http://example.com'); + return $response; + } +)->get('http://example.com'); +``` #### Global Middleware @@ -398,9 +462,11 @@ Http::globalResponseMiddleware(fn ($response) => $response->withHeader( You may specify additional [Guzzle request options](http://docs.guzzlephp.org/en/stable/request-options.html) for an outgoing request using the `withOptions` method. The `withOptions` method accepts an array of key / value pairs: - $response = Http::withOptions([ - 'debug' => true, - ])->get('http://example.com/users'); +```php +$response = Http::withOptions([ + 'debug' => true, +])->get('http://example.com/users'); +``` #### Global Options @@ -428,31 +494,35 @@ Sometimes, you may wish to make multiple HTTP requests concurrently. In other wo Thankfully, you may accomplish this using the `pool` method. The `pool` method accepts a closure which receives an `Illuminate\Http\Client\Pool` instance, allowing you to easily add requests to the request pool for dispatching: - use Illuminate\Http\Client\Pool; - use Illuminate\Support\Facades\Http; +```php +use Illuminate\Http\Client\Pool; +use Illuminate\Support\Facades\Http; - $responses = Http::pool(fn (Pool $pool) => [ - $pool->get('http://localhost/first'), - $pool->get('http://localhost/second'), - $pool->get('http://localhost/third'), - ]); +$responses = Http::pool(fn (Pool $pool) => [ + $pool->get('http://localhost/first'), + $pool->get('http://localhost/second'), + $pool->get('http://localhost/third'), +]); - return $responses[0]->ok() && - $responses[1]->ok() && - $responses[2]->ok(); +return $responses[0]->ok() && + $responses[1]->ok() && + $responses[2]->ok(); +``` As you can see, each response instance can be accessed based on the order it was added to the pool. If you wish, you can name the requests using the `as` method, which allows you to access the corresponding responses by name: - use Illuminate\Http\Client\Pool; - use Illuminate\Support\Facades\Http; +```php +use Illuminate\Http\Client\Pool; +use Illuminate\Support\Facades\Http; - $responses = Http::pool(fn (Pool $pool) => [ - $pool->as('first')->get('http://localhost/first'), - $pool->as('second')->get('http://localhost/second'), - $pool->as('third')->get('http://localhost/third'), - ]); +$responses = Http::pool(fn (Pool $pool) => [ + $pool->as('first')->get('http://localhost/first'), + $pool->as('second')->get('http://localhost/second'), + $pool->as('third')->get('http://localhost/third'), +]); - return $responses['first']->ok(); +return $responses['first']->ok(); +``` #### Customizing Concurrent Requests @@ -511,110 +581,130 @@ Many Laravel services provide functionality to help you easily and expressively For example, to instruct the HTTP client to return empty, `200` status code responses for every request, you may call the `fake` method with no arguments: - use Illuminate\Support\Facades\Http; +```php +use Illuminate\Support\Facades\Http; - Http::fake(); +Http::fake(); - $response = Http::post(/* ... */); +$response = Http::post(/* ... */); +``` #### Faking Specific URLs Alternatively, you may pass an array to the `fake` method. The array's keys should represent URL patterns that you wish to fake and their associated responses. The `*` character may be used as a wildcard character. Any requests made to URLs that have not been faked will actually be executed. You may use the `Http` facade's `response` method to construct stub / fake responses for these endpoints: - Http::fake([ - // Stub a JSON response for GitHub endpoints... - 'github.com/*' => Http::response(['foo' => 'bar'], 200, $headers), +```php +Http::fake([ + // Stub a JSON response for GitHub endpoints... + 'github.com/*' => Http::response(['foo' => 'bar'], 200, $headers), - // Stub a string response for Google endpoints... - 'google.com/*' => Http::response('Hello World', 200, $headers), - ]); + // Stub a string response for Google endpoints... + 'google.com/*' => Http::response('Hello World', 200, $headers), +]); +``` If you would like to specify a fallback URL pattern that will stub all unmatched URLs, you may use a single `*` character: - Http::fake([ - // Stub a JSON response for GitHub endpoints... - 'github.com/*' => Http::response(['foo' => 'bar'], 200, ['Headers']), +```php +Http::fake([ + // Stub a JSON response for GitHub endpoints... + 'github.com/*' => Http::response(['foo' => 'bar'], 200, ['Headers']), - // Stub a string response for all other endpoints... - '*' => Http::response('Hello World', 200, ['Headers']), - ]); + // Stub a string response for all other endpoints... + '*' => Http::response('Hello World', 200, ['Headers']), +]); +``` For convenience, simple string, JSON, and empty responses may be generated by providing a string, array, or integer as the response: - Http::fake([ - 'google.com/*' => 'Hello World', - 'github.com/*' => ['foo' => 'bar'], - 'chatgpt.com/*' => 200, - ]); +```php +Http::fake([ + 'google.com/*' => 'Hello World', + 'github.com/*' => ['foo' => 'bar'], + 'chatgpt.com/*' => 200, +]); +``` #### Faking Connection Exceptions Sometimes you may need to test your application's behavior if the HTTP client encounters an `Illuminate\Http\Client\ConnectionException` when attempting to make a request. You can instruct the HTTP client to throw a connection exception using the `failedConnection` method: - Http::fake([ - 'github.com/*' => Http::failedConnection(), - ]); +```php +Http::fake([ + 'github.com/*' => Http::failedConnection(), +]); +``` #### Faking Response Sequences Sometimes you may need to specify that a single URL should return a series of fake responses in a specific order. You may accomplish this using the `Http::sequence` method to build the responses: - Http::fake([ - // Stub a series of responses for GitHub endpoints... - 'github.com/*' => Http::sequence() - ->push('Hello World', 200) - ->push(['foo' => 'bar'], 200) - ->pushStatus(404), - ]); +```php +Http::fake([ + // Stub a series of responses for GitHub endpoints... + 'github.com/*' => Http::sequence() + ->push('Hello World', 200) + ->push(['foo' => 'bar'], 200) + ->pushStatus(404), +]); +``` When all the responses in a response sequence have been consumed, any further requests will cause the response sequence to throw an exception. If you would like to specify a default response that should be returned when a sequence is empty, you may use the `whenEmpty` method: - Http::fake([ - // Stub a series of responses for GitHub endpoints... - 'github.com/*' => Http::sequence() - ->push('Hello World', 200) - ->push(['foo' => 'bar'], 200) - ->whenEmpty(Http::response()), - ]); +```php +Http::fake([ + // Stub a series of responses for GitHub endpoints... + 'github.com/*' => Http::sequence() + ->push('Hello World', 200) + ->push(['foo' => 'bar'], 200) + ->whenEmpty(Http::response()), +]); +``` If you would like to fake a sequence of responses but do not need to specify a specific URL pattern that should be faked, you may use the `Http::fakeSequence` method: - Http::fakeSequence() - ->push('Hello World', 200) - ->whenEmpty(Http::response()); +```php +Http::fakeSequence() + ->push('Hello World', 200) + ->whenEmpty(Http::response()); +``` #### Fake Callback If you require more complicated logic to determine what responses to return for certain endpoints, you may pass a closure to the `fake` method. This closure will receive an instance of `Illuminate\Http\Client\Request` and should return a response instance. Within your closure, you may perform whatever logic is necessary to determine what type of response to return: - use Illuminate\Http\Client\Request; +```php +use Illuminate\Http\Client\Request; - Http::fake(function (Request $request) { - return Http::response('Hello World', 200); - }); +Http::fake(function (Request $request) { + return Http::response('Hello World', 200); +}); +``` ### Preventing Stray Requests If you would like to ensure that all requests sent via the HTTP client have been faked throughout your individual test or complete test suite, you can call the `preventStrayRequests` method. After calling this method, any requests that do not have a corresponding fake response will throw an exception rather than making the actual HTTP request: - use Illuminate\Support\Facades\Http; +```php +use Illuminate\Support\Facades\Http; - Http::preventStrayRequests(); +Http::preventStrayRequests(); - Http::fake([ - 'github.com/*' => Http::response('ok'), - ]); +Http::fake([ + 'github.com/*' => Http::response('ok'), +]); - // An "ok" response is returned... - Http::get('https://github.com/laravel/framework'); +// An "ok" response is returned... +Http::get('https://github.com/laravel/framework'); - // An exception is thrown... - Http::get('https://laravel.com'); +// An exception is thrown... +Http::get('https://laravel.com'); +``` ### Inspecting Requests @@ -623,52 +713,60 @@ When faking responses, you may occasionally wish to inspect the requests the cli The `assertSent` method accepts a closure which will receive an `Illuminate\Http\Client\Request` instance and should return a boolean value indicating if the request matches your expectations. In order for the test to pass, at least one request must have been issued matching the given expectations: - use Illuminate\Http\Client\Request; - use Illuminate\Support\Facades\Http; +```php +use Illuminate\Http\Client\Request; +use Illuminate\Support\Facades\Http; - Http::fake(); +Http::fake(); - Http::withHeaders([ - 'X-First' => 'foo', - ])->post('http://example.com/users', [ - 'name' => 'Taylor', - 'role' => 'Developer', - ]); +Http::withHeaders([ + 'X-First' => 'foo', +])->post('http://example.com/users', [ + 'name' => 'Taylor', + 'role' => 'Developer', +]); - Http::assertSent(function (Request $request) { - return $request->hasHeader('X-First', 'foo') && - $request->url() == 'http://example.com/users' && - $request['name'] == 'Taylor' && - $request['role'] == 'Developer'; - }); +Http::assertSent(function (Request $request) { + return $request->hasHeader('X-First', 'foo') && + $request->url() == 'http://example.com/users' && + $request['name'] == 'Taylor' && + $request['role'] == 'Developer'; +}); +``` If needed, you may assert that a specific request was not sent using the `assertNotSent` method: - use Illuminate\Http\Client\Request; - use Illuminate\Support\Facades\Http; +```php +use Illuminate\Http\Client\Request; +use Illuminate\Support\Facades\Http; - Http::fake(); +Http::fake(); - Http::post('http://example.com/users', [ - 'name' => 'Taylor', - 'role' => 'Developer', - ]); +Http::post('http://example.com/users', [ + 'name' => 'Taylor', + 'role' => 'Developer', +]); - Http::assertNotSent(function (Request $request) { - return $request->url() === 'http://example.com/posts'; - }); +Http::assertNotSent(function (Request $request) { + return $request->url() === 'http://example.com/posts'; +}); +``` You may use the `assertSentCount` method to assert how many requests were "sent" during the test: - Http::fake(); +```php +Http::fake(); - Http::assertSentCount(5); +Http::assertSentCount(5); +``` Or, you may use the `assertNothingSent` method to assert that no requests were sent during the test: - Http::fake(); +```php +Http::fake(); - Http::assertNothingSent(); +Http::assertNothingSent(); +``` #### Recording Requests / Responses @@ -716,15 +814,17 @@ Laravel fires three events during the process of sending HTTP requests. The `Req The `RequestSending` and `ConnectionFailed` events both contain a public `$request` property that you may use to inspect the `Illuminate\Http\Client\Request` instance. Likewise, the `ResponseReceived` event contains a `$request` property as well as a `$response` property which may be used to inspect the `Illuminate\Http\Client\Response` instance. You may create [event listeners](/docs/{{version}}/events) for these events within your application: - use Illuminate\Http\Client\Events\RequestSending; +```php +use Illuminate\Http\Client\Events\RequestSending; - class LogRequest +class LogRequest +{ + /** + * Handle the given event. + */ + public function handle(RequestSending $event): void { - /** - * Handle the given event. - */ - public function handle(RequestSending $event): void - { - // $event->request ... - } + // $event->request ... } +} +``` diff --git a/http-tests.md b/http-tests.md index e64e4e66bf1..29a978a6802 100644 --- a/http-tests.md +++ b/http-tests.md @@ -257,7 +257,9 @@ class ExampleTest extends TestCase You may also specify which guard should be used to authenticate the given user by passing the guard name as the second argument to the `actingAs` method. The guard that is provided to the `actingAs` method will also become the default guard for the duration of the test: - $this->actingAs($user, 'web') +```php +$this->actingAs($user, 'web') +``` ### Debugging Responses @@ -410,11 +412,15 @@ Exceptions::assertNothingReported(); You may totally disable exception handling for a given request by invoking the `withoutExceptionHandling` method before making your request: - $response = $this->withoutExceptionHandling()->get('/'); +```php +$response = $this->withoutExceptionHandling()->get('/'); +``` In addition, if you would like to ensure that your application is not utilizing features that have been deprecated by the PHP language or the libraries your application is using, you may invoke the `withoutDeprecationHandling` method before making your request. When deprecation handling is disabled, deprecation warnings will be converted to exceptions, thus causing your test to fail: - $response = $this->withoutDeprecationHandling()->get('/'); +```php +$response = $this->withoutDeprecationHandling()->get('/'); +``` The `assertThrows` method may be used to assert that code within a given closure throws an exception of the specified type: @@ -578,7 +584,9 @@ class ExampleTest extends TestCase The `assertJsonPath` method also accepts a closure, which may be used to dynamically determine if the assertion should pass: - $response->assertJsonPath('team.owner.name', fn (string $name) => strlen($name) >= 3); +```php +$response->assertJsonPath('team.owner.name', fn (string $name) => strlen($name) >= 3); +``` ### Fluent JSON Testing @@ -638,108 +646,128 @@ However, you should be aware that not including the `etc` method in your asserti To assert that an attribute is present or absent, you may use the `has` and `missing` methods: - $response->assertJson(fn (AssertableJson $json) => - $json->has('data') - ->missing('message') - ); +```php +$response->assertJson(fn (AssertableJson $json) => + $json->has('data') + ->missing('message') +); +``` In addition, the `hasAll` and `missingAll` methods allow asserting the presence or absence of multiple attributes simultaneously: - $response->assertJson(fn (AssertableJson $json) => - $json->hasAll(['status', 'data']) - ->missingAll(['message', 'code']) - ); +```php +$response->assertJson(fn (AssertableJson $json) => + $json->hasAll(['status', 'data']) + ->missingAll(['message', 'code']) +); +``` You may use the `hasAny` method to determine if at least one of a given list of attributes is present: - $response->assertJson(fn (AssertableJson $json) => - $json->has('status') - ->hasAny('data', 'message', 'code') - ); +```php +$response->assertJson(fn (AssertableJson $json) => + $json->has('status') + ->hasAny('data', 'message', 'code') +); +``` #### Asserting Against JSON Collections Often, your route will return a JSON response that contains multiple items, such as multiple users: - Route::get('/users', function () { - return User::all(); - }); +```php +Route::get('/users', function () { + return User::all(); +}); +``` In these situations, we may use the fluent JSON object's `has` method to make assertions against the users included in the response. For example, let's assert that the JSON response contains three users. Next, we'll make some assertions about the first user in the collection using the `first` method. The `first` method accepts a closure which receives another assertable JSON string that we can use to make assertions about the first object in the JSON collection: - $response - ->assertJson(fn (AssertableJson $json) => - $json->has(3) - ->first(fn (AssertableJson $json) => - $json->where('id', 1) - ->where('name', 'Victoria Faith') - ->where('email', fn (string $email) => str($email)->is('victoria@gmail.com')) - ->missing('password') - ->etc() - ) - ); +```php +$response + ->assertJson(fn (AssertableJson $json) => + $json->has(3) + ->first(fn (AssertableJson $json) => + $json->where('id', 1) + ->where('name', 'Victoria Faith') + ->where('email', fn (string $email) => str($email)->is('victoria@gmail.com')) + ->missing('password') + ->etc() + ) + ); +``` #### Scoping JSON Collection Assertions Sometimes, your application's routes will return JSON collections that are assigned named keys: - Route::get('/users', function () { - return [ - 'meta' => [...], - 'users' => User::all(), - ]; - }) +```php +Route::get('/users', function () { + return [ + 'meta' => [...], + 'users' => User::all(), + ]; +}) +``` When testing these routes, you may use the `has` method to assert against the number of items in the collection. In addition, you may use the `has` method to scope a chain of assertions: - $response - ->assertJson(fn (AssertableJson $json) => - $json->has('meta') - ->has('users', 3) - ->has('users.0', fn (AssertableJson $json) => - $json->where('id', 1) - ->where('name', 'Victoria Faith') - ->where('email', fn (string $email) => str($email)->is('victoria@gmail.com')) - ->missing('password') - ->etc() - ) - ); +```php +$response + ->assertJson(fn (AssertableJson $json) => + $json->has('meta') + ->has('users', 3) + ->has('users.0', fn (AssertableJson $json) => + $json->where('id', 1) + ->where('name', 'Victoria Faith') + ->where('email', fn (string $email) => str($email)->is('victoria@gmail.com')) + ->missing('password') + ->etc() + ) + ); +``` However, instead of making two separate calls to the `has` method to assert against the `users` collection, you may make a single call which provides a closure as its third parameter. When doing so, the closure will automatically be invoked and scoped to the first item in the collection: - $response - ->assertJson(fn (AssertableJson $json) => - $json->has('meta') - ->has('users', 3, fn (AssertableJson $json) => - $json->where('id', 1) - ->where('name', 'Victoria Faith') - ->where('email', fn (string $email) => str($email)->is('victoria@gmail.com')) - ->missing('password') - ->etc() - ) - ); +```php +$response + ->assertJson(fn (AssertableJson $json) => + $json->has('meta') + ->has('users', 3, fn (AssertableJson $json) => + $json->where('id', 1) + ->where('name', 'Victoria Faith') + ->where('email', fn (string $email) => str($email)->is('victoria@gmail.com')) + ->missing('password') + ->etc() + ) + ); +``` #### Asserting JSON Types You may only want to assert that the properties in the JSON response are of a certain type. The `Illuminate\Testing\Fluent\AssertableJson` class provides the `whereType` and `whereAllType` methods for doing just that: - $response->assertJson(fn (AssertableJson $json) => - $json->whereType('id', 'integer') - ->whereAllType([ - 'users.0.name' => 'string', - 'meta' => 'array' - ]) - ); +```php +$response->assertJson(fn (AssertableJson $json) => + $json->whereType('id', 'integer') + ->whereAllType([ + 'users.0.name' => 'string', + 'meta' => 'array' + ]) +); +``` You may specify multiple types using the `|` character, or passing an array of types as the second parameter to the `whereType` method. The assertion will be successful if the response value is any of the listed types: - $response->assertJson(fn (AssertableJson $json) => - $json->whereType('name', 'string|null') - ->whereType('id', ['string', 'integer']) - ); +```php +$response->assertJson(fn (AssertableJson $json) => + $json->whereType('name', 'string|null') + ->whereType('id', ['string', 'integer']) +); +``` The `whereType` and `whereAllType` methods recognize the following types: `string`, `integer`, `double`, `boolean`, `array`, and `null`. @@ -795,28 +823,36 @@ class ExampleTest extends TestCase If you would like to assert that a given file does not exist, you may use the `assertMissing` method provided by the `Storage` facade: - Storage::fake('avatars'); +```php +Storage::fake('avatars'); - // ... +// ... - Storage::disk('avatars')->assertMissing('missing.jpg'); +Storage::disk('avatars')->assertMissing('missing.jpg'); +``` #### Fake File Customization When creating files using the `fake` method provided by the `UploadedFile` class, you may specify the width, height, and size of the image (in kilobytes) in order to better test your application's validation rules: - UploadedFile::fake()->image('avatar.jpg', $width, $height)->size(100); +```php +UploadedFile::fake()->image('avatar.jpg', $width, $height)->size(100); +``` In addition to creating images, you may create files of any other type using the `create` method: - UploadedFile::fake()->create('document.pdf', $sizeInKilobytes); +```php +UploadedFile::fake()->create('document.pdf', $sizeInKilobytes); +``` If needed, you may pass a `$mimeType` argument to the method to explicitly define the MIME type that should be returned by the file: - UploadedFile::fake()->create( - 'document.pdf', $sizeInKilobytes, 'application/pdf' - ); +```php +UploadedFile::fake()->create( + 'document.pdf', $sizeInKilobytes, 'application/pdf' +); +``` ## Testing Views @@ -855,36 +891,44 @@ The `TestView` class provides the following assertion methods: `assertSee`, `ass If needed, you may get the raw, rendered view contents by casting the `TestView` instance to a string: - $contents = (string) $this->view('welcome'); +```php +$contents = (string) $this->view('welcome'); +``` #### Sharing Errors Some views may depend on errors shared in the [global error bag provided by Laravel](/docs/{{version}}/validation#quick-displaying-the-validation-errors). To hydrate the error bag with error messages, you may use the `withViewErrors` method: - $view = $this->withViewErrors([ - 'name' => ['Please provide a valid name.'] - ])->view('form'); +```php +$view = $this->withViewErrors([ + 'name' => ['Please provide a valid name.'] +])->view('form'); - $view->assertSee('Please provide a valid name.'); +$view->assertSee('Please provide a valid name.'); +``` ### Rendering Blade and Components If necessary, you may use the `blade` method to evaluate and render a raw [Blade](/docs/{{version}}/blade) string. Like the `view` method, the `blade` method returns an instance of `Illuminate\Testing\TestView`: - $view = $this->blade( - '', - ['name' => 'Taylor'] - ); +```php +$view = $this->blade( + '', + ['name' => 'Taylor'] +); - $view->assertSee('Taylor'); +$view->assertSee('Taylor'); +``` You may use the `component` method to evaluate and render a [Blade component](/docs/{{version}}/blade#components). The `component` method returns an instance of `Illuminate\Testing\TestComponent`: - $view = $this->component(Profile::class, ['name' => 'Taylor']); +```php +$view = $this->component(Profile::class, ['name' => 'Taylor']); - $view->assertSee('Taylor'); +$view->assertSee('Taylor'); +``` ## Available Assertions @@ -990,95 +1034,123 @@ Laravel's `Illuminate\Testing\TestResponse` class provides a variety of custom a Assert that the response has a bad request (400) HTTP status code: - $response->assertBadRequest(); +```php +$response->assertBadRequest(); +``` #### assertAccepted Assert that the response has an accepted (202) HTTP status code: - $response->assertAccepted(); +```php +$response->assertAccepted(); +``` #### assertConflict Assert that the response has a conflict (409) HTTP status code: - $response->assertConflict(); +```php +$response->assertConflict(); +``` #### assertCookie Assert that the response contains the given cookie: - $response->assertCookie($cookieName, $value = null); +```php +$response->assertCookie($cookieName, $value = null); +``` #### assertCookieExpired Assert that the response contains the given cookie and it is expired: - $response->assertCookieExpired($cookieName); +```php +$response->assertCookieExpired($cookieName); +``` #### assertCookieNotExpired Assert that the response contains the given cookie and it is not expired: - $response->assertCookieNotExpired($cookieName); +```php +$response->assertCookieNotExpired($cookieName); +``` #### assertCookieMissing Assert that the response does not contain the given cookie: - $response->assertCookieMissing($cookieName); +```php +$response->assertCookieMissing($cookieName); +``` #### assertCreated Assert that the response has a 201 HTTP status code: - $response->assertCreated(); +```php +$response->assertCreated(); +``` #### assertDontSee Assert that the given string is not contained within the response returned by the application. This assertion will automatically escape the given string unless you pass a second argument of `false`: - $response->assertDontSee($value, $escaped = true); +```php +$response->assertDontSee($value, $escaped = true); +``` #### assertDontSeeText Assert that the given string is not contained within the response text. This assertion will automatically escape the given string unless you pass a second argument of `false`. This method will pass the response content to the `strip_tags` PHP function before making the assertion: - $response->assertDontSeeText($value, $escaped = true); +```php +$response->assertDontSeeText($value, $escaped = true); +``` #### assertDownload Assert that the response is a "download". Typically, this means the invoked route that returned the response returned a `Response::download` response, `BinaryFileResponse`, or `Storage::download` response: - $response->assertDownload(); +```php +$response->assertDownload(); +``` If you wish, you may assert that the downloadable file was assigned a given file name: - $response->assertDownload('image.jpg'); +```php +$response->assertDownload('image.jpg'); +``` #### assertExactJson Assert that the response contains an exact match of the given JSON data: - $response->assertExactJson(array $data); +```php +$response->assertExactJson(array $data); +``` #### assertExactJsonStructure Assert that the response contains an exact match of the given JSON structure: - $response->assertExactJsonStructure(array $data); +```php +$response->assertExactJsonStructure(array $data); +``` This method is a more strict variant of [assertJsonStructure](#assert-json-structure). In contrast with `assertJsonStructure`, this method will fail if the response contains any keys that aren't explicitly included in the expected JSON structure. @@ -1087,49 +1159,63 @@ This method is a more strict variant of [assertJsonStructure](#assert-json-struc Assert that the response has a forbidden (403) HTTP status code: - $response->assertForbidden(); +```php +$response->assertForbidden(); +``` #### assertFound Assert that the response has a found (302) HTTP status code: - $response->assertFound(); +```php +$response->assertFound(); +``` #### assertGone Assert that the response has a gone (410) HTTP status code: - $response->assertGone(); +```php +$response->assertGone(); +``` #### assertHeader Assert that the given header and value is present on the response: - $response->assertHeader($headerName, $value = null); +```php +$response->assertHeader($headerName, $value = null); +``` #### assertHeaderMissing Assert that the given header is not present on the response: - $response->assertHeaderMissing($headerName); +```php +$response->assertHeaderMissing($headerName); +``` #### assertInternalServerError Assert that the response has an "Internal Server Error" (500) HTTP status code: - $response->assertInternalServerError(); +```php +$response->assertInternalServerError(); +``` #### assertJson Assert that the response contains the given JSON data: - $response->assertJson(array $data, $strict = false); +```php +$response->assertJson(array $data, $strict = false); +``` The `assertJson` method converts the response to an array to verify that the given array exists within the JSON response returned by the application. So, if there are other properties in the JSON response, this test will still pass as long as the given fragment is present. @@ -1138,59 +1224,73 @@ The `assertJson` method converts the response to an array to verify that the giv Assert that the response JSON has an array with the expected number of items at the given key: - $response->assertJsonCount($count, $key = null); +```php +$response->assertJsonCount($count, $key = null); +``` #### assertJsonFragment Assert that the response contains the given JSON data anywhere in the response: - Route::get('/users', function () { - return [ - 'users' => [ - [ - 'name' => 'Taylor Otwell', - ], +```php +Route::get('/users', function () { + return [ + 'users' => [ + [ + 'name' => 'Taylor Otwell', ], - ]; - }); + ], + ]; +}); - $response->assertJsonFragment(['name' => 'Taylor Otwell']); +$response->assertJsonFragment(['name' => 'Taylor Otwell']); +``` #### assertJsonIsArray Assert that the response JSON is an array: - $response->assertJsonIsArray(); +```php +$response->assertJsonIsArray(); +``` #### assertJsonIsObject Assert that the response JSON is an object: - $response->assertJsonIsObject(); +```php +$response->assertJsonIsObject(); +``` #### assertJsonMissing Assert that the response does not contain the given JSON data: - $response->assertJsonMissing(array $data); +```php +$response->assertJsonMissing(array $data); +``` #### assertJsonMissingExact Assert that the response does not contain the exact JSON data: - $response->assertJsonMissingExact(array $data); +```php +$response->assertJsonMissingExact(array $data); +``` #### assertJsonMissingValidationErrors Assert that the response has no JSON validation errors for the given keys: - $response->assertJsonMissingValidationErrors($keys); +```php +$response->assertJsonMissingValidationErrors($keys); +``` > [!NOTE] > The more generic [assertValid](#assert-valid) method may be used to assert that a response does not have validation errors that were returned as JSON **and** that no errors were flashed to session storage. @@ -1200,7 +1300,9 @@ Assert that the response has no JSON validation errors for the given keys: Assert that the response contains the given data at the specified path: - $response->assertJsonPath($path, $expectedValue); +```php +$response->assertJsonPath($path, $expectedValue); +``` For example, if the following JSON response is returned by your application: @@ -1214,14 +1316,18 @@ For example, if the following JSON response is returned by your application: You may assert that the `name` property of the `user` object matches a given value like so: - $response->assertJsonPath('user.name', 'Steve Schoger'); +```php +$response->assertJsonPath('user.name', 'Steve Schoger'); +``` #### assertJsonMissingPath Assert that the response does not contain the given path: - $response->assertJsonMissingPath($path); +```php +$response->assertJsonMissingPath($path); +``` For example, if the following JSON response is returned by your application: @@ -1235,14 +1341,18 @@ For example, if the following JSON response is returned by your application: You may assert that it does not contain the `email` property of the `user` object: - $response->assertJsonMissingPath('user.email'); +```php +$response->assertJsonMissingPath('user.email'); +``` #### assertJsonStructure Assert that the response has a given JSON structure: - $response->assertJsonStructure(array $structure); +```php +$response->assertJsonStructure(array $structure); +``` For example, if the JSON response returned by your application contains the following data: @@ -1256,11 +1366,13 @@ For example, if the JSON response returned by your application contains the foll You may assert that the JSON structure matches your expectations like so: - $response->assertJsonStructure([ - 'user' => [ - 'name', - ] - ]); +```php +$response->assertJsonStructure([ + 'user' => [ + 'name', + ] +]); +``` Sometimes, JSON responses returned by your application may contain arrays of objects: @@ -1283,22 +1395,26 @@ Sometimes, JSON responses returned by your application may contain arrays of obj In this situation, you may use the `*` character to assert against the structure of all of the objects in the array: - $response->assertJsonStructure([ - 'user' => [ - '*' => [ - 'name', - 'age', - 'location' - ] +```php +$response->assertJsonStructure([ + 'user' => [ + '*' => [ + 'name', + 'age', + 'location' ] - ]); + ] +]); +``` #### assertJsonValidationErrors Assert that the response has the given JSON validation errors for the given keys. This method should be used when asserting against responses where the validation errors are returned as a JSON structure instead of being flashed to the session: - $response->assertJsonValidationErrors(array $data, $responseKey = 'errors'); +```php +$response->assertJsonValidationErrors(array $data, $responseKey = 'errors'); +``` > [!NOTE] > The more generic [assertInvalid](#assert-invalid) method may be used to assert that a response has validation errors returned as JSON **or** that errors were flashed to session storage. @@ -1308,215 +1424,277 @@ Assert that the response has the given JSON validation errors for the given keys Assert the response has any JSON validation errors for the given key: - $response->assertJsonValidationErrorFor(string $key, $responseKey = 'errors'); +```php +$response->assertJsonValidationErrorFor(string $key, $responseKey = 'errors'); +``` #### assertMethodNotAllowed Assert that the response has a method not allowed (405) HTTP status code: - $response->assertMethodNotAllowed(); +```php +$response->assertMethodNotAllowed(); +``` #### assertMovedPermanently Assert that the response has a moved permanently (301) HTTP status code: - $response->assertMovedPermanently(); +```php +$response->assertMovedPermanently(); +``` #### assertLocation Assert that the response has the given URI value in the `Location` header: - $response->assertLocation($uri); +```php +$response->assertLocation($uri); +``` #### assertContent Assert that the given string matches the response content: - $response->assertContent($value); +```php +$response->assertContent($value); +``` #### assertNoContent Assert that the response has the given HTTP status code and no content: - $response->assertNoContent($status = 204); +```php +$response->assertNoContent($status = 204); +``` #### assertStreamedContent Assert that the given string matches the streamed response content: - $response->assertStreamedContent($value); +```php +$response->assertStreamedContent($value); +``` #### assertNotFound Assert that the response has a not found (404) HTTP status code: - $response->assertNotFound(); +```php +$response->assertNotFound(); +``` #### assertOk Assert that the response has a 200 HTTP status code: - $response->assertOk(); +```php +$response->assertOk(); +``` #### assertPaymentRequired Assert that the response has a payment required (402) HTTP status code: - $response->assertPaymentRequired(); +```php +$response->assertPaymentRequired(); +``` #### assertPlainCookie Assert that the response contains the given unencrypted cookie: - $response->assertPlainCookie($cookieName, $value = null); +```php +$response->assertPlainCookie($cookieName, $value = null); +``` #### assertRedirect Assert that the response is a redirect to the given URI: - $response->assertRedirect($uri = null); +```php +$response->assertRedirect($uri = null); +``` #### assertRedirectContains Assert whether the response is redirecting to a URI that contains the given string: - $response->assertRedirectContains($string); +```php +$response->assertRedirectContains($string); +``` #### assertRedirectToRoute Assert that the response is a redirect to the given [named route](/docs/{{version}}/routing#named-routes): - $response->assertRedirectToRoute($name, $parameters = []); +```php +$response->assertRedirectToRoute($name, $parameters = []); +``` #### assertRedirectToSignedRoute Assert that the response is a redirect to the given [signed route](/docs/{{version}}/urls#signed-urls): - $response->assertRedirectToSignedRoute($name = null, $parameters = []); +```php +$response->assertRedirectToSignedRoute($name = null, $parameters = []); +``` #### assertRequestTimeout Assert that the response has a request timeout (408) HTTP status code: - $response->assertRequestTimeout(); +```php +$response->assertRequestTimeout(); +``` #### assertSee Assert that the given string is contained within the response. This assertion will automatically escape the given string unless you pass a second argument of `false`: - $response->assertSee($value, $escaped = true); +```php +$response->assertSee($value, $escaped = true); +``` #### assertSeeInOrder Assert that the given strings are contained in order within the response. This assertion will automatically escape the given strings unless you pass a second argument of `false`: - $response->assertSeeInOrder(array $values, $escaped = true); +```php +$response->assertSeeInOrder(array $values, $escaped = true); +``` #### assertSeeText Assert that the given string is contained within the response text. This assertion will automatically escape the given string unless you pass a second argument of `false`. The response content will be passed to the `strip_tags` PHP function before the assertion is made: - $response->assertSeeText($value, $escaped = true); +```php +$response->assertSeeText($value, $escaped = true); +``` #### assertSeeTextInOrder Assert that the given strings are contained in order within the response text. This assertion will automatically escape the given strings unless you pass a second argument of `false`. The response content will be passed to the `strip_tags` PHP function before the assertion is made: - $response->assertSeeTextInOrder(array $values, $escaped = true); +```php +$response->assertSeeTextInOrder(array $values, $escaped = true); +``` #### assertServerError Assert that the response has a server error (>= 500 , < 600) HTTP status code: - $response->assertServerError(); +```php +$response->assertServerError(); +``` #### assertServiceUnavailable Assert that the response has a "Service Unavailable" (503) HTTP status code: - $response->assertServiceUnavailable(); +```php +$response->assertServiceUnavailable(); +``` #### assertSessionHas Assert that the session contains the given piece of data: - $response->assertSessionHas($key, $value = null); +```php +$response->assertSessionHas($key, $value = null); +``` If needed, a closure can be provided as the second argument to the `assertSessionHas` method. The assertion will pass if the closure returns `true`: - $response->assertSessionHas($key, function (User $value) { - return $value->name === 'Taylor Otwell'; - }); +```php +$response->assertSessionHas($key, function (User $value) { + return $value->name === 'Taylor Otwell'; +}); +``` #### assertSessionHasInput Assert that the session has a given value in the [flashed input array](/docs/{{version}}/responses#redirecting-with-flashed-session-data): - $response->assertSessionHasInput($key, $value = null); +```php +$response->assertSessionHasInput($key, $value = null); +``` If needed, a closure can be provided as the second argument to the `assertSessionHasInput` method. The assertion will pass if the closure returns `true`: - use Illuminate\Support\Facades\Crypt; +```php +use Illuminate\Support\Facades\Crypt; - $response->assertSessionHasInput($key, function (string $value) { - return Crypt::decryptString($value) === 'secret'; - }); +$response->assertSessionHasInput($key, function (string $value) { + return Crypt::decryptString($value) === 'secret'; +}); +``` #### assertSessionHasAll Assert that the session contains a given array of key / value pairs: - $response->assertSessionHasAll(array $data); +```php +$response->assertSessionHasAll(array $data); +``` For example, if your application's session contains `name` and `status` keys, you may assert that both exist and have the specified values like so: - $response->assertSessionHasAll([ - 'name' => 'Taylor Otwell', - 'status' => 'active', - ]); +```php +$response->assertSessionHasAll([ + 'name' => 'Taylor Otwell', + 'status' => 'active', +]); +``` #### assertSessionHasErrors Assert that the session contains an error for the given `$keys`. If `$keys` is an associative array, assert that the session contains a specific error message (value) for each field (key). This method should be used when testing routes that flash validation errors to the session instead of returning them as a JSON structure: - $response->assertSessionHasErrors( - array $keys = [], $format = null, $errorBag = 'default' - ); +```php +$response->assertSessionHasErrors( + array $keys = [], $format = null, $errorBag = 'default' +); +``` For example, to assert that the `name` and `email` fields have validation error messages that were flashed to the session, you may invoke the `assertSessionHasErrors` method like so: - $response->assertSessionHasErrors(['name', 'email']); +```php +$response->assertSessionHasErrors(['name', 'email']); +``` Or, you may assert that a given field has a particular validation error message: - $response->assertSessionHasErrors([ - 'name' => 'The given name was invalid.' - ]); +```php +$response->assertSessionHasErrors([ + 'name' => 'The given name was invalid.' +]); +``` > [!NOTE] > The more generic [assertInvalid](#assert-invalid) method may be used to assert that a response has validation errors returned as JSON **or** that errors were flashed to session storage. @@ -1526,21 +1704,27 @@ Or, you may assert that a given field has a particular validation error message: Assert that the session contains an error for the given `$keys` within a specific [error bag](/docs/{{version}}/validation#named-error-bags). If `$keys` is an associative array, assert that the session contains a specific error message (value) for each field (key), within the error bag: - $response->assertSessionHasErrorsIn($errorBag, $keys = [], $format = null); +```php +$response->assertSessionHasErrorsIn($errorBag, $keys = [], $format = null); +``` #### assertSessionHasNoErrors Assert that the session has no validation errors: - $response->assertSessionHasNoErrors(); +```php +$response->assertSessionHasNoErrors(); +``` #### assertSessionDoesntHaveErrors Assert that the session has no validation errors for the given keys: - $response->assertSessionDoesntHaveErrors($keys = [], $format = null, $errorBag = 'default'); +```php +$response->assertSessionDoesntHaveErrors($keys = [], $format = null, $errorBag = 'default'); +``` > [!NOTE] > The more generic [assertValid](#assert-valid) method may be used to assert that a response does not have validation errors that were returned as JSON **and** that no errors were flashed to session storage. @@ -1550,87 +1734,111 @@ Assert that the session has no validation errors for the given keys: Assert that the session does not contain the given key: - $response->assertSessionMissing($key); +```php +$response->assertSessionMissing($key); +``` #### assertStatus Assert that the response has a given HTTP status code: - $response->assertStatus($code); +```php +$response->assertStatus($code); +``` #### assertSuccessful Assert that the response has a successful (>= 200 and < 300) HTTP status code: - $response->assertSuccessful(); +```php +$response->assertSuccessful(); +``` #### assertTooManyRequests Assert that the response has a too many requests (429) HTTP status code: - $response->assertTooManyRequests(); +```php +$response->assertTooManyRequests(); +``` #### assertUnauthorized Assert that the response has an unauthorized (401) HTTP status code: - $response->assertUnauthorized(); +```php +$response->assertUnauthorized(); +``` #### assertUnprocessable Assert that the response has an unprocessable entity (422) HTTP status code: - $response->assertUnprocessable(); +```php +$response->assertUnprocessable(); +``` #### assertUnsupportedMediaType Assert that the response has an unsupported media type (415) HTTP status code: - $response->assertUnsupportedMediaType(); +```php +$response->assertUnsupportedMediaType(); +``` #### assertValid Assert that the response has no validation errors for the given keys. This method may be used for asserting against responses where the validation errors are returned as a JSON structure or where the validation errors have been flashed to the session: - // Assert that no validation errors are present... - $response->assertValid(); +```php +// Assert that no validation errors are present... +$response->assertValid(); - // Assert that the given keys do not have validation errors... - $response->assertValid(['name', 'email']); +// Assert that the given keys do not have validation errors... +$response->assertValid(['name', 'email']); +``` #### assertInvalid Assert that the response has validation errors for the given keys. This method may be used for asserting against responses where the validation errors are returned as a JSON structure or where the validation errors have been flashed to the session: - $response->assertInvalid(['name', 'email']); +```php +$response->assertInvalid(['name', 'email']); +``` You may also assert that a given key has a particular validation error message. When doing so, you may provide the entire message or only a small portion of the message: - $response->assertInvalid([ - 'name' => 'The name field is required.', - 'email' => 'valid email address', - ]); +```php +$response->assertInvalid([ + 'name' => 'The name field is required.', + 'email' => 'valid email address', +]); +``` #### assertViewHas Assert that the response view contains a given piece of data: - $response->assertViewHas($key, $value = null); +```php +$response->assertViewHas($key, $value = null); +``` Passing a closure as the second argument to the `assertViewHas` method will allow you to inspect and make assertions against a particular piece of view data: - $response->assertViewHas('user', function (User $user) { - return $user->name === 'Taylor'; - }); +```php +$response->assertViewHas('user', function (User $user) { + return $user->name === 'Taylor'; +}); +``` In addition, view data may be accessed as array variables on the response, allowing you to conveniently inspect it: @@ -1647,35 +1855,45 @@ $this->assertEquals('Taylor', $response['name']); Assert that the response view has a given list of data: - $response->assertViewHasAll(array $data); +```php +$response->assertViewHasAll(array $data); +``` This method may be used to assert that the view simply contains data matching the given keys: - $response->assertViewHasAll([ - 'name', - 'email', - ]); +```php +$response->assertViewHasAll([ + 'name', + 'email', +]); +``` Or, you may assert that the view data is present and has specific values: - $response->assertViewHasAll([ - 'name' => 'Taylor Otwell', - 'email' => 'taylor@example.com,', - ]); +```php +$response->assertViewHasAll([ + 'name' => 'Taylor Otwell', + 'email' => 'taylor@example.com,', +]); +``` #### assertViewIs Assert that the given view was returned by the route: - $response->assertViewIs($value); +```php +$response->assertViewIs($value); +``` #### assertViewMissing Assert that the given data key was not made available to the view returned in the application's response: - $response->assertViewMissing($key); +```php +$response->assertViewMissing($key); +``` ### Authentication Assertions @@ -1687,21 +1905,27 @@ Laravel also provides a variety of authentication related assertions that you ma Assert that a user is authenticated: - $this->assertAuthenticated($guard = null); +```php +$this->assertAuthenticated($guard = null); +``` #### assertGuest Assert that a user is not authenticated: - $this->assertGuest($guard = null); +```php +$this->assertGuest($guard = null); +``` #### assertAuthenticatedAs Assert that a specific user is authenticated: - $this->assertAuthenticatedAs($user, $guard = null); +```php +$this->assertAuthenticatedAs($user, $guard = null); +``` ## Validation Assertions @@ -1713,22 +1937,28 @@ Laravel provides two primary validation related assertions that you may use to e Assert that the response has no validation errors for the given keys. This method may be used for asserting against responses where the validation errors are returned as a JSON structure or where the validation errors have been flashed to the session: - // Assert that no validation errors are present... - $response->assertValid(); +```php +// Assert that no validation errors are present... +$response->assertValid(); - // Assert that the given keys do not have validation errors... - $response->assertValid(['name', 'email']); +// Assert that the given keys do not have validation errors... +$response->assertValid(['name', 'email']); +``` #### assertInvalid Assert that the response has validation errors for the given keys. This method may be used for asserting against responses where the validation errors are returned as a JSON structure or where the validation errors have been flashed to the session: - $response->assertInvalid(['name', 'email']); +```php +$response->assertInvalid(['name', 'email']); +``` You may also assert that a given key has a particular validation error message. When doing so, you may provide the entire message or only a small portion of the message: - $response->assertInvalid([ - 'name' => 'The name field is required.', - 'email' => 'valid email address', - ]); +```php +$response->assertInvalid([ + 'name' => 'The name field is required.', + 'email' => 'valid email address', +]); +``` diff --git a/localization.md b/localization.md index c5e0313db6b..7022bffe5b9 100644 --- a/localization.md +++ b/localization.md @@ -22,17 +22,21 @@ Laravel's localization features provide a convenient way to retrieve strings in Laravel provides two ways to manage translation strings. First, language strings may be stored in files within the application's `lang` directory. Within this directory, there may be subdirectories for each language supported by the application. This is the approach Laravel uses to manage translation strings for built-in Laravel features such as validation error messages: - /lang - /en - messages.php - /es - messages.php +```text +/lang + /en + messages.php + /es + messages.php +``` Or, translation strings may be defined within JSON files that are placed within the `lang` directory. When taking this approach, each language supported by your application would have a corresponding JSON file within this directory. This approach is recommended for applications that have a large number of translatable strings: - /lang - en.json - es.json +```text +/lang + en.json + es.json +``` We'll discuss each approach to managing translation strings within this documentation. @@ -54,47 +58,53 @@ You may also configure a "fallback language", which will be used when the defaul You may modify the default language for a single HTTP request at runtime using the `setLocale` method provided by the `App` facade: - use Illuminate\Support\Facades\App; +```php +use Illuminate\Support\Facades\App; - Route::get('/greeting/{locale}', function (string $locale) { - if (! in_array($locale, ['en', 'es', 'fr'])) { - abort(400); - } +Route::get('/greeting/{locale}', function (string $locale) { + if (! in_array($locale, ['en', 'es', 'fr'])) { + abort(400); + } - App::setLocale($locale); + App::setLocale($locale); - // ... - }); + // ... +}); +``` #### Determining the Current Locale You may use the `currentLocale` and `isLocale` methods on the `App` facade to determine the current locale or check if the locale is a given value: - use Illuminate\Support\Facades\App; +```php +use Illuminate\Support\Facades\App; - $locale = App::currentLocale(); +$locale = App::currentLocale(); - if (App::isLocale('en')) { - // ... - } +if (App::isLocale('en')) { + // ... +} +``` ### Pluralization Language You may instruct Laravel's "pluralizer", which is used by Eloquent and other portions of the framework to convert singular strings to plural strings, to use a language other than English. This may be accomplished by invoking the `useLanguage` method within the `boot` method of one of your application's service providers. The pluralizer's currently supported languages are: `french`, `norwegian-bokmal`, `portuguese`, `spanish`, and `turkish`: - use Illuminate\Support\Pluralizer; +```php +use Illuminate\Support\Pluralizer; - /** - * Bootstrap any application services. - */ - public function boot(): void - { - Pluralizer::useLanguage('spanish'); +/** + * Bootstrap any application services. + */ +public function boot(): void +{ + Pluralizer::useLanguage('spanish'); - // ... - } + // ... +} +``` > [!WARNING] > If you customize the pluralizer's language, you should explicitly define your Eloquent model's [table names](/docs/{{version}}/eloquent#table-names). @@ -107,21 +117,25 @@ You may instruct Laravel's "pluralizer", which is used by Eloquent and other por Typically, translation strings are stored in files within the `lang` directory. Within this directory, there should be a subdirectory for each language supported by your application. This is the approach Laravel uses to manage translation strings for built-in Laravel features such as validation error messages: - /lang - /en - messages.php - /es - messages.php +```text +/lang + /en + messages.php + /es + messages.php +``` All language files return an array of keyed strings. For example: - 'Welcome to our application!', - ]; +return [ + 'welcome' => 'Welcome to our application!', +]; +``` > [!WARNING] > For languages that differ by territory, you should name the language directories according to the ISO 15897. For example, "en_GB" should be used for British English rather than "en-gb". @@ -148,35 +162,47 @@ You should not define translation string keys that conflict with other translati You may retrieve translation strings from your language files using the `__` helper function. If you are using "short keys" to define your translation strings, you should pass the file that contains the key and the key itself to the `__` function using "dot" syntax. For example, let's retrieve the `welcome` translation string from the `lang/en/messages.php` language file: - echo __('messages.welcome'); +```php +echo __('messages.welcome'); +``` If the specified translation string does not exist, the `__` function will return the translation string key. So, using the example above, the `__` function would return `messages.welcome` if the translation string does not exist. If you are using your [default translation strings as your translation keys](#using-translation-strings-as-keys), you should pass the default translation of your string to the `__` function; - echo __('I love programming.'); +```php +echo __('I love programming.'); +``` Again, if the translation string does not exist, the `__` function will return the translation string key that it was given. If you are using the [Blade templating engine](/docs/{{version}}/blade), you may use the `{{ }}` echo syntax to display the translation string: - {{ __('messages.welcome') }} +```blade +{{ __('messages.welcome') }} +``` ### Replacing Parameters in Translation Strings If you wish, you may define placeholders in your translation strings. All placeholders are prefixed with a `:`. For example, you may define a welcome message with a placeholder name: - 'welcome' => 'Welcome, :name', +```php +'welcome' => 'Welcome, :name', +``` To replace the placeholders when retrieving a translation string, you may pass an array of replacements as the second argument to the `__` function: - echo __('messages.welcome', ['name' => 'dayle']); +```php +echo __('messages.welcome', ['name' => 'dayle']); +``` If your placeholder contains all capital letters, or only has its first letter capitalized, the translated value will be capitalized accordingly: - 'welcome' => 'Welcome, :NAME', // Welcome, DAYLE - 'goodbye' => 'Goodbye, :Name', // Goodbye, Dayle +```php +'welcome' => 'Welcome, :NAME', // Welcome, DAYLE +'goodbye' => 'Goodbye, :Name', // Goodbye, Dayle +``` #### Object Replacement Formatting @@ -185,25 +211,29 @@ If you attempt to provide an object as a translation placeholder, the object's ` In these cases, Laravel allows you to register a custom formatting handler for that particular type of object. To accomplish this, you should invoke the translator's `stringable` method. The `stringable` method accepts a closure, which should type-hint the type of object that it is responsible for formatting. Typically, the `stringable` method should be invoked within the `boot` method of your application's `AppServiceProvider` class: - use Illuminate\Support\Facades\Lang; - use Money\Money; - - /** - * Bootstrap any application services. - */ - public function boot(): void - { - Lang::stringable(function (Money $money) { - return $money->formatTo('en_GB'); - }); - } +```php +use Illuminate\Support\Facades\Lang; +use Money\Money; + +/** + * Bootstrap any application services. + */ +public function boot(): void +{ + Lang::stringable(function (Money $money) { + return $money->formatTo('en_GB'); + }); +} +``` ### Pluralization Pluralization is a complex problem, as different languages have a variety of complex rules for pluralization; however, Laravel can help you translate strings differently based on pluralization rules that you define. Using a `|` character, you may distinguish singular and plural forms of a string: - 'apples' => 'There is one apple|There are many apples', +```php +'apples' => 'There is one apple|There are many apples', +``` Of course, pluralization is also supported when using [translation strings as keys](#using-translation-strings-as-keys): @@ -215,21 +245,29 @@ Of course, pluralization is also supported when using [translation strings as ke You may even create more complex pluralization rules which specify translation strings for multiple ranges of values: - 'apples' => '{0} There are none|[1,19] There are some|[20,*] There are many', +```php +'apples' => '{0} There are none|[1,19] There are some|[20,*] There are many', +``` After defining a translation string that has pluralization options, you may use the `trans_choice` function to retrieve the line for a given "count". In this example, since the count is greater than one, the plural form of the translation string is returned: - echo trans_choice('messages.apples', 10); +```php +echo trans_choice('messages.apples', 10); +``` You may also define placeholder attributes in pluralization strings. These placeholders may be replaced by passing an array as the third argument to the `trans_choice` function: - 'minutes_ago' => '{1} :value minute ago|[2,*] :value minutes ago', +```php +'minutes_ago' => '{1} :value minute ago|[2,*] :value minutes ago', - echo trans_choice('time.minutes_ago', 5, ['value' => 5]); +echo trans_choice('time.minutes_ago', 5, ['value' => 5]); +``` If you would like to display the integer value that was passed to the `trans_choice` function, you may use the built-in `:count` placeholder: - 'apples' => '{0} There are none|{1} There is one|[2,*] There are :count', +```php +'apples' => '{0} There are none|{1} There is one|[2,*] There are :count', +``` ## Overriding Package Language Files diff --git a/logging.md b/logging.md index b7d5ced1330..e9bd266c85d 100644 --- a/logging.md +++ b/logging.md @@ -63,11 +63,13 @@ Each log channel is powered by a "driver". The driver determines how and where t By default, Monolog is instantiated with a "channel name" that matches the current environment, such as `production` or `local`. To change this value, you may add a `name` option to your channel's configuration: - 'stack' => [ - 'driver' => 'stack', - 'name' => 'channel-name', - 'channels' => ['single', 'slack'], - ], +```php +'stack' => [ + 'driver' => 'stack', + 'name' => 'channel-name', + 'channels' => ['single', 'slack'], +], +``` ### Channel Prerequisites @@ -114,23 +116,27 @@ By default, Slack will only receive logs at the `critical` level and above; howe PHP, Laravel, and other libraries often notify their users that some of their features have been deprecated and will be removed in a future version. If you would like to log these deprecation warnings, you may specify your preferred `deprecations` log channel using the `LOG_DEPRECATIONS_CHANNEL` environment variable, or within your application's `config/logging.php` configuration file: - 'deprecations' => [ - 'channel' => env('LOG_DEPRECATIONS_CHANNEL', 'null'), - 'trace' => env('LOG_DEPRECATIONS_TRACE', false), - ], +```php +'deprecations' => [ + 'channel' => env('LOG_DEPRECATIONS_CHANNEL', 'null'), + 'trace' => env('LOG_DEPRECATIONS_TRACE', false), +], - 'channels' => [ - // ... - ] +'channels' => [ + // ... +] +``` Or, you may define a log channel named `deprecations`. If a log channel with this name exists, it will always be used to log deprecations: - 'channels' => [ - 'deprecations' => [ - 'driver' => 'single', - 'path' => storage_path('logs/php-deprecation-warnings.log'), - ], +```php +'channels' => [ + 'deprecations' => [ + 'driver' => 'single', + 'path' => storage_path('logs/php-deprecation-warnings.log'), ], +], +``` ## Building Log Stacks @@ -172,128 +178,142 @@ Take note of the `level` configuration option present on the `syslog` and `slack So, imagine we log a message using the `debug` method: - Log::debug('An informational message.'); +```php +Log::debug('An informational message.'); +``` Given our configuration, the `syslog` channel will write the message to the system log; however, since the error message is not `critical` or above, it will not be sent to Slack. However, if we log an `emergency` message, it will be sent to both the system log and Slack since the `emergency` level is above our minimum level threshold for both channels: - Log::emergency('The system is down!'); +```php +Log::emergency('The system is down!'); +``` ## Writing Log Messages You may write information to the logs using the `Log` [facade](/docs/{{version}}/facades). As previously mentioned, the logger provides the eight logging levels defined in the [RFC 5424 specification](https://tools.ietf.org/html/rfc5424): **emergency**, **alert**, **critical**, **error**, **warning**, **notice**, **info** and **debug**: - use Illuminate\Support\Facades\Log; - - Log::emergency($message); - Log::alert($message); - Log::critical($message); - Log::error($message); - Log::warning($message); - Log::notice($message); - Log::info($message); - Log::debug($message); +```php +use Illuminate\Support\Facades\Log; + +Log::emergency($message); +Log::alert($message); +Log::critical($message); +Log::error($message); +Log::warning($message); +Log::notice($message); +Log::info($message); +Log::debug($message); +``` You may call any of these methods to log a message for the corresponding level. By default, the message will be written to the default log channel as configured by your `logging` configuration file: - $id]); - - return view('user.profile', [ - 'user' => User::findOrFail($id) - ]); - } + Log::info('Showing the user profile for user: {id}', ['id' => $id]); + + return view('user.profile', [ + 'user' => User::findOrFail($id) + ]); } +} +``` ### Contextual Information An array of contextual data may be passed to the log methods. This contextual data will be formatted and displayed with the log message: - use Illuminate\Support\Facades\Log; +```php +use Illuminate\Support\Facades\Log; - Log::info('User {id} failed to login.', ['id' => $user->id]); +Log::info('User {id} failed to login.', ['id' => $user->id]); +``` Occasionally, you may wish to specify some contextual information that should be included with all subsequent log entries in a particular channel. For example, you may wish to log a request ID that is associated with each incoming request to your application. To accomplish this, you may call the `Log` facade's `withContext` method: - $requestId - ]); + Log::withContext([ + 'request-id' => $requestId + ]); - $response = $next($request); + $response = $next($request); - $response->headers->set('Request-Id', $requestId); + $response->headers->set('Request-Id', $requestId); - return $response; - } + return $response; } +} +``` If you would like to share contextual information across _all_ logging channels, you may invoke the `Log::shareContext()` method. This method will provide the contextual information to all created channels and any channels that are created subsequently: - $requestId + ]); - class AssignRequestId - { - /** - * Handle an incoming request. - * - * @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next - */ - public function handle(Request $request, Closure $next): Response - { - $requestId = (string) Str::uuid(); - - Log::shareContext([ - 'request-id' => $requestId - ]); - - // ... - } + // ... } +} +``` > [!NOTE] > If you need to share log context while processing queued jobs, you may utilize [job middleware](/docs/{{version}}/queues#job-middleware). @@ -303,36 +323,44 @@ If you would like to share contextual information across _all_ logging channels, Sometimes you may wish to log a message to a channel other than your application's default channel. You may use the `channel` method on the `Log` facade to retrieve and log to any channel defined in your configuration file: - use Illuminate\Support\Facades\Log; +```php +use Illuminate\Support\Facades\Log; - Log::channel('slack')->info('Something happened!'); +Log::channel('slack')->info('Something happened!'); +``` If you would like to create an on-demand logging stack consisting of multiple channels, you may use the `stack` method: - Log::stack(['single', 'slack'])->info('Something happened!'); +```php +Log::stack(['single', 'slack'])->info('Something happened!'); +``` #### On-Demand Channels It is also possible to create an on-demand channel by providing the configuration at runtime without that configuration being present in your application's `logging` configuration file. To accomplish this, you may pass a configuration array to the `Log` facade's `build` method: - use Illuminate\Support\Facades\Log; +```php +use Illuminate\Support\Facades\Log; - Log::build([ - 'driver' => 'single', - 'path' => storage_path('logs/custom.log'), - ])->info('Something happened!'); +Log::build([ + 'driver' => 'single', + 'path' => storage_path('logs/custom.log'), +])->info('Something happened!'); +``` You may also wish to include an on-demand channel in an on-demand logging stack. This can be achieved by including your on-demand channel instance in the array passed to the `stack` method: - use Illuminate\Support\Facades\Log; +```php +use Illuminate\Support\Facades\Log; - $channel = Log::build([ - 'driver' => 'single', - 'path' => storage_path('logs/custom.log'), - ]); +$channel = Log::build([ + 'driver' => 'single', + 'path' => storage_path('logs/custom.log'), +]); - Log::stack(['slack', $channel])->info('Something happened!'); +Log::stack(['slack', $channel])->info('Something happened!'); +``` ## Monolog Channel Customization @@ -344,37 +372,41 @@ Sometimes you may need complete control over how Monolog is configured for an ex To get started, define a `tap` array on the channel's configuration. The `tap` array should contain a list of classes that should have an opportunity to customize (or "tap" into) the Monolog instance after it is created. There is no conventional location where these classes should be placed, so you are free to create a directory within your application to contain these classes: - 'single' => [ - 'driver' => 'single', - 'tap' => [App\Logging\CustomizeFormatter::class], - 'path' => storage_path('logs/laravel.log'), - 'level' => env('LOG_LEVEL', 'debug'), - 'replace_placeholders' => true, - ], +```php +'single' => [ + 'driver' => 'single', + 'tap' => [App\Logging\CustomizeFormatter::class], + 'path' => storage_path('logs/laravel.log'), + 'level' => env('LOG_LEVEL', 'debug'), + 'replace_placeholders' => true, +], +``` Once you have configured the `tap` option on your channel, you're ready to define the class that will customize your Monolog instance. This class only needs a single method: `__invoke`, which receives an `Illuminate\Log\Logger` instance. The `Illuminate\Log\Logger` instance proxies all method calls to the underlying Monolog instance: - getHandlers() as $handler) { - $handler->setFormatter(new LineFormatter( - '[%datetime%] %channel%.%level_name%: %message% %context% %extra%' - )); - } + foreach ($logger->getHandlers() as $handler) { + $handler->setFormatter(new LineFormatter( + '[%datetime%] %channel%.%level_name%: %message% %context% %extra%' + )); } } +} +``` > [!NOTE] > All of your "tap" classes are resolved by the [service container](/docs/{{version}}/container), so any constructor dependencies they require will automatically be injected. @@ -386,36 +418,42 @@ Monolog has a variety of [available handlers](https://github.com/Seldaek/monolog When using the `monolog` driver, the `handler` configuration option is used to specify which handler will be instantiated. Optionally, any constructor parameters the handler needs may be specified using the `with` configuration option: - 'logentries' => [ - 'driver' => 'monolog', - 'handler' => Monolog\Handler\SyslogUdpHandler::class, - 'with' => [ - 'host' => 'my.logentries.internal.datahubhost.company.com', - 'port' => '10000', - ], +```php +'logentries' => [ + 'driver' => 'monolog', + 'handler' => Monolog\Handler\SyslogUdpHandler::class, + 'with' => [ + 'host' => 'my.logentries.internal.datahubhost.company.com', + 'port' => '10000', ], +], +``` #### Monolog Formatters When using the `monolog` driver, the Monolog `LineFormatter` will be used as the default formatter. However, you may customize the type of formatter passed to the handler using the `formatter` and `formatter_with` configuration options: - 'browser' => [ - 'driver' => 'monolog', - 'handler' => Monolog\Handler\BrowserConsoleHandler::class, - 'formatter' => Monolog\Formatter\HtmlFormatter::class, - 'formatter_with' => [ - 'dateFormat' => 'Y-m-d', - ], +```php +'browser' => [ + 'driver' => 'monolog', + 'handler' => Monolog\Handler\BrowserConsoleHandler::class, + 'formatter' => Monolog\Formatter\HtmlFormatter::class, + 'formatter_with' => [ + 'dateFormat' => 'Y-m-d', ], +], +``` If you are using a Monolog handler that is capable of providing its own formatter, you may set the value of the `formatter` configuration option to `default`: - 'newrelic' => [ - 'driver' => 'monolog', - 'handler' => Monolog\Handler\NewRelicHandler::class, - 'formatter' => 'default', - ], +```php +'newrelic' => [ + 'driver' => 'monolog', + 'handler' => Monolog\Handler\NewRelicHandler::class, + 'formatter' => 'default', +], +``` #### Monolog Processors @@ -424,54 +462,60 @@ Monolog can also process messages before logging them. You can create your own p If you would like to customize the processors for a `monolog` driver, add a `processors` configuration value to your channel's configuration: - 'memory' => [ - 'driver' => 'monolog', - 'handler' => Monolog\Handler\StreamHandler::class, - 'with' => [ - 'stream' => 'php://stderr', - ], - 'processors' => [ - // Simple syntax... - Monolog\Processor\MemoryUsageProcessor::class, - - // With options... - [ - 'processor' => Monolog\Processor\PsrLogMessageProcessor::class, - 'with' => ['removeUsedContextFields' => true], - ], - ], - ], +```php +'memory' => [ + 'driver' => 'monolog', + 'handler' => Monolog\Handler\StreamHandler::class, + 'with' => [ + 'stream' => 'php://stderr', + ], + 'processors' => [ + // Simple syntax... + Monolog\Processor\MemoryUsageProcessor::class, + + // With options... + [ + 'processor' => Monolog\Processor\PsrLogMessageProcessor::class, + 'with' => ['removeUsedContextFields' => true], + ], + ], +], +``` ### Creating Custom Channels via Factories If you would like to define an entirely custom channel in which you have full control over Monolog's instantiation and configuration, you may specify a `custom` driver type in your `config/logging.php` configuration file. Your configuration should include a `via` option that contains the name of the factory class which will be invoked to create the Monolog instance: - 'channels' => [ - 'example-custom-channel' => [ - 'driver' => 'custom', - 'via' => App\Logging\CreateCustomLogger::class, - ], +```php +'channels' => [ + 'example-custom-channel' => [ + 'driver' => 'custom', + 'via' => App\Logging\CreateCustomLogger::class, ], +], +``` Once you have configured the `custom` driver channel, you're ready to define the class that will create your Monolog instance. This class only needs a single `__invoke` method which should return the Monolog logger instance. The method will receive the channels configuration array as its only argument: - ## Tailing Log Messages Using Pail diff --git a/mail.md b/mail.md index e764550a9e2..b70b444e831 100644 --- a/mail.md +++ b/mail.md @@ -61,34 +61,42 @@ composer require symfony/mailgun-mailer symfony/http-client Next, you will need to make two changes in your application's `config/mail.php` configuration file. First, set your default mailer to `mailgun`: - 'default' => env('MAIL_MAILER', 'mailgun'), +```php +'default' => env('MAIL_MAILER', 'mailgun'), +``` Second, add the following configuration array to your array of `mailers`: - 'mailgun' => [ - 'transport' => 'mailgun', - // 'client' => [ - // 'timeout' => 5, - // ], - ], +```php +'mailgun' => [ + 'transport' => 'mailgun', + // 'client' => [ + // 'timeout' => 5, + // ], +], +``` After configuring your application's default mailer, add the following options to your `config/services.php` configuration file: - 'mailgun' => [ - 'domain' => env('MAILGUN_DOMAIN'), - 'secret' => env('MAILGUN_SECRET'), - 'endpoint' => env('MAILGUN_ENDPOINT', 'api.mailgun.net'), - 'scheme' => 'https', - ], +```php +'mailgun' => [ + 'domain' => env('MAILGUN_DOMAIN'), + 'secret' => env('MAILGUN_SECRET'), + 'endpoint' => env('MAILGUN_ENDPOINT', 'api.mailgun.net'), + 'scheme' => 'https', +], +``` If you are not using the United States [Mailgun region](https://documentation.mailgun.com/en/latest/api-intro.html#mailgun-regions), you may define your region's endpoint in the `services` configuration file: - 'mailgun' => [ - 'domain' => env('MAILGUN_DOMAIN'), - 'secret' => env('MAILGUN_SECRET'), - 'endpoint' => env('MAILGUN_ENDPOINT', 'api.eu.mailgun.net'), - 'scheme' => 'https', - ], +```php +'mailgun' => [ + 'domain' => env('MAILGUN_DOMAIN'), + 'secret' => env('MAILGUN_SECRET'), + 'endpoint' => env('MAILGUN_ENDPOINT', 'api.eu.mailgun.net'), + 'scheme' => 'https', +], +``` #### Postmark Driver @@ -101,19 +109,23 @@ composer require symfony/postmark-mailer symfony/http-client Next, set the `default` option in your application's `config/mail.php` configuration file to `postmark`. After configuring your application's default mailer, ensure that your `config/services.php` configuration file contains the following options: - 'postmark' => [ - 'token' => env('POSTMARK_TOKEN'), - ], +```php +'postmark' => [ + 'token' => env('POSTMARK_TOKEN'), +], +``` If you would like to specify the Postmark message stream that should be used by a given mailer, you may add the `message_stream_id` configuration option to the mailer's configuration array. This configuration array can be found in your application's `config/mail.php` configuration file: - 'postmark' => [ - 'transport' => 'postmark', - 'message_stream_id' => env('POSTMARK_MESSAGE_STREAM_ID'), - // 'client' => [ - // 'timeout' => 5, - // ], - ], +```php +'postmark' => [ + 'transport' => 'postmark', + 'message_stream_id' => env('POSTMARK_MESSAGE_STREAM_ID'), + // 'client' => [ + // 'timeout' => 5, + // ], +], +``` This way you are also able to set up multiple Postmark mailers with different message streams. @@ -128,9 +140,11 @@ composer require resend/resend-php Next, set the `default` option in your application's `config/mail.php` configuration file to `resend`. After configuring your application's default mailer, ensure that your `config/services.php` configuration file contains the following options: - 'resend' => [ - 'key' => env('RESEND_KEY'), - ], +```php +'resend' => [ + 'key' => env('RESEND_KEY'), +], +``` #### SES Driver @@ -143,20 +157,24 @@ composer require aws/aws-sdk-php Next, set the `default` option in your `config/mail.php` configuration file to `ses` and verify that your `config/services.php` configuration file contains the following options: - 'ses' => [ - 'key' => env('AWS_ACCESS_KEY_ID'), - 'secret' => env('AWS_SECRET_ACCESS_KEY'), - 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'), - ], +```php +'ses' => [ + 'key' => env('AWS_ACCESS_KEY_ID'), + 'secret' => env('AWS_SECRET_ACCESS_KEY'), + 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'), +], +``` To utilize AWS [temporary credentials](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_temp_use-resources.html) via a session token, you may add a `token` key to your application's SES configuration: - 'ses' => [ - 'key' => env('AWS_ACCESS_KEY_ID'), - 'secret' => env('AWS_SECRET_ACCESS_KEY'), - 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'), - 'token' => env('AWS_SESSION_TOKEN'), - ], +```php +'ses' => [ + 'key' => env('AWS_ACCESS_KEY_ID'), + 'secret' => env('AWS_SECRET_ACCESS_KEY'), + 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'), + 'token' => env('AWS_SESSION_TOKEN'), +], +``` To interact with SES's [subscription management features](https://docs.aws.amazon.com/ses/latest/dg/sending-email-subscription-management.html), you may return the `X-Ses-List-Management-Options` header in the array returned by the [`headers`](#headers) method of a mail message: @@ -176,17 +194,19 @@ public function headers(): Headers If you would like to define [additional options](https://docs.aws.amazon.com/aws-sdk-php/v3/api/api-sesv2-2019-09-27.html#sendemail) that Laravel should pass to the AWS SDK's `SendEmail` method when sending an email, you may define an `options` array within your `ses` configuration: - 'ses' => [ - 'key' => env('AWS_ACCESS_KEY_ID'), - 'secret' => env('AWS_SECRET_ACCESS_KEY'), - 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'), - 'options' => [ - 'ConfigurationSetName' => 'MyConfigurationSet', - 'EmailTags' => [ - ['Name' => 'foo', 'Value' => 'bar'], - ], +```php +'ses' => [ + 'key' => env('AWS_ACCESS_KEY_ID'), + 'secret' => env('AWS_SECRET_ACCESS_KEY'), + 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'), + 'options' => [ + 'ConfigurationSetName' => 'MyConfigurationSet', + 'EmailTags' => [ + ['Name' => 'foo', 'Value' => 'bar'], ], ], +], +``` #### MailerSend Driver @@ -224,43 +244,51 @@ Sometimes, an external service you have configured to send your application's ma To accomplish this, you should define a mailer within your application's `mail` configuration file that uses the `failover` transport. The configuration array for your application's `failover` mailer should contain an array of `mailers` that reference the order in which configured mailers should be chosen for delivery: - 'mailers' => [ - 'failover' => [ - 'transport' => 'failover', - 'mailers' => [ - 'postmark', - 'mailgun', - 'sendmail', - ], +```php +'mailers' => [ + 'failover' => [ + 'transport' => 'failover', + 'mailers' => [ + 'postmark', + 'mailgun', + 'sendmail', ], - - // ... ], + // ... +], +``` + Once your failover mailer has been defined, you should set this mailer as the default mailer used by your application by specifying its name as the value of the `default` configuration key within your application's `mail` configuration file: - 'default' => env('MAIL_MAILER', 'failover'), +```php +'default' => env('MAIL_MAILER', 'failover'), +``` ### Round Robin Configuration The `roundrobin` transport allows you to distribute your mailing workload across multiple mailers. To get started, define a mailer within your application's `mail` configuration file that uses the `roundrobin` transport. The configuration array for your application's `roundrobin` mailer should contain an array of `mailers` that reference which configured mailers should be used for delivery: - 'mailers' => [ - 'roundrobin' => [ - 'transport' => 'roundrobin', - 'mailers' => [ - 'ses', - 'postmark', - ], +```php +'mailers' => [ + 'roundrobin' => [ + 'transport' => 'roundrobin', + 'mailers' => [ + 'ses', + 'postmark', ], - - // ... ], + // ... +], +``` + Once your round robin mailer has been defined, you should set this mailer as the default mailer used by your application by specifying its name as the value of the `default` configuration key within your application's `mail` configuration file: - 'default' => env('MAIL_MAILER', 'roundrobin'), +```php +'default' => env('MAIL_MAILER', 'roundrobin'), +``` The round robin transport selects a random mailer from the list of configured mailers and then switches to the next available mailer for each subsequent email. In contrast to `failover` transport, which helps to achieve *[high availability](https://en.wikipedia.org/wiki/High_availability)*, the `roundrobin` transport provides *[load balancing](https://en.wikipedia.org/wiki/Load_balancing_(computing))*. @@ -288,58 +316,68 @@ The `envelope` method returns an `Illuminate\Mail\Mailables\Envelope` object tha First, let's explore configuring the sender of the email. Or, in other words, who the email is going to be "from". There are two ways to configure the sender. First, you may specify the "from" address on your message's envelope: - use Illuminate\Mail\Mailables\Address; - use Illuminate\Mail\Mailables\Envelope; - - /** - * Get the message envelope. - */ - public function envelope(): Envelope - { - return new Envelope( - from: new Address('jeffrey@example.com', 'Jeffrey Way'), - subject: 'Order Shipped', - ); - } - -If you would like, you may also specify a `replyTo` address: +```php +use Illuminate\Mail\Mailables\Address; +use Illuminate\Mail\Mailables\Envelope; +/** + * Get the message envelope. + */ +public function envelope(): Envelope +{ return new Envelope( from: new Address('jeffrey@example.com', 'Jeffrey Way'), - replyTo: [ - new Address('taylor@example.com', 'Taylor Otwell'), - ], subject: 'Order Shipped', ); +} +``` + +If you would like, you may also specify a `replyTo` address: + +```php +return new Envelope( + from: new Address('jeffrey@example.com', 'Jeffrey Way'), + replyTo: [ + new Address('taylor@example.com', 'Taylor Otwell'), + ], + subject: 'Order Shipped', +); +``` #### Using a Global `from` Address However, if your application uses the same "from" address for all of its emails, it can become cumbersome to add it to each mailable class you generate. Instead, you may specify a global "from" address in your `config/mail.php` configuration file. This address will be used if no other "from" address is specified within the mailable class: - 'from' => [ - 'address' => env('MAIL_FROM_ADDRESS', 'hello@example.com'), - 'name' => env('MAIL_FROM_NAME', 'Example'), - ], +```php +'from' => [ + 'address' => env('MAIL_FROM_ADDRESS', 'hello@example.com'), + 'name' => env('MAIL_FROM_NAME', 'Example'), +], +``` In addition, you may define a global "reply_to" address within your `config/mail.php` configuration file: - 'reply_to' => ['address' => 'example@example.com', 'name' => 'App Name'], +```php +'reply_to' => ['address' => 'example@example.com', 'name' => 'App Name'], +``` ### Configuring the View Within a mailable class's `content` method, you may define the `view`, or which template should be used when rendering the email's contents. Since each email typically uses a [Blade template](/docs/{{version}}/blade) to render its contents, you have the full power and convenience of the Blade templating engine when building your email's HTML: - /** - * Get the message content definition. - */ - public function content(): Content - { - return new Content( - view: 'mail.orders.shipped', - ); - } +```php +/** + * Get the message content definition. + */ +public function content(): Content +{ + return new Content( + view: 'mail.orders.shipped', + ); +} +``` > [!NOTE] > You may wish to create a `resources/views/emails` directory to house all of your email templates; however, you are free to place them wherever you wish within your `resources/views` directory. @@ -349,23 +387,27 @@ Within a mailable class's `content` method, you may define the `view`, or which If you would like to define a plain-text version of your email, you may specify the plain-text template when creating the message's `Content` definition. Like the `view` parameter, the `text` parameter should be a template name which will be used to render the contents of the email. You are free to define both an HTML and plain-text version of your message: - /** - * Get the message content definition. - */ - public function content(): Content - { - return new Content( - view: 'mail.orders.shipped', - text: 'mail.orders.shipped-text' - ); - } - -For clarity, the `html` parameter may be used as an alias of the `view` parameter: - +```php +/** + * Get the message content definition. + */ +public function content(): Content +{ return new Content( - html: 'mail.orders.shipped', + view: 'mail.orders.shipped', text: 'mail.orders.shipped-text' ); +} +``` + +For clarity, the `html` parameter may be used as an alias of the `view` parameter: + +```php +return new Content( + html: 'mail.orders.shipped', + text: 'mail.orders.shipped-text' +); +``` ### View Data @@ -375,192 +417,212 @@ For clarity, the `html` parameter may be used as an alias of the `view` paramete Typically, you will want to pass some data to your view that you can utilize when rendering the email's HTML. There are two ways you may make data available to your view. First, any public property defined on your mailable class will automatically be made available to the view. So, for example, you may pass data into your mailable class's constructor and set that data to public properties defined on the class: - - Price: {{ $order->price }} - +```blade +
+ Price: {{ $order->price }} +
+``` #### Via the `with` Parameter: If you would like to customize the format of your email's data before it is sent to the template, you may manually pass your data to the view via the `Content` definition's `with` parameter. Typically, you will still pass data via the mailable class's constructor; however, you should set this data to `protected` or `private` properties so the data is not automatically made available to the template: - $this->order->name, - 'orderPrice' => $this->order->price, - ], - ); - } + return new Content( + view: 'mail.orders.shipped', + with: [ + 'orderName' => $this->order->name, + 'orderPrice' => $this->order->price, + ], + ); } +} +``` Once the data has been passed to the `with` method, it will automatically be available in your view, so you may access it like you would access any other data in your Blade templates: -
- Price: {{ $orderPrice }} -
+```blade +
+ Price: {{ $orderPrice }} +
+``` ### Attachments To add attachments to an email, you will add attachments to the array returned by the message's `attachments` method. First, you may add an attachment by providing a file path to the `fromPath` method provided by the `Attachment` class: - use Illuminate\Mail\Mailables\Attachment; +```php +use Illuminate\Mail\Mailables\Attachment; - /** - * Get the attachments for the message. - * - * @return array - */ - public function attachments(): array - { - return [ - Attachment::fromPath('/path/to/file'), - ]; - } +/** + * Get the attachments for the message. + * + * @return array + */ +public function attachments(): array +{ + return [ + Attachment::fromPath('/path/to/file'), + ]; +} +``` When attaching files to a message, you may also specify the display name and / or MIME type for the attachment using the `as` and `withMime` methods: - /** - * Get the attachments for the message. - * - * @return array - */ - public function attachments(): array - { - return [ - Attachment::fromPath('/path/to/file') - ->as('name.pdf') - ->withMime('application/pdf'), - ]; - } +```php +/** + * Get the attachments for the message. + * + * @return array + */ +public function attachments(): array +{ + return [ + Attachment::fromPath('/path/to/file') + ->as('name.pdf') + ->withMime('application/pdf'), + ]; +} +``` #### Attaching Files From Disk If you have stored a file on one of your [filesystem disks](/docs/{{version}}/filesystem), you may attach it to the email using the `fromStorage` attachment method: - /** - * Get the attachments for the message. - * - * @return array - */ - public function attachments(): array - { - return [ - Attachment::fromStorage('/path/to/file'), - ]; - } +```php +/** + * Get the attachments for the message. + * + * @return array + */ +public function attachments(): array +{ + return [ + Attachment::fromStorage('/path/to/file'), + ]; +} +``` Of course, you may also specify the attachment's name and MIME type: - /** - * Get the attachments for the message. - * - * @return array - */ - public function attachments(): array - { - return [ - Attachment::fromStorage('/path/to/file') - ->as('name.pdf') - ->withMime('application/pdf'), - ]; - } +```php +/** + * Get the attachments for the message. + * + * @return array + */ +public function attachments(): array +{ + return [ + Attachment::fromStorage('/path/to/file') + ->as('name.pdf') + ->withMime('application/pdf'), + ]; +} +``` The `fromStorageDisk` method may be used if you need to specify a storage disk other than your default disk: - /** - * Get the attachments for the message. - * - * @return array - */ - public function attachments(): array - { - return [ - Attachment::fromStorageDisk('s3', '/path/to/file') - ->as('name.pdf') - ->withMime('application/pdf'), - ]; - } +```php +/** + * Get the attachments for the message. + * + * @return array + */ +public function attachments(): array +{ + return [ + Attachment::fromStorageDisk('s3', '/path/to/file') + ->as('name.pdf') + ->withMime('application/pdf'), + ]; +} +``` #### Raw Data Attachments The `fromData` attachment method may be used to attach a raw string of bytes as an attachment. For example, you might use this method if you have generated a PDF in memory and want to attach it to the email without writing it to disk. The `fromData` method accepts a closure which resolves the raw data bytes as well as the name that the attachment should be assigned: - /** - * Get the attachments for the message. - * - * @return array - */ - public function attachments(): array - { - return [ - Attachment::fromData(fn () => $this->pdf, 'Report.pdf') - ->withMime('application/pdf'), - ]; - } +```php +/** + * Get the attachments for the message. + * + * @return array + */ +public function attachments(): array +{ + return [ + Attachment::fromData(fn () => $this->pdf, 'Report.pdf') + ->withMime('application/pdf'), + ]; +} +``` ### Inline Attachments @@ -598,54 +660,64 @@ While attaching files to messages via simple string paths is often sufficient, i To get started, implement the `Illuminate\Contracts\Mail\Attachable` interface on the object that will be attachable to messages. This interface dictates that your class defines a `toMailAttachment` method that returns an `Illuminate\Mail\Attachment` instance: - - */ - public function attachments(): array - { - return [$this->photo]; - } +```php +/** + * Get the attachments for the message. + * + * @return array + */ +public function attachments(): array +{ + return [$this->photo]; +} +``` Of course, attachment data may be stored on a remote file storage service such as Amazon S3. So, Laravel also allows you to generate attachment instances from data that is stored on one of your application's [filesystem disks](/docs/{{version}}/filesystem): - // Create an attachment from a file on your default disk... - return Attachment::fromStorage($this->path); +```php +// Create an attachment from a file on your default disk... +return Attachment::fromStorage($this->path); - // Create an attachment from a file on a specific disk... - return Attachment::fromStorageDisk('backblaze', $this->path); +// Create an attachment from a file on a specific disk... +return Attachment::fromStorageDisk('backblaze', $this->path); +``` In addition, you may create attachment instances via data that you have in memory. To accomplish this, provide a closure to the `fromData` method. The closure should return the raw data that represents the attachment: - return Attachment::fromData(fn () => $this->content, 'Photo Name'); +```php +return Attachment::fromData(fn () => $this->content, 'Photo Name'); +``` Laravel also provides additional methods that you may use to customize your attachments. For example, you may use the `as` and `withMime` methods to customize the file's name and MIME type: - return Attachment::fromPath('/path/to/file') - ->as('Photo Name') - ->withMime('image/jpeg'); +```php +return Attachment::fromPath('/path/to/file') + ->as('Photo Name') + ->withMime('image/jpeg'); +``` ### Headers @@ -654,44 +726,48 @@ Sometimes you may need to attach additional headers to the outgoing message. For To accomplish this, define a `headers` method on your mailable. The `headers` method should return an `Illuminate\Mail\Mailables\Headers` instance. This class accepts `messageId`, `references`, and `text` parameters. Of course, you may provide only the parameters you need for your particular message: - use Illuminate\Mail\Mailables\Headers; +```php +use Illuminate\Mail\Mailables\Headers; - /** - * Get the message headers. - */ - public function headers(): Headers - { - return new Headers( - messageId: 'custom-message-id@example.com', - references: ['previous-message@example.com'], - text: [ - 'X-Custom-Header' => 'Custom Value', - ], - ); - } +/** + * Get the message headers. + */ +public function headers(): Headers +{ + return new Headers( + messageId: 'custom-message-id@example.com', + references: ['previous-message@example.com'], + text: [ + 'X-Custom-Header' => 'Custom Value', + ], + ); +} +``` ### Tags and Metadata Some third-party email providers such as Mailgun and Postmark support message "tags" and "metadata", which may be used to group and track emails sent by your application. You may add tags and metadata to an email message via your `Envelope` definition: - use Illuminate\Mail\Mailables\Envelope; +```php +use Illuminate\Mail\Mailables\Envelope; - /** - * Get the message envelope. - * - * @return \Illuminate\Mail\Mailables\Envelope - */ - public function envelope(): Envelope - { - return new Envelope( - subject: 'Order Shipped', - tags: ['shipment'], - metadata: [ - 'order_id' => $this->order->id, - ], - ); - } +/** + * Get the message envelope. + * + * @return \Illuminate\Mail\Mailables\Envelope + */ +public function envelope(): Envelope +{ + return new Envelope( + subject: 'Order Shipped', + tags: ['shipment'], + metadata: [ + 'order_id' => $this->order->id, + ], + ); +} +``` If your application is using the Mailgun driver, you may consult Mailgun's documentation for more information on [tags](https://documentation.mailgun.com/docs/mailgun/user-manual/tracking-messages/#tagging) and [metadata](https://documentation.mailgun.com/docs/mailgun/user-manual/tracking-messages/#attaching-data-to-messages). Likewise, the Postmark documentation may also be consulted for more information on their support for [tags](https://postmarkapp.com/blog/tags-support-for-smtp) and [metadata](https://postmarkapp.com/support/article/1125-custom-metadata-faq). @@ -702,23 +778,25 @@ If your application is using Amazon SES to send emails, you should use the `meta Laravel's mail capabilities are powered by Symfony Mailer. Laravel allows you to register custom callbacks that will be invoked with the Symfony Message instance before sending the message. This gives you an opportunity to deeply customize the message before it is sent. To accomplish this, define a `using` parameter on your `Envelope` definition: - use Illuminate\Mail\Mailables\Envelope; - use Symfony\Component\Mime\Email; +```php +use Illuminate\Mail\Mailables\Envelope; +use Symfony\Component\Mime\Email; - /** - * Get the message envelope. - */ - public function envelope(): Envelope - { - return new Envelope( - subject: 'Order Shipped', - using: [ - function (Email $message) { - // ... - }, - ] - ); - } +/** + * Get the message envelope. + */ +public function envelope(): Envelope +{ + return new Envelope( + subject: 'Order Shipped', + using: [ + function (Email $message) { + // ... + }, + ] + ); +} +``` ## Markdown Mailables @@ -736,20 +814,22 @@ php artisan make:mail OrderShipped --markdown=mail.orders.shipped Then, when configuring the mailable `Content` definition within its `content` method, use the `markdown` parameter instead of the `view` parameter: - use Illuminate\Mail\Mailables\Content; +```php +use Illuminate\Mail\Mailables\Content; - /** - * Get the message content definition. - */ - public function content(): Content - { - return new Content( - markdown: 'mail.orders.shipped', - with: [ - 'url' => $this->orderUrl, - ], - ); - } +/** + * Get the message content definition. + */ +public function content(): Content +{ + return new Content( + markdown: 'mail.orders.shipped', + with: [ + 'url' => $this->orderUrl, + ], + ); +} +``` ### Writing Markdown Messages @@ -835,58 +915,66 @@ To customize the theme for an individual mailable, you may set the `$theme` prop To send a message, use the `to` method on the `Mail` [facade](/docs/{{version}}/facades). The `to` method accepts an email address, a user instance, or a collection of users. If you pass an object or collection of objects, the mailer will automatically use their `email` and `name` properties when determining the email's recipients, so make sure these attributes are available on your objects. Once you have specified your recipients, you may pass an instance of your mailable class to the `send` method: - order_id); + $order = Order::findOrFail($request->order_id); - // Ship the order... + // Ship the order... - Mail::to($request->user())->send(new OrderShipped($order)); + Mail::to($request->user())->send(new OrderShipped($order)); - return redirect('/orders'); - } + return redirect('/orders'); } +} +``` You are not limited to just specifying the "to" recipients when sending a message. You are free to set "to", "cc", and "bcc" recipients by chaining their respective methods together: - Mail::to($request->user()) - ->cc($moreUsers) - ->bcc($evenMoreUsers) - ->send(new OrderShipped($order)); +```php +Mail::to($request->user()) + ->cc($moreUsers) + ->bcc($evenMoreUsers) + ->send(new OrderShipped($order)); +``` #### Looping Over Recipients Occasionally, you may need to send a mailable to a list of recipients by iterating over an array of recipients / email addresses. However, since the `to` method appends email addresses to the mailable's list of recipients, each iteration through the loop will send another email to every previous recipient. Therefore, you should always re-create the mailable instance for each recipient: - foreach (['taylor@example.com', 'dries@example.com'] as $recipient) { - Mail::to($recipient)->send(new OrderShipped($order)); - } +```php +foreach (['taylor@example.com', 'dries@example.com'] as $recipient) { + Mail::to($recipient)->send(new OrderShipped($order)); +} +``` #### Sending Mail via a Specific Mailer By default, Laravel will send email using the mailer configured as the `default` mailer in your application's `mail` configuration file. However, you may use the `mailer` method to send a message using a specific mailer configuration: - Mail::mailer('postmark') - ->to($request->user()) - ->send(new OrderShipped($order)); +```php +Mail::mailer('postmark') + ->to($request->user()) + ->send(new OrderShipped($order)); +``` ### Queueing Mail @@ -896,10 +984,12 @@ By default, Laravel will send email using the mailer configured as the `default` Since sending email messages can negatively impact the response time of your application, many developers choose to queue email messages for background sending. Laravel makes this easy using its built-in [unified queue API](/docs/{{version}}/queues). To queue a mail message, use the `queue` method on the `Mail` facade after specifying the message's recipients: - Mail::to($request->user()) - ->cc($moreUsers) - ->bcc($evenMoreUsers) - ->queue(new OrderShipped($order)); +```php +Mail::to($request->user()) + ->cc($moreUsers) + ->bcc($evenMoreUsers) + ->queue(new OrderShipped($order)); +``` This method will automatically take care of pushing a job onto the queue so the message is sent in the background. You will need to [configure your queues](/docs/{{version}}/queues) before using this feature. @@ -908,36 +998,42 @@ This method will automatically take care of pushing a job onto the queue so the If you wish to delay the delivery of a queued email message, you may use the `later` method. As its first argument, the `later` method accepts a `DateTime` instance indicating when the message should be sent: - Mail::to($request->user()) - ->cc($moreUsers) - ->bcc($evenMoreUsers) - ->later(now()->addMinutes(10), new OrderShipped($order)); +```php +Mail::to($request->user()) + ->cc($moreUsers) + ->bcc($evenMoreUsers) + ->later(now()->addMinutes(10), new OrderShipped($order)); +``` #### Pushing to Specific Queues Since all mailable classes generated using the `make:mail` command make use of the `Illuminate\Bus\Queueable` trait, you may call the `onQueue` and `onConnection` methods on any mailable class instance, allowing you to specify the connection and queue name for the message: - $message = (new OrderShipped($order)) - ->onConnection('sqs') - ->onQueue('emails'); - - Mail::to($request->user()) - ->cc($moreUsers) - ->bcc($evenMoreUsers) - ->queue($message); +```php +$message = (new OrderShipped($order)) + ->onConnection('sqs') + ->onQueue('emails'); + +Mail::to($request->user()) + ->cc($moreUsers) + ->bcc($evenMoreUsers) + ->queue($message); +``` #### Queueing by Default If you have mailable classes that you want to always be queued, you may implement the `ShouldQueue` contract on the class. Now, even if you call the `send` method when mailing, the mailable will still be queued since it implements the contract: - use Illuminate\Contracts\Queue\ShouldQueue; +```php +use Illuminate\Contracts\Queue\ShouldQueue; - class OrderShipped extends Mailable implements ShouldQueue - { - // ... - } +class OrderShipped extends Mailable implements ShouldQueue +{ + // ... +} +``` #### Queued Mailables and Database Transactions @@ -946,33 +1042,37 @@ When queued mailables are dispatched within database transactions, they may be p If your queue connection's `after_commit` configuration option is set to `false`, you may still indicate that a particular queued mailable should be dispatched after all open database transactions have been committed by calling the `afterCommit` method when sending the mail message: - Mail::to($request->user())->send( - (new OrderShipped($order))->afterCommit() - ); +```php +Mail::to($request->user())->send( + (new OrderShipped($order))->afterCommit() +); +``` Alternatively, you may call the `afterCommit` method from your mailable's constructor: - afterCommit(); - } + $this->afterCommit(); } +} +``` > [!NOTE] > To learn more about working around these issues, please review the documentation regarding [queued jobs and database transactions](/docs/{{version}}/queues#jobs-and-database-transactions). @@ -982,23 +1082,27 @@ Alternatively, you may call the `afterCommit` method from your mailable's constr Sometimes you may wish to capture the HTML content of a mailable without sending it. To accomplish this, you may call the `render` method of the mailable. This method will return the evaluated HTML content of the mailable as a string: - use App\Mail\InvoicePaid; - use App\Models\Invoice; +```php +use App\Mail\InvoicePaid; +use App\Models\Invoice; - $invoice = Invoice::find(1); +$invoice = Invoice::find(1); - return (new InvoicePaid($invoice))->render(); +return (new InvoicePaid($invoice))->render(); +``` ### Previewing Mailables in the Browser When designing a mailable's template, it is convenient to quickly preview the rendered mailable in your browser like a typical Blade template. For this reason, Laravel allows you to return any mailable directly from a route closure or controller. When a mailable is returned, it will be rendered and displayed in the browser, allowing you to quickly preview its design without needing to send it to an actual email address: - Route::get('/mailable', function () { - $invoice = App\Models\Invoice::find(1); +```php +Route::get('/mailable', function () { + $invoice = App\Models\Invoice::find(1); - return new App\Mail\InvoicePaid($invoice); - }); + return new App\Mail\InvoicePaid($invoice); +}); +``` ## Localizing Mailables @@ -1007,31 +1111,37 @@ Laravel allows you to send mailables in a locale other than the request's curren To accomplish this, the `Mail` facade offers a `locale` method to set the desired language. The application will change into this locale when the mailable's template is being evaluated and then revert back to the previous locale when evaluation is complete: - Mail::to($request->user())->locale('es')->send( - new OrderShipped($order) - ); +```php +Mail::to($request->user())->locale('es')->send( + new OrderShipped($order) +); +``` ### User Preferred Locales Sometimes, applications store each user's preferred locale. By implementing the `HasLocalePreference` contract on one or more of your models, you may instruct Laravel to use this stored locale when sending mail: - use Illuminate\Contracts\Translation\HasLocalePreference; +```php +use Illuminate\Contracts\Translation\HasLocalePreference; - class User extends Model implements HasLocalePreference +class User extends Model implements HasLocalePreference +{ + /** + * Get the user's preferred locale. + */ + public function preferredLocale(): string { - /** - * Get the user's preferred locale. - */ - public function preferredLocale(): string - { - return $this->locale; - } + return $this->locale; } +} +``` Once you have implemented the interface, Laravel will automatically use the preferred locale when sending mailables and notifications to the model. Therefore, there is no need to call the `locale` method when using this interface: - Mail::to($request->user())->send(new OrderShipped($order)); +```php +Mail::to($request->user())->send(new OrderShipped($order)); +``` ## Testing @@ -1194,59 +1304,69 @@ class ExampleTest extends TestCase If you are queueing mailables for delivery in the background, you should use the `assertQueued` method instead of `assertSent`: - Mail::assertQueued(OrderShipped::class); - Mail::assertNotQueued(OrderShipped::class); - Mail::assertNothingQueued(); - Mail::assertQueuedCount(3); +```php +Mail::assertQueued(OrderShipped::class); +Mail::assertNotQueued(OrderShipped::class); +Mail::assertNothingQueued(); +Mail::assertQueuedCount(3); +``` You may pass a closure to the `assertSent`, `assertNotSent`, `assertQueued`, or `assertNotQueued` methods in order to assert that a mailable was sent that passes a given "truth test". If at least one mailable was sent that passes the given truth test then the assertion will be successful: - Mail::assertSent(function (OrderShipped $mail) use ($order) { - return $mail->order->id === $order->id; - }); +```php +Mail::assertSent(function (OrderShipped $mail) use ($order) { + return $mail->order->id === $order->id; +}); +``` When calling the `Mail` facade's assertion methods, the mailable instance accepted by the provided closure exposes helpful methods for examining the mailable: - Mail::assertSent(OrderShipped::class, function (OrderShipped $mail) use ($user) { - return $mail->hasTo($user->email) && - $mail->hasCc('...') && - $mail->hasBcc('...') && - $mail->hasReplyTo('...') && - $mail->hasFrom('...') && - $mail->hasSubject('...'); - }); +```php +Mail::assertSent(OrderShipped::class, function (OrderShipped $mail) use ($user) { + return $mail->hasTo($user->email) && + $mail->hasCc('...') && + $mail->hasBcc('...') && + $mail->hasReplyTo('...') && + $mail->hasFrom('...') && + $mail->hasSubject('...'); +}); +``` The mailable instance also includes several helpful methods for examining the attachments on a mailable: - use Illuminate\Mail\Mailables\Attachment; +```php +use Illuminate\Mail\Mailables\Attachment; - Mail::assertSent(OrderShipped::class, function (OrderShipped $mail) { - return $mail->hasAttachment( - Attachment::fromPath('/path/to/file') - ->as('name.pdf') - ->withMime('application/pdf') - ); - }); +Mail::assertSent(OrderShipped::class, function (OrderShipped $mail) { + return $mail->hasAttachment( + Attachment::fromPath('/path/to/file') + ->as('name.pdf') + ->withMime('application/pdf') + ); +}); - Mail::assertSent(OrderShipped::class, function (OrderShipped $mail) { - return $mail->hasAttachment( - Attachment::fromStorageDisk('s3', '/path/to/file') - ); - }); +Mail::assertSent(OrderShipped::class, function (OrderShipped $mail) { + return $mail->hasAttachment( + Attachment::fromStorageDisk('s3', '/path/to/file') + ); +}); - Mail::assertSent(OrderShipped::class, function (OrderShipped $mail) use ($pdfData) { - return $mail->hasAttachment( - Attachment::fromData(fn () => $pdfData, 'name.pdf') - ); - }); +Mail::assertSent(OrderShipped::class, function (OrderShipped $mail) use ($pdfData) { + return $mail->hasAttachment( + Attachment::fromData(fn () => $pdfData, 'name.pdf') + ); +}); +``` You may have noticed that there are two methods for asserting that mail was not sent: `assertNotSent` and `assertNotQueued`. Sometimes you may wish to assert that no mail was sent **or** queued. To accomplish this, you may use the `assertNothingOutgoing` and `assertNotOutgoing` methods: - Mail::assertNothingOutgoing(); +```php +Mail::assertNothingOutgoing(); - Mail::assertNotOutgoing(function (OrderShipped $mail) use ($order) { - return $mail->order->id === $order->id; - }); +Mail::assertNotOutgoing(function (OrderShipped $mail) use ($order) { + return $mail->order->id === $order->id; +}); +``` ## Mail and Local Development @@ -1270,106 +1390,116 @@ If you are using [Laravel Sail](/docs/{{version}}/sail), you may preview your me Finally, you may specify a global "to" address by invoking the `alwaysTo` method offered by the `Mail` facade. Typically, this method should be called from the `boot` method of one of your application's service providers: - use Illuminate\Support\Facades\Mail; +```php +use Illuminate\Support\Facades\Mail; - /** - * Bootstrap any application services. - */ - public function boot(): void - { - if ($this->app->environment('local')) { - Mail::alwaysTo('taylor@example.com'); - } +/** + * Bootstrap any application services. + */ +public function boot(): void +{ + if ($this->app->environment('local')) { + Mail::alwaysTo('taylor@example.com'); } +} +``` ## Events Laravel dispatches two events while sending mail messages. The `MessageSending` event is dispatched prior to a message being sent, while the `MessageSent` event is dispatched after a message has been sent. Remember, these events are dispatched when the mail is being *sent*, not when it is queued. You may create [event listeners](/docs/{{version}}/events) for these events within your application: - use Illuminate\Mail\Events\MessageSending; - // use Illuminate\Mail\Events\MessageSent; +```php +use Illuminate\Mail\Events\MessageSending; +// use Illuminate\Mail\Events\MessageSent; - class LogMessage +class LogMessage +{ + /** + * Handle the given event. + */ + public function handle(MessageSending $event): void { - /** - * Handle the given event. - */ - public function handle(MessageSending $event): void - { - // ... - } + // ... } +} +``` ## Custom Transports Laravel includes a variety of mail transports; however, you may wish to write your own transports to deliver email via other services that Laravel does not support out of the box. To get started, define a class that extends the `Symfony\Component\Mailer\Transport\AbstractTransport` class. Then, implement the `doSend` and `__toString()` methods on your transport: - use MailchimpTransactional\ApiClient; - use Symfony\Component\Mailer\SentMessage; - use Symfony\Component\Mailer\Transport\AbstractTransport; - use Symfony\Component\Mime\Address; - use Symfony\Component\Mime\MessageConverter; +```php +use MailchimpTransactional\ApiClient; +use Symfony\Component\Mailer\SentMessage; +use Symfony\Component\Mailer\Transport\AbstractTransport; +use Symfony\Component\Mime\Address; +use Symfony\Component\Mime\MessageConverter; - class MailchimpTransport extends AbstractTransport - { - /** - * Create a new Mailchimp transport instance. - */ - public function __construct( - protected ApiClient $client, - ) { - parent::__construct(); - } - - /** - * {@inheritDoc} - */ - protected function doSend(SentMessage $message): void - { - $email = MessageConverter::toEmail($message->getOriginalMessage()); - - $this->client->messages->send(['message' => [ - 'from_email' => $email->getFrom(), - 'to' => collect($email->getTo())->map(function (Address $email) { - return ['email' => $email->getAddress(), 'type' => 'to']; - })->all(), - 'subject' => $email->getSubject(), - 'text' => $email->getTextBody(), - ]]); - } - - /** - * Get the string representation of the transport. - */ - public function __toString(): string - { - return 'mailchimp'; - } +class MailchimpTransport extends AbstractTransport +{ + /** + * Create a new Mailchimp transport instance. + */ + public function __construct( + protected ApiClient $client, + ) { + parent::__construct(); } -Once you've defined your custom transport, you may register it via the `extend` method provided by the `Mail` facade. Typically, this should be done within the `boot` method of your application's `AppServiceProvider` service provider. A `$config` argument will be passed to the closure provided to the `extend` method. This argument will contain the configuration array defined for the mailer in the application's `config/mail.php` configuration file: - - use App\Mail\MailchimpTransport; - use Illuminate\Support\Facades\Mail; + /** + * {@inheritDoc} + */ + protected function doSend(SentMessage $message): void + { + $email = MessageConverter::toEmail($message->getOriginalMessage()); + + $this->client->messages->send(['message' => [ + 'from_email' => $email->getFrom(), + 'to' => collect($email->getTo())->map(function (Address $email) { + return ['email' => $email->getAddress(), 'type' => 'to']; + })->all(), + 'subject' => $email->getSubject(), + 'text' => $email->getTextBody(), + ]]); + } /** - * Bootstrap any application services. + * Get the string representation of the transport. */ - public function boot(): void + public function __toString(): string { - Mail::extend('mailchimp', function (array $config = []) { - return new MailchimpTransport(/* ... */); - }); + return 'mailchimp'; } +} +``` + +Once you've defined your custom transport, you may register it via the `extend` method provided by the `Mail` facade. Typically, this should be done within the `boot` method of your application's `AppServiceProvider` service provider. A `$config` argument will be passed to the closure provided to the `extend` method. This argument will contain the configuration array defined for the mailer in the application's `config/mail.php` configuration file: + +```php +use App\Mail\MailchimpTransport; +use Illuminate\Support\Facades\Mail; + +/** + * Bootstrap any application services. + */ +public function boot(): void +{ + Mail::extend('mailchimp', function (array $config = []) { + return new MailchimpTransport(/* ... */); + }); +} +``` Once your custom transport has been defined and registered, you may create a mailer definition within your application's `config/mail.php` configuration file that utilizes the new transport: - 'mailchimp' => [ - 'transport' => 'mailchimp', - // ... - ], +```php +'mailchimp' => [ + 'transport' => 'mailchimp', + // ... +], +``` ### Additional Symfony Transports @@ -1382,35 +1512,41 @@ composer require symfony/brevo-mailer symfony/http-client Once the Brevo mailer package has been installed, you may add an entry for your Brevo API credentials to your application's `services` configuration file: - 'brevo' => [ - 'key' => 'your-api-key', - ], +```php +'brevo' => [ + 'key' => 'your-api-key', +], +``` Next, you may use the `Mail` facade's `extend` method to register the transport with Laravel. Typically, this should be done within the `boot` method of a service provider: - use Illuminate\Support\Facades\Mail; - use Symfony\Component\Mailer\Bridge\Brevo\Transport\BrevoTransportFactory; - use Symfony\Component\Mailer\Transport\Dsn; +```php +use Illuminate\Support\Facades\Mail; +use Symfony\Component\Mailer\Bridge\Brevo\Transport\BrevoTransportFactory; +use Symfony\Component\Mailer\Transport\Dsn; - /** - * Bootstrap any application services. - */ - public function boot(): void - { - Mail::extend('brevo', function () { - return (new BrevoTransportFactory)->create( - new Dsn( - 'brevo+api', - 'default', - config('services.brevo.key') - ) - ); - }); - } +/** + * Bootstrap any application services. + */ +public function boot(): void +{ + Mail::extend('brevo', function () { + return (new BrevoTransportFactory)->create( + new Dsn( + 'brevo+api', + 'default', + config('services.brevo.key') + ) + ); + }); +} +``` Once your transport has been registered, you may create a mailer definition within your application's config/mail.php configuration file that utilizes the new transport: - 'brevo' => [ - 'transport' => 'brevo', - // ... - ], +```php +'brevo' => [ + 'transport' => 'brevo', + // ... +], +``` diff --git a/middleware.md b/middleware.md index 95db7bb8b7d..c696ea8597b 100644 --- a/middleware.md +++ b/middleware.md @@ -29,30 +29,32 @@ php artisan make:middleware EnsureTokenIsValid This command will place a new `EnsureTokenIsValid` class within your `app/Http/Middleware` directory. In this middleware, we will only allow access to the route if the supplied `token` input matches a specified value. Otherwise, we will redirect the users back to the `/home` URI: - input('token') !== 'my-secret-token') { - return redirect('/home'); - } - - return $next($request); + if ($request->input('token') !== 'my-secret-token') { + return redirect('/home'); } + + return $next($request); } +} +``` As you can see, if the given `token` does not match our secret token, the middleware will return an HTTP redirect to the client; otherwise, the request will be passed further into the application. To pass the request deeper into the application (allowing the middleware to "pass"), you should call the `$next` callback with the `$request`. @@ -66,45 +68,49 @@ It's best to envision middleware as a series of "layers" HTTP requests must pass Of course, a middleware can perform tasks before or after passing the request deeper into the application. For example, the following middleware would perform some task **before** the request is handled by the application: - ## Registering Middleware @@ -114,11 +120,13 @@ However, this middleware would perform its task **after** the request is handled If you want a middleware to run during every HTTP request to your application, you may append it to the global middleware stack in your application's `bootstrap/app.php` file: - use App\Http\Middleware\EnsureTokenIsValid; +```php +use App\Http\Middleware\EnsureTokenIsValid; - ->withMiddleware(function (Middleware $middleware) { - $middleware->append(EnsureTokenIsValid::class); - }) +->withMiddleware(function (Middleware $middleware) { + $middleware->append(EnsureTokenIsValid::class); +}) +``` The `$middleware` object provided to the `withMiddleware` closure is an instance of `Illuminate\Foundation\Configuration\Middleware` and is responsible for managing the middleware assigned to your application's routes. The `append` method adds the middleware to the end of the list of global middleware. If you would like to add a middleware to the beginning of the list, you should use the `prepend` method. @@ -127,62 +135,72 @@ The `$middleware` object provided to the `withMiddleware` closure is an instance If you would like to manage Laravel's global middleware stack manually, you may provide Laravel's default stack of global middleware to the `use` method. Then, you may adjust the default middleware stack as necessary: - ->withMiddleware(function (Middleware $middleware) { - $middleware->use([ - \Illuminate\Foundation\Http\Middleware\InvokeDeferredCallbacks::class, - // \Illuminate\Http\Middleware\TrustHosts::class, - \Illuminate\Http\Middleware\TrustProxies::class, - \Illuminate\Http\Middleware\HandleCors::class, - \Illuminate\Foundation\Http\Middleware\PreventRequestsDuringMaintenance::class, - \Illuminate\Http\Middleware\ValidatePostSize::class, - \Illuminate\Foundation\Http\Middleware\TrimStrings::class, - \Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class, - ]); - }) +```php +->withMiddleware(function (Middleware $middleware) { + $middleware->use([ + \Illuminate\Foundation\Http\Middleware\InvokeDeferredCallbacks::class, + // \Illuminate\Http\Middleware\TrustHosts::class, + \Illuminate\Http\Middleware\TrustProxies::class, + \Illuminate\Http\Middleware\HandleCors::class, + \Illuminate\Foundation\Http\Middleware\PreventRequestsDuringMaintenance::class, + \Illuminate\Http\Middleware\ValidatePostSize::class, + \Illuminate\Foundation\Http\Middleware\TrimStrings::class, + \Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class, + ]); +}) +``` ### Assigning Middleware to Routes If you would like to assign middleware to specific routes, you may invoke the `middleware` method when defining the route: - use App\Http\Middleware\EnsureTokenIsValid; +```php +use App\Http\Middleware\EnsureTokenIsValid; - Route::get('/profile', function () { - // ... - })->middleware(EnsureTokenIsValid::class); +Route::get('/profile', function () { + // ... +})->middleware(EnsureTokenIsValid::class); +``` You may assign multiple middleware to the route by passing an array of middleware names to the `middleware` method: - Route::get('/', function () { - // ... - })->middleware([First::class, Second::class]); +```php +Route::get('/', function () { + // ... +})->middleware([First::class, Second::class]); +``` #### Excluding Middleware When assigning middleware to a group of routes, you may occasionally need to prevent the middleware from being applied to an individual route within the group. You may accomplish this using the `withoutMiddleware` method: - use App\Http\Middleware\EnsureTokenIsValid; +```php +use App\Http\Middleware\EnsureTokenIsValid; - Route::middleware([EnsureTokenIsValid::class])->group(function () { - Route::get('/', function () { - // ... - }); - - Route::get('/profile', function () { - // ... - })->withoutMiddleware([EnsureTokenIsValid::class]); +Route::middleware([EnsureTokenIsValid::class])->group(function () { + Route::get('/', function () { + // ... }); + Route::get('/profile', function () { + // ... + })->withoutMiddleware([EnsureTokenIsValid::class]); +}); +``` + You may also exclude a given set of middleware from an entire [group](/docs/{{version}}/routing#route-groups) of route definitions: - use App\Http\Middleware\EnsureTokenIsValid; +```php +use App\Http\Middleware\EnsureTokenIsValid; - Route::withoutMiddleware([EnsureTokenIsValid::class])->group(function () { - Route::get('/profile', function () { - // ... - }); +Route::withoutMiddleware([EnsureTokenIsValid::class])->group(function () { + Route::get('/profile', function () { + // ... }); +}); +``` The `withoutMiddleware` method can only remove route middleware and does not apply to [global middleware](#global-middleware). @@ -191,30 +209,34 @@ The `withoutMiddleware` method can only remove route middleware and does not app Sometimes you may want to group several middleware under a single key to make them easier to assign to routes. You may accomplish this using the `appendToGroup` method within your application's `bootstrap/app.php` file: - use App\Http\Middleware\First; - use App\Http\Middleware\Second; +```php +use App\Http\Middleware\First; +use App\Http\Middleware\Second; - ->withMiddleware(function (Middleware $middleware) { - $middleware->appendToGroup('group-name', [ - First::class, - Second::class, - ]); +->withMiddleware(function (Middleware $middleware) { + $middleware->appendToGroup('group-name', [ + First::class, + Second::class, + ]); - $middleware->prependToGroup('group-name', [ - First::class, - Second::class, - ]); - }) + $middleware->prependToGroup('group-name', [ + First::class, + Second::class, + ]); +}) +``` Middleware groups may be assigned to routes and controller actions using the same syntax as individual middleware: - Route::get('/', function () { - // ... - })->middleware('group-name'); +```php +Route::get('/', function () { + // ... +})->middleware('group-name'); - Route::middleware(['group-name'])->group(function () { - // ... - }); +Route::middleware(['group-name'])->group(function () { + // ... +}); +``` #### Laravel's Default Middleware Groups @@ -244,56 +266,64 @@ Laravel includes predefined `web` and `api` middleware groups that contain commo If you would like to append or prepend middleware to these groups, you may use the `web` and `api` methods within your application's `bootstrap/app.php` file. The `web` and `api` methods are convenient alternatives to the `appendToGroup` method: - use App\Http\Middleware\EnsureTokenIsValid; - use App\Http\Middleware\EnsureUserIsSubscribed; +```php +use App\Http\Middleware\EnsureTokenIsValid; +use App\Http\Middleware\EnsureUserIsSubscribed; - ->withMiddleware(function (Middleware $middleware) { - $middleware->web(append: [ - EnsureUserIsSubscribed::class, - ]); +->withMiddleware(function (Middleware $middleware) { + $middleware->web(append: [ + EnsureUserIsSubscribed::class, + ]); - $middleware->api(prepend: [ - EnsureTokenIsValid::class, - ]); - }) + $middleware->api(prepend: [ + EnsureTokenIsValid::class, + ]); +}) +``` You may even replace one of Laravel's default middleware group entries with a custom middleware of your own: - use App\Http\Middleware\StartCustomSession; - use Illuminate\Session\Middleware\StartSession; +```php +use App\Http\Middleware\StartCustomSession; +use Illuminate\Session\Middleware\StartSession; - $middleware->web(replace: [ - StartSession::class => StartCustomSession::class, - ]); +$middleware->web(replace: [ + StartSession::class => StartCustomSession::class, +]); +``` Or, you may remove a middleware entirely: - $middleware->web(remove: [ - StartSession::class, - ]); +```php +$middleware->web(remove: [ + StartSession::class, +]); +``` #### Manually Managing Laravel's Default Middleware Groups If you would like to manually manage all of the middleware within Laravel's default `web` and `api` middleware groups, you may redefine the groups entirely. The example below will define the `web` and `api` middleware groups with their default middleware, allowing you to customize them as necessary: - ->withMiddleware(function (Middleware $middleware) { - $middleware->group('web', [ - \Illuminate\Cookie\Middleware\EncryptCookies::class, - \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class, - \Illuminate\Session\Middleware\StartSession::class, - \Illuminate\View\Middleware\ShareErrorsFromSession::class, - \Illuminate\Foundation\Http\Middleware\ValidateCsrfToken::class, - \Illuminate\Routing\Middleware\SubstituteBindings::class, - // \Illuminate\Session\Middleware\AuthenticateSession::class, - ]); - - $middleware->group('api', [ - // \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class, - // 'throttle:api', - \Illuminate\Routing\Middleware\SubstituteBindings::class, - ]); - }) +```php +->withMiddleware(function (Middleware $middleware) { + $middleware->group('web', [ + \Illuminate\Cookie\Middleware\EncryptCookies::class, + \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class, + \Illuminate\Session\Middleware\StartSession::class, + \Illuminate\View\Middleware\ShareErrorsFromSession::class, + \Illuminate\Foundation\Http\Middleware\ValidateCsrfToken::class, + \Illuminate\Routing\Middleware\SubstituteBindings::class, + // \Illuminate\Session\Middleware\AuthenticateSession::class, + ]); + + $middleware->group('api', [ + // \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class, + // 'throttle:api', + \Illuminate\Routing\Middleware\SubstituteBindings::class, + ]); +}) +``` > [!NOTE] > By default, the `web` and `api` middleware groups are automatically applied to your application's corresponding `routes/web.php` and `routes/api.php` files by the `bootstrap/app.php` file. @@ -303,19 +333,23 @@ If you would like to manually manage all of the middleware within Laravel's defa You may assign aliases to middleware in your application's `bootstrap/app.php` file. Middleware aliases allow you to define a short alias for a given middleware class, which can be especially useful for middleware with long class names: - use App\Http\Middleware\EnsureUserIsSubscribed; +```php +use App\Http\Middleware\EnsureUserIsSubscribed; - ->withMiddleware(function (Middleware $middleware) { - $middleware->alias([ - 'subscribed' => EnsureUserIsSubscribed::class - ]); - }) +->withMiddleware(function (Middleware $middleware) { + $middleware->alias([ + 'subscribed' => EnsureUserIsSubscribed::class + ]); +}) +``` Once the middleware alias has been defined in your application's `bootstrap/app.php` file, you may use the alias when assigning the middleware to routes: - Route::get('/profile', function () { - // ... - })->middleware('subscribed'); +```php +Route::get('/profile', function () { + // ... +})->middleware('subscribed'); +``` For convenience, some of Laravel's built-in middleware are aliased by default. For example, the `auth` middleware is an alias for the `Illuminate\Auth\Middleware\Authenticate` middleware. Below is a list of the default middleware aliases: @@ -343,22 +377,24 @@ For convenience, some of Laravel's built-in middleware are aliased by default. F Rarely, you may need your middleware to execute in a specific order but not have control over their order when they are assigned to the route. In these situations, you may specify your middleware priority using the `priority` method in your application's `bootstrap/app.php` file: - ->withMiddleware(function (Middleware $middleware) { - $middleware->priority([ - \Illuminate\Foundation\Http\Middleware\HandlePrecognitiveRequests::class, - \Illuminate\Cookie\Middleware\EncryptCookies::class, - \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class, - \Illuminate\Session\Middleware\StartSession::class, - \Illuminate\View\Middleware\ShareErrorsFromSession::class, - \Illuminate\Foundation\Http\Middleware\ValidateCsrfToken::class, - \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class, - \Illuminate\Routing\Middleware\ThrottleRequests::class, - \Illuminate\Routing\Middleware\ThrottleRequestsWithRedis::class, - \Illuminate\Routing\Middleware\SubstituteBindings::class, - \Illuminate\Contracts\Auth\Middleware\AuthenticatesRequests::class, - \Illuminate\Auth\Middleware\Authorize::class, - ]); - }) +```php +->withMiddleware(function (Middleware $middleware) { + $middleware->priority([ + \Illuminate\Foundation\Http\Middleware\HandlePrecognitiveRequests::class, + \Illuminate\Cookie\Middleware\EncryptCookies::class, + \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class, + \Illuminate\Session\Middleware\StartSession::class, + \Illuminate\View\Middleware\ShareErrorsFromSession::class, + \Illuminate\Foundation\Http\Middleware\ValidateCsrfToken::class, + \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class, + \Illuminate\Routing\Middleware\ThrottleRequests::class, + \Illuminate\Routing\Middleware\ThrottleRequestsWithRedis::class, + \Illuminate\Routing\Middleware\SubstituteBindings::class, + \Illuminate\Contracts\Auth\Middleware\AuthenticatesRequests::class, + \Illuminate\Auth\Middleware\Authorize::class, + ]); +}) +``` ## Middleware Parameters @@ -367,90 +403,100 @@ Middleware can also receive additional parameters. For example, if your applicat Additional middleware parameters will be passed to the middleware after the `$next` argument: - user()->hasRole($role)) { - // Redirect... - } - - return $next($request); + if (! $request->user()->hasRole($role)) { + // Redirect... } + return $next($request); } +} +``` + Middleware parameters may be specified when defining the route by separating the middleware name and parameters with a `:`: - use App\Http\Middleware\EnsureUserHasRole; +```php +use App\Http\Middleware\EnsureUserHasRole; - Route::put('/post/{id}', function (string $id) { - // ... - })->middleware(EnsureUserHasRole::class.':editor'); +Route::put('/post/{id}', function (string $id) { + // ... +})->middleware(EnsureUserHasRole::class.':editor'); +``` Multiple parameters may be delimited by commas: - Route::put('/post/{id}', function (string $id) { - // ... - })->middleware(EnsureUserHasRole::class.':editor,publisher'); +```php +Route::put('/post/{id}', function (string $id) { + // ... +})->middleware(EnsureUserHasRole::class.':editor,publisher'); +``` ## Terminable Middleware Sometimes a middleware may need to do some work after the HTTP response has been sent to the browser. If you define a `terminate` method on your middleware and your web server is using FastCGI, the `terminate` method will automatically be called after the response is sent to the browser: - app->singleton(TerminatingMiddleware::class); - } +/** + * Register any application services. + */ +public function register(): void +{ + $this->app->singleton(TerminatingMiddleware::class); +} +``` diff --git a/migrations.md b/migrations.md index f77fbd56360..0a44d45fbd1 100644 --- a/migrations.md +++ b/migrations.md @@ -80,55 +80,59 @@ A migration class contains two methods: `up` and `down`. The `up` method is used Within both of these methods, you may use the Laravel schema builder to expressively create and modify tables. To learn about all of the methods available on the `Schema` builder, [check out its documentation](#creating-tables). For example, the following migration creates a `flights` table: - id(); - $table->string('name'); - $table->string('airline'); - $table->timestamps(); - }); - } - - /** - * Reverse the migrations. - */ - public function down(): void - { - Schema::drop('flights'); - } - }; - - -#### Setting the Migration Connection +```php +id(); + $table->string('name'); + $table->string('airline'); + $table->timestamps(); + }); + } /** - * Run the migrations. + * Reverse the migrations. */ - public function up(): void + public function down(): void { - // ... + Schema::drop('flights'); } +}; +``` + + +#### Setting the Migration Connection + +If your migration will be interacting with a database connection other than your application's default database connection, you should set the `$connection` property of your migration: + +```php +/** + * The database connection that should be used by the migration. + * + * @var string + */ +protected $connection = 'pgsql'; + +/** + * Run the migrations. + */ +public function up(): void +{ + // ... +} +``` ## Running Migrations @@ -190,9 +194,9 @@ php artisan migrate:rollback --step=5 You may roll back a specific "batch" of migrations by providing the `batch` option to the `rollback` command, where the `batch` option corresponds to a batch value within your application's `migrations` database table. For example, the following command will roll back all migrations in batch three: - ```shell +```shell php artisan migrate:rollback --batch=3 - ``` +``` If you would like to see the SQL statements that will be executed by the migrations without actually running them, you may provide the `--pretend` flag to the `migrate:rollback` command: @@ -252,15 +256,17 @@ php artisan migrate:fresh --database=admin To create a new database table, use the `create` method on the `Schema` facade. The `create` method accepts two arguments: the first is the name of the table, while the second is a closure which receives a `Blueprint` object that may be used to define the new table: - use Illuminate\Database\Schema\Blueprint; - use Illuminate\Support\Facades\Schema; +```php +use Illuminate\Database\Schema\Blueprint; +use Illuminate\Support\Facades\Schema; - Schema::create('users', function (Blueprint $table) { - $table->id(); - $table->string('name'); - $table->string('email'); - $table->timestamps(); - }); +Schema::create('users', function (Blueprint $table) { + $table->id(); + $table->string('name'); + $table->string('email'); + $table->timestamps(); +}); +``` When creating the table, you may use any of the schema builder's [column methods](#creating-columns) to define the table's columns. @@ -269,86 +275,104 @@ When creating the table, you may use any of the schema builder's [column methods You may determine the existence of a table, column, or index using the `hasTable`, `hasColumn`, and `hasIndex` methods: - if (Schema::hasTable('users')) { - // The "users" table exists... - } +```php +if (Schema::hasTable('users')) { + // The "users" table exists... +} - if (Schema::hasColumn('users', 'email')) { - // The "users" table exists and has an "email" column... - } +if (Schema::hasColumn('users', 'email')) { + // The "users" table exists and has an "email" column... +} - if (Schema::hasIndex('users', ['email'], 'unique')) { - // The "users" table exists and has a unique index on the "email" column... - } +if (Schema::hasIndex('users', ['email'], 'unique')) { + // The "users" table exists and has a unique index on the "email" column... +} +``` #### Database Connection and Table Options If you want to perform a schema operation on a database connection that is not your application's default connection, use the `connection` method: - Schema::connection('sqlite')->create('users', function (Blueprint $table) { - $table->id(); - }); +```php +Schema::connection('sqlite')->create('users', function (Blueprint $table) { + $table->id(); +}); +``` In addition, a few other properties and methods may be used to define other aspects of the table's creation. The `engine` property may be used to specify the table's storage engine when using MariaDB or MySQL: - Schema::create('users', function (Blueprint $table) { - $table->engine('InnoDB'); +```php +Schema::create('users', function (Blueprint $table) { + $table->engine('InnoDB'); - // ... - }); + // ... +}); +``` The `charset` and `collation` properties may be used to specify the character set and collation for the created table when using MariaDB or MySQL: - Schema::create('users', function (Blueprint $table) { - $table->charset('utf8mb4'); - $table->collation('utf8mb4_unicode_ci'); +```php +Schema::create('users', function (Blueprint $table) { + $table->charset('utf8mb4'); + $table->collation('utf8mb4_unicode_ci'); - // ... - }); + // ... +}); +``` The `temporary` method may be used to indicate that the table should be "temporary". Temporary tables are only visible to the current connection's database session and are dropped automatically when the connection is closed: - Schema::create('calculations', function (Blueprint $table) { - $table->temporary(); +```php +Schema::create('calculations', function (Blueprint $table) { + $table->temporary(); - // ... - }); + // ... +}); +``` If you would like to add a "comment" to a database table, you may invoke the `comment` method on the table instance. Table comments are currently only supported by MariaDB, MySQL, and PostgreSQL: - Schema::create('calculations', function (Blueprint $table) { - $table->comment('Business calculations'); +```php +Schema::create('calculations', function (Blueprint $table) { + $table->comment('Business calculations'); - // ... - }); + // ... +}); +``` ### Updating Tables The `table` method on the `Schema` facade may be used to update existing tables. Like the `create` method, the `table` method accepts two arguments: the name of the table and a closure that receives a `Blueprint` instance you may use to add columns or indexes to the table: - use Illuminate\Database\Schema\Blueprint; - use Illuminate\Support\Facades\Schema; +```php +use Illuminate\Database\Schema\Blueprint; +use Illuminate\Support\Facades\Schema; - Schema::table('users', function (Blueprint $table) { - $table->integer('votes'); - }); +Schema::table('users', function (Blueprint $table) { + $table->integer('votes'); +}); +``` ### Renaming / Dropping Tables To rename an existing database table, use the `rename` method: - use Illuminate\Support\Facades\Schema; +```php +use Illuminate\Support\Facades\Schema; - Schema::rename($from, $to); +Schema::rename($from, $to); +``` To drop an existing table, you may use the `drop` or `dropIfExists` methods: - Schema::drop('users'); +```php +Schema::drop('users'); - Schema::dropIfExists('users'); +Schema::dropIfExists('users'); +``` #### Renaming Tables With Foreign Keys @@ -363,12 +387,14 @@ Before renaming a table, you should verify that any foreign key constraints on t The `table` method on the `Schema` facade may be used to update existing tables. Like the `create` method, the `table` method accepts two arguments: the name of the table and a closure that receives an `Illuminate\Database\Schema\Blueprint` instance you may use to add columns to the table: - use Illuminate\Database\Schema\Blueprint; - use Illuminate\Support\Facades\Schema; +```php +use Illuminate\Database\Schema\Blueprint; +use Illuminate\Support\Facades\Schema; - Schema::table('users', function (Blueprint $table) { - $table->integer('votes'); - }); +Schema::table('users', function (Blueprint $table) { + $table->integer('votes'); +}); +``` ### Available Column Types @@ -540,125 +566,161 @@ The schema builder blueprint offers a variety of methods that correspond to the The `bigIncrements` method creates an auto-incrementing `UNSIGNED BIGINT` (primary key) equivalent column: - $table->bigIncrements('id'); +```php +$table->bigIncrements('id'); +``` #### `bigInteger()` {.collection-method} The `bigInteger` method creates a `BIGINT` equivalent column: - $table->bigInteger('votes'); +```php +$table->bigInteger('votes'); +``` #### `binary()` {.collection-method} The `binary` method creates a `BLOB` equivalent column: - $table->binary('photo'); +```php +$table->binary('photo'); +``` When utilizing MySQL, MariaDB, or SQL Server, you may pass `length` and `fixed` arguments to create `VARBINARY` or `BINARY` equivalent column: - $table->binary('data', length: 16); // VARBINARY(16) +```php +$table->binary('data', length: 16); // VARBINARY(16) - $table->binary('data', length: 16, fixed: true); // BINARY(16) +$table->binary('data', length: 16, fixed: true); // BINARY(16) +``` #### `boolean()` {.collection-method} The `boolean` method creates a `BOOLEAN` equivalent column: - $table->boolean('confirmed'); +```php +$table->boolean('confirmed'); +``` #### `char()` {.collection-method} The `char` method creates a `CHAR` equivalent column with of a given length: - $table->char('name', length: 100); +```php +$table->char('name', length: 100); +``` #### `dateTimeTz()` {.collection-method} The `dateTimeTz` method creates a `DATETIME` (with timezone) equivalent column with an optional fractional seconds precision: - $table->dateTimeTz('created_at', precision: 0); +```php +$table->dateTimeTz('created_at', precision: 0); +``` #### `dateTime()` {.collection-method} The `dateTime` method creates a `DATETIME` equivalent column with an optional fractional seconds precision: - $table->dateTime('created_at', precision: 0); +```php +$table->dateTime('created_at', precision: 0); +``` #### `date()` {.collection-method} The `date` method creates a `DATE` equivalent column: - $table->date('created_at'); +```php +$table->date('created_at'); +``` #### `decimal()` {.collection-method} The `decimal` method creates a `DECIMAL` equivalent column with the given precision (total digits) and scale (decimal digits): - $table->decimal('amount', total: 8, places: 2); +```php +$table->decimal('amount', total: 8, places: 2); +``` #### `double()` {.collection-method} The `double` method creates a `DOUBLE` equivalent column: - $table->double('amount'); +```php +$table->double('amount'); +``` #### `enum()` {.collection-method} The `enum` method creates a `ENUM` equivalent column with the given valid values: - $table->enum('difficulty', ['easy', 'hard']); +```php +$table->enum('difficulty', ['easy', 'hard']); +``` #### `float()` {.collection-method} The `float` method creates a `FLOAT` equivalent column with the given precision: - $table->float('amount', precision: 53); +```php +$table->float('amount', precision: 53); +``` #### `foreignId()` {.collection-method} The `foreignId` method creates an `UNSIGNED BIGINT` equivalent column: - $table->foreignId('user_id'); +```php +$table->foreignId('user_id'); +``` #### `foreignIdFor()` {.collection-method} The `foreignIdFor` method adds a `{column}_id` equivalent column for a given model class. The column type will be `UNSIGNED BIGINT`, `CHAR(36)`, or `CHAR(26)` depending on the model key type: - $table->foreignIdFor(User::class); +```php +$table->foreignIdFor(User::class); +``` #### `foreignUlid()` {.collection-method} The `foreignUlid` method creates a `ULID` equivalent column: - $table->foreignUlid('user_id'); +```php +$table->foreignUlid('user_id'); +``` #### `foreignUuid()` {.collection-method} The `foreignUuid` method creates a `UUID` equivalent column: - $table->foreignUuid('user_id'); +```php +$table->foreignUuid('user_id'); +``` #### `geography()` {.collection-method} The `geography` method creates a `GEOGRAPHY` equivalent column with the given spatial type and SRID (Spatial Reference System Identifier): - $table->geography('coordinates', subtype: 'point', srid: 4326); +```php +$table->geography('coordinates', subtype: 'point', srid: 4326); +``` > [!NOTE] > Support for spatial types depends on your database driver. Please refer to your database's documentation. If your application is utilizing a PostgreSQL database, you must install the [PostGIS](https://postgis.net) extension before the `geography` method may be used. @@ -668,7 +730,9 @@ The `geography` method creates a `GEOGRAPHY` equivalent column with the given sp The `geometry` method creates a `GEOMETRY` equivalent column with the given spatial type and SRID (Spatial Reference System Identifier): - $table->geometry('positions', subtype: 'point', srid: 0); +```php +$table->geometry('positions', subtype: 'point', srid: 0); +``` > [!NOTE] > Support for spatial types depends on your database driver. Please refer to your database's documentation. If your application is utilizing a PostgreSQL database, you must install the [PostGIS](https://postgis.net) extension before the `geometry` method may be used. @@ -678,28 +742,36 @@ The `geometry` method creates a `GEOMETRY` equivalent column with the given spat The `id` method is an alias of the `bigIncrements` method. By default, the method will create an `id` column; however, you may pass a column name if you would like to assign a different name to the column: - $table->id(); +```php +$table->id(); +``` #### `increments()` {.collection-method} The `increments` method creates an auto-incrementing `UNSIGNED INTEGER` equivalent column as a primary key: - $table->increments('id'); +```php +$table->increments('id'); +``` #### `integer()` {.collection-method} The `integer` method creates an `INTEGER` equivalent column: - $table->integer('votes'); +```php +$table->integer('votes'); +``` #### `ipAddress()` {.collection-method} The `ipAddress` method creates a `VARCHAR` equivalent column: - $table->ipAddress('visitor'); +```php +$table->ipAddress('visitor'); +``` When using PostgreSQL, an `INET` column will be created. @@ -708,7 +780,9 @@ When using PostgreSQL, an `INET` column will be created. The `json` method creates a `JSON` equivalent column: - $table->json('options'); +```php +$table->json('options'); +``` When using SQLite, a `TEXT` column will be created. @@ -717,7 +791,9 @@ When using SQLite, a `TEXT` column will be created. The `jsonb` method creates a `JSONB` equivalent column: - $table->jsonb('options'); +```php +$table->jsonb('options'); +``` When using SQLite, a `TEXT` column will be created. @@ -726,43 +802,57 @@ When using SQLite, a `TEXT` column will be created. The `longText` method creates a `LONGTEXT` equivalent column: - $table->longText('description'); +```php +$table->longText('description'); +``` When utilizing MySQL or MariaDB, you may apply a `binary` character set to the column in order to create a `LONGBLOB` equivalent column: - $table->longText('data')->charset('binary'); // LONGBLOB +```php +$table->longText('data')->charset('binary'); // LONGBLOB +``` #### `macAddress()` {.collection-method} The `macAddress` method creates a column that is intended to hold a MAC address. Some database systems, such as PostgreSQL, have a dedicated column type for this type of data. Other database systems will use a string equivalent column: - $table->macAddress('device'); +```php +$table->macAddress('device'); +``` #### `mediumIncrements()` {.collection-method} The `mediumIncrements` method creates an auto-incrementing `UNSIGNED MEDIUMINT` equivalent column as a primary key: - $table->mediumIncrements('id'); +```php +$table->mediumIncrements('id'); +``` #### `mediumInteger()` {.collection-method} The `mediumInteger` method creates a `MEDIUMINT` equivalent column: - $table->mediumInteger('votes'); +```php +$table->mediumInteger('votes'); +``` #### `mediumText()` {.collection-method} The `mediumText` method creates a `MEDIUMTEXT` equivalent column: - $table->mediumText('description'); +```php +$table->mediumText('description'); +``` When utilizing MySQL or MariaDB, you may apply a `binary` character set to the column in order to create a `MEDIUMBLOB` equivalent column: - $table->mediumText('data')->charset('binary'); // MEDIUMBLOB +```php +$table->mediumText('data')->charset('binary'); // MEDIUMBLOB +``` #### `morphs()` {.collection-method} @@ -771,190 +861,246 @@ The `morphs` method is a convenience method that adds a `{column}_id` equivalent This method is intended to be used when defining the columns necessary for a polymorphic [Eloquent relationship](/docs/{{version}}/eloquent-relationships). In the following example, `taggable_id` and `taggable_type` columns would be created: - $table->morphs('taggable'); +```php +$table->morphs('taggable'); +``` #### `nullableMorphs()` {.collection-method} The method is similar to the [morphs](#column-method-morphs) method; however, the columns that are created will be "nullable": - $table->nullableMorphs('taggable'); +```php +$table->nullableMorphs('taggable'); +``` #### `nullableUlidMorphs()` {.collection-method} The method is similar to the [ulidMorphs](#column-method-ulidMorphs) method; however, the columns that are created will be "nullable": - $table->nullableUlidMorphs('taggable'); +```php +$table->nullableUlidMorphs('taggable'); +``` #### `nullableUuidMorphs()` {.collection-method} The method is similar to the [uuidMorphs](#column-method-uuidMorphs) method; however, the columns that are created will be "nullable": - $table->nullableUuidMorphs('taggable'); +```php +$table->nullableUuidMorphs('taggable'); +``` #### `rememberToken()` {.collection-method} The `rememberToken` method creates a nullable, `VARCHAR(100)` equivalent column that is intended to store the current "remember me" [authentication token](/docs/{{version}}/authentication#remembering-users): - $table->rememberToken(); +```php +$table->rememberToken(); +``` #### `set()` {.collection-method} The `set` method creates a `SET` equivalent column with the given list of valid values: - $table->set('flavors', ['strawberry', 'vanilla']); +```php +$table->set('flavors', ['strawberry', 'vanilla']); +``` #### `smallIncrements()` {.collection-method} The `smallIncrements` method creates an auto-incrementing `UNSIGNED SMALLINT` equivalent column as a primary key: - $table->smallIncrements('id'); +```php +$table->smallIncrements('id'); +``` #### `smallInteger()` {.collection-method} The `smallInteger` method creates a `SMALLINT` equivalent column: - $table->smallInteger('votes'); +```php +$table->smallInteger('votes'); +``` #### `softDeletesTz()` {.collection-method} The `softDeletesTz` method adds a nullable `deleted_at` `TIMESTAMP` (with timezone) equivalent column with an optional fractional seconds precision. This column is intended to store the `deleted_at` timestamp needed for Eloquent's "soft delete" functionality: - $table->softDeletesTz('deleted_at', precision: 0); +```php +$table->softDeletesTz('deleted_at', precision: 0); +``` #### `softDeletes()` {.collection-method} The `softDeletes` method adds a nullable `deleted_at` `TIMESTAMP` equivalent column with an optional fractional seconds precision. This column is intended to store the `deleted_at` timestamp needed for Eloquent's "soft delete" functionality: - $table->softDeletes('deleted_at', precision: 0); +```php +$table->softDeletes('deleted_at', precision: 0); +``` #### `string()` {.collection-method} The `string` method creates a `VARCHAR` equivalent column of the given length: - $table->string('name', length: 100); +```php +$table->string('name', length: 100); +``` #### `text()` {.collection-method} The `text` method creates a `TEXT` equivalent column: - $table->text('description'); +```php +$table->text('description'); +``` When utilizing MySQL or MariaDB, you may apply a `binary` character set to the column in order to create a `BLOB` equivalent column: - $table->text('data')->charset('binary'); // BLOB +```php +$table->text('data')->charset('binary'); // BLOB +``` #### `timeTz()` {.collection-method} The `timeTz` method creates a `TIME` (with timezone) equivalent column with an optional fractional seconds precision: - $table->timeTz('sunrise', precision: 0); +```php +$table->timeTz('sunrise', precision: 0); +``` #### `time()` {.collection-method} The `time` method creates a `TIME` equivalent column with an optional fractional seconds precision: - $table->time('sunrise', precision: 0); +```php +$table->time('sunrise', precision: 0); +``` #### `timestampTz()` {.collection-method} The `timestampTz` method creates a `TIMESTAMP` (with timezone) equivalent column with an optional fractional seconds precision: - $table->timestampTz('added_at', precision: 0); +```php +$table->timestampTz('added_at', precision: 0); +``` #### `timestamp()` {.collection-method} The `timestamp` method creates a `TIMESTAMP` equivalent column with an optional fractional seconds precision: - $table->timestamp('added_at', precision: 0); +```php +$table->timestamp('added_at', precision: 0); +``` #### `timestampsTz()` {.collection-method} The `timestampsTz` method creates `created_at` and `updated_at` `TIMESTAMP` (with timezone) equivalent columns with an optional fractional seconds precision: - $table->timestampsTz(precision: 0); +```php +$table->timestampsTz(precision: 0); +``` #### `timestamps()` {.collection-method} The `timestamps` method creates `created_at` and `updated_at` `TIMESTAMP` equivalent columns with an optional fractional seconds precision: - $table->timestamps(precision: 0); +```php +$table->timestamps(precision: 0); +``` #### `tinyIncrements()` {.collection-method} The `tinyIncrements` method creates an auto-incrementing `UNSIGNED TINYINT` equivalent column as a primary key: - $table->tinyIncrements('id'); +```php +$table->tinyIncrements('id'); +``` #### `tinyInteger()` {.collection-method} The `tinyInteger` method creates a `TINYINT` equivalent column: - $table->tinyInteger('votes'); +```php +$table->tinyInteger('votes'); +``` #### `tinyText()` {.collection-method} The `tinyText` method creates a `TINYTEXT` equivalent column: - $table->tinyText('notes'); +```php +$table->tinyText('notes'); +``` When utilizing MySQL or MariaDB, you may apply a `binary` character set to the column in order to create a `TINYBLOB` equivalent column: - $table->tinyText('data')->charset('binary'); // TINYBLOB +```php +$table->tinyText('data')->charset('binary'); // TINYBLOB +``` #### `unsignedBigInteger()` {.collection-method} The `unsignedBigInteger` method creates an `UNSIGNED BIGINT` equivalent column: - $table->unsignedBigInteger('votes'); +```php +$table->unsignedBigInteger('votes'); +``` #### `unsignedInteger()` {.collection-method} The `unsignedInteger` method creates an `UNSIGNED INTEGER` equivalent column: - $table->unsignedInteger('votes'); +```php +$table->unsignedInteger('votes'); +``` #### `unsignedMediumInteger()` {.collection-method} The `unsignedMediumInteger` method creates an `UNSIGNED MEDIUMINT` equivalent column: - $table->unsignedMediumInteger('votes'); +```php +$table->unsignedMediumInteger('votes'); +``` #### `unsignedSmallInteger()` {.collection-method} The `unsignedSmallInteger` method creates an `UNSIGNED SMALLINT` equivalent column: - $table->unsignedSmallInteger('votes'); +```php +$table->unsignedSmallInteger('votes'); +``` #### `unsignedTinyInteger()` {.collection-method} The `unsignedTinyInteger` method creates an `UNSIGNED TINYINT` equivalent column: - $table->unsignedTinyInteger('votes'); +```php +$table->unsignedTinyInteger('votes'); +``` #### `ulidMorphs()` {.collection-method} @@ -963,7 +1109,9 @@ The `ulidMorphs` method is a convenience method that adds a `{column}_id` `CHAR( This method is intended to be used when defining the columns necessary for a polymorphic [Eloquent relationship](/docs/{{version}}/eloquent-relationships) that use ULID identifiers. In the following example, `taggable_id` and `taggable_type` columns would be created: - $table->ulidMorphs('taggable'); +```php +$table->ulidMorphs('taggable'); +``` #### `uuidMorphs()` {.collection-method} @@ -972,47 +1120,59 @@ The `uuidMorphs` method is a convenience method that adds a `{column}_id` `CHAR( This method is intended to be used when defining the columns necessary for a polymorphic [Eloquent relationship](/docs/{{version}}/eloquent-relationships) that use UUID identifiers. In the following example, `taggable_id` and `taggable_type` columns would be created: - $table->uuidMorphs('taggable'); +```php +$table->uuidMorphs('taggable'); +``` #### `ulid()` {.collection-method} The `ulid` method creates a `ULID` equivalent column: - $table->ulid('id'); +```php +$table->ulid('id'); +``` #### `uuid()` {.collection-method} The `uuid` method creates a `UUID` equivalent column: - $table->uuid('id'); +```php +$table->uuid('id'); +``` #### `vector()` {.collection-method} The `vector` method creates a `vector` equivalent column: - $table->vector('embedding', dimensions: 100); +```php +$table->vector('embedding', dimensions: 100); +``` #### `year()` {.collection-method} The `year` method creates a `YEAR` equivalent column: - $table->year('birth_year'); +```php +$table->year('birth_year'); +``` ### Column Modifiers In addition to the column types listed above, there are several column "modifiers" you may use when adding a column to a database table. For example, to make the column "nullable", you may use the `nullable` method: - use Illuminate\Database\Schema\Blueprint; - use Illuminate\Support\Facades\Schema; +```php +use Illuminate\Database\Schema\Blueprint; +use Illuminate\Support\Facades\Schema; - Schema::table('users', function (Blueprint $table) { - $table->string('email')->nullable(); - }); +Schema::table('users', function (Blueprint $table) { + $table->string('email')->nullable(); +}); +``` The following table contains all of the available column modifiers. This list does not include [index modifiers](#creating-indexes): @@ -1045,27 +1205,29 @@ The following table contains all of the available column modifiers. This list do The `default` modifier accepts a value or an `Illuminate\Database\Query\Expression` instance. Using an `Expression` instance will prevent Laravel from wrapping the value in quotes and allow you to use database specific functions. One situation where this is particularly useful is when you need to assign default values to JSON columns: - id(); - $table->json('movies')->default(new Expression('(JSON_ARRAY())')); - $table->timestamps(); - }); - } - }; + Schema::create('flights', function (Blueprint $table) { + $table->id(); + $table->json('movies')->default(new Expression('(JSON_ARRAY())')); + $table->timestamps(); + }); + } +}; +``` > [!WARNING] > Support for default expressions depends on your database driver, database version, and the field type. Please refer to your database's documentation. @@ -1075,26 +1237,32 @@ The `default` modifier accepts a value or an `Illuminate\Database\Query\Expressi When using the MariaDB or MySQL database, the `after` method may be used to add columns after an existing column in the schema: - $table->after('password', function (Blueprint $table) { - $table->string('address_line1'); - $table->string('address_line2'); - $table->string('city'); - }); +```php +$table->after('password', function (Blueprint $table) { + $table->string('address_line1'); + $table->string('address_line2'); + $table->string('city'); +}); +``` ### Modifying Columns The `change` method allows you to modify the type and attributes of existing columns. For example, you may wish to increase the size of a `string` column. To see the `change` method in action, let's increase the size of the `name` column from 25 to 50. To accomplish this, we simply define the new state of the column and then call the `change` method: - Schema::table('users', function (Blueprint $table) { - $table->string('name', 50)->change(); - }); +```php +Schema::table('users', function (Blueprint $table) { + $table->string('name', 50)->change(); +}); +``` When modifying a column, you must explicitly include all the modifiers you want to keep on the column definition - any missing attribute will be dropped. For example, to retain the `unsigned`, `default`, and `comment` attributes, you must call each modifier explicitly when changing the column: - Schema::table('users', function (Blueprint $table) { - $table->integer('votes')->unsigned()->default(1)->comment('my comment')->change(); - }); +```php +Schema::table('users', function (Blueprint $table) { + $table->integer('votes')->unsigned()->default(1)->comment('my comment')->change(); +}); +``` The `change` method does not change the indexes of the column. Therefore, you may use index modifiers to explicitly add or drop an index when modifying the column: @@ -1111,24 +1279,30 @@ $table->char('postal_code', 10)->unique(false)->change(); To rename a column, you may use the `renameColumn` method provided by the schema builder: - Schema::table('users', function (Blueprint $table) { - $table->renameColumn('from', 'to'); - }); +```php +Schema::table('users', function (Blueprint $table) { + $table->renameColumn('from', 'to'); +}); +``` ### Dropping Columns To drop a column, you may use the `dropColumn` method on the schema builder: - Schema::table('users', function (Blueprint $table) { - $table->dropColumn('votes'); - }); +```php +Schema::table('users', function (Blueprint $table) { + $table->dropColumn('votes'); +}); +``` You may drop multiple columns from a table by passing an array of column names to the `dropColumn` method: - Schema::table('users', function (Blueprint $table) { - $table->dropColumn(['votes', 'avatar', 'location']); - }); +```php +Schema::table('users', function (Blueprint $table) { + $table->dropColumn(['votes', 'avatar', 'location']); +}); +``` #### Available Command Aliases @@ -1156,24 +1330,32 @@ Laravel provides several convenient methods related to dropping common types of The Laravel schema builder supports several types of indexes. The following example creates a new `email` column and specifies that its values should be unique. To create the index, we can chain the `unique` method onto the column definition: - use Illuminate\Database\Schema\Blueprint; - use Illuminate\Support\Facades\Schema; +```php +use Illuminate\Database\Schema\Blueprint; +use Illuminate\Support\Facades\Schema; - Schema::table('users', function (Blueprint $table) { - $table->string('email')->unique(); - }); +Schema::table('users', function (Blueprint $table) { + $table->string('email')->unique(); +}); +``` Alternatively, you may create the index after defining the column. To do so, you should call the `unique` method on the schema builder blueprint. This method accepts the name of the column that should receive a unique index: - $table->unique('email'); +```php +$table->unique('email'); +``` You may even pass an array of columns to an index method to create a compound (or composite) index: - $table->index(['account_id', 'created_at']); +```php +$table->index(['account_id', 'created_at']); +``` When creating an index, Laravel will automatically generate an index name based on the table, column names, and the index type, but you may pass a second argument to the method to specify the index name yourself: - $table->unique('email', 'unique_email'); +```php +$table->unique('email', 'unique_email'); +``` #### Available Index Types @@ -1199,7 +1381,9 @@ Laravel's schema builder blueprint class provides methods for creating each type To rename an index, you may use the `renameIndex` method provided by the schema builder blueprint. This method accepts the current index name as its first argument and the desired name as its second argument: - $table->renameIndex('from', 'to') +```php +$table->renameIndex('from', 'to') +``` ### Dropping Indexes @@ -1220,44 +1404,54 @@ To drop an index, you must specify the index's name. By default, Laravel automat If you pass an array of columns into a method that drops indexes, the conventional index name will be generated based on the table name, columns, and index type: - Schema::table('geo', function (Blueprint $table) { - $table->dropIndex(['state']); // Drops index 'geo_state_index' - }); +```php +Schema::table('geo', function (Blueprint $table) { + $table->dropIndex(['state']); // Drops index 'geo_state_index' +}); +``` ### Foreign Key Constraints Laravel also provides support for creating foreign key constraints, which are used to force referential integrity at the database level. For example, let's define a `user_id` column on the `posts` table that references the `id` column on a `users` table: - use Illuminate\Database\Schema\Blueprint; - use Illuminate\Support\Facades\Schema; +```php +use Illuminate\Database\Schema\Blueprint; +use Illuminate\Support\Facades\Schema; - Schema::table('posts', function (Blueprint $table) { - $table->unsignedBigInteger('user_id'); +Schema::table('posts', function (Blueprint $table) { + $table->unsignedBigInteger('user_id'); - $table->foreign('user_id')->references('id')->on('users'); - }); + $table->foreign('user_id')->references('id')->on('users'); +}); +``` Since this syntax is rather verbose, Laravel provides additional, terser methods that use conventions to provide a better developer experience. When using the `foreignId` method to create your column, the example above can be rewritten like so: - Schema::table('posts', function (Blueprint $table) { - $table->foreignId('user_id')->constrained(); - }); +```php +Schema::table('posts', function (Blueprint $table) { + $table->foreignId('user_id')->constrained(); +}); +``` The `foreignId` method creates an `UNSIGNED BIGINT` equivalent column, while the `constrained` method will use conventions to determine the table and column being referenced. If your table name does not match Laravel's conventions, you may manually provide it to the `constrained` method. In addition, the name that should be assigned to the generated index may be specified as well: - Schema::table('posts', function (Blueprint $table) { - $table->foreignId('user_id')->constrained( - table: 'users', indexName: 'posts_user_id' - ); - }); +```php +Schema::table('posts', function (Blueprint $table) { + $table->foreignId('user_id')->constrained( + table: 'users', indexName: 'posts_user_id' + ); +}); +``` You may also specify the desired action for the "on delete" and "on update" properties of the constraint: - $table->foreignId('user_id') - ->constrained() - ->onUpdate('cascade') - ->onDelete('cascade'); +```php +$table->foreignId('user_id') + ->constrained() + ->onUpdate('cascade') + ->onDelete('cascade'); +``` An alternative, expressive syntax is also provided for these actions: @@ -1278,33 +1472,41 @@ An alternative, expressive syntax is also provided for these actions: Any additional [column modifiers](#column-modifiers) must be called before the `constrained` method: - $table->foreignId('user_id') - ->nullable() - ->constrained(); +```php +$table->foreignId('user_id') + ->nullable() + ->constrained(); +``` #### Dropping Foreign Keys To drop a foreign key, you may use the `dropForeign` method, passing the name of the foreign key constraint to be deleted as an argument. Foreign key constraints use the same naming convention as indexes. In other words, the foreign key constraint name is based on the name of the table and the columns in the constraint, followed by a "\_foreign" suffix: - $table->dropForeign('posts_user_id_foreign'); +```php +$table->dropForeign('posts_user_id_foreign'); +``` Alternatively, you may pass an array containing the column name that holds the foreign key to the `dropForeign` method. The array will be converted to a foreign key constraint name using Laravel's constraint naming conventions: - $table->dropForeign(['user_id']); +```php +$table->dropForeign(['user_id']); +``` #### Toggling Foreign Key Constraints You may enable or disable foreign key constraints within your migrations by using the following methods: - Schema::enableForeignKeyConstraints(); +```php +Schema::enableForeignKeyConstraints(); - Schema::disableForeignKeyConstraints(); +Schema::disableForeignKeyConstraints(); - Schema::withoutForeignKeyConstraints(function () { - // Constraints disabled within this closure... - }); +Schema::withoutForeignKeyConstraints(function () { + // Constraints disabled within this closure... +}); +``` > [!WARNING] > SQLite disables foreign key constraints by default. When using SQLite, make sure to [enable foreign key support](/docs/{{version}}/database#configuration) in your database configuration before attempting to create them in your migrations. diff --git a/mocking.md b/mocking.md index 28d8bff165b..dd9673b91dd 100644 --- a/mocking.md +++ b/mocking.md @@ -51,57 +51,65 @@ public function test_something_can_be_mocked(): void In order to make this more convenient, you may use the `mock` method that is provided by Laravel's base test case class. For example, the following example is equivalent to the example above: - use App\Service; - use Mockery\MockInterface; +```php +use App\Service; +use Mockery\MockInterface; - $mock = $this->mock(Service::class, function (MockInterface $mock) { - $mock->shouldReceive('process')->once(); - }); +$mock = $this->mock(Service::class, function (MockInterface $mock) { + $mock->shouldReceive('process')->once(); +}); +``` You may use the `partialMock` method when you only need to mock a few methods of an object. The methods that are not mocked will be executed normally when called: - use App\Service; - use Mockery\MockInterface; +```php +use App\Service; +use Mockery\MockInterface; - $mock = $this->partialMock(Service::class, function (MockInterface $mock) { - $mock->shouldReceive('process')->once(); - }); +$mock = $this->partialMock(Service::class, function (MockInterface $mock) { + $mock->shouldReceive('process')->once(); +}); +``` Similarly, if you want to [spy](http://docs.mockery.io/en/latest/reference/spies.html) on an object, Laravel's base test case class offers a `spy` method as a convenient wrapper around the `Mockery::spy` method. Spies are similar to mocks; however, spies record any interaction between the spy and the code being tested, allowing you to make assertions after the code is executed: - use App\Service; +```php +use App\Service; - $spy = $this->spy(Service::class); +$spy = $this->spy(Service::class); - // ... +// ... - $spy->shouldHaveReceived('process'); +$spy->shouldHaveReceived('process'); +``` ## Mocking Facades Unlike traditional static method calls, [facades](/docs/{{version}}/facades) (including [real-time facades](/docs/{{version}}/facades#real-time-facades)) may be mocked. This provides a great advantage over traditional static methods and grants you the same testability that you would have if you were using traditional dependency injection. When testing, you may often want to mock a call to a Laravel facade that occurs in one of your controllers. For example, consider the following controller action: - travel(5)->days(function () { - // Test something five days into the future... - }); +```php +$this->travel(5)->days(function () { + // Test something five days into the future... +}); - $this->travelTo(now()->subDays(10), function () { - // Test something during a given moment... - }); +$this->travelTo(now()->subDays(10), function () { + // Test something during a given moment... +}); +``` The `freezeTime` method may be used to freeze the current time. Similarly, the `freezeSecond` method will freeze the current time but at the start of the current second: - use Illuminate\Support\Carbon; +```php +use Illuminate\Support\Carbon; - // Freeze time and resume normal time after executing closure... - $this->freezeTime(function (Carbon $time) { - // ... - }); +// Freeze time and resume normal time after executing closure... +$this->freezeTime(function (Carbon $time) { + // ... +}); - // Freeze time at the current second and resume normal time after executing closure... - $this->freezeSecond(function (Carbon $time) { - // ... - }) +// Freeze time at the current second and resume normal time after executing closure... +$this->freezeSecond(function (Carbon $time) { + // ... +}) +``` As you would expect, all of the methods discussed above are primarily useful for testing time sensitive application behavior, such as locking inactive posts on a discussion forum: diff --git a/notifications.md b/notifications.md index 58dc24b2b4f..fa9dc7292df 100644 --- a/notifications.md +++ b/notifications.md @@ -77,23 +77,27 @@ This command will place a fresh notification class in your `app/Notifications` d Notifications may be sent in two ways: using the `notify` method of the `Notifiable` trait or using the `Notification` [facade](/docs/{{version}}/facades). The `Notifiable` trait is included on your application's `App\Models\User` model by default: - notify(new InvoicePaid($invoice)); +$user->notify(new InvoicePaid($invoice)); +``` > [!NOTE] > Remember, you may use the `Notifiable` trait on any of your models. You are not limited to only including it on your `User` model. @@ -103,13 +107,17 @@ The `notify` method that is provided by this trait expects to receive a notifica Alternatively, you may send notifications via the `Notification` [facade](/docs/{{version}}/facades). This approach is useful when you need to send a notification to multiple notifiable entities such as a collection of users. To send notifications using the facade, pass all of the notifiable entities and the notification instance to the `send` method: - use Illuminate\Support\Facades\Notification; +```php +use Illuminate\Support\Facades\Notification; - Notification::send($users, new InvoicePaid($invoice)); +Notification::send($users, new InvoicePaid($invoice)); +``` You can also send notifications immediately using the `sendNow` method. This method will send the notification immediately even if the notification implements the `ShouldQueue` interface: - Notification::sendNow($developers, new DeploymentCompleted($deployment)); +```php +Notification::sendNow($developers, new DeploymentCompleted($deployment)); +``` ### Specifying Delivery Channels @@ -121,15 +129,17 @@ Every notification class has a `via` method that determines on which channels th The `via` method receives a `$notifiable` instance, which will be an instance of the class to which the notification is being sent. You may use `$notifiable` to determine which channels the notification should be delivered on: - /** - * Get the notification's delivery channels. - * - * @return array - */ - public function via(object $notifiable): array - { - return $notifiable->prefers_sms ? ['vonage'] : ['mail', 'database']; - } +```php +/** + * Get the notification's delivery channels. + * + * @return array + */ +public function via(object $notifiable): array +{ + return $notifiable->prefers_sms ? ['vonage'] : ['mail', 'database']; +} +``` ### Queueing Notifications @@ -139,24 +149,28 @@ The `via` method receives a `$notifiable` instance, which will be an instance of Sending notifications can take time, especially if the channel needs to make an external API call to deliver the notification. To speed up your application's response time, let your notification be queued by adding the `ShouldQueue` interface and `Queueable` trait to your class. The interface and trait are already imported for all notifications generated using the `make:notification` command, so you may immediately add them to your notification class: - notify(new InvoicePaid($invoice)); +```php +$user->notify(new InvoicePaid($invoice)); +``` When queueing notifications, a queued job will be created for each recipient and channel combination. For example, six jobs will be dispatched to the queue if your notification has three recipients and two channels. @@ -165,111 +179,125 @@ When queueing notifications, a queued job will be created for each recipient and If you would like to delay the delivery of the notification, you may chain the `delay` method onto your notification instantiation: - $delay = now()->addMinutes(10); +```php +$delay = now()->addMinutes(10); - $user->notify((new InvoicePaid($invoice))->delay($delay)); +$user->notify((new InvoicePaid($invoice))->delay($delay)); +``` You may pass an array to the `delay` method to specify the delay amount for specific channels: - $user->notify((new InvoicePaid($invoice))->delay([ - 'mail' => now()->addMinutes(5), - 'sms' => now()->addMinutes(10), - ])); +```php +$user->notify((new InvoicePaid($invoice))->delay([ + 'mail' => now()->addMinutes(5), + 'sms' => now()->addMinutes(10), +])); +``` Alternatively, you may define a `withDelay` method on the notification class itself. The `withDelay` method should return an array of channel names and delay values: - /** - * Determine the notification's delivery delay. - * - * @return array - */ - public function withDelay(object $notifiable): array - { - return [ - 'mail' => now()->addMinutes(5), - 'sms' => now()->addMinutes(10), - ]; - } +```php +/** + * Determine the notification's delivery delay. + * + * @return array + */ +public function withDelay(object $notifiable): array +{ + return [ + 'mail' => now()->addMinutes(5), + 'sms' => now()->addMinutes(10), + ]; +} +``` #### Customizing the Notification Queue Connection By default, queued notifications will be queued using your application's default queue connection. If you would like to specify a different connection that should be used for a particular notification, you may call the `onConnection` method from your notification's constructor: - onConnection('redis'); - } - } +use Illuminate\Bus\Queueable; +use Illuminate\Contracts\Queue\ShouldQueue; +use Illuminate\Notifications\Notification; -Or, if you would like to specify a specific queue connection that should be used for each notification channel supported by the notification, you may define a `viaConnections` method on your notification. This method should return an array of channel name / queue connection name pairs: +class InvoicePaid extends Notification implements ShouldQueue +{ + use Queueable; /** - * Determine which connections should be used for each notification channel. - * - * @return array + * Create a new notification instance. */ - public function viaConnections(): array + public function __construct() { - return [ - 'mail' => 'redis', - 'database' => 'sync', - ]; + $this->onConnection('redis'); } +} +``` + +Or, if you would like to specify a specific queue connection that should be used for each notification channel supported by the notification, you may define a `viaConnections` method on your notification. This method should return an array of channel name / queue connection name pairs: + +```php +/** + * Determine which connections should be used for each notification channel. + * + * @return array + */ +public function viaConnections(): array +{ + return [ + 'mail' => 'redis', + 'database' => 'sync', + ]; +} +``` #### Customizing Notification Channel Queues If you would like to specify a specific queue that should be used for each notification channel supported by the notification, you may define a `viaQueues` method on your notification. This method should return an array of channel name / queue name pairs: - /** - * Determine which queues should be used for each notification channel. - * - * @return array - */ - public function viaQueues(): array - { - return [ - 'mail' => 'mail-queue', - 'slack' => 'slack-queue', - ]; - } +```php +/** + * Determine which queues should be used for each notification channel. + * + * @return array + */ +public function viaQueues(): array +{ + return [ + 'mail' => 'mail-queue', + 'slack' => 'slack-queue', + ]; +} +``` #### Queued Notification Middleware Queued notifications may define middleware [just like queued jobs](/docs/{{version}}/queues#job-middleware). To get started, define a `middleware` method on your notification class. The `middleware` method will receive `$notifiable` and `$channel` variables, which allow you to customize the returned middleware based on the notification's destination: - use Illuminate\Queue\Middleware\RateLimited; +```php +use Illuminate\Queue\Middleware\RateLimited; - /** - * Get the middleware the notification job should pass through. - * - * @return array - */ - public function middleware(object $notifiable, string $channel) - { - return match ($channel) { - 'email' => [new RateLimited('postmark')], - 'slack' => [new RateLimited('slack')], - default => [], - }; - } +/** + * Get the middleware the notification job should pass through. + * + * @return array + */ +public function middleware(object $notifiable, string $channel) +{ + return match ($channel) { + 'email' => [new RateLimited('postmark')], + 'slack' => [new RateLimited('slack')], + default => [], + }; +} +``` #### Queued Notifications and Database Transactions @@ -278,32 +306,36 @@ When queued notifications are dispatched within database transactions, they may If your queue connection's `after_commit` configuration option is set to `false`, you may still indicate that a particular queued notification should be dispatched after all open database transactions have been committed by calling the `afterCommit` method when sending the notification: - use App\Notifications\InvoicePaid; +```php +use App\Notifications\InvoicePaid; - $user->notify((new InvoicePaid($invoice))->afterCommit()); +$user->notify((new InvoicePaid($invoice))->afterCommit()); +``` Alternatively, you may call the `afterCommit` method from your notification's constructor: - afterCommit(); - } + /** + * Create a new notification instance. + */ + public function __construct() + { + $this->afterCommit(); } +} +``` > [!NOTE] > To learn more about working around these issues, please review the documentation regarding [queued jobs and database transactions](/docs/{{version}}/queues#jobs-and-database-transactions). @@ -315,40 +347,48 @@ After a queued notification has been dispatched for the queue for background pro However, if you would like to make the final determination on whether the queued notification should be sent after it is being processed by a queue worker, you may define a `shouldSend` method on the notification class. If this method returns `false`, the notification will not be sent: - /** - * Determine if the notification should be sent. - */ - public function shouldSend(object $notifiable, string $channel): bool - { - return $this->invoice->isPaid(); - } +```php +/** + * Determine if the notification should be sent. + */ +public function shouldSend(object $notifiable, string $channel): bool +{ + return $this->invoice->isPaid(); +} +``` ### On-Demand Notifications Sometimes you may need to send a notification to someone who is not stored as a "user" of your application. Using the `Notification` facade's `route` method, you may specify ad-hoc notification routing information before sending the notification: - use Illuminate\Broadcasting\Channel; - use Illuminate\Support\Facades\Notification; +```php +use Illuminate\Broadcasting\Channel; +use Illuminate\Support\Facades\Notification; - Notification::route('mail', 'taylor@example.com') - ->route('vonage', '5555555555') - ->route('slack', '#slack-channel') - ->route('broadcast', [new Channel('channel-name')]) - ->notify(new InvoicePaid($invoice)); +Notification::route('mail', 'taylor@example.com') + ->route('vonage', '5555555555') + ->route('slack', '#slack-channel') + ->route('broadcast', [new Channel('channel-name')]) + ->notify(new InvoicePaid($invoice)); +``` If you would like to provide the recipient's name when sending an on-demand notification to the `mail` route, you may provide an array that contains the email address as the key and the name as the value of the first element in the array: - Notification::route('mail', [ - 'barrett@example.com' => 'Barrett Blair', - ])->notify(new InvoicePaid($invoice)); +```php +Notification::route('mail', [ + 'barrett@example.com' => 'Barrett Blair', +])->notify(new InvoicePaid($invoice)); +``` Using the `routes` method, you may provide ad-hoc routing information for multiple notification channels at once: - Notification::routes([ - 'mail' => ['barrett@example.com' => 'Barrett Blair'], - 'vonage' => '5555555555', - ])->notify(new InvoicePaid($invoice)); +```php +Notification::routes([ + 'mail' => ['barrett@example.com' => 'Barrett Blair'], + 'vonage' => '5555555555', +])->notify(new InvoicePaid($invoice)); +``` ## Mail Notifications @@ -360,20 +400,22 @@ If a notification supports being sent as an email, you should define a `toMail` The `MailMessage` class contains a few simple methods to help you build transactional email messages. Mail messages may contain lines of text as well as a "call to action". Let's take a look at an example `toMail` method: - /** - * Get the mail representation of the notification. - */ - public function toMail(object $notifiable): MailMessage - { - $url = url('/invoice/'.$this->invoice->id); - - return (new MailMessage) - ->greeting('Hello!') - ->line('One of your invoices has been paid!') - ->lineIf($this->amount > 0, "Amount paid: {$this->amount}") - ->action('View Invoice', $url) - ->line('Thank you for using our application!'); - } +```php +/** + * Get the mail representation of the notification. + */ +public function toMail(object $notifiable): MailMessage +{ + $url = url('/invoice/'.$this->invoice->id); + + return (new MailMessage) + ->greeting('Hello!') + ->line('One of your invoices has been paid!') + ->lineIf($this->amount > 0, "Amount paid: {$this->amount}") + ->action('View Invoice', $url) + ->line('Thank you for using our application!'); +} +``` > [!NOTE] > Note we are using `$this->invoice->id` in our `toMail` method. You may pass any data your notification needs to generate its message into the notification's constructor. @@ -390,133 +432,149 @@ In this example, we register a greeting, a line of text, a call to action, and t Some notifications inform users of errors, such as a failed invoice payment. You may indicate that a mail message is regarding an error by calling the `error` method when building your message. When using the `error` method on a mail message, the call to action button will be red instead of black: - /** - * Get the mail representation of the notification. - */ - public function toMail(object $notifiable): MailMessage - { - return (new MailMessage) - ->error() - ->subject('Invoice Payment Failed') - ->line('...'); - } +```php +/** + * Get the mail representation of the notification. + */ +public function toMail(object $notifiable): MailMessage +{ + return (new MailMessage) + ->error() + ->subject('Invoice Payment Failed') + ->line('...'); +} +``` #### Other Mail Notification Formatting Options Instead of defining the "lines" of text in the notification class, you may use the `view` method to specify a custom template that should be used to render the notification email: - /** - * Get the mail representation of the notification. - */ - public function toMail(object $notifiable): MailMessage - { - return (new MailMessage)->view( - 'mail.invoice.paid', ['invoice' => $this->invoice] - ); - } +```php +/** + * Get the mail representation of the notification. + */ +public function toMail(object $notifiable): MailMessage +{ + return (new MailMessage)->view( + 'mail.invoice.paid', ['invoice' => $this->invoice] + ); +} +``` You may specify a plain-text view for the mail message by passing the view name as the second element of an array that is given to the `view` method: - /** - * Get the mail representation of the notification. - */ - public function toMail(object $notifiable): MailMessage - { - return (new MailMessage)->view( - ['mail.invoice.paid', 'mail.invoice.paid-text'], - ['invoice' => $this->invoice] - ); - } +```php +/** + * Get the mail representation of the notification. + */ +public function toMail(object $notifiable): MailMessage +{ + return (new MailMessage)->view( + ['mail.invoice.paid', 'mail.invoice.paid-text'], + ['invoice' => $this->invoice] + ); +} +``` Or, if your message only has a plain-text view, you may utilize the `text` method: - /** - * Get the mail representation of the notification. - */ - public function toMail(object $notifiable): MailMessage - { - return (new MailMessage)->text( - 'mail.invoice.paid-text', ['invoice' => $this->invoice] - ); - } +```php +/** + * Get the mail representation of the notification. + */ +public function toMail(object $notifiable): MailMessage +{ + return (new MailMessage)->text( + 'mail.invoice.paid-text', ['invoice' => $this->invoice] + ); +} +``` ### Customizing the Sender By default, the email's sender / from address is defined in the `config/mail.php` configuration file. However, you may specify the from address for a specific notification using the `from` method: - /** - * Get the mail representation of the notification. - */ - public function toMail(object $notifiable): MailMessage - { - return (new MailMessage) - ->from('barrett@example.com', 'Barrett Blair') - ->line('...'); - } +```php +/** + * Get the mail representation of the notification. + */ +public function toMail(object $notifiable): MailMessage +{ + return (new MailMessage) + ->from('barrett@example.com', 'Barrett Blair') + ->line('...'); +} +``` ### Customizing the Recipient When sending notifications via the `mail` channel, the notification system will automatically look for an `email` property on your notifiable entity. You may customize which email address is used to deliver the notification by defining a `routeNotificationForMail` method on the notifiable entity: - |string + */ + public function routeNotificationForMail(Notification $notification): array|string { - use Notifiable; - - /** - * Route notifications for the mail channel. - * - * @return array|string - */ - public function routeNotificationForMail(Notification $notification): array|string - { - // Return email address only... - return $this->email_address; + // Return email address only... + return $this->email_address; - // Return email address and name... - return [$this->email_address => $this->name]; - } + // Return email address and name... + return [$this->email_address => $this->name]; } +} +``` ### Customizing the Subject By default, the email's subject is the class name of the notification formatted to "Title Case". So, if your notification class is named `InvoicePaid`, the email's subject will be `Invoice Paid`. If you would like to specify a different subject for the message, you may call the `subject` method when building your message: - /** - * Get the mail representation of the notification. - */ - public function toMail(object $notifiable): MailMessage - { - return (new MailMessage) - ->subject('Notification Subject') - ->line('...'); - } +```php +/** + * Get the mail representation of the notification. + */ +public function toMail(object $notifiable): MailMessage +{ + return (new MailMessage) + ->subject('Notification Subject') + ->line('...'); +} +``` ### Customizing the Mailer By default, the email notification will be sent using the default mailer defined in the `config/mail.php` configuration file. However, you may specify a different mailer at runtime by calling the `mailer` method when building your message: - /** - * Get the mail representation of the notification. - */ - public function toMail(object $notifiable): MailMessage - { - return (new MailMessage) - ->mailer('postmark') - ->line('...'); - } +```php +/** + * Get the mail representation of the notification. + */ +public function toMail(object $notifiable): MailMessage +{ + return (new MailMessage) + ->mailer('postmark') + ->line('...'); +} +``` ### Customizing the Templates @@ -532,98 +590,110 @@ php artisan vendor:publish --tag=laravel-notifications To add attachments to an email notification, use the `attach` method while building your message. The `attach` method accepts the absolute path to the file as its first argument: - /** - * Get the mail representation of the notification. - */ - public function toMail(object $notifiable): MailMessage - { - return (new MailMessage) - ->greeting('Hello!') - ->attach('/path/to/file'); - } +```php +/** + * Get the mail representation of the notification. + */ +public function toMail(object $notifiable): MailMessage +{ + return (new MailMessage) + ->greeting('Hello!') + ->attach('/path/to/file'); +} +``` > [!NOTE] > The `attach` method offered by notification mail messages also accepts [attachable objects](/docs/{{version}}/mail#attachable-objects). Please consult the comprehensive [attachable object documentation](/docs/{{version}}/mail#attachable-objects) to learn more. When attaching files to a message, you may also specify the display name and / or MIME type by passing an `array` as the second argument to the `attach` method: - /** - * Get the mail representation of the notification. - */ - public function toMail(object $notifiable): MailMessage - { - return (new MailMessage) - ->greeting('Hello!') - ->attach('/path/to/file', [ - 'as' => 'name.pdf', - 'mime' => 'application/pdf', - ]); - } +```php +/** + * Get the mail representation of the notification. + */ +public function toMail(object $notifiable): MailMessage +{ + return (new MailMessage) + ->greeting('Hello!') + ->attach('/path/to/file', [ + 'as' => 'name.pdf', + 'mime' => 'application/pdf', + ]); +} +``` Unlike attaching files in mailable objects, you may not attach a file directly from a storage disk using `attachFromStorage`. You should rather use the `attach` method with an absolute path to the file on the storage disk. Alternatively, you could return a [mailable](/docs/{{version}}/mail#generating-mailables) from the `toMail` method: - use App\Mail\InvoicePaid as InvoicePaidMailable; +```php +use App\Mail\InvoicePaid as InvoicePaidMailable; - /** - * Get the mail representation of the notification. - */ - public function toMail(object $notifiable): Mailable - { - return (new InvoicePaidMailable($this->invoice)) - ->to($notifiable->email) - ->attachFromStorage('/path/to/file'); - } +/** + * Get the mail representation of the notification. + */ +public function toMail(object $notifiable): Mailable +{ + return (new InvoicePaidMailable($this->invoice)) + ->to($notifiable->email) + ->attachFromStorage('/path/to/file'); +} +``` When necessary, multiple files may be attached to a message using the `attachMany` method: - /** - * Get the mail representation of the notification. - */ - public function toMail(object $notifiable): MailMessage - { - return (new MailMessage) - ->greeting('Hello!') - ->attachMany([ - '/path/to/forge.svg', - '/path/to/vapor.svg' => [ - 'as' => 'Logo.svg', - 'mime' => 'image/svg+xml', - ], - ]); - } +```php +/** + * Get the mail representation of the notification. + */ +public function toMail(object $notifiable): MailMessage +{ + return (new MailMessage) + ->greeting('Hello!') + ->attachMany([ + '/path/to/forge.svg', + '/path/to/vapor.svg' => [ + 'as' => 'Logo.svg', + 'mime' => 'image/svg+xml', + ], + ]); +} +``` #### Raw Data Attachments The `attachData` method may be used to attach a raw string of bytes as an attachment. When calling the `attachData` method, you should provide the filename that should be assigned to the attachment: - /** - * Get the mail representation of the notification. - */ - public function toMail(object $notifiable): MailMessage - { - return (new MailMessage) - ->greeting('Hello!') - ->attachData($this->pdf, 'name.pdf', [ - 'mime' => 'application/pdf', - ]); - } +```php +/** + * Get the mail representation of the notification. + */ +public function toMail(object $notifiable): MailMessage +{ + return (new MailMessage) + ->greeting('Hello!') + ->attachData($this->pdf, 'name.pdf', [ + 'mime' => 'application/pdf', + ]); +} +``` ### Adding Tags and Metadata Some third-party email providers such as Mailgun and Postmark support message "tags" and "metadata", which may be used to group and track emails sent by your application. You may add tags and metadata to an email message via the `tag` and `metadata` methods: - /** - * Get the mail representation of the notification. - */ - public function toMail(object $notifiable): MailMessage - { - return (new MailMessage) - ->greeting('Comment Upvoted!') - ->tag('upvote') - ->metadata('comment_id', $this->comment->id); - } +```php +/** + * Get the mail representation of the notification. + */ +public function toMail(object $notifiable): MailMessage +{ + return (new MailMessage) + ->greeting('Comment Upvoted!') + ->tag('upvote') + ->metadata('comment_id', $this->comment->id); +} +``` If your application is using the Mailgun driver, you may consult Mailgun's documentation for more information on [tags](https://documentation.mailgun.com/en/latest/user_manual.html#tagging-1) and [metadata](https://documentation.mailgun.com/en/latest/user_manual.html#attaching-data-to-messages). Likewise, the Postmark documentation may also be consulted for more information on their support for [tags](https://postmarkapp.com/blog/tags-support-for-smtp) and [metadata](https://postmarkapp.com/support/article/1125-custom-metadata-faq). @@ -634,74 +704,82 @@ If your application is using Amazon SES to send emails, you should use the `meta The `withSymfonyMessage` method of the `MailMessage` class allows you to register a closure which will be invoked with the Symfony Message instance before sending the message. This gives you an opportunity to deeply customize the message before it is delivered: - use Symfony\Component\Mime\Email; +```php +use Symfony\Component\Mime\Email; - /** - * Get the mail representation of the notification. - */ - public function toMail(object $notifiable): MailMessage - { - return (new MailMessage) - ->withSymfonyMessage(function (Email $message) { - $message->getHeaders()->addTextHeader( - 'Custom-Header', 'Header Value' - ); - }); - } +/** + * Get the mail representation of the notification. + */ +public function toMail(object $notifiable): MailMessage +{ + return (new MailMessage) + ->withSymfonyMessage(function (Email $message) { + $message->getHeaders()->addTextHeader( + 'Custom-Header', 'Header Value' + ); + }); +} +``` ### Using Mailables If needed, you may return a full [mailable object](/docs/{{version}}/mail) from your notification's `toMail` method. When returning a `Mailable` instead of a `MailMessage`, you will need to specify the message recipient using the mailable object's `to` method: - use App\Mail\InvoicePaid as InvoicePaidMailable; - use Illuminate\Mail\Mailable; +```php +use App\Mail\InvoicePaid as InvoicePaidMailable; +use Illuminate\Mail\Mailable; - /** - * Get the mail representation of the notification. - */ - public function toMail(object $notifiable): Mailable - { - return (new InvoicePaidMailable($this->invoice)) - ->to($notifiable->email); - } +/** + * Get the mail representation of the notification. + */ +public function toMail(object $notifiable): Mailable +{ + return (new InvoicePaidMailable($this->invoice)) + ->to($notifiable->email); +} +``` #### Mailables and On-Demand Notifications If you are sending an [on-demand notification](#on-demand-notifications), the `$notifiable` instance given to the `toMail` method will be an instance of `Illuminate\Notifications\AnonymousNotifiable`, which offers a `routeNotificationFor` method that may be used to retrieve the email address the on-demand notification should be sent to: - use App\Mail\InvoicePaid as InvoicePaidMailable; - use Illuminate\Notifications\AnonymousNotifiable; - use Illuminate\Mail\Mailable; +```php +use App\Mail\InvoicePaid as InvoicePaidMailable; +use Illuminate\Notifications\AnonymousNotifiable; +use Illuminate\Mail\Mailable; - /** - * Get the mail representation of the notification. - */ - public function toMail(object $notifiable): Mailable - { - $address = $notifiable instanceof AnonymousNotifiable - ? $notifiable->routeNotificationFor('mail') - : $notifiable->email; +/** + * Get the mail representation of the notification. + */ +public function toMail(object $notifiable): Mailable +{ + $address = $notifiable instanceof AnonymousNotifiable + ? $notifiable->routeNotificationFor('mail') + : $notifiable->email; - return (new InvoicePaidMailable($this->invoice)) - ->to($address); - } + return (new InvoicePaidMailable($this->invoice)) + ->to($address); +} +``` ### Previewing Mail Notifications When designing a mail notification template, it is convenient to quickly preview the rendered mail message in your browser like a typical Blade template. For this reason, Laravel allows you to return any mail message generated by a mail notification directly from a route closure or controller. When a `MailMessage` is returned, it will be rendered and displayed in the browser, allowing you to quickly preview its design without needing to send it to an actual email address: - use App\Models\Invoice; - use App\Notifications\InvoicePaid; +```php +use App\Models\Invoice; +use App\Notifications\InvoicePaid; - Route::get('/notification', function () { - $invoice = Invoice::find(1); +Route::get('/notification', function () { + $invoice = Invoice::find(1); - return (new InvoicePaid($invoice)) - ->toMail($invoice->user); - }); + return (new InvoicePaid($invoice)) + ->toMail($invoice->user); +}); +``` ## Markdown Mail Notifications @@ -719,17 +797,19 @@ php artisan make:notification InvoicePaid --markdown=mail.invoice.paid Like all other mail notifications, notifications that use Markdown templates should define a `toMail` method on their notification class. However, instead of using the `line` and `action` methods to construct the notification, use the `markdown` method to specify the name of the Markdown template that should be used. An array of data you wish to make available to the template may be passed as the method's second argument: - /** - * Get the mail representation of the notification. - */ - public function toMail(object $notifiable): MailMessage - { - $url = url('/invoice/'.$this->invoice->id); +```php +/** + * Get the mail representation of the notification. + */ +public function toMail(object $notifiable): MailMessage +{ + $url = url('/invoice/'.$this->invoice->id); - return (new MailMessage) - ->subject('Invoice Paid') - ->markdown('mail.invoice.paid', ['url' => $url]); - } + return (new MailMessage) + ->subject('Invoice Paid') + ->markdown('mail.invoice.paid', ['url' => $url]); +} +``` ### Writing the Message @@ -807,16 +887,18 @@ If you would like to build an entirely new theme for Laravel's Markdown componen To customize the theme for an individual notification, you may call the `theme` method while building the notification's mail message. The `theme` method accepts the name of the theme that should be used when sending the notification: - /** - * Get the mail representation of the notification. - */ - public function toMail(object $notifiable): MailMessage - { - return (new MailMessage) - ->theme('invoice') - ->subject('Invoice Paid') - ->markdown('mail.invoice.paid', ['url' => $url]); - } +```php +/** + * Get the mail representation of the notification. + */ +public function toMail(object $notifiable): MailMessage +{ + return (new MailMessage) + ->theme('invoice') + ->subject('Invoice Paid') + ->markdown('mail.invoice.paid', ['url' => $url]); +} +``` ## Database Notifications @@ -842,30 +924,34 @@ php artisan migrate If a notification supports being stored in a database table, you should define a `toDatabase` or `toArray` method on the notification class. This method will receive a `$notifiable` entity and should return a plain PHP array. The returned array will be encoded as JSON and stored in the `data` column of your `notifications` table. Let's take a look at an example `toArray` method: - /** - * Get the array representation of the notification. - * - * @return array - */ - public function toArray(object $notifiable): array - { - return [ - 'invoice_id' => $this->invoice->id, - 'amount' => $this->invoice->amount, - ]; - } +```php +/** + * Get the array representation of the notification. + * + * @return array + */ +public function toArray(object $notifiable): array +{ + return [ + 'invoice_id' => $this->invoice->id, + 'amount' => $this->invoice->amount, + ]; +} +``` When the notification is stored in your application's database, the `type` column will be populated with the notification's class name. However, you may customize this behavior by defining a `databaseType` method on your notification class: - /** - * Get the notification's database type. - * - * @return string - */ - public function databaseType(object $notifiable): string - { - return 'invoice-paid'; - } +```php +/** + * Get the notification's database type. + * + * @return string + */ +public function databaseType(object $notifiable): string +{ + return 'invoice-paid'; +} +``` #### `toDatabase` vs. `toArray` @@ -877,19 +963,23 @@ The `toArray` method is also used by the `broadcast` channel to determine which Once notifications are stored in the database, you need a convenient way to access them from your notifiable entities. The `Illuminate\Notifications\Notifiable` trait, which is included on Laravel's default `App\Models\User` model, includes a `notifications` [Eloquent relationship](/docs/{{version}}/eloquent-relationships) that returns the notifications for the entity. To fetch notifications, you may access this method like any other Eloquent relationship. By default, notifications will be sorted by the `created_at` timestamp with the most recent notifications at the beginning of the collection: - $user = App\Models\User::find(1); +```php +$user = App\Models\User::find(1); - foreach ($user->notifications as $notification) { - echo $notification->type; - } +foreach ($user->notifications as $notification) { + echo $notification->type; +} +``` If you want to retrieve only the "unread" notifications, you may use the `unreadNotifications` relationship. Again, these notifications will be sorted by the `created_at` timestamp with the most recent notifications at the beginning of the collection: - $user = App\Models\User::find(1); +```php +$user = App\Models\User::find(1); - foreach ($user->unreadNotifications as $notification) { - echo $notification->type; - } +foreach ($user->unreadNotifications as $notification) { + echo $notification->type; +} +``` > [!NOTE] > To access your notifications from your JavaScript client, you should define a notification controller for your application which returns the notifications for a notifiable entity, such as the current user. You may then make an HTTP request to that controller's URL from your JavaScript client. @@ -899,25 +989,33 @@ If you want to retrieve only the "unread" notifications, you may use the `unread Typically, you will want to mark a notification as "read" when a user views it. The `Illuminate\Notifications\Notifiable` trait provides a `markAsRead` method, which updates the `read_at` column on the notification's database record: - $user = App\Models\User::find(1); +```php +$user = App\Models\User::find(1); - foreach ($user->unreadNotifications as $notification) { - $notification->markAsRead(); - } +foreach ($user->unreadNotifications as $notification) { + $notification->markAsRead(); +} +``` However, instead of looping through each notification, you may use the `markAsRead` method directly on a collection of notifications: - $user->unreadNotifications->markAsRead(); +```php +$user->unreadNotifications->markAsRead(); +``` You may also use a mass-update query to mark all of the notifications as read without retrieving them from the database: - $user = App\Models\User::find(1); +```php +$user = App\Models\User::find(1); - $user->unreadNotifications()->update(['read_at' => now()]); +$user->unreadNotifications()->update(['read_at' => now()]); +``` You may `delete` the notifications to remove them from the table entirely: - $user->notifications()->delete(); +```php +$user->notifications()->delete(); +``` ## Broadcast Notifications @@ -932,76 +1030,86 @@ Before broadcasting notifications, you should configure and be familiar with Lar The `broadcast` channel broadcasts notifications using Laravel's [event broadcasting](/docs/{{version}}/broadcasting) services, allowing your JavaScript powered frontend to catch notifications in realtime. If a notification supports broadcasting, you can define a `toBroadcast` method on the notification class. This method will receive a `$notifiable` entity and should return a `BroadcastMessage` instance. If the `toBroadcast` method does not exist, the `toArray` method will be used to gather the data that should be broadcast. The returned data will be encoded as JSON and broadcast to your JavaScript powered frontend. Let's take a look at an example `toBroadcast` method: - use Illuminate\Notifications\Messages\BroadcastMessage; +```php +use Illuminate\Notifications\Messages\BroadcastMessage; - /** - * Get the broadcastable representation of the notification. - */ - public function toBroadcast(object $notifiable): BroadcastMessage - { - return new BroadcastMessage([ - 'invoice_id' => $this->invoice->id, - 'amount' => $this->invoice->amount, - ]); - } +/** + * Get the broadcastable representation of the notification. + */ +public function toBroadcast(object $notifiable): BroadcastMessage +{ + return new BroadcastMessage([ + 'invoice_id' => $this->invoice->id, + 'amount' => $this->invoice->amount, + ]); +} +``` #### Broadcast Queue Configuration All broadcast notifications are queued for broadcasting. If you would like to configure the queue connection or queue name that is used to queue the broadcast operation, you may use the `onConnection` and `onQueue` methods of the `BroadcastMessage`: - return (new BroadcastMessage($data)) - ->onConnection('sqs') - ->onQueue('broadcasts'); +```php +return (new BroadcastMessage($data)) + ->onConnection('sqs') + ->onQueue('broadcasts'); +``` #### Customizing the Notification Type In addition to the data you specify, all broadcast notifications also have a `type` field containing the full class name of the notification. If you would like to customize the notification `type`, you may define a `broadcastType` method on the notification class: - /** - * Get the type of the notification being broadcast. - */ - public function broadcastType(): string - { - return 'broadcast.message'; - } +```php +/** + * Get the type of the notification being broadcast. + */ +public function broadcastType(): string +{ + return 'broadcast.message'; +} +``` ### Listening for Notifications Notifications will broadcast on a private channel formatted using a `{notifiable}.{id}` convention. So, if you are sending a notification to an `App\Models\User` instance with an ID of `1`, the notification will be broadcast on the `App.Models.User.1` private channel. When using [Laravel Echo](/docs/{{version}}/broadcasting#client-side-installation), you may easily listen for notifications on a channel using the `notification` method: - Echo.private('App.Models.User.' + userId) - .notification((notification) => { - console.log(notification.type); - }); +```js +Echo.private('App.Models.User.' + userId) + .notification((notification) => { + console.log(notification.type); + }); +``` #### Customizing the Notification Channel If you would like to customize which channel that an entity's broadcast notifications are broadcast on, you may define a `receivesBroadcastNotificationsOn` method on the notifiable entity: - id; - } + /** + * The channels the user receives notification broadcasts on. + */ + public function receivesBroadcastNotificationsOn(): string + { + return 'users.'.$this->id; } +} +``` ## SMS Notifications @@ -1011,106 +1119,120 @@ If you would like to customize which channel that an entity's broadcast notifica Sending SMS notifications in Laravel is powered by [Vonage](https://www.vonage.com/) (formerly known as Nexmo). Before you can send notifications via Vonage, you need to install the `laravel/vonage-notification-channel` and `guzzlehttp/guzzle` packages: - composer require laravel/vonage-notification-channel guzzlehttp/guzzle +```shell +composer require laravel/vonage-notification-channel guzzlehttp/guzzle +``` The package includes a [configuration file](https://github.com/laravel/vonage-notification-channel/blob/3.x/config/vonage.php). However, you are not required to export this configuration file to your own application. You can simply use the `VONAGE_KEY` and `VONAGE_SECRET` environment variables to define your Vonage public and secret keys. After defining your keys, you should set a `VONAGE_SMS_FROM` environment variable that defines the phone number that your SMS messages should be sent from by default. You may generate this phone number within the Vonage control panel: - VONAGE_SMS_FROM=15556666666 +```ini +VONAGE_SMS_FROM=15556666666 +``` ### Formatting SMS Notifications If a notification supports being sent as an SMS, you should define a `toVonage` method on the notification class. This method will receive a `$notifiable` entity and should return an `Illuminate\Notifications\Messages\VonageMessage` instance: - use Illuminate\Notifications\Messages\VonageMessage; +```php +use Illuminate\Notifications\Messages\VonageMessage; - /** - * Get the Vonage / SMS representation of the notification. - */ - public function toVonage(object $notifiable): VonageMessage - { - return (new VonageMessage) - ->content('Your SMS message content'); - } +/** + * Get the Vonage / SMS representation of the notification. + */ +public function toVonage(object $notifiable): VonageMessage +{ + return (new VonageMessage) + ->content('Your SMS message content'); +} +``` #### Unicode Content If your SMS message will contain unicode characters, you should call the `unicode` method when constructing the `VonageMessage` instance: - use Illuminate\Notifications\Messages\VonageMessage; +```php +use Illuminate\Notifications\Messages\VonageMessage; - /** - * Get the Vonage / SMS representation of the notification. - */ - public function toVonage(object $notifiable): VonageMessage - { - return (new VonageMessage) - ->content('Your unicode message') - ->unicode(); - } +/** + * Get the Vonage / SMS representation of the notification. + */ +public function toVonage(object $notifiable): VonageMessage +{ + return (new VonageMessage) + ->content('Your unicode message') + ->unicode(); +} +``` ### Customizing the "From" Number If you would like to send some notifications from a phone number that is different from the phone number specified by your `VONAGE_SMS_FROM` environment variable, you may call the `from` method on a `VonageMessage` instance: - use Illuminate\Notifications\Messages\VonageMessage; +```php +use Illuminate\Notifications\Messages\VonageMessage; - /** - * Get the Vonage / SMS representation of the notification. - */ - public function toVonage(object $notifiable): VonageMessage - { - return (new VonageMessage) - ->content('Your SMS message content') - ->from('15554443333'); - } +/** + * Get the Vonage / SMS representation of the notification. + */ +public function toVonage(object $notifiable): VonageMessage +{ + return (new VonageMessage) + ->content('Your SMS message content') + ->from('15554443333'); +} +``` ### Adding a Client Reference If you would like to keep track of costs per user, team, or client, you may add a "client reference" to the notification. Vonage will allow you to generate reports using this client reference so that you can better understand a particular customer's SMS usage. The client reference can be any string up to 40 characters: - use Illuminate\Notifications\Messages\VonageMessage; +```php +use Illuminate\Notifications\Messages\VonageMessage; - /** - * Get the Vonage / SMS representation of the notification. - */ - public function toVonage(object $notifiable): VonageMessage - { - return (new VonageMessage) - ->clientReference((string) $notifiable->id) - ->content('Your SMS message content'); - } +/** + * Get the Vonage / SMS representation of the notification. + */ +public function toVonage(object $notifiable): VonageMessage +{ + return (new VonageMessage) + ->clientReference((string) $notifiable->id) + ->content('Your SMS message content'); +} +``` ### Routing SMS Notifications To route Vonage notifications to the proper phone number, define a `routeNotificationForVonage` method on your notifiable entity: - phone_number; - } + /** + * Route notifications for the Vonage channel. + */ + public function routeNotificationForVonage(Notification $notification): string + { + return $this->phone_number; } +} +``` ## Slack Notifications @@ -1130,12 +1252,14 @@ If you only need to send notifications to the same Slack workspace that the App Next, copy the App's "Bot User OAuth Token" and place it within a `slack` configuration array in your application's `services.php` configuration file. This token can be found on the "OAuth & Permissions" tab within Slack: - 'slack' => [ - 'notifications' => [ - 'bot_user_oauth_token' => env('SLACK_BOT_USER_OAUTH_TOKEN'), - 'channel' => env('SLACK_BOT_USER_DEFAULT_CHANNEL'), - ], +```php +'slack' => [ + 'notifications' => [ + 'bot_user_oauth_token' => env('SLACK_BOT_USER_OAUTH_TOKEN'), + 'channel' => env('SLACK_BOT_USER_DEFAULT_CHANNEL'), ], +], +``` #### App Distribution @@ -1147,70 +1271,74 @@ If your application will be sending notifications to external Slack workspaces t If a notification supports being sent as a Slack message, you should define a `toSlack` method on the notification class. This method will receive a `$notifiable` entity and should return an `Illuminate\Notifications\Slack\SlackMessage` instance. You can construct rich notifications using [Slack's Block Kit API](https://api.slack.com/block-kit). The following example may be previewed in [Slack's Block Kit builder](https://app.slack.com/block-kit-builder/T01KWS6K23Z#%7B%22blocks%22:%5B%7B%22type%22:%22header%22,%22text%22:%7B%22type%22:%22plain_text%22,%22text%22:%22Invoice%20Paid%22%7D%7D,%7B%22type%22:%22context%22,%22elements%22:%5B%7B%22type%22:%22plain_text%22,%22text%22:%22Customer%20%231234%22%7D%5D%7D,%7B%22type%22:%22section%22,%22text%22:%7B%22type%22:%22plain_text%22,%22text%22:%22An%20invoice%20has%20been%20paid.%22%7D,%22fields%22:%5B%7B%22type%22:%22mrkdwn%22,%22text%22:%22*Invoice%20No:*%5Cn1000%22%7D,%7B%22type%22:%22mrkdwn%22,%22text%22:%22*Invoice%20Recipient:*%5Cntaylor@laravel.com%22%7D%5D%7D,%7B%22type%22:%22divider%22%7D,%7B%22type%22:%22section%22,%22text%22:%7B%22type%22:%22plain_text%22,%22text%22:%22Congratulations!%22%7D%7D%5D%7D): - use Illuminate\Notifications\Slack\BlockKit\Blocks\ContextBlock; - use Illuminate\Notifications\Slack\BlockKit\Blocks\SectionBlock; - use Illuminate\Notifications\Slack\BlockKit\Composites\ConfirmObject; - use Illuminate\Notifications\Slack\SlackMessage; +```php +use Illuminate\Notifications\Slack\BlockKit\Blocks\ContextBlock; +use Illuminate\Notifications\Slack\BlockKit\Blocks\SectionBlock; +use Illuminate\Notifications\Slack\BlockKit\Composites\ConfirmObject; +use Illuminate\Notifications\Slack\SlackMessage; - /** - * Get the Slack representation of the notification. - */ - public function toSlack(object $notifiable): SlackMessage - { - return (new SlackMessage) - ->text('One of your invoices has been paid!') - ->headerBlock('Invoice Paid') - ->contextBlock(function (ContextBlock $block) { - $block->text('Customer #1234'); - }) - ->sectionBlock(function (SectionBlock $block) { - $block->text('An invoice has been paid.'); - $block->field("*Invoice No:*\n1000")->markdown(); - $block->field("*Invoice Recipient:*\ntaylor@laravel.com")->markdown(); - }) - ->dividerBlock() - ->sectionBlock(function (SectionBlock $block) { - $block->text('Congratulations!'); - }); - } +/** + * Get the Slack representation of the notification. + */ +public function toSlack(object $notifiable): SlackMessage +{ + return (new SlackMessage) + ->text('One of your invoices has been paid!') + ->headerBlock('Invoice Paid') + ->contextBlock(function (ContextBlock $block) { + $block->text('Customer #1234'); + }) + ->sectionBlock(function (SectionBlock $block) { + $block->text('An invoice has been paid.'); + $block->field("*Invoice No:*\n1000")->markdown(); + $block->field("*Invoice Recipient:*\ntaylor@laravel.com")->markdown(); + }) + ->dividerBlock() + ->sectionBlock(function (SectionBlock $block) { + $block->text('Congratulations!'); + }); +} +``` #### Using Slack's Block Kit Builder Template Instead of using the fluent message builder methods to construct your Block Kit message, you may provide the raw JSON payload generated by Slack's Block Kit Builder to the `usingBlockKitTemplate` method: - use Illuminate\Notifications\Slack\SlackMessage; - use Illuminate\Support\Str; +```php +use Illuminate\Notifications\Slack\SlackMessage; +use Illuminate\Support\Str; - /** - * Get the Slack representation of the notification. - */ - public function toSlack(object $notifiable): SlackMessage - { - $template = <<usingBlockKitTemplate($template); - } + return (new SlackMessage) + ->usingBlockKitTemplate($template); +} +``` ### Slack Interactivity @@ -1219,81 +1347,87 @@ Slack's Block Kit notification system provides powerful features to [handle user In the following example, which utilizes the `actionsBlock` method, Slack will send a `POST` request to your "Request URL" with a payload containing the Slack user who clicked the button, the ID of the clicked button, and more. Your application can then determine the action to take based on the payload. You should also [verify the request](https://api.slack.com/authentication/verifying-requests-from-slack) was made by Slack: - use Illuminate\Notifications\Slack\BlockKit\Blocks\ActionsBlock; - use Illuminate\Notifications\Slack\BlockKit\Blocks\ContextBlock; - use Illuminate\Notifications\Slack\BlockKit\Blocks\SectionBlock; - use Illuminate\Notifications\Slack\SlackMessage; +```php +use Illuminate\Notifications\Slack\BlockKit\Blocks\ActionsBlock; +use Illuminate\Notifications\Slack\BlockKit\Blocks\ContextBlock; +use Illuminate\Notifications\Slack\BlockKit\Blocks\SectionBlock; +use Illuminate\Notifications\Slack\SlackMessage; - /** - * Get the Slack representation of the notification. - */ - public function toSlack(object $notifiable): SlackMessage - { - return (new SlackMessage) - ->text('One of your invoices has been paid!') - ->headerBlock('Invoice Paid') - ->contextBlock(function (ContextBlock $block) { - $block->text('Customer #1234'); - }) - ->sectionBlock(function (SectionBlock $block) { - $block->text('An invoice has been paid.'); - }) - ->actionsBlock(function (ActionsBlock $block) { - // ID defaults to "button_acknowledge_invoice"... - $block->button('Acknowledge Invoice')->primary(); - - // Manually configure the ID... - $block->button('Deny')->danger()->id('deny_invoice'); - }); - } +/** + * Get the Slack representation of the notification. + */ +public function toSlack(object $notifiable): SlackMessage +{ + return (new SlackMessage) + ->text('One of your invoices has been paid!') + ->headerBlock('Invoice Paid') + ->contextBlock(function (ContextBlock $block) { + $block->text('Customer #1234'); + }) + ->sectionBlock(function (SectionBlock $block) { + $block->text('An invoice has been paid.'); + }) + ->actionsBlock(function (ActionsBlock $block) { + // ID defaults to "button_acknowledge_invoice"... + $block->button('Acknowledge Invoice')->primary(); + + // Manually configure the ID... + $block->button('Deny')->danger()->id('deny_invoice'); + }); +} +``` #### Confirmation Modals If you would like users to be required to confirm an action before it is performed, you may invoke the `confirm` method when defining your button. The `confirm` method accepts a message and a closure which receives a `ConfirmObject` instance: - use Illuminate\Notifications\Slack\BlockKit\Blocks\ActionsBlock; - use Illuminate\Notifications\Slack\BlockKit\Blocks\ContextBlock; - use Illuminate\Notifications\Slack\BlockKit\Blocks\SectionBlock; - use Illuminate\Notifications\Slack\BlockKit\Composites\ConfirmObject; - use Illuminate\Notifications\Slack\SlackMessage; - - /** - * Get the Slack representation of the notification. - */ - public function toSlack(object $notifiable): SlackMessage - { - return (new SlackMessage) - ->text('One of your invoices has been paid!') - ->headerBlock('Invoice Paid') - ->contextBlock(function (ContextBlock $block) { - $block->text('Customer #1234'); - }) - ->sectionBlock(function (SectionBlock $block) { - $block->text('An invoice has been paid.'); - }) - ->actionsBlock(function (ActionsBlock $block) { - $block->button('Acknowledge Invoice') - ->primary() - ->confirm( - 'Acknowledge the payment and send a thank you email?', - function (ConfirmObject $dialog) { - $dialog->confirm('Yes'); - $dialog->deny('No'); - } - ); - }); - } +```php +use Illuminate\Notifications\Slack\BlockKit\Blocks\ActionsBlock; +use Illuminate\Notifications\Slack\BlockKit\Blocks\ContextBlock; +use Illuminate\Notifications\Slack\BlockKit\Blocks\SectionBlock; +use Illuminate\Notifications\Slack\BlockKit\Composites\ConfirmObject; +use Illuminate\Notifications\Slack\SlackMessage; + +/** + * Get the Slack representation of the notification. + */ +public function toSlack(object $notifiable): SlackMessage +{ + return (new SlackMessage) + ->text('One of your invoices has been paid!') + ->headerBlock('Invoice Paid') + ->contextBlock(function (ContextBlock $block) { + $block->text('Customer #1234'); + }) + ->sectionBlock(function (SectionBlock $block) { + $block->text('An invoice has been paid.'); + }) + ->actionsBlock(function (ActionsBlock $block) { + $block->button('Acknowledge Invoice') + ->primary() + ->confirm( + 'Acknowledge the payment and send a thank you email?', + function (ConfirmObject $dialog) { + $dialog->confirm('Yes'); + $dialog->deny('No'); + } + ); + }); +} +``` #### Inspecting Slack Blocks If you would like to quickly inspect the blocks you've been building, you can invoke the `dd` method on the `SlackMessage` instance. The `dd` method will generate and dump a URL to Slack's [Block Kit Builder](https://app.slack.com/block-kit-builder/), which displays a preview of the payload and notification in your browser. You may pass `true` to the `dd` method to dump the raw payload: - return (new SlackMessage) - ->text('One of your invoices has been paid!') - ->headerBlock('Invoice Paid') - ->dd(); +```php +return (new SlackMessage) + ->text('One of your invoices has been paid!') + ->headerBlock('Invoice Paid') + ->dd(); +``` ### Routing Slack Notifications @@ -1306,26 +1440,28 @@ To direct Slack notifications to the appropriate Slack team and channel, define For instance, returning `#support-channel` from the `routeNotificationForSlack` method will send the notification to the `#support-channel` channel in the workspace associated with the Bot User OAuth token located in your application's `services.php` configuration file: - ### Notifying External Slack Workspaces @@ -1337,27 +1473,29 @@ Of course, you will often want to send notifications to the Slack workspaces own Once you have obtained the bot token and stored it within your application's database, you may utilize the `SlackRoute::make` method to route a notification to the user's workspace. In addition, your application will likely need to offer an opportunity for the user to specify which channel notifications should be sent to: - slack_channel, $this->slack_token); - } + /** + * Route notifications for the Slack channel. + */ + public function routeNotificationForSlack(Notification $notification): mixed + { + return SlackRoute::make($this->slack_channel, $this->slack_token); } +} +``` ## Localizing Notifications @@ -1366,35 +1504,43 @@ Laravel allows you to send notifications in a locale other than the HTTP request To accomplish this, the `Illuminate\Notifications\Notification` class offers a `locale` method to set the desired language. The application will change into this locale when the notification is being evaluated and then revert back to the previous locale when evaluation is complete: - $user->notify((new InvoicePaid($invoice))->locale('es')); +```php +$user->notify((new InvoicePaid($invoice))->locale('es')); +``` Localization of multiple notifiable entries may also be achieved via the `Notification` facade: - Notification::locale('es')->send( - $users, new InvoicePaid($invoice) - ); +```php +Notification::locale('es')->send( + $users, new InvoicePaid($invoice) +); +``` ### User Preferred Locales Sometimes, applications store each user's preferred locale. By implementing the `HasLocalePreference` contract on your notifiable model, you may instruct Laravel to use this stored locale when sending a notification: - use Illuminate\Contracts\Translation\HasLocalePreference; +```php +use Illuminate\Contracts\Translation\HasLocalePreference; - class User extends Model implements HasLocalePreference +class User extends Model implements HasLocalePreference +{ + /** + * Get the user's preferred locale. + */ + public function preferredLocale(): string { - /** - * Get the user's preferred locale. - */ - public function preferredLocale(): string - { - return $this->locale; - } + return $this->locale; } +} +``` Once you have implemented the interface, Laravel will automatically use the preferred locale when sending notifications and mailables to the model. Therefore, there is no need to call the `locale` method when using this interface: - $user->notify(new InvoicePaid($invoice)); +```php +$user->notify(new InvoicePaid($invoice)); +``` ## Testing @@ -1470,28 +1616,34 @@ class ExampleTest extends TestCase You may pass a closure to the `assertSentTo` or `assertNotSentTo` methods in order to assert that a notification was sent that passes a given "truth test". If at least one notification was sent that passes the given truth test then the assertion will be successful: - Notification::assertSentTo( - $user, - function (OrderShipped $notification, array $channels) use ($order) { - return $notification->order->id === $order->id; - } - ); +```php +Notification::assertSentTo( + $user, + function (OrderShipped $notification, array $channels) use ($order) { + return $notification->order->id === $order->id; + } +); +``` #### On-Demand Notifications If the code you are testing sends [on-demand notifications](#on-demand-notifications), you can test that the on-demand notification was sent via the `assertSentOnDemand` method: - Notification::assertSentOnDemand(OrderShipped::class); +```php +Notification::assertSentOnDemand(OrderShipped::class); +``` By passing a closure as the second argument to the `assertSentOnDemand` method, you may determine if an on-demand notification was sent to the correct "route" address: - Notification::assertSentOnDemand( - OrderShipped::class, - function (OrderShipped $notification, array $channels, object $notifiable) use ($user) { - return $notifiable->routes['mail'] === $user->email; - } - ); +```php +Notification::assertSentOnDemand( + OrderShipped::class, + function (OrderShipped $notification, array $channels, object $notifiable) use ($user) { + return $notifiable->routes['mail'] === $user->email; + } +); +``` ## Notification Events @@ -1501,71 +1653,81 @@ By passing a closure as the second argument to the `assertSentOnDemand` method, When a notification is sending, the `Illuminate\Notifications\Events\NotificationSending` event is dispatched by the notification system. This contains the "notifiable" entity and the notification instance itself. You may create [event listeners](/docs/{{version}}/events) for this event within your application: - use Illuminate\Notifications\Events\NotificationSending; - - class CheckNotificationStatus - { - /** - * Handle the given event. - */ - public function handle(NotificationSending $event): void - { - // ... - } - } - -The notification will not be sent if an event listener for the `NotificationSending` event returns `false` from its `handle` method: +```php +use Illuminate\Notifications\Events\NotificationSending; +class CheckNotificationStatus +{ /** * Handle the given event. */ - public function handle(NotificationSending $event): bool + public function handle(NotificationSending $event): void { - return false; + // ... } +} +``` + +The notification will not be sent if an event listener for the `NotificationSending` event returns `false` from its `handle` method: + +```php +/** + * Handle the given event. + */ +public function handle(NotificationSending $event): bool +{ + return false; +} +``` Within an event listener, you may access the `notifiable`, `notification`, and `channel` properties on the event to learn more about the notification recipient or the notification itself: - /** - * Handle the given event. - */ - public function handle(NotificationSending $event): void - { - // $event->channel - // $event->notifiable - // $event->notification - } +```php +/** + * Handle the given event. + */ +public function handle(NotificationSending $event): void +{ + // $event->channel + // $event->notifiable + // $event->notification +} +``` #### Notification Sent Event When a notification is sent, the `Illuminate\Notifications\Events\NotificationSent` [event](/docs/{{version}}/events) is dispatched by the notification system. This contains the "notifiable" entity and the notification instance itself. You may create [event listeners](/docs/{{version}}/events) for this event within your application: - use Illuminate\Notifications\Events\NotificationSent; - - class LogNotification - { - /** - * Handle the given event. - */ - public function handle(NotificationSent $event): void - { - // ... - } - } - -Within an event listener, you may access the `notifiable`, `notification`, `channel`, and `response` properties on the event to learn more about the notification recipient or the notification itself: +```php +use Illuminate\Notifications\Events\NotificationSent; +class LogNotification +{ /** * Handle the given event. */ public function handle(NotificationSent $event): void { - // $event->channel - // $event->notifiable - // $event->notification - // $event->response + // ... } +} +``` + +Within an event listener, you may access the `notifiable`, `notification`, `channel`, and `response` properties on the event to learn more about the notification recipient or the notification itself: + +```php +/** + * Handle the given event. + */ +public function handle(NotificationSent $event): void +{ + // $event->channel + // $event->notifiable + // $event->notification + // $event->response +} +``` ## Custom Channels @@ -1574,54 +1736,58 @@ Laravel ships with a handful of notification channels, but you may want to write Within the `send` method, you may call methods on the notification to retrieve a message object understood by your channel and then send the notification to the `$notifiable` instance however you wish: - toVoice($notifiable); + $message = $notification->toVoice($notifiable); - // Send notification to the $notifiable instance... - } + // Send notification to the $notifiable instance... } +} +``` Once your notification channel class has been defined, you may return the class name from the `via` method of any of your notifications. In this example, the `toVoice` method of your notification can return whatever object you choose to represent voice messages. For example, you might define your own `VoiceMessage` class to represent these messages: - publishes([ - __DIR__.'/../config/courier.php' => config_path('courier.php'), - ]); - } +```php +/** + * Bootstrap any package services. + */ +public function boot(): void +{ + $this->publishes([ + __DIR__.'/../config/courier.php' => config_path('courier.php'), + ]); +} +``` Now, when users of your package execute Laravel's `vendor:publish` command, your file will be copied to the specified publish location. Once your configuration has been published, its values may be accessed like any other configuration file: - $value = config('courier.option'); +```php +$value = config('courier.option'); +``` > [!WARNING] > You should not define closures in your configuration files. They cannot be serialized correctly when users execute the `config:cache` Artisan command. @@ -117,15 +121,17 @@ You may also merge your own package configuration file with the application's pu The `mergeConfigFrom` method accepts the path to your package's configuration file as its first argument and the name of the application's copy of the configuration file as its second argument: - /** - * Register any application services. - */ - public function register(): void - { - $this->mergeConfigFrom( - __DIR__.'/../config/courier.php', 'courier' - ); - } +```php +/** + * Register any application services. + */ +public function register(): void +{ + $this->mergeConfigFrom( + __DIR__.'/../config/courier.php', 'courier' + ); +} +``` > [!WARNING] > This method only merges the first level of the configuration array. If your users partially define a multi-dimensional configuration array, the missing options will not be merged. @@ -135,45 +141,53 @@ The `mergeConfigFrom` method accepts the path to your package's configuration fi If your package contains routes, you may load them using the `loadRoutesFrom` method. This method will automatically determine if the application's routes are cached and will not load your routes file if the routes have already been cached: - /** - * Bootstrap any package services. - */ - public function boot(): void - { - $this->loadRoutesFrom(__DIR__.'/../routes/web.php'); - } +```php +/** + * Bootstrap any package services. + */ +public function boot(): void +{ + $this->loadRoutesFrom(__DIR__.'/../routes/web.php'); +} +``` ### Migrations If your package contains [database migrations](/docs/{{version}}/migrations), you may use the `publishesMigrations` method to inform Laravel that the given directory or file contains migrations. When Laravel publishes the migrations, it will automatically update the timestamp within their filename to reflect the current date and time: - /** - * Bootstrap any package services. - */ - public function boot(): void - { - $this->publishesMigrations([ - __DIR__.'/../database/migrations' => database_path('migrations'), - ]); - } +```php +/** + * Bootstrap any package services. + */ +public function boot(): void +{ + $this->publishesMigrations([ + __DIR__.'/../database/migrations' => database_path('migrations'), + ]); +} +``` ### Language Files If your package contains [language files](/docs/{{version}}/localization), you may use the `loadTranslationsFrom` method to inform Laravel how to load them. For example, if your package is named `courier`, you should add the following to your service provider's `boot` method: - /** - * Bootstrap any package services. - */ - public function boot(): void - { - $this->loadTranslationsFrom(__DIR__.'/../lang', 'courier'); - } +```php +/** + * Bootstrap any package services. + */ +public function boot(): void +{ + $this->loadTranslationsFrom(__DIR__.'/../lang', 'courier'); +} +``` Package translation lines are referenced using the `package::file.line` syntax convention. So, you may load the `courier` package's `welcome` line from the `messages` file like so: - echo trans('courier::messages.welcome'); +```php +echo trans('courier::messages.welcome'); +``` You can register JSON translation files for your package using the `loadJsonTranslationsFrom` method. This method accepts the path to the directory that contains your package's JSON translation files: @@ -192,17 +206,19 @@ public function boot(): void If you would like to publish your package's language files to the application's `lang/vendor` directory, you may use the service provider's `publishes` method. The `publishes` method accepts an array of package paths and their desired publish locations. For example, to publish the language files for the `courier` package, you may do the following: - /** - * Bootstrap any package services. - */ - public function boot(): void - { - $this->loadTranslationsFrom(__DIR__.'/../lang', 'courier'); +```php +/** + * Bootstrap any package services. + */ +public function boot(): void +{ + $this->loadTranslationsFrom(__DIR__.'/../lang', 'courier'); - $this->publishes([ - __DIR__.'/../lang' => $this->app->langPath('vendor/courier'), - ]); - } + $this->publishes([ + __DIR__.'/../lang' => $this->app->langPath('vendor/courier'), + ]); +} +``` Now, when users of your package execute Laravel's `vendor:publish` Artisan command, your package's language files will be published to the specified publish location. @@ -211,19 +227,23 @@ Now, when users of your package execute Laravel's `vendor:publish` Artisan comma To register your package's [views](/docs/{{version}}/views) with Laravel, you need to tell Laravel where the views are located. You may do this using the service provider's `loadViewsFrom` method. The `loadViewsFrom` method accepts two arguments: the path to your view templates and your package's name. For example, if your package's name is `courier`, you would add the following to your service provider's `boot` method: - /** - * Bootstrap any package services. - */ - public function boot(): void - { - $this->loadViewsFrom(__DIR__.'/../resources/views', 'courier'); - } +```php +/** + * Bootstrap any package services. + */ +public function boot(): void +{ + $this->loadViewsFrom(__DIR__.'/../resources/views', 'courier'); +} +``` Package views are referenced using the `package::view` syntax convention. So, once your view path is registered in a service provider, you may load the `dashboard` view from the `courier` package like so: - Route::get('/dashboard', function () { - return view('courier::dashboard'); - }); +```php +Route::get('/dashboard', function () { + return view('courier::dashboard'); +}); +``` #### Overriding Package Views @@ -235,17 +255,19 @@ When you use the `loadViewsFrom` method, Laravel actually registers two location If you would like to make your views available for publishing to the application's `resources/views/vendor` directory, you may use the service provider's `publishes` method. The `publishes` method accepts an array of package view paths and their desired publish locations: - /** - * Bootstrap the package services. - */ - public function boot(): void - { - $this->loadViewsFrom(__DIR__.'/../resources/views', 'courier'); +```php +/** + * Bootstrap the package services. + */ +public function boot(): void +{ + $this->loadViewsFrom(__DIR__.'/../resources/views', 'courier'); - $this->publishes([ - __DIR__.'/../resources/views' => resource_path('views/vendor/courier'), - ]); - } + $this->publishes([ + __DIR__.'/../resources/views' => resource_path('views/vendor/courier'), + ]); +} +``` Now, when users of your package execute Laravel's `vendor:publish` Artisan command, your package's views will be copied to the specified publish location. @@ -254,16 +276,18 @@ Now, when users of your package execute Laravel's `vendor:publish` Artisan comma If you are building a package that utilizes Blade components or placing components in non-conventional directories, you will need to manually register your component class and its HTML tag alias so that Laravel knows where to find the component. You should typically register your components in the `boot` method of your package's service provider: - use Illuminate\Support\Facades\Blade; - use VendorPackage\View\Components\AlertComponent; +```php +use Illuminate\Support\Facades\Blade; +use VendorPackage\View\Components\AlertComponent; - /** - * Bootstrap your package's services. - */ - public function boot(): void - { - Blade::component('package-alert', AlertComponent::class); - } +/** + * Bootstrap your package's services. + */ +public function boot(): void +{ + Blade::component('package-alert', AlertComponent::class); +} +``` Once your component has been registered, it may be rendered using its tag alias: @@ -276,15 +300,17 @@ Once your component has been registered, it may be rendered using its tag alias: Alternatively, you may use the `componentNamespace` method to autoload component classes by convention. For example, a `Nightshade` package might have `Calendar` and `ColorPicker` components that reside within the `Nightshade\Views\Components` namespace: - use Illuminate\Support\Facades\Blade; +```php +use Illuminate\Support\Facades\Blade; - /** - * Bootstrap your package's services. - */ - public function boot(): void - { - Blade::componentNamespace('Nightshade\\Views\\Components', 'nightshade'); - } +/** + * Bootstrap your package's services. + */ +public function boot(): void +{ + Blade::componentNamespace('Nightshade\\Views\\Components', 'nightshade'); +} +``` This will allow the usage of package components by their vendor namespace using the `package-name::` syntax: @@ -309,69 +335,77 @@ If your package contains anonymous components, they must be placed within a `com Laravel's built-in `about` Artisan command provides a synopsis of the application's environment and configuration. Packages may push additional information to this command's output via the `AboutCommand` class. Typically, this information may be added from your package service provider's `boot` method: - use Illuminate\Foundation\Console\AboutCommand; +```php +use Illuminate\Foundation\Console\AboutCommand; - /** - * Bootstrap any application services. - */ - public function boot(): void - { - AboutCommand::add('My Package', fn () => ['Version' => '1.0.0']); - } +/** + * Bootstrap any application services. + */ +public function boot(): void +{ + AboutCommand::add('My Package', fn () => ['Version' => '1.0.0']); +} +``` ## Commands To register your package's Artisan commands with Laravel, you may use the `commands` method. This method expects an array of command class names. Once the commands have been registered, you may execute them using the [Artisan CLI](/docs/{{version}}/artisan): - use Courier\Console\Commands\InstallCommand; - use Courier\Console\Commands\NetworkCommand; - - /** - * Bootstrap any package services. - */ - public function boot(): void - { - if ($this->app->runningInConsole()) { - $this->commands([ - InstallCommand::class, - NetworkCommand::class, - ]); - } +```php +use Courier\Console\Commands\InstallCommand; +use Courier\Console\Commands\NetworkCommand; + +/** + * Bootstrap any package services. + */ +public function boot(): void +{ + if ($this->app->runningInConsole()) { + $this->commands([ + InstallCommand::class, + NetworkCommand::class, + ]); } +} +``` ### Optimize Commands Laravel's [`optimize` command](/docs/{{version}}/deployment#optimization) caches the application's configuration, events, routes, and views. Using the `optimizes` method, you may register your package's own Artisan commands that should be invoked when the `optimize` and `optimize:clear` commands are executed: - /** - * Bootstrap any package services. - */ - public function boot(): void - { - if ($this->app->runningInConsole()) { - $this->optimizes( - optimize: 'package:optimize', - clear: 'package:clear-optimizations', - ); - } +```php +/** + * Bootstrap any package services. + */ +public function boot(): void +{ + if ($this->app->runningInConsole()) { + $this->optimizes( + optimize: 'package:optimize', + clear: 'package:clear-optimizations', + ); } +} +``` ## Public Assets Your package may have assets such as JavaScript, CSS, and images. To publish these assets to the application's `public` directory, use the service provider's `publishes` method. In this example, we will also add a `public` asset group tag, which may be used to easily publish groups of related assets: - /** - * Bootstrap any package services. - */ - public function boot(): void - { - $this->publishes([ - __DIR__.'/../public' => public_path('vendor/courier'), - ], 'public'); - } +```php +/** + * Bootstrap any package services. + */ +public function boot(): void +{ + $this->publishes([ + __DIR__.'/../public' => public_path('vendor/courier'), + ], 'public'); +} +``` Now, when your package's users execute the `vendor:publish` command, your assets will be copied to the specified publish location. Since users will typically need to overwrite the assets every time the package is updated, you may use the `--force` flag: @@ -384,19 +418,21 @@ php artisan vendor:publish --tag=public --force You may want to publish groups of package assets and resources separately. For instance, you might want to allow your users to publish your package's configuration files without being forced to publish your package's assets. You may do this by "tagging" them when calling the `publishes` method from a package's service provider. For example, let's use tags to define two publish groups for the `courier` package (`courier-config` and `courier-migrations`) in the `boot` method of the package's service provider: - /** - * Bootstrap any package services. - */ - public function boot(): void - { - $this->publishes([ - __DIR__.'/../config/package.php' => config_path('package.php') - ], 'courier-config'); - - $this->publishesMigrations([ - __DIR__.'/../database/migrations/' => database_path('migrations') - ], 'courier-migrations'); - } +```php +/** + * Bootstrap any package services. + */ +public function boot(): void +{ + $this->publishes([ + __DIR__.'/../config/package.php' => config_path('package.php') + ], 'courier-config'); + + $this->publishesMigrations([ + __DIR__.'/../database/migrations/' => database_path('migrations') + ], 'courier-migrations'); +} +``` Now your users may publish these groups separately by referencing their tag when executing the `vendor:publish` command: diff --git a/pagination.md b/pagination.md index 553cc9167c0..cf83aa612de 100644 --- a/pagination.md +++ b/pagination.md @@ -46,26 +46,28 @@ There are several ways to paginate items. The simplest is by using the `paginate In this example, the only argument passed to the `paginate` method is the number of items you would like displayed "per page". In this case, let's specify that we would like to display `15` items per page: - DB::table('users')->paginate(15) - ]); - } + return view('user.index', [ + 'users' => DB::table('users')->paginate(15) + ]); } +} +``` #### Simple Pagination @@ -74,39 +76,51 @@ The `paginate` method counts the total number of records matched by the query be Therefore, if you only need to display simple "Next" and "Previous" links in your application's UI, you may use the `simplePaginate` method to perform a single, efficient query: - $users = DB::table('users')->simplePaginate(15); +```php +$users = DB::table('users')->simplePaginate(15); +``` ### Paginating Eloquent Results You may also paginate [Eloquent](/docs/{{version}}/eloquent) queries. In this example, we will paginate the `App\Models\User` model and indicate that we plan to display 15 records per page. As you can see, the syntax is nearly identical to paginating query builder results: - use App\Models\User; +```php +use App\Models\User; - $users = User::paginate(15); +$users = User::paginate(15); +``` Of course, you may call the `paginate` method after setting other constraints on the query, such as `where` clauses: - $users = User::where('votes', '>', 100)->paginate(15); +```php +$users = User::where('votes', '>', 100)->paginate(15); +``` You may also use the `simplePaginate` method when paginating Eloquent models: - $users = User::where('votes', '>', 100)->simplePaginate(15); +```php +$users = User::where('votes', '>', 100)->simplePaginate(15); +``` Similarly, you may use the `cursorPaginate` method to cursor paginate Eloquent models: - $users = User::where('votes', '>', 100)->cursorPaginate(15); +```php +$users = User::where('votes', '>', 100)->cursorPaginate(15); +``` #### Multiple Paginator Instances per Page Sometimes you may need to render two separate paginators on a single screen that is rendered by your application. However, if both paginator instances use the `page` query string parameter to store the current page, the two paginator's will conflict. To resolve this conflict, you may pass the name of the query string parameter you wish to use to store the paginator's current page via the third argument provided to the `paginate`, `simplePaginate`, and `cursorPaginate` methods: - use App\Models\User; +```php +use App\Models\User; - $users = User::where('votes', '>', 100)->paginate( - $perPage = 15, $columns = ['*'], $pageName = 'users' - ); +$users = User::where('votes', '>', 100)->paginate( + $perPage = 15, $columns = ['*'], $pageName = 'users' +); +``` ### Cursor Pagination @@ -121,7 +135,9 @@ http://localhost/users?cursor=eyJpZCI6MTUsIl9wb2ludHNUb05leHRJdGVtcyI6dHJ1ZX0 You may create a cursor based paginator instance via the `cursorPaginate` method offered by the query builder. This method returns an instance of `Illuminate\Pagination\CursorPaginator`: - $users = DB::table('users')->orderBy('id')->cursorPaginate(15); +```php +$users = DB::table('users')->orderBy('id')->cursorPaginate(15); +``` Once you have retrieved a cursor paginator instance, you may [display the pagination results](#displaying-pagination-results) as you typically would when using the `paginate` and `simplePaginate` methods. For more information on the instance methods offered by the cursor paginator, please consult the [cursor paginator instance method documentation](#cursor-paginator-instance-methods). @@ -170,41 +186,49 @@ In other words, the `Paginator` corresponds to the `simplePaginate` method on th By default, links generated by the paginator will match the current request's URI. However, the paginator's `withPath` method allows you to customize the URI used by the paginator when generating links. For example, if you want the paginator to generate links like `http://example.com/admin/users?page=N`, you should pass `/admin/users` to the `withPath` method: - use App\Models\User; +```php +use App\Models\User; - Route::get('/users', function () { - $users = User::paginate(15); +Route::get('/users', function () { + $users = User::paginate(15); - $users->withPath('/admin/users'); + $users->withPath('/admin/users'); - // ... - }); + // ... +}); +``` #### Appending Query String Values You may append to the query string of pagination links using the `appends` method. For example, to append `sort=votes` to each pagination link, you should make the following call to `appends`: - use App\Models\User; +```php +use App\Models\User; - Route::get('/users', function () { - $users = User::paginate(15); +Route::get('/users', function () { + $users = User::paginate(15); - $users->appends(['sort' => 'votes']); + $users->appends(['sort' => 'votes']); - // ... - }); + // ... +}); +``` You may use the `withQueryString` method if you would like to append all of the current request's query string values to the pagination links: - $users = User::paginate(15)->withQueryString(); +```php +$users = User::paginate(15)->withQueryString(); +``` #### Appending Hash Fragments If you need to append a "hash fragment" to URLs generated by the paginator, you may use the `fragment` method. For example, to append `#users` to the end of each pagination link, you should invoke the `fragment` method like so: - $users = User::paginate(15)->fragment('users'); +```php +$users = User::paginate(15)->fragment('users'); +``` ## Displaying Pagination Results @@ -239,35 +263,39 @@ When the paginator displays pagination links, the current page number is display The Laravel paginator classes implement the `Illuminate\Contracts\Support\Jsonable` Interface contract and expose the `toJson` method, so it's very easy to convert your pagination results to JSON. You may also convert a paginator instance to JSON by returning it from a route or controller action: - use App\Models\User; +```php +use App\Models\User; - Route::get('/users', function () { - return User::paginate(); - }); +Route::get('/users', function () { + return User::paginate(); +}); +``` The JSON from the paginator will include meta information such as `total`, `current_page`, `last_page`, and more. The result records are available via the `data` key in the JSON array. Here is an example of the JSON created by returning a paginator instance from a route: - { - "total": 50, - "per_page": 15, - "current_page": 1, - "last_page": 4, - "first_page_url": "http://laravel.app?page=1", - "last_page_url": "http://laravel.app?page=4", - "next_page_url": "http://laravel.app?page=2", - "prev_page_url": null, - "path": "http://laravel.app", - "from": 1, - "to": 15, - "data":[ - { - // Record... - }, - { - // Record... - } - ] - } +```json +{ + "total": 50, + "per_page": 15, + "current_page": 1, + "last_page": 4, + "first_page_url": "http://laravel.app?page=1", + "last_page_url": "http://laravel.app?page=4", + "next_page_url": "http://laravel.app?page=2", + "prev_page_url": null, + "path": "http://laravel.app", + "from": 1, + "to": 15, + "data":[ + { + // Record... + }, + { + // Record... + } + ] +} +``` ## Customizing the Pagination View @@ -291,41 +319,45 @@ This command will place the views in your application's `resources/views/vendor/ If you would like to designate a different file as the default pagination view, you may invoke the paginator's `defaultView` and `defaultSimpleView` methods within the `boot` method of your `App\Providers\AppServiceProvider` class: - ### Using Bootstrap Laravel includes pagination views built using [Bootstrap CSS](https://getbootstrap.com/). To use these views instead of the default Tailwind views, you may call the paginator's `useBootstrapFour` or `useBootstrapFive` methods within the `boot` method of your `App\Providers\AppServiceProvider` class: - use Illuminate\Pagination\Paginator; - - /** - * Bootstrap any application services. - */ - public function boot(): void - { - Paginator::useBootstrapFive(); - Paginator::useBootstrapFour(); - } +```php +use Illuminate\Pagination\Paginator; + +/** + * Bootstrap any application services. + */ +public function boot(): void +{ + Paginator::useBootstrapFive(); + Paginator::useBootstrapFour(); +} +``` ## Paginator / LengthAwarePaginator Instance Methods diff --git a/passport.md b/passport.md index 9aaa57cb908..26729dd3ec0 100644 --- a/passport.md +++ b/passport.md @@ -73,33 +73,37 @@ Additionally, this command will ask if you would like to use UUIDs as the primar After running the `install:api` command, add the `Laravel\Passport\HasApiTokens` trait to your `App\Models\User` model. This trait will provide a few helper methods to your model which allow you to inspect the authenticated user's token and scopes: - [ - 'web' => [ - 'driver' => 'session', - 'provider' => 'users', - ], +```php +'guards' => [ + 'web' => [ + 'driver' => 'session', + 'provider' => 'users', + ], - 'api' => [ - 'driver' => 'passport', - 'provider' => 'users', - ], + 'api' => [ + 'driver' => 'passport', + 'provider' => 'users', ], +], +``` ### Deploying Passport @@ -112,13 +116,15 @@ php artisan passport:keys If necessary, you may define the path where Passport's keys should be loaded from. You may use the `Passport::loadKeysFrom` method to accomplish this. Typically, this method should be called from the `boot` method of your application's `App\Providers\AppServiceProvider` class: - /** - * Bootstrap any application services. - */ - public function boot(): void - { - Passport::loadKeysFrom(__DIR__.'/../secrets/oauth'); - } +```php +/** + * Bootstrap any application services. + */ +public function boot(): void +{ + Passport::loadKeysFrom(__DIR__.'/../secrets/oauth'); +} +``` #### Loading Keys From the Environment @@ -154,9 +160,11 @@ When upgrading to a new major version of Passport, it's important that you caref If you would like your client's secrets to be hashed when stored in your database, you should call the `Passport::hashClientSecrets` method in the `boot` method of your `App\Providers\AppServiceProvider` class: - use Laravel\Passport\Passport; +```php +use Laravel\Passport\Passport; - Passport::hashClientSecrets(); +Passport::hashClientSecrets(); +``` Once enabled, all of your client secrets will only be displayable to the user immediately after they are created. Since the plain-text client secret value is never stored in the database, it is not possible to recover the secret's value if it is lost. @@ -165,15 +173,17 @@ Once enabled, all of your client secrets will only be displayable to the user im By default, Passport issues long-lived access tokens that expire after one year. If you would like to configure a longer / shorter token lifetime, you may use the `tokensExpireIn`, `refreshTokensExpireIn`, and `personalAccessTokensExpireIn` methods. These methods should be called from the `boot` method of your application's `App\Providers\AppServiceProvider` class: - /** - * Bootstrap any application services. - */ - public function boot(): void - { - Passport::tokensExpireIn(now()->addDays(15)); - Passport::refreshTokensExpireIn(now()->addDays(30)); - Passport::personalAccessTokensExpireIn(now()->addMonths(6)); - } +```php +/** + * Bootstrap any application services. + */ +public function boot(): void +{ + Passport::tokensExpireIn(now()->addDays(15)); + Passport::refreshTokensExpireIn(now()->addDays(30)); + Passport::personalAccessTokensExpireIn(now()->addMonths(6)); +} +``` > [!WARNING] > The `expires_at` columns on Passport's database tables are read-only and for display purposes only. When issuing tokens, Passport stores the expiration information within the signed and encrypted tokens. If you need to invalidate a token you should [revoke it](#revoking-tokens). @@ -183,57 +193,65 @@ By default, Passport issues long-lived access tokens that expire after one year. You are free to extend the models used internally by Passport by defining your own model and extending the corresponding Passport model: - use Laravel\Passport\Client as PassportClient; +```php +use Laravel\Passport\Client as PassportClient; - class Client extends PassportClient - { - // ... - } +class Client extends PassportClient +{ + // ... +} +``` After defining your model, you may instruct Passport to use your custom model via the `Laravel\Passport\Passport` class. Typically, you should inform Passport about your custom models in the `boot` method of your application's `App\Providers\AppServiceProvider` class: - use App\Models\Passport\AuthCode; - use App\Models\Passport\Client; - use App\Models\Passport\PersonalAccessClient; - use App\Models\Passport\RefreshToken; - use App\Models\Passport\Token; - - /** - * Bootstrap any application services. - */ - public function boot(): void - { - Passport::useTokenModel(Token::class); - Passport::useRefreshTokenModel(RefreshToken::class); - Passport::useAuthCodeModel(AuthCode::class); - Passport::useClientModel(Client::class); - Passport::usePersonalAccessClientModel(PersonalAccessClient::class); - } +```php +use App\Models\Passport\AuthCode; +use App\Models\Passport\Client; +use App\Models\Passport\PersonalAccessClient; +use App\Models\Passport\RefreshToken; +use App\Models\Passport\Token; + +/** + * Bootstrap any application services. + */ +public function boot(): void +{ + Passport::useTokenModel(Token::class); + Passport::useRefreshTokenModel(RefreshToken::class); + Passport::useAuthCodeModel(AuthCode::class); + Passport::useClientModel(Client::class); + Passport::usePersonalAccessClientModel(PersonalAccessClient::class); +} +``` ### Overriding Routes Sometimes you may wish to customize the routes defined by Passport. To achieve this, you first need to ignore the routes registered by Passport by adding `Passport::ignoreRoutes` to the `register` method of your application's `AppServiceProvider`: - use Laravel\Passport\Passport; +```php +use Laravel\Passport\Passport; - /** - * Register any application services. - */ - public function register(): void - { - Passport::ignoreRoutes(); - } +/** + * Register any application services. + */ +public function register(): void +{ + Passport::ignoreRoutes(); +} +``` Then, you may copy the routes defined by Passport in [its routes file](https://github.com/laravel/passport/blob/11.x/routes/web.php) to your application's `routes/web.php` file and modify them to your liking: - Route::group([ - 'as' => 'passport.', - 'prefix' => config('passport.path', 'oauth'), - 'namespace' => '\Laravel\Passport\Http\Controllers', - ], function () { - // Passport routes... - }); +```php +Route::group([ + 'as' => 'passport.', + 'prefix' => config('passport.path', 'oauth'), + 'namespace' => '\Laravel\Passport\Http\Controllers', +], function () { + // Passport routes... +}); +``` ## Issuing Access Tokens @@ -345,23 +363,25 @@ axios.delete('/oauth/clients/' + clientId) Once a client has been created, developers may use their client ID and secret to request an authorization code and access token from your application. First, the consuming application should make a redirect request to your application's `/oauth/authorize` route like so: - use Illuminate\Http\Request; - use Illuminate\Support\Str; +```php +use Illuminate\Http\Request; +use Illuminate\Support\Str; - Route::get('/redirect', function (Request $request) { - $request->session()->put('state', $state = Str::random(40)); +Route::get('/redirect', function (Request $request) { + $request->session()->put('state', $state = Str::random(40)); - $query = http_build_query([ - 'client_id' => 'client-id', - 'redirect_uri' => 'http://third-party-app.com/callback', - 'response_type' => 'code', - 'scope' => '', - 'state' => $state, - // 'prompt' => '', // "none", "consent", or "login" - ]); + $query = http_build_query([ + 'client_id' => 'client-id', + 'redirect_uri' => 'http://third-party-app.com/callback', + 'response_type' => 'code', + 'scope' => '', + 'state' => $state, + // 'prompt' => '', // "none", "consent", or "login" + ]); - return redirect('http://passport-app.test/oauth/authorize?'.$query); - }); + return redirect('http://passport-app.test/oauth/authorize?'.$query); +}); +``` The `prompt` parameter may be used to specify the authentication behavior of the Passport application. @@ -385,50 +405,54 @@ php artisan vendor:publish --tag=passport-views Sometimes you may wish to skip the authorization prompt, such as when authorizing a first-party client. You may accomplish this by [extending the `Client` model](#overriding-default-models) and defining a `skipsAuthorization` method. If `skipsAuthorization` returns `true` the client will be approved and the user will be redirected back to the `redirect_uri` immediately, unless the consuming application has explicitly set the `prompt` parameter when redirecting for authorization: - firstParty(); - } + return $this->firstParty(); } +} +``` #### Converting Authorization Codes to Access Tokens If the user approves the authorization request, they will be redirected back to the consuming application. The consumer should first verify the `state` parameter against the value that was stored prior to the redirect. If the state parameter matches then the consumer should issue a `POST` request to your application to request an access token. The request should include the authorization code that was issued by your application when the user approved the authorization request: - use Illuminate\Http\Request; - use Illuminate\Support\Facades\Http; +```php +use Illuminate\Http\Request; +use Illuminate\Support\Facades\Http; - Route::get('/callback', function (Request $request) { - $state = $request->session()->pull('state'); +Route::get('/callback', function (Request $request) { + $state = $request->session()->pull('state'); - throw_unless( - strlen($state) > 0 && $state === $request->state, - InvalidArgumentException::class, - 'Invalid state value.' - ); + throw_unless( + strlen($state) > 0 && $state === $request->state, + InvalidArgumentException::class, + 'Invalid state value.' + ); - $response = Http::asForm()->post('http://passport-app.test/oauth/token', [ - 'grant_type' => 'authorization_code', - 'client_id' => 'client-id', - 'client_secret' => 'client-secret', - 'redirect_uri' => 'http://third-party-app.com/callback', - 'code' => $request->code, - ]); + $response = Http::asForm()->post('http://passport-app.test/oauth/token', [ + 'grant_type' => 'authorization_code', + 'client_id' => 'client-id', + 'client_secret' => 'client-secret', + 'redirect_uri' => 'http://third-party-app.com/callback', + 'code' => $request->code, + ]); - return $response->json(); - }); + return $response->json(); +}); +``` This `/oauth/token` route will return a JSON response containing `access_token`, `refresh_token`, and `expires_in` attributes. The `expires_in` attribute contains the number of seconds until the access token expires. @@ -466,17 +490,19 @@ axios.delete('/oauth/tokens/' + tokenId); If your application issues short-lived access tokens, users will need to refresh their access tokens via the refresh token that was provided to them when the access token was issued: - use Illuminate\Support\Facades\Http; +```php +use Illuminate\Support\Facades\Http; - $response = Http::asForm()->post('http://passport-app.test/oauth/token', [ - 'grant_type' => 'refresh_token', - 'refresh_token' => 'the-refresh-token', - 'client_id' => 'client-id', - 'client_secret' => 'client-secret', - 'scope' => '', - ]); +$response = Http::asForm()->post('http://passport-app.test/oauth/token', [ + 'grant_type' => 'refresh_token', + 'refresh_token' => 'the-refresh-token', + 'client_id' => 'client-id', + 'client_secret' => 'client-secret', + 'scope' => '', +]); - return $response->json(); +return $response->json(); +``` This `/oauth/token` route will return a JSON response containing `access_token`, `refresh_token`, and `expires_in` attributes. The `expires_in` attribute contains the number of seconds until the access token expires. @@ -485,17 +511,19 @@ This `/oauth/token` route will return a JSON response containing `access_token`, You may revoke a token by using the `revokeAccessToken` method on the `Laravel\Passport\TokenRepository`. You may revoke a token's refresh tokens using the `revokeRefreshTokensByAccessTokenId` method on the `Laravel\Passport\RefreshTokenRepository`. These classes may be resolved using Laravel's [service container](/docs/{{version}}/container): - use Laravel\Passport\TokenRepository; - use Laravel\Passport\RefreshTokenRepository; +```php +use Laravel\Passport\TokenRepository; +use Laravel\Passport\RefreshTokenRepository; - $tokenRepository = app(TokenRepository::class); - $refreshTokenRepository = app(RefreshTokenRepository::class); +$tokenRepository = app(TokenRepository::class); +$refreshTokenRepository = app(RefreshTokenRepository::class); - // Revoke an access token... - $tokenRepository->revokeAccessToken($tokenId); +// Revoke an access token... +$tokenRepository->revokeAccessToken($tokenId); - // Revoke all of the token's refresh tokens... - $refreshTokenRepository->revokeRefreshTokensByAccessTokenId($tokenId); +// Revoke all of the token's refresh tokens... +$refreshTokenRepository->revokeRefreshTokensByAccessTokenId($tokenId); +``` ### Purging Tokens @@ -518,9 +546,11 @@ php artisan passport:purge --expired You may also configure a [scheduled job](/docs/{{version}}/scheduling) in your application's `routes/console.php` file to automatically prune your tokens on a schedule: - use Illuminate\Support\Facades\Schedule; +```php +use Illuminate\Support\Facades\Schedule; - Schedule::command('passport:purge')->hourly(); +Schedule::command('passport:purge')->hourly(); +``` ## Authorization Code Grant With PKCE @@ -548,42 +578,46 @@ The code verifier should be a random string of between 43 and 128 characters con The code challenge should be a Base64 encoded string with URL and filename-safe characters. The trailing `'='` characters should be removed and no line breaks, whitespace, or other additional characters should be present. - $encoded = base64_encode(hash('sha256', $code_verifier, true)); +```php +$encoded = base64_encode(hash('sha256', $code_verifier, true)); - $codeChallenge = strtr(rtrim($encoded, '='), '+/', '-_'); +$codeChallenge = strtr(rtrim($encoded, '='), '+/', '-_'); +``` #### Redirecting for Authorization Once a client has been created, you may use the client ID and the generated code verifier and code challenge to request an authorization code and access token from your application. First, the consuming application should make a redirect request to your application's `/oauth/authorize` route: - use Illuminate\Http\Request; - use Illuminate\Support\Str; +```php +use Illuminate\Http\Request; +use Illuminate\Support\Str; - Route::get('/redirect', function (Request $request) { - $request->session()->put('state', $state = Str::random(40)); +Route::get('/redirect', function (Request $request) { + $request->session()->put('state', $state = Str::random(40)); - $request->session()->put( - 'code_verifier', $code_verifier = Str::random(128) - ); + $request->session()->put( + 'code_verifier', $code_verifier = Str::random(128) + ); - $codeChallenge = strtr(rtrim( - base64_encode(hash('sha256', $code_verifier, true)) - , '='), '+/', '-_'); + $codeChallenge = strtr(rtrim( + base64_encode(hash('sha256', $code_verifier, true)) + , '='), '+/', '-_'); - $query = http_build_query([ - 'client_id' => 'client-id', - 'redirect_uri' => 'http://third-party-app.com/callback', - 'response_type' => 'code', - 'scope' => '', - 'state' => $state, - 'code_challenge' => $codeChallenge, - 'code_challenge_method' => 'S256', - // 'prompt' => '', // "none", "consent", or "login" - ]); + $query = http_build_query([ + 'client_id' => 'client-id', + 'redirect_uri' => 'http://third-party-app.com/callback', + 'response_type' => 'code', + 'scope' => '', + 'state' => $state, + 'code_challenge' => $codeChallenge, + 'code_challenge_method' => 'S256', + // 'prompt' => '', // "none", "consent", or "login" + ]); - return redirect('http://passport-app.test/oauth/authorize?'.$query); - }); + return redirect('http://passport-app.test/oauth/authorize?'.$query); +}); +``` #### Converting Authorization Codes to Access Tokens @@ -592,29 +626,31 @@ If the user approves the authorization request, they will be redirected back to If the state parameter matches, the consumer should issue a `POST` request to your application to request an access token. The request should include the authorization code that was issued by your application when the user approved the authorization request along with the originally generated code verifier: - use Illuminate\Http\Request; - use Illuminate\Support\Facades\Http; +```php +use Illuminate\Http\Request; +use Illuminate\Support\Facades\Http; - Route::get('/callback', function (Request $request) { - $state = $request->session()->pull('state'); +Route::get('/callback', function (Request $request) { + $state = $request->session()->pull('state'); - $codeVerifier = $request->session()->pull('code_verifier'); + $codeVerifier = $request->session()->pull('code_verifier'); - throw_unless( - strlen($state) > 0 && $state === $request->state, - InvalidArgumentException::class - ); + throw_unless( + strlen($state) > 0 && $state === $request->state, + InvalidArgumentException::class + ); - $response = Http::asForm()->post('http://passport-app.test/oauth/token', [ - 'grant_type' => 'authorization_code', - 'client_id' => 'client-id', - 'redirect_uri' => 'http://third-party-app.com/callback', - 'code_verifier' => $codeVerifier, - 'code' => $request->code, - ]); + $response = Http::asForm()->post('http://passport-app.test/oauth/token', [ + 'grant_type' => 'authorization_code', + 'client_id' => 'client-id', + 'redirect_uri' => 'http://third-party-app.com/callback', + 'code_verifier' => $codeVerifier, + 'code' => $request->code, + ]); - return $response->json(); - }); + return $response->json(); +}); +``` ## Password Grant Tokens @@ -626,13 +662,15 @@ The OAuth2 password grant allows your other first-party clients, such as a mobil To enable the password grant, call the `enablePasswordGrant` method in the `boot` method of your application's `App\Providers\AppServiceProvider` class: - /** - * Bootstrap any application services. - */ - public function boot(): void - { - Passport::enablePasswordGrant(); - } +```php +/** + * Bootstrap any application services. + */ +public function boot(): void +{ + Passport::enablePasswordGrant(); +} +``` ### Creating a Password Grant Client @@ -648,18 +686,20 @@ php artisan passport:client --password Once you have created a password grant client, you may request an access token by issuing a `POST` request to the `/oauth/token` route with the user's email address and password. Remember, this route is already registered by Passport so there is no need to define it manually. If the request is successful, you will receive an `access_token` and `refresh_token` in the JSON response from the server: - use Illuminate\Support\Facades\Http; +```php +use Illuminate\Support\Facades\Http; - $response = Http::asForm()->post('http://passport-app.test/oauth/token', [ - 'grant_type' => 'password', - 'client_id' => 'client-id', - 'client_secret' => 'client-secret', - 'username' => 'taylor@laravel.com', - 'password' => 'my-password', - 'scope' => '', - ]); +$response = Http::asForm()->post('http://passport-app.test/oauth/token', [ + 'grant_type' => 'password', + 'client_id' => 'client-id', + 'client_secret' => 'client-secret', + 'username' => 'taylor@laravel.com', + 'password' => 'my-password', + 'scope' => '', +]); - return $response->json(); +return $response->json(); +``` > [!NOTE] > Remember, access tokens are long-lived by default. However, you are free to [configure your maximum access token lifetime](#configuration) if needed. @@ -669,16 +709,18 @@ Once you have created a password grant client, you may request an access token b When using the password grant or client credentials grant, you may wish to authorize the token for all of the scopes supported by your application. You can do this by requesting the `*` scope. If you request the `*` scope, the `can` method on the token instance will always return `true`. This scope may only be assigned to a token that is issued using the `password` or `client_credentials` grant: - use Illuminate\Support\Facades\Http; - - $response = Http::asForm()->post('http://passport-app.test/oauth/token', [ - 'grant_type' => 'password', - 'client_id' => 'client-id', - 'client_secret' => 'client-secret', - 'username' => 'taylor@laravel.com', - 'password' => 'my-password', - 'scope' => '*', - ]); +```php +use Illuminate\Support\Facades\Http; + +$response = Http::asForm()->post('http://passport-app.test/oauth/token', [ + 'grant_type' => 'password', + 'client_id' => 'client-id', + 'client_secret' => 'client-secret', + 'username' => 'taylor@laravel.com', + 'password' => 'my-password', + 'scope' => '*', +]); +``` ### Customizing the User Provider @@ -690,53 +732,57 @@ If your application uses more than one [authentication user provider](/docs/{{ve When authenticating using the password grant, Passport will use the `email` attribute of your authenticatable model as the "username". However, you may customize this behavior by defining a `findForPassport` method on your model: - where('username', $username)->first(); - } + return $this->where('username', $username)->first(); } +} +``` ### Customizing the Password Validation When authenticating using the password grant, Passport will use the `password` attribute of your model to validate the given password. If your model does not have a `password` attribute or you wish to customize the password validation logic, you can define a `validateForPassportPasswordGrant` method on your model: - password); - } + return Hash::check($password, $this->password); } +} +``` ## Implicit Grant Tokens @@ -746,32 +792,36 @@ When authenticating using the password grant, Passport will use the `password` a The implicit grant is similar to the authorization code grant; however, the token is returned to the client without exchanging an authorization code. This grant is most commonly used for JavaScript or mobile applications where the client credentials can't be securely stored. To enable the grant, call the `enableImplicitGrant` method in the `boot` method of your application's `App\Providers\AppServiceProvider` class: - /** - * Bootstrap any application services. - */ - public function boot(): void - { - Passport::enableImplicitGrant(); - } +```php +/** + * Bootstrap any application services. + */ +public function boot(): void +{ + Passport::enableImplicitGrant(); +} +``` Once the grant has been enabled, developers may use their client ID to request an access token from your application. The consuming application should make a redirect request to your application's `/oauth/authorize` route like so: - use Illuminate\Http\Request; +```php +use Illuminate\Http\Request; - Route::get('/redirect', function (Request $request) { - $request->session()->put('state', $state = Str::random(40)); +Route::get('/redirect', function (Request $request) { + $request->session()->put('state', $state = Str::random(40)); - $query = http_build_query([ - 'client_id' => 'client-id', - 'redirect_uri' => 'http://third-party-app.com/callback', - 'response_type' => 'token', - 'scope' => '', - 'state' => $state, - // 'prompt' => '', // "none", "consent", or "login" - ]); + $query = http_build_query([ + 'client_id' => 'client-id', + 'redirect_uri' => 'http://third-party-app.com/callback', + 'response_type' => 'token', + 'scope' => '', + 'state' => $state, + // 'prompt' => '', // "none", "consent", or "login" + ]); - return redirect('http://passport-app.test/oauth/authorize?'.$query); - }); + return redirect('http://passport-app.test/oauth/authorize?'.$query); +}); +``` > [!NOTE] > Remember, the `/oauth/authorize` route is already defined by Passport. You do not need to manually define this route. @@ -789,41 +839,49 @@ php artisan passport:client --client Next, to use this grant type, register a middleware alias for the `CheckClientCredentials` middleware. You may define middleware aliases in your application's `bootstrap/app.php` file: - use Laravel\Passport\Http\Middleware\CheckClientCredentials; +```php +use Laravel\Passport\Http\Middleware\CheckClientCredentials; - ->withMiddleware(function (Middleware $middleware) { - $middleware->alias([ - 'client' => CheckClientCredentials::class - ]); - }) +->withMiddleware(function (Middleware $middleware) { + $middleware->alias([ + 'client' => CheckClientCredentials::class + ]); +}) +``` Then, attach the middleware to a route: - Route::get('/orders', function (Request $request) { - ... - })->middleware('client'); +```php +Route::get('/orders', function (Request $request) { + ... +})->middleware('client'); +``` To restrict access to the route to specific scopes, you may provide a comma-delimited list of the required scopes when attaching the `client` middleware to the route: - Route::get('/orders', function (Request $request) { - ... - })->middleware('client:check-status,your-scope'); +```php +Route::get('/orders', function (Request $request) { + ... +})->middleware('client:check-status,your-scope'); +``` ### Retrieving Tokens To retrieve a token using this grant type, make a request to the `oauth/token` endpoint: - use Illuminate\Support\Facades\Http; +```php +use Illuminate\Support\Facades\Http; - $response = Http::asForm()->post('http://passport-app.test/oauth/token', [ - 'grant_type' => 'client_credentials', - 'client_id' => 'client-id', - 'client_secret' => 'client-secret', - 'scope' => 'your-scope', - ]); +$response = Http::asForm()->post('http://passport-app.test/oauth/token', [ + 'grant_type' => 'client_credentials', + 'client_id' => 'client-id', + 'client_secret' => 'client-secret', + 'scope' => 'your-scope', +]); - return $response->json()['access_token']; +return $response->json()['access_token']; +``` ## Personal Access Tokens @@ -854,15 +912,17 @@ PASSPORT_PERSONAL_ACCESS_CLIENT_SECRET="unhashed-client-secret-value" Once you have created a personal access client, you may issue tokens for a given user using the `createToken` method on the `App\Models\User` model instance. The `createToken` method accepts the name of the token as its first argument and an optional array of [scopes](#token-scopes) as its second argument: - use App\Models\User; +```php +use App\Models\User; - $user = User::find(1); +$user = User::find(1); - // Creating a token without scopes... - $token = $user->createToken('Token Name')->accessToken; +// Creating a token without scopes... +$token = $user->createToken('Token Name')->accessToken; - // Creating a token with scopes... - $token = $user->createToken('My Token', ['place-orders'])->accessToken; +// Creating a token with scopes... +$token = $user->createToken('My Token', ['place-orders'])->accessToken; +``` #### JSON API @@ -932,9 +992,11 @@ axios.delete('/oauth/personal-access-tokens/' + tokenId); Passport includes an [authentication guard](/docs/{{version}}/authentication#adding-custom-guards) that will validate access tokens on incoming requests. Once you have configured the `api` guard to use the `passport` driver, you only need to specify the `auth:api` middleware on any routes that should require a valid access token: - Route::get('/user', function () { - // ... - })->middleware('auth:api'); +```php +Route::get('/user', function () { + // ... +})->middleware('auth:api'); +``` > [!WARNING] > If you are using the [client credentials grant](#client-credentials-grant-tokens), you should use [the `client` middleware](#client-credentials-grant-tokens) to protect your routes instead of the `auth:api` middleware. @@ -944,21 +1006,25 @@ Passport includes an [authentication guard](/docs/{{version}}/authentication#add If your application authenticates different types of users that perhaps use entirely different Eloquent models, you will likely need to define a guard configuration for each user provider type in your application. This allows you to protect requests intended for specific user providers. For example, given the following guard configuration the `config/auth.php` configuration file: - 'api' => [ - 'driver' => 'passport', - 'provider' => 'users', - ], +```php +'api' => [ + 'driver' => 'passport', + 'provider' => 'users', +], - 'api-customers' => [ - 'driver' => 'passport', - 'provider' => 'customers', - ], +'api-customers' => [ + 'driver' => 'passport', + 'provider' => 'customers', +], +``` The following route will utilize the `api-customers` guard, which uses the `customers` user provider, to authenticate incoming requests: - Route::get('/customer', function () { - // ... - })->middleware('auth:api-customers'); +```php +Route::get('/customer', function () { + // ... +})->middleware('auth:api-customers'); +``` > [!NOTE] > For more information on using multiple user providers with Passport, please consult the [password grant documentation](#customizing-the-user-provider). @@ -968,14 +1034,16 @@ The following route will utilize the `api-customers` guard, which uses the `cust When calling routes that are protected by Passport, your application's API consumers should specify their access token as a `Bearer` token in the `Authorization` header of their request. For example, when using the Guzzle HTTP library: - use Illuminate\Support\Facades\Http; +```php +use Illuminate\Support\Facades\Http; - $response = Http::withHeaders([ - 'Accept' => 'application/json', - 'Authorization' => 'Bearer '.$accessToken, - ])->get('https://passport-app.test/api/user'); +$response = Http::withHeaders([ + 'Accept' => 'application/json', + 'Authorization' => 'Bearer '.$accessToken, +])->get('https://passport-app.test/api/user'); - return $response->json(); +return $response->json(); +``` ## Token Scopes @@ -987,33 +1055,37 @@ Scopes allow your API clients to request a specific set of permissions when requ You may define your API's scopes using the `Passport::tokensCan` method in the `boot` method of your application's `App\Providers\AppServiceProvider` class. The `tokensCan` method accepts an array of scope names and scope descriptions. The scope description may be anything you wish and will be displayed to users on the authorization approval screen: - /** - * Bootstrap any application services. - */ - public function boot(): void - { - Passport::tokensCan([ - 'place-orders' => 'Place orders', - 'check-status' => 'Check order status', - ]); - } +```php +/** + * Bootstrap any application services. + */ +public function boot(): void +{ + Passport::tokensCan([ + 'place-orders' => 'Place orders', + 'check-status' => 'Check order status', + ]); +} +``` ### Default Scope If a client does not request any specific scopes, you may configure your Passport server to attach default scope(s) to the token using the `setDefaultScope` method. Typically, you should call this method from the `boot` method of your application's `App\Providers\AppServiceProvider` class: - use Laravel\Passport\Passport; +```php +use Laravel\Passport\Passport; - Passport::tokensCan([ - 'place-orders' => 'Place orders', - 'check-status' => 'Check order status', - ]); +Passport::tokensCan([ + 'place-orders' => 'Place orders', + 'check-status' => 'Check order status', +]); - Passport::setDefaultScope([ - 'check-status', - 'place-orders', - ]); +Passport::setDefaultScope([ + 'check-status', + 'place-orders', +]); +``` > [!NOTE] > Passport's default scopes do not apply to personal access tokens that are generated by the user. @@ -1026,90 +1098,110 @@ If a client does not request any specific scopes, you may configure your Passpor When requesting an access token using the authorization code grant, consumers should specify their desired scopes as the `scope` query string parameter. The `scope` parameter should be a space-delimited list of scopes: - Route::get('/redirect', function () { - $query = http_build_query([ - 'client_id' => 'client-id', - 'redirect_uri' => 'http://example.com/callback', - 'response_type' => 'code', - 'scope' => 'place-orders check-status', - ]); +```php +Route::get('/redirect', function () { + $query = http_build_query([ + 'client_id' => 'client-id', + 'redirect_uri' => 'http://example.com/callback', + 'response_type' => 'code', + 'scope' => 'place-orders check-status', + ]); - return redirect('http://passport-app.test/oauth/authorize?'.$query); - }); + return redirect('http://passport-app.test/oauth/authorize?'.$query); +}); +``` #### When Issuing Personal Access Tokens If you are issuing personal access tokens using the `App\Models\User` model's `createToken` method, you may pass the array of desired scopes as the second argument to the method: - $token = $user->createToken('My Token', ['place-orders'])->accessToken; +```php +$token = $user->createToken('My Token', ['place-orders'])->accessToken; +``` ### Checking Scopes Passport includes two middleware that may be used to verify that an incoming request is authenticated with a token that has been granted a given scope. To get started, define the following middleware aliases in your application's `bootstrap/app.php` file: - use Laravel\Passport\Http\Middleware\CheckForAnyScope; - use Laravel\Passport\Http\Middleware\CheckScopes; +```php +use Laravel\Passport\Http\Middleware\CheckForAnyScope; +use Laravel\Passport\Http\Middleware\CheckScopes; - ->withMiddleware(function (Middleware $middleware) { - $middleware->alias([ - 'scopes' => CheckScopes::class, - 'scope' => CheckForAnyScope::class, - ]); - }) +->withMiddleware(function (Middleware $middleware) { + $middleware->alias([ + 'scopes' => CheckScopes::class, + 'scope' => CheckForAnyScope::class, + ]); +}) +``` #### Check For All Scopes The `scopes` middleware may be assigned to a route to verify that the incoming request's access token has all of the listed scopes: - Route::get('/orders', function () { - // Access token has both "check-status" and "place-orders" scopes... - })->middleware(['auth:api', 'scopes:check-status,place-orders']); +```php +Route::get('/orders', function () { + // Access token has both "check-status" and "place-orders" scopes... +})->middleware(['auth:api', 'scopes:check-status,place-orders']); +``` #### Check for Any Scopes The `scope` middleware may be assigned to a route to verify that the incoming request's access token has *at least one* of the listed scopes: - Route::get('/orders', function () { - // Access token has either "check-status" or "place-orders" scope... - })->middleware(['auth:api', 'scope:check-status,place-orders']); +```php +Route::get('/orders', function () { + // Access token has either "check-status" or "place-orders" scope... +})->middleware(['auth:api', 'scope:check-status,place-orders']); +``` #### Checking Scopes on a Token Instance Once an access token authenticated request has entered your application, you may still check if the token has a given scope using the `tokenCan` method on the authenticated `App\Models\User` instance: - use Illuminate\Http\Request; +```php +use Illuminate\Http\Request; - Route::get('/orders', function (Request $request) { - if ($request->user()->tokenCan('place-orders')) { - // ... - } - }); +Route::get('/orders', function (Request $request) { + if ($request->user()->tokenCan('place-orders')) { + // ... + } +}); +``` #### Additional Scope Methods The `scopeIds` method will return an array of all defined IDs / names: - use Laravel\Passport\Passport; +```php +use Laravel\Passport\Passport; - Passport::scopeIds(); +Passport::scopeIds(); +``` The `scopes` method will return an array of all defined scopes as instances of `Laravel\Passport\Scope`: - Passport::scopes(); +```php +Passport::scopes(); +``` The `scopesFor` method will return an array of `Laravel\Passport\Scope` instances matching the given IDs / names: - Passport::scopesFor(['place-orders', 'check-status']); +```php +Passport::scopesFor(['place-orders', 'check-status']); +``` You may determine if a given scope has been defined using the `hasScope` method: - Passport::hasScope('place-orders'); +```php +Passport::hasScope('place-orders'); +``` ## Consuming Your API With JavaScript @@ -1118,36 +1210,42 @@ When building an API, it can be extremely useful to be able to consume your own Typically, if you want to consume your API from your JavaScript application, you would need to manually send an access token to the application and pass it with each request to your application. However, Passport includes a middleware that can handle this for you. All you need to do is append the `CreateFreshApiToken` middleware to the `web` middleware group in your application's `bootstrap/app.php` file: - use Laravel\Passport\Http\Middleware\CreateFreshApiToken; +```php +use Laravel\Passport\Http\Middleware\CreateFreshApiToken; - ->withMiddleware(function (Middleware $middleware) { - $middleware->web(append: [ - CreateFreshApiToken::class, - ]); - }) +->withMiddleware(function (Middleware $middleware) { + $middleware->web(append: [ + CreateFreshApiToken::class, + ]); +}) +``` > [!WARNING] > You should ensure that the `CreateFreshApiToken` middleware is the last middleware listed in your middleware stack. This middleware will attach a `laravel_token` cookie to your outgoing responses. This cookie contains an encrypted JWT that Passport will use to authenticate API requests from your JavaScript application. The JWT has a lifetime equal to your `session.lifetime` configuration value. Now, since the browser will automatically send the cookie with all subsequent requests, you may make requests to your application's API without explicitly passing an access token: - axios.get('/api/user') - .then(response => { - console.log(response.data); - }); +```js +axios.get('/api/user') + .then(response => { + console.log(response.data); + }); +``` #### Customizing the Cookie Name If needed, you can customize the `laravel_token` cookie's name using the `Passport::cookie` method. Typically, this method should be called from the `boot` method of your application's `App\Providers\AppServiceProvider` class: - /** - * Bootstrap any application services. - */ - public function boot(): void - { - Passport::cookie('custom_name'); - } +```php +/** + * Bootstrap any application services. + */ +public function boot(): void +{ + Passport::cookie('custom_name'); +} +``` #### CSRF Protection diff --git a/passwords.md b/passwords.md index 21a5833e329..e9682f30634 100644 --- a/passwords.md +++ b/passwords.md @@ -52,9 +52,11 @@ To properly implement support for allowing users to reset their passwords, we wi First, we will define the routes that are needed to request password reset links. To get started, we will define a route that returns a view with the password reset link request form: - Route::get('/forgot-password', function () { - return view('auth.forgot-password'); - })->middleware('guest')->name('password.request'); +```php +Route::get('/forgot-password', function () { + return view('auth.forgot-password'); +})->middleware('guest')->name('password.request'); +``` The view that is returned by this route should have a form containing an `email` field, which will allow the user to request a password reset link for a given email address. @@ -63,20 +65,22 @@ The view that is returned by this route should have a form containing an `email` Next, we will define a route that handles the form submission request from the "forgot password" view. This route will be responsible for validating the email address and sending the password reset request to the corresponding user: - use Illuminate\Http\Request; - use Illuminate\Support\Facades\Password; +```php +use Illuminate\Http\Request; +use Illuminate\Support\Facades\Password; - Route::post('/forgot-password', function (Request $request) { - $request->validate(['email' => 'required|email']); +Route::post('/forgot-password', function (Request $request) { + $request->validate(['email' => 'required|email']); - $status = Password::sendResetLink( - $request->only('email') - ); + $status = Password::sendResetLink( + $request->only('email') + ); - return $status === Password::ResetLinkSent - ? back()->with(['status' => __($status)]) - : back()->withErrors(['email' => __($status)]); - })->middleware('guest')->name('password.email'); + return $status === Password::ResetLinkSent + ? back()->with(['status' => __($status)]) + : back()->withErrors(['email' => __($status)]); +})->middleware('guest')->name('password.email'); +``` Before moving on, let's examine this route in more detail. First, the request's `email` attribute is validated. Next, we will use Laravel's built-in "password broker" (via the `Password` facade) to send a password reset link to the user. The password broker will take care of retrieving the user by the given field (in this case, the email address) and sending the user a password reset link via Laravel's built-in [notification system](/docs/{{version}}/notifications). @@ -98,9 +102,11 @@ You may be wondering how Laravel knows how to retrieve the user record from your Next, we will define the routes necessary to actually reset the password once the user clicks on the password reset link that has been emailed to them and provides a new password. First, let's define the route that will display the reset password form that is displayed when the user clicks the reset password link. This route will receive a `token` parameter that we will use later to verify the password reset request: - Route::get('/reset-password/{token}', function (string $token) { - return view('auth.reset-password', ['token' => $token]); - })->middleware('guest')->name('password.reset'); +```php +Route::get('/reset-password/{token}', function (string $token) { + return view('auth.reset-password', ['token' => $token]); +})->middleware('guest')->name('password.reset'); +``` The view that is returned by this route should display a form containing an `email` field, a `password` field, a `password_confirmation` field, and a hidden `token` field, which should contain the value of the secret `$token` received by our route. @@ -109,37 +115,39 @@ The view that is returned by this route should display a form containing an `ema Of course, we need to define a route to actually handle the password reset form submission. This route will be responsible for validating the incoming request and updating the user's password in the database: - use App\Models\User; - use Illuminate\Auth\Events\PasswordReset; - use Illuminate\Http\Request; - use Illuminate\Support\Facades\Hash; - use Illuminate\Support\Facades\Password; - use Illuminate\Support\Str; - - Route::post('/reset-password', function (Request $request) { - $request->validate([ - 'token' => 'required', - 'email' => 'required|email', - 'password' => 'required|min:8|confirmed', - ]); - - $status = Password::reset( - $request->only('email', 'password', 'password_confirmation', 'token'), - function (User $user, string $password) { - $user->forceFill([ - 'password' => Hash::make($password) - ])->setRememberToken(Str::random(60)); - - $user->save(); - - event(new PasswordReset($user)); - } - ); - - return $status === Password::PasswordReset - ? redirect()->route('login')->with('status', __($status)) - : back()->withErrors(['email' => [__($status)]]); - })->middleware('guest')->name('password.update'); +```php +use App\Models\User; +use Illuminate\Auth\Events\PasswordReset; +use Illuminate\Http\Request; +use Illuminate\Support\Facades\Hash; +use Illuminate\Support\Facades\Password; +use Illuminate\Support\Str; + +Route::post('/reset-password', function (Request $request) { + $request->validate([ + 'token' => 'required', + 'email' => 'required|email', + 'password' => 'required|min:8|confirmed', + ]); + + $status = Password::reset( + $request->only('email', 'password', 'password_confirmation', 'token'), + function (User $user, string $password) { + $user->forceFill([ + 'password' => Hash::make($password) + ])->setRememberToken(Str::random(60)); + + $user->save(); + + event(new PasswordReset($user)); + } + ); + + return $status === Password::PasswordReset + ? redirect()->route('login')->with('status', __($status)) + : back()->withErrors(['email' => [__($status)]]); +})->middleware('guest')->name('password.update'); +``` Before moving on, let's examine this route in more detail. First, the request's `token`, `email`, and `password` attributes are validated. Next, we will use Laravel's built-in "password broker" (via the `Password` facade) to validate the password reset request credentials. @@ -160,9 +168,11 @@ php artisan auth:clear-resets If you would like to automate this process, consider adding the command to your application's [scheduler](/docs/{{version}}/scheduling): - use Illuminate\Support\Facades\Schedule; +```php +use Illuminate\Support\Facades\Schedule; - Schedule::command('auth:clear-resets')->everyFifteenMinutes(); +Schedule::command('auth:clear-resets')->everyFifteenMinutes(); +``` ## Customization @@ -172,34 +182,38 @@ If you would like to automate this process, consider adding the command to your You may customize the password reset link URL using the `createUrlUsing` method provided by the `ResetPassword` notification class. This method accepts a closure which receives the user instance that is receiving the notification as well as the password reset link token. Typically, you should call this method from your `App\Providers\AppServiceProvider` service provider's `boot` method: - use App\Models\User; - use Illuminate\Auth\Notifications\ResetPassword; - - /** - * Bootstrap any application services. - */ - public function boot(): void - { - ResetPassword::createUrlUsing(function (User $user, string $token) { - return 'https://example.com/reset-password?token='.$token; - }); - } +```php +use App\Models\User; +use Illuminate\Auth\Notifications\ResetPassword; + +/** + * Bootstrap any application services. + */ +public function boot(): void +{ + ResetPassword::createUrlUsing(function (User $user, string $token) { + return 'https://example.com/reset-password?token='.$token; + }); +} +``` #### Reset Email Customization You may easily modify the notification class used to send the password reset link to the user. To get started, override the `sendPasswordResetNotification` method on your `App\Models\User` model. Within this method, you may send the notification using any [notification class](/docs/{{version}}/notifications) of your own creation. The password reset `$token` is the first argument received by the method. You may use this `$token` to build the password reset URL of your choice and send your notification to the user: - use App\Notifications\ResetPasswordNotification; +```php +use App\Notifications\ResetPasswordNotification; - /** - * Send a password reset notification to the user. - * - * @param string $token - */ - public function sendPasswordResetNotification($token): void - { - $url = 'https://example.com/reset-password?token='.$token; +/** + * Send a password reset notification to the user. + * + * @param string $token + */ +public function sendPasswordResetNotification($token): void +{ + $url = 'https://example.com/reset-password?token='.$token; - $this->notify(new ResetPasswordNotification($url)); - } + $this->notify(new ResetPasswordNotification($url)); +} +``` diff --git a/pennant.md b/pennant.md index 619b2707924..f158490a59c 100644 --- a/pennant.md +++ b/pennant.md @@ -273,37 +273,41 @@ class PodcastController The `when` method may be used to fluently execute a given closure if a feature is active. Additionally, a second closure may be provided and will be executed if the feature is inactive: - $this->resolveNewApiResponse($request), - fn () => $this->resolveLegacyApiResponse($request), - ); - } - - // ... + return Feature::when(NewApi::class, + fn () => $this->resolveNewApiResponse($request), + fn () => $this->resolveLegacyApiResponse($request), + ); } + // ... +} +``` + The `unless` method serves as the inverse of the `when` method, executing the first closure if the feature is inactive: - return Feature::unless(NewApi::class, - fn () => $this->resolveLegacyApiResponse($request), - fn () => $this->resolveNewApiResponse($request), - ); +```php +return Feature::unless(NewApi::class, + fn () => $this->resolveLegacyApiResponse($request), + fn () => $this->resolveNewApiResponse($request), +); +``` ### The `HasFeatures` Trait @@ -497,7 +501,9 @@ When checking a feature, Pennant will create an in-memory cache of the result. I If you need to manually flush the in-memory cache, you may use the `flushCache` method offered by the `Feature` facade: - Feature::flushCache(); +```php +Feature::flushCache(); +``` ## Scope @@ -698,17 +704,21 @@ Pennant's included Blade directive also makes it easy to conditionally render co When calling the [conditional `when`](#conditional-execution) method, the feature's rich value will be provided to the first closure: - Feature::when('purchase-button', - fn ($color) => /* ... */, - fn () => /* ... */, - ); +```php +Feature::when('purchase-button', + fn ($color) => /* ... */, + fn () => /* ... */, +); +``` Likewise, when calling the conditional `unless` method, the feature's rich value will be provided to the optional second closure: - Feature::unless('purchase-button', - fn () => /* ... */, - fn ($color) => /* ... */, - ); +```php +Feature::unless('purchase-button', + fn () => /* ... */, + fn ($color) => /* ... */, +); +``` ## Retrieving Multiple Features @@ -740,25 +750,27 @@ However, class based features are dynamically registered and are not known by Pe If you would like to ensure that feature classes are always included when using the `all` method, you may use Pennant's feature discovery capabilities. To get started, invoke the `discover` method in one of your application's service providers: - [ +```php +'stores' => [ - 'redis' => [ - 'driver' => 'redis', - 'connection' => null, - ], + 'redis' => [ + 'driver' => 'redis', + 'connection' => null, + ], - // ... + // ... - ], +], +``` ### Defining Features Externally diff --git a/processes.md b/processes.md index 7195f182029..eefc6acf766 100644 --- a/processes.md +++ b/processes.md @@ -618,16 +618,18 @@ Process::assertRanTimes(function (PendingProcess $process, ProcessResult $result If you would like to ensure that all invoked processes have been faked throughout your individual test or complete test suite, you can call the `preventStrayProcesses` method. After calling this method, any processes that do not have a corresponding fake result will throw an exception rather than starting an actual process: - use Illuminate\Support\Facades\Process; +```php +use Illuminate\Support\Facades\Process; - Process::preventStrayProcesses(); +Process::preventStrayProcesses(); - Process::fake([ - 'ls *' => 'Test output...', - ]); +Process::fake([ + 'ls *' => 'Test output...', +]); - // Fake response is returned... - Process::run('ls -la'); +// Fake response is returned... +Process::run('ls -la'); - // An exception is thrown... - Process::run('bash import.sh'); +// An exception is thrown... +Process::run('bash import.sh'); +``` diff --git a/prompts.md b/prompts.md index 441f5c6a1b6..22bf02e6083 100644 --- a/prompts.md +++ b/prompts.md @@ -922,7 +922,7 @@ $progress->finish(); The `clear` function may be used to clear the user's terminal: -``` +```php use function Laravel\Prompts\clear; clear(); diff --git a/providers.md b/providers.md index f68718dfb67..2aadd5a9a99 100644 --- a/providers.md +++ b/providers.md @@ -39,26 +39,28 @@ As mentioned previously, within the `register` method, you should only bind thin Let's take a look at a basic service provider. Within any of your service provider methods, you always have access to the `$app` property which provides access to the service container: - app->singleton(Connection::class, function (Application $app) { - return new Connection(config('riak')); - }); - } + $this->app->singleton(Connection::class, function (Application $app) { + return new Connection(config('riak')); + }); } +} +``` This service provider only defines a `register` method, and uses that method to define an implementation of `App\Services\Riak\Connection` in the service container. If you're not yet familiar with Laravel's service container, check out [its documentation](/docs/{{version}}/container). @@ -67,100 +69,110 @@ This service provider only defines a `register` method, and uses that method to If your service provider registers many simple bindings, you may wish to use the `bindings` and `singletons` properties instead of manually registering each container binding. When the service provider is loaded by the framework, it will automatically check for these properties and register their bindings: - DigitalOceanServerProvider::class, - ]; - - /** - * All of the container singletons that should be registered. - * - * @var array - */ - public $singletons = [ - DowntimeNotifier::class => PingdomDowntimeNotifier::class, - ServerProvider::class => ServerToolsProvider::class, - ]; - } +class AppServiceProvider extends ServiceProvider +{ + /** + * All of the container bindings that should be registered. + * + * @var array + */ + public $bindings = [ + ServerProvider::class => DigitalOceanServerProvider::class, + ]; + + /** + * All of the container singletons that should be registered. + * + * @var array + */ + public $singletons = [ + DowntimeNotifier::class => PingdomDowntimeNotifier::class, + ServerProvider::class => ServerToolsProvider::class, + ]; +} +``` ### The Boot Method So, what if we need to register a [view composer](/docs/{{version}}/views#view-composers) within our service provider? This should be done within the `boot` method. **This method is called after all other service providers have been registered**, meaning you have access to all other services that have been registered by the framework: - #### Boot Method Dependency Injection You may type-hint dependencies for your service provider's `boot` method. The [service container](/docs/{{version}}/container) will automatically inject any dependencies you need: - use Illuminate\Contracts\Routing\ResponseFactory; - - /** - * Bootstrap any application services. - */ - public function boot(ResponseFactory $response): void - { - $response->macro('serialized', function (mixed $value) { - // ... - }); - } +```php +use Illuminate\Contracts\Routing\ResponseFactory; + +/** + * Bootstrap any application services. + */ +public function boot(ResponseFactory $response): void +{ + $response->macro('serialized', function (mixed $value) { + // ... + }); +} +``` ## Registering Providers All service providers are registered in the `bootstrap/providers.php` configuration file. This file returns an array that contains the class names of your application's service providers: - ## Deferred Providers @@ -171,34 +183,36 @@ Laravel compiles and stores a list of all of the services supplied by deferred s To defer the loading of a provider, implement the `\Illuminate\Contracts\Support\DeferrableProvider` interface and define a `provides` method. The `provides` method should return the service container bindings registered by the provider: - app->singleton(Connection::class, function (Application $app) { + return new Connection($app['config']['riak']); + }); + } - class RiakServiceProvider extends ServiceProvider implements DeferrableProvider + /** + * Get the services provided by the provider. + * + * @return array + */ + public function provides(): array { - /** - * Register any application services. - */ - public function register(): void - { - $this->app->singleton(Connection::class, function (Application $app) { - return new Connection($app['config']['riak']); - }); - } - - /** - * Get the services provided by the provider. - * - * @return array - */ - public function provides(): array - { - return [Connection::class]; - } + return [Connection::class]; } +} +``` diff --git a/pulse.md b/pulse.md index 4d870ce3c13..e129001bfc2 100644 --- a/pulse.md +++ b/pulse.md @@ -729,7 +729,7 @@ class TopSellers extends Card The `aggregate` method returns a collection of PHP `stdClass` objects. Each object will contain the `key` property captured earlier, along with keys for each of the requested aggregates: -``` +```blade @foreach ($topSellers as $seller) {{ $seller->key }} {{ $seller->sum }} diff --git a/queries.md b/queries.md index 72e474e858e..7902e65c969 100644 --- a/queries.md +++ b/queries.md @@ -53,35 +53,39 @@ The Laravel query builder uses PDO parameter binding to protect your application You may use the `table` method provided by the `DB` facade to begin a query. The `table` method returns a fluent query builder instance for the given table, allowing you to chain more constraints onto the query and then finally retrieve the results of the query using the `get` method: - get(); - - return view('user.index', ['users' => $users]); - } + $users = DB::table('users')->get(); + + return view('user.index', ['users' => $users]); } +} +``` The `get` method returns an `Illuminate\Support\Collection` instance containing the results of the query where each result is an instance of the PHP `stdClass` object. You may access each column's value by accessing the column as a property of the object: - use Illuminate\Support\Facades\DB; +```php +use Illuminate\Support\Facades\DB; - $users = DB::table('users')->get(); +$users = DB::table('users')->get(); - foreach ($users as $user) { - echo $user->name; - } +foreach ($users as $user) { + echo $user->name; +} +``` > [!NOTE] > Laravel collections provide a variety of extremely powerful methods for mapping and reducing data. For more information on Laravel collections, check out the [collection documentation](/docs/{{version}}/collections). @@ -91,75 +95,93 @@ The `get` method returns an `Illuminate\Support\Collection` instance containing If you just need to retrieve a single row from a database table, you may use the `DB` facade's `first` method. This method will return a single `stdClass` object: - $user = DB::table('users')->where('name', 'John')->first(); +```php +$user = DB::table('users')->where('name', 'John')->first(); - return $user->email; +return $user->email; +``` If you would like to retrieve a single row from a database table, but throw an `Illuminate\Database\RecordNotFoundException` if no matching row is found, you may use the `firstOrFail` method. If the `RecordNotFoundException` is not caught, a 404 HTTP response is automatically sent back to the client: - $user = DB::table('users')->where('name', 'John')->firstOrFail(); +```php +$user = DB::table('users')->where('name', 'John')->firstOrFail(); +``` If you don't need an entire row, you may extract a single value from a record using the `value` method. This method will return the value of the column directly: - $email = DB::table('users')->where('name', 'John')->value('email'); +```php +$email = DB::table('users')->where('name', 'John')->value('email'); +``` To retrieve a single row by its `id` column value, use the `find` method: - $user = DB::table('users')->find(3); +```php +$user = DB::table('users')->find(3); +``` #### Retrieving a List of Column Values If you would like to retrieve an `Illuminate\Support\Collection` instance containing the values of a single column, you may use the `pluck` method. In this example, we'll retrieve a collection of user titles: - use Illuminate\Support\Facades\DB; +```php +use Illuminate\Support\Facades\DB; - $titles = DB::table('users')->pluck('title'); +$titles = DB::table('users')->pluck('title'); - foreach ($titles as $title) { - echo $title; - } +foreach ($titles as $title) { + echo $title; +} +``` You may specify the column that the resulting collection should use as its keys by providing a second argument to the `pluck` method: - $titles = DB::table('users')->pluck('title', 'name'); +```php +$titles = DB::table('users')->pluck('title', 'name'); - foreach ($titles as $name => $title) { - echo $title; - } +foreach ($titles as $name => $title) { + echo $title; +} +``` ### Chunking Results If you need to work with thousands of database records, consider using the `chunk` method provided by the `DB` facade. This method retrieves a small chunk of results at a time and feeds each chunk into a closure for processing. For example, let's retrieve the entire `users` table in chunks of 100 records at a time: - use Illuminate\Support\Collection; - use Illuminate\Support\Facades\DB; +```php +use Illuminate\Support\Collection; +use Illuminate\Support\Facades\DB; - DB::table('users')->orderBy('id')->chunk(100, function (Collection $users) { - foreach ($users as $user) { - // ... - } - }); +DB::table('users')->orderBy('id')->chunk(100, function (Collection $users) { + foreach ($users as $user) { + // ... + } +}); +``` You may stop further chunks from being processed by returning `false` from the closure: - DB::table('users')->orderBy('id')->chunk(100, function (Collection $users) { - // Process the records... +```php +DB::table('users')->orderBy('id')->chunk(100, function (Collection $users) { + // Process the records... - return false; - }); + return false; +}); +``` If you are updating database records while chunking results, your chunk results could change in unexpected ways. If you plan to update the retrieved records while chunking, it is always best to use the `chunkById` method instead. This method will automatically paginate the results based on the record's primary key: - DB::table('users')->where('active', false) - ->chunkById(100, function (Collection $users) { - foreach ($users as $user) { - DB::table('users') - ->where('id', $user->id) - ->update(['active' => true]); - } - }); +```php +DB::table('users')->where('active', false) + ->chunkById(100, function (Collection $users) { + foreach ($users as $user) { + DB::table('users') + ->where('id', $user->id) + ->update(['active' => true]); + } + }); +``` Since the `chunkById` and `lazyById` methods add their own "where" conditions to the query being executed, you should typically [logically group](#logical-grouping) your own conditions within a closure: @@ -210,30 +232,36 @@ DB::table('users')->where('active', false) The query builder also provides a variety of methods for retrieving aggregate values like `count`, `max`, `min`, `avg`, and `sum`. You may call any of these methods after constructing your query: - use Illuminate\Support\Facades\DB; +```php +use Illuminate\Support\Facades\DB; - $users = DB::table('users')->count(); +$users = DB::table('users')->count(); - $price = DB::table('orders')->max('price'); +$price = DB::table('orders')->max('price'); +``` Of course, you may combine these methods with other clauses to fine-tune how your aggregate value is calculated: - $price = DB::table('orders') - ->where('finalized', 1) - ->avg('price'); +```php +$price = DB::table('orders') + ->where('finalized', 1) + ->avg('price'); +``` #### Determining if Records Exist Instead of using the `count` method to determine if any records exist that match your query's constraints, you may use the `exists` and `doesntExist` methods: - if (DB::table('orders')->where('finalized', 1)->exists()) { - // ... - } +```php +if (DB::table('orders')->where('finalized', 1)->exists()) { + // ... +} - if (DB::table('orders')->where('finalized', 1)->doesntExist()) { - // ... - } +if (DB::table('orders')->where('finalized', 1)->doesntExist()) { + // ... +} +``` ## Select Statements @@ -243,32 +271,40 @@ Instead of using the `count` method to determine if any records exist that match You may not always want to select all columns from a database table. Using the `select` method, you can specify a custom "select" clause for the query: - use Illuminate\Support\Facades\DB; +```php +use Illuminate\Support\Facades\DB; - $users = DB::table('users') - ->select('name', 'email as user_email') - ->get(); +$users = DB::table('users') + ->select('name', 'email as user_email') + ->get(); +``` The `distinct` method allows you to force the query to return distinct results: - $users = DB::table('users')->distinct()->get(); +```php +$users = DB::table('users')->distinct()->get(); +``` If you already have a query builder instance and you wish to add a column to its existing select clause, you may use the `addSelect` method: - $query = DB::table('users')->select('name'); +```php +$query = DB::table('users')->select('name'); - $users = $query->addSelect('age')->get(); +$users = $query->addSelect('age')->get(); +``` ## Raw Expressions Sometimes you may need to insert an arbitrary string into a query. To create a raw string expression, you may use the `raw` method provided by the `DB` facade: - $users = DB::table('users') - ->select(DB::raw('count(*) as user_count, status')) - ->where('status', '<>', 1) - ->groupBy('status') - ->get(); +```php +$users = DB::table('users') + ->select(DB::raw('count(*) as user_count, status')) + ->where('status', '<>', 1) + ->groupBy('status') + ->get(); +``` > [!WARNING] > Raw statements will be injected into the query as strings, so you should be extremely careful to avoid creating SQL injection vulnerabilities. @@ -283,48 +319,58 @@ Instead of using the `DB::raw` method, you may also use the following methods to The `selectRaw` method can be used in place of `addSelect(DB::raw(/* ... */))`. This method accepts an optional array of bindings as its second argument: - $orders = DB::table('orders') - ->selectRaw('price * ? as price_with_tax', [1.0825]) - ->get(); +```php +$orders = DB::table('orders') + ->selectRaw('price * ? as price_with_tax', [1.0825]) + ->get(); +``` #### `whereRaw / orWhereRaw` The `whereRaw` and `orWhereRaw` methods can be used to inject a raw "where" clause into your query. These methods accept an optional array of bindings as their second argument: - $orders = DB::table('orders') - ->whereRaw('price > IF(state = "TX", ?, 100)', [200]) - ->get(); +```php +$orders = DB::table('orders') + ->whereRaw('price > IF(state = "TX", ?, 100)', [200]) + ->get(); +``` #### `havingRaw / orHavingRaw` The `havingRaw` and `orHavingRaw` methods may be used to provide a raw string as the value of the "having" clause. These methods accept an optional array of bindings as their second argument: - $orders = DB::table('orders') - ->select('department', DB::raw('SUM(price) as total_sales')) - ->groupBy('department') - ->havingRaw('SUM(price) > ?', [2500]) - ->get(); +```php +$orders = DB::table('orders') + ->select('department', DB::raw('SUM(price) as total_sales')) + ->groupBy('department') + ->havingRaw('SUM(price) > ?', [2500]) + ->get(); +``` #### `orderByRaw` The `orderByRaw` method may be used to provide a raw string as the value of the "order by" clause: - $orders = DB::table('orders') - ->orderByRaw('updated_at - created_at DESC') - ->get(); +```php +$orders = DB::table('orders') + ->orderByRaw('updated_at - created_at DESC') + ->get(); +``` ### `groupByRaw` The `groupByRaw` method may be used to provide a raw string as the value of the `group by` clause: - $orders = DB::table('orders') - ->select('city', 'state') - ->groupByRaw('city, state') - ->get(); +```php +$orders = DB::table('orders') + ->select('city', 'state') + ->groupByRaw('city, state') + ->get(); +``` ## Joins @@ -334,70 +380,82 @@ The `groupByRaw` method may be used to provide a raw string as the value of the The query builder may also be used to add join clauses to your queries. To perform a basic "inner join", you may use the `join` method on a query builder instance. The first argument passed to the `join` method is the name of the table you need to join to, while the remaining arguments specify the column constraints for the join. You may even join multiple tables in a single query: - use Illuminate\Support\Facades\DB; +```php +use Illuminate\Support\Facades\DB; - $users = DB::table('users') - ->join('contacts', 'users.id', '=', 'contacts.user_id') - ->join('orders', 'users.id', '=', 'orders.user_id') - ->select('users.*', 'contacts.phone', 'orders.price') - ->get(); +$users = DB::table('users') + ->join('contacts', 'users.id', '=', 'contacts.user_id') + ->join('orders', 'users.id', '=', 'orders.user_id') + ->select('users.*', 'contacts.phone', 'orders.price') + ->get(); +``` #### Left Join / Right Join Clause If you would like to perform a "left join" or "right join" instead of an "inner join", use the `leftJoin` or `rightJoin` methods. These methods have the same signature as the `join` method: - $users = DB::table('users') - ->leftJoin('posts', 'users.id', '=', 'posts.user_id') - ->get(); +```php +$users = DB::table('users') + ->leftJoin('posts', 'users.id', '=', 'posts.user_id') + ->get(); - $users = DB::table('users') - ->rightJoin('posts', 'users.id', '=', 'posts.user_id') - ->get(); +$users = DB::table('users') + ->rightJoin('posts', 'users.id', '=', 'posts.user_id') + ->get(); +``` #### Cross Join Clause You may use the `crossJoin` method to perform a "cross join". Cross joins generate a cartesian product between the first table and the joined table: - $sizes = DB::table('sizes') - ->crossJoin('colors') - ->get(); +```php +$sizes = DB::table('sizes') + ->crossJoin('colors') + ->get(); +``` #### Advanced Join Clauses You may also specify more advanced join clauses. To get started, pass a closure as the second argument to the `join` method. The closure will receive a `Illuminate\Database\Query\JoinClause` instance which allows you to specify constraints on the "join" clause: - DB::table('users') - ->join('contacts', function (JoinClause $join) { - $join->on('users.id', '=', 'contacts.user_id')->orOn(/* ... */); - }) - ->get(); +```php +DB::table('users') + ->join('contacts', function (JoinClause $join) { + $join->on('users.id', '=', 'contacts.user_id')->orOn(/* ... */); + }) + ->get(); +``` If you would like to use a "where" clause on your joins, you may use the `where` and `orWhere` methods provided by the `JoinClause` instance. Instead of comparing two columns, these methods will compare the column against a value: - DB::table('users') - ->join('contacts', function (JoinClause $join) { - $join->on('users.id', '=', 'contacts.user_id') - ->where('contacts.user_id', '>', 5); - }) - ->get(); +```php +DB::table('users') + ->join('contacts', function (JoinClause $join) { + $join->on('users.id', '=', 'contacts.user_id') + ->where('contacts.user_id', '>', 5); + }) + ->get(); +``` #### Subquery Joins You may use the `joinSub`, `leftJoinSub`, and `rightJoinSub` methods to join a query to a subquery. Each of these methods receives three arguments: the subquery, its table alias, and a closure that defines the related columns. In this example, we will retrieve a collection of users where each user record also contains the `created_at` timestamp of the user's most recently published blog post: - $latestPosts = DB::table('posts') - ->select('user_id', DB::raw('MAX(created_at) as last_post_created_at')) - ->where('is_published', true) - ->groupBy('user_id'); - - $users = DB::table('users') - ->joinSub($latestPosts, 'latest_posts', function (JoinClause $join) { - $join->on('users.id', '=', 'latest_posts.user_id'); - })->get(); +```php +$latestPosts = DB::table('posts') + ->select('user_id', DB::raw('MAX(created_at) as last_post_created_at')) + ->where('is_published', true) + ->groupBy('user_id'); + +$users = DB::table('users') + ->joinSub($latestPosts, 'latest_posts', function (JoinClause $join) { + $join->on('users.id', '=', 'latest_posts.user_id'); + })->get(); +``` #### Lateral Joins @@ -409,30 +467,34 @@ You may use the `joinLateral` and `leftJoinLateral` methods to perform a "latera In this example, we will retrieve a collection of users as well as the user's three most recent blog posts. Each user can produce up to three rows in the result set: one for each of their most recent blog posts. The join condition is specified with a `whereColumn` clause within the subquery, referencing the current user row: - $latestPosts = DB::table('posts') - ->select('id as post_id', 'title as post_title', 'created_at as post_created_at') - ->whereColumn('user_id', 'users.id') - ->orderBy('created_at', 'desc') - ->limit(3); - - $users = DB::table('users') - ->joinLateral($latestPosts, 'latest_posts') - ->get(); +```php +$latestPosts = DB::table('posts') + ->select('id as post_id', 'title as post_title', 'created_at as post_created_at') + ->whereColumn('user_id', 'users.id') + ->orderBy('created_at', 'desc') + ->limit(3); + +$users = DB::table('users') + ->joinLateral($latestPosts, 'latest_posts') + ->get(); +``` ## Unions The query builder also provides a convenient method to "union" two or more queries together. For example, you may create an initial query and use the `union` method to union it with more queries: - use Illuminate\Support\Facades\DB; +```php +use Illuminate\Support\Facades\DB; - $first = DB::table('users') - ->whereNull('first_name'); +$first = DB::table('users') + ->whereNull('first_name'); - $users = DB::table('users') - ->whereNull('last_name') - ->union($first) - ->get(); +$users = DB::table('users') + ->whereNull('last_name') + ->union($first) + ->get(); +``` In addition to the `union` method, the query builder provides a `unionAll` method. Queries that are combined using the `unionAll` method will not have their duplicate results removed. The `unionAll` method has the same method signature as the `union` method. @@ -446,35 +508,43 @@ You may use the query builder's `where` method to add "where" clauses to the que For example, the following query retrieves users where the value of the `votes` column is equal to `100` and the value of the `age` column is greater than `35`: - $users = DB::table('users') - ->where('votes', '=', 100) - ->where('age', '>', 35) - ->get(); +```php +$users = DB::table('users') + ->where('votes', '=', 100) + ->where('age', '>', 35) + ->get(); +``` For convenience, if you want to verify that a column is `=` to a given value, you may pass the value as the second argument to the `where` method. Laravel will assume you would like to use the `=` operator: - $users = DB::table('users')->where('votes', 100)->get(); +```php +$users = DB::table('users')->where('votes', 100)->get(); +``` As previously mentioned, you may use any operator that is supported by your database system: - $users = DB::table('users') - ->where('votes', '>=', 100) - ->get(); +```php +$users = DB::table('users') + ->where('votes', '>=', 100) + ->get(); - $users = DB::table('users') - ->where('votes', '<>', 100) - ->get(); +$users = DB::table('users') + ->where('votes', '<>', 100) + ->get(); - $users = DB::table('users') - ->where('name', 'like', 'T%') - ->get(); +$users = DB::table('users') + ->where('name', 'like', 'T%') + ->get(); +``` You may also pass an array of conditions to the `where` function. Each element of the array should be an array containing the three arguments typically passed to the `where` method: - $users = DB::table('users')->where([ - ['status', '=', '1'], - ['subscribed', '<>', '1'], - ])->get(); +```php +$users = DB::table('users')->where([ + ['status', '=', '1'], + ['subscribed', '<>', '1'], +])->get(); +``` > [!WARNING] > PDO does not support binding column names. Therefore, you should never allow user input to dictate the column names referenced by your queries, including "order by" columns. @@ -487,20 +557,24 @@ You may also pass an array of conditions to the `where` function. Each element o When chaining together calls to the query builder's `where` method, the "where" clauses will be joined together using the `and` operator. However, you may use the `orWhere` method to join a clause to the query using the `or` operator. The `orWhere` method accepts the same arguments as the `where` method: - $users = DB::table('users') - ->where('votes', '>', 100) - ->orWhere('name', 'John') - ->get(); +```php +$users = DB::table('users') + ->where('votes', '>', 100) + ->orWhere('name', 'John') + ->get(); +``` If you need to group an "or" condition within parentheses, you may pass a closure as the first argument to the `orWhere` method: - $users = DB::table('users') - ->where('votes', '>', 100) - ->orWhere(function (Builder $query) { - $query->where('name', 'Abigail') - ->where('votes', '>', 50); - }) - ->get(); +```php +$users = DB::table('users') + ->where('votes', '>', 100) + ->orWhere(function (Builder $query) { + $query->where('name', 'Abigail') + ->where('votes', '>', 50); + }) + ->get(); +``` The example above will produce the following SQL: @@ -516,26 +590,30 @@ select * from users where votes > 100 or (name = 'Abigail' and votes > 50) The `whereNot` and `orWhereNot` methods may be used to negate a given group of query constraints. For example, the following query excludes products that are on clearance or which have a price that is less than ten: - $products = DB::table('products') - ->whereNot(function (Builder $query) { - $query->where('clearance', true) - ->orWhere('price', '<', 10); - }) - ->get(); +```php +$products = DB::table('products') + ->whereNot(function (Builder $query) { + $query->where('clearance', true) + ->orWhere('price', '<', 10); + }) + ->get(); +``` ### Where Any / All / None Clauses Sometimes you may need to apply the same query constraints to multiple columns. For example, you may want to retrieve all records where any columns in a given list are `LIKE` a given value. You may accomplish this using the `whereAny` method: - $users = DB::table('users') - ->where('active', true) - ->whereAny([ - 'name', - 'email', - 'phone', - ], 'like', 'Example%') - ->get(); +```php +$users = DB::table('users') + ->where('active', true) + ->whereAny([ + 'name', + 'email', + 'phone', + ], 'like', 'Example%') + ->get(); +``` The query above will result in the following SQL: @@ -551,13 +629,15 @@ WHERE active = true AND ( Similarly, the `whereAll` method may be used to retrieve records where all of the given columns match a given constraint: - $posts = DB::table('posts') - ->where('published', true) - ->whereAll([ - 'title', - 'content', - ], 'like', '%Laravel%') - ->get(); +```php +$posts = DB::table('posts') + ->where('published', true) + ->whereAll([ + 'title', + 'content', + ], 'like', '%Laravel%') + ->get(); +``` The query above will result in the following SQL: @@ -572,14 +652,16 @@ WHERE published = true AND ( The `whereNone` method may be used to retrieve records where none of the given columns match a given constraint: - $posts = DB::table('albums') - ->where('published', true) - ->whereNone([ - 'title', - 'lyrics', - 'tags', - ], 'like', '%explicit%') - ->get(); +```php +$posts = DB::table('albums') + ->where('published', true) + ->whereNone([ + 'title', + 'lyrics', + 'tags', + ], 'like', '%explicit%') + ->get(); +``` The query above will result in the following SQL: @@ -598,31 +680,39 @@ WHERE published = true AND NOT ( Laravel also supports querying JSON column types on databases that provide support for JSON column types. Currently, this includes MariaDB 10.3+, MySQL 8.0+, PostgreSQL 12.0+, SQL Server 2017+, and SQLite 3.39.0+. To query a JSON column, use the `->` operator: - $users = DB::table('users') - ->where('preferences->dining->meal', 'salad') - ->get(); +```php +$users = DB::table('users') + ->where('preferences->dining->meal', 'salad') + ->get(); +``` You may use `whereJsonContains` to query JSON arrays: - $users = DB::table('users') - ->whereJsonContains('options->languages', 'en') - ->get(); +```php +$users = DB::table('users') + ->whereJsonContains('options->languages', 'en') + ->get(); +``` If your application uses the MariaDB, MySQL, or PostgreSQL databases, you may pass an array of values to the `whereJsonContains` method: - $users = DB::table('users') - ->whereJsonContains('options->languages', ['en', 'de']) - ->get(); +```php +$users = DB::table('users') + ->whereJsonContains('options->languages', ['en', 'de']) + ->get(); +``` You may use `whereJsonLength` method to query JSON arrays by their length: - $users = DB::table('users') - ->whereJsonLength('options->languages', 0) - ->get(); +```php +$users = DB::table('users') + ->whereJsonLength('options->languages', 0) + ->get(); - $users = DB::table('users') - ->whereJsonLength('options->languages', '>', 1) - ->get(); +$users = DB::table('users') + ->whereJsonLength('options->languages', '>', 1) + ->get(); +``` ### Additional Where Clauses @@ -631,35 +721,45 @@ You may use `whereJsonLength` method to query JSON arrays by their length: The `whereLike` method allows you to add "LIKE" clauses to your query for pattern matching. These methods provide a database-agnostic way of performing string matching queries, with the ability to toggle case-sensitivity. By default, string matching is case-insensitive: - $users = DB::table('users') - ->whereLike('name', '%John%') - ->get(); +```php +$users = DB::table('users') + ->whereLike('name', '%John%') + ->get(); +``` You can enable a case-sensitive search via the `caseSensitive` argument: - $users = DB::table('users') - ->whereLike('name', '%John%', caseSensitive: true) - ->get(); +```php +$users = DB::table('users') + ->whereLike('name', '%John%', caseSensitive: true) + ->get(); +``` The `orWhereLike` method allows you to add an "or" clause with a LIKE condition: - $users = DB::table('users') - ->where('votes', '>', 100) - ->orWhereLike('name', '%John%') - ->get(); +```php +$users = DB::table('users') + ->where('votes', '>', 100) + ->orWhereLike('name', '%John%') + ->get(); +``` The `whereNotLike` method allows you to add "NOT LIKE" clauses to your query: - $users = DB::table('users') - ->whereNotLike('name', '%John%') - ->get(); +```php +$users = DB::table('users') + ->whereNotLike('name', '%John%') + ->get(); +``` Similarly, you can use `orWhereNotLike` to add an "or" clause with a NOT LIKE condition: - $users = DB::table('users') - ->where('votes', '>', 100) - ->orWhereNotLike('name', '%John%') - ->get(); +```php +$users = DB::table('users') + ->where('votes', '>', 100) + ->orWhereNotLike('name', '%John%') + ->get(); +``` > [!WARNING] > The `whereLike` case-sensitive search option is currently not supported on SQL Server. @@ -668,23 +768,29 @@ Similarly, you can use `orWhereNotLike` to add an "or" clause with a NOT LIKE co The `whereIn` method verifies that a given column's value is contained within the given array: - $users = DB::table('users') - ->whereIn('id', [1, 2, 3]) - ->get(); +```php +$users = DB::table('users') + ->whereIn('id', [1, 2, 3]) + ->get(); +``` The `whereNotIn` method verifies that the given column's value is not contained in the given array: - $users = DB::table('users') - ->whereNotIn('id', [1, 2, 3]) - ->get(); +```php +$users = DB::table('users') + ->whereNotIn('id', [1, 2, 3]) + ->get(); +``` You may also provide a query object as the `whereIn` method's second argument: - $activeUsers = DB::table('users')->select('id')->where('is_active', 1); +```php +$activeUsers = DB::table('users')->select('id')->where('is_active', 1); - $users = DB::table('comments') - ->whereIn('user_id', $activeUsers) - ->get(); +$users = DB::table('comments') + ->whereIn('user_id', $activeUsers) + ->get(); +``` The example above will produce the following SQL: @@ -703,158 +809,196 @@ select * from comments where user_id in ( The `whereBetween` method verifies that a column's value is between two values: - $users = DB::table('users') - ->whereBetween('votes', [1, 100]) - ->get(); +```php +$users = DB::table('users') + ->whereBetween('votes', [1, 100]) + ->get(); +``` **whereNotBetween / orWhereNotBetween** The `whereNotBetween` method verifies that a column's value lies outside of two values: - $users = DB::table('users') - ->whereNotBetween('votes', [1, 100]) - ->get(); +```php +$users = DB::table('users') + ->whereNotBetween('votes', [1, 100]) + ->get(); +``` **whereBetweenColumns / whereNotBetweenColumns / orWhereBetweenColumns / orWhereNotBetweenColumns** The `whereBetweenColumns` method verifies that a column's value is between the two values of two columns in the same table row: - $patients = DB::table('patients') - ->whereBetweenColumns('weight', ['minimum_allowed_weight', 'maximum_allowed_weight']) - ->get(); +```php +$patients = DB::table('patients') + ->whereBetweenColumns('weight', ['minimum_allowed_weight', 'maximum_allowed_weight']) + ->get(); +``` The `whereNotBetweenColumns` method verifies that a column's value lies outside the two values of two columns in the same table row: - $patients = DB::table('patients') - ->whereNotBetweenColumns('weight', ['minimum_allowed_weight', 'maximum_allowed_weight']) - ->get(); +```php +$patients = DB::table('patients') + ->whereNotBetweenColumns('weight', ['minimum_allowed_weight', 'maximum_allowed_weight']) + ->get(); +``` **whereNull / whereNotNull / orWhereNull / orWhereNotNull** The `whereNull` method verifies that the value of the given column is `NULL`: - $users = DB::table('users') - ->whereNull('updated_at') - ->get(); +```php +$users = DB::table('users') + ->whereNull('updated_at') + ->get(); +``` The `whereNotNull` method verifies that the column's value is not `NULL`: - $users = DB::table('users') - ->whereNotNull('updated_at') - ->get(); +```php +$users = DB::table('users') + ->whereNotNull('updated_at') + ->get(); +``` **whereDate / whereMonth / whereDay / whereYear / whereTime** The `whereDate` method may be used to compare a column's value against a date: - $users = DB::table('users') - ->whereDate('created_at', '2016-12-31') - ->get(); +```php +$users = DB::table('users') + ->whereDate('created_at', '2016-12-31') + ->get(); +``` The `whereMonth` method may be used to compare a column's value against a specific month: - $users = DB::table('users') - ->whereMonth('created_at', '12') - ->get(); +```php +$users = DB::table('users') + ->whereMonth('created_at', '12') + ->get(); +``` The `whereDay` method may be used to compare a column's value against a specific day of the month: - $users = DB::table('users') - ->whereDay('created_at', '31') - ->get(); +```php +$users = DB::table('users') + ->whereDay('created_at', '31') + ->get(); +``` The `whereYear` method may be used to compare a column's value against a specific year: - $users = DB::table('users') - ->whereYear('created_at', '2016') - ->get(); +```php +$users = DB::table('users') + ->whereYear('created_at', '2016') + ->get(); +``` The `whereTime` method may be used to compare a column's value against a specific time: - $users = DB::table('users') - ->whereTime('created_at', '=', '11:20:45') - ->get(); +```php +$users = DB::table('users') + ->whereTime('created_at', '=', '11:20:45') + ->get(); +``` **wherePast / whereFuture / whereToday / whereBeforeToday / whereAfterToday** The `wherePast` and `whereFuture` methods may be used to determine if a column's value is in the past or future: - $invoices = DB::table('invoices') - ->wherePast('due_at') - ->get(); +```php +$invoices = DB::table('invoices') + ->wherePast('due_at') + ->get(); - $invoices = DB::table('invoices') - ->whereFuture('due_at') - ->get(); +$invoices = DB::table('invoices') + ->whereFuture('due_at') + ->get(); +``` The `whereNowOrPast` and `whereNowOrFuture` methods may be used to determine if a column's value is in the past or future, inclusive of the current date and time: - $invoices = DB::table('invoices') - ->whereNowOrPast('due_at') - ->get(); +```php +$invoices = DB::table('invoices') + ->whereNowOrPast('due_at') + ->get(); - $invoices = DB::table('invoices') - ->whereNowOrFuture('due_at') - ->get(); +$invoices = DB::table('invoices') + ->whereNowOrFuture('due_at') + ->get(); +``` The `whereToday`, `whereBeforeToday`, and `whereAfterToday` methods may be used to determine if a column's value is today, before today, or after today, respectively: - $invoices = DB::table('invoices') - ->whereToday('due_at') - ->get(); +```php +$invoices = DB::table('invoices') + ->whereToday('due_at') + ->get(); - $invoices = DB::table('invoices') - ->whereBeforeToday('due_at') - ->get(); +$invoices = DB::table('invoices') + ->whereBeforeToday('due_at') + ->get(); - $invoices = DB::table('invoices') - ->whereAfterToday('due_at') - ->get(); +$invoices = DB::table('invoices') + ->whereAfterToday('due_at') + ->get(); +``` Similarly, the `whereTodayOrBefore` and `whereTodayOrAfter` methods may be used to determine if a column's value is before today or after today, inclusive of today's date: - $invoices = DB::table('invoices') - ->whereTodayOrBefore('due_at') - ->get(); +```php +$invoices = DB::table('invoices') + ->whereTodayOrBefore('due_at') + ->get(); - $invoices = DB::table('invoices') - ->whereTodayOrAfter('due_at') - ->get(); +$invoices = DB::table('invoices') + ->whereTodayOrAfter('due_at') + ->get(); +``` **whereColumn / orWhereColumn** The `whereColumn` method may be used to verify that two columns are equal: - $users = DB::table('users') - ->whereColumn('first_name', 'last_name') - ->get(); +```php +$users = DB::table('users') + ->whereColumn('first_name', 'last_name') + ->get(); +``` You may also pass a comparison operator to the `whereColumn` method: - $users = DB::table('users') - ->whereColumn('updated_at', '>', 'created_at') - ->get(); +```php +$users = DB::table('users') + ->whereColumn('updated_at', '>', 'created_at') + ->get(); +``` You may also pass an array of column comparisons to the `whereColumn` method. These conditions will be joined using the `and` operator: - $users = DB::table('users') - ->whereColumn([ - ['first_name', '=', 'last_name'], - ['updated_at', '>', 'created_at'], - ])->get(); +```php +$users = DB::table('users') + ->whereColumn([ + ['first_name', '=', 'last_name'], + ['updated_at', '>', 'created_at'], + ])->get(); +``` ### Logical Grouping Sometimes you may need to group several "where" clauses within parentheses in order to achieve your query's desired logical grouping. In fact, you should generally always group calls to the `orWhere` method in parentheses in order to avoid unexpected query behavior. To accomplish this, you may pass a closure to the `where` method: - $users = DB::table('users') - ->where('name', '=', 'John') - ->where(function (Builder $query) { - $query->where('votes', '>', 100) - ->orWhere('title', '=', 'Admin'); - }) - ->get(); +```php +$users = DB::table('users') + ->where('name', '=', 'John') + ->where(function (Builder $query) { + $query->where('votes', '>', 100) + ->orWhere('title', '=', 'Admin'); + }) + ->get(); +``` As you can see, passing a closure into the `where` method instructs the query builder to begin a constraint group. The closure will receive a query builder instance which you can use to set the constraints that should be contained within the parenthesis group. The example above will produce the following SQL: @@ -873,23 +1017,27 @@ select * from users where name = 'John' and (votes > 100 or title = 'Admin') The `whereExists` method allows you to write "where exists" SQL clauses. The `whereExists` method accepts a closure which will receive a query builder instance, allowing you to define the query that should be placed inside of the "exists" clause: - $users = DB::table('users') - ->whereExists(function (Builder $query) { - $query->select(DB::raw(1)) - ->from('orders') - ->whereColumn('orders.user_id', 'users.id'); - }) - ->get(); +```php +$users = DB::table('users') + ->whereExists(function (Builder $query) { + $query->select(DB::raw(1)) + ->from('orders') + ->whereColumn('orders.user_id', 'users.id'); + }) + ->get(); +``` Alternatively, you may provide a query object to the `whereExists` method instead of a closure: - $orders = DB::table('orders') - ->select(DB::raw(1)) - ->whereColumn('orders.user_id', 'users.id'); +```php +$orders = DB::table('orders') + ->select(DB::raw(1)) + ->whereColumn('orders.user_id', 'users.id'); - $users = DB::table('users') - ->whereExists($orders) - ->get(); +$users = DB::table('users') + ->whereExists($orders) + ->get(); +``` Both of the examples above will produce the following SQL: @@ -907,25 +1055,29 @@ where exists ( Sometimes you may need to construct a "where" clause that compares the results of a subquery to a given value. You may accomplish this by passing a closure and a value to the `where` method. For example, the following query will retrieve all users who have a recent "membership" of a given type; - use App\Models\User; - use Illuminate\Database\Query\Builder; - - $users = User::where(function (Builder $query) { - $query->select('type') - ->from('membership') - ->whereColumn('membership.user_id', 'users.id') - ->orderByDesc('membership.start_date') - ->limit(1); - }, 'Pro')->get(); +```php +use App\Models\User; +use Illuminate\Database\Query\Builder; + +$users = User::where(function (Builder $query) { + $query->select('type') + ->from('membership') + ->whereColumn('membership.user_id', 'users.id') + ->orderByDesc('membership.start_date') + ->limit(1); +}, 'Pro')->get(); +``` Or, you may need to construct a "where" clause that compares a column to the results of a subquery. You may accomplish this by passing a column, operator, and closure to the `where` method. For example, the following query will retrieve all income records where the amount is less than average; - use App\Models\Income; - use Illuminate\Database\Query\Builder; +```php +use App\Models\Income; +use Illuminate\Database\Query\Builder; - $incomes = Income::where('amount', '<', function (Builder $query) { - $query->selectRaw('avg(i.amount)')->from('incomes as i'); - })->get(); +$incomes = Income::where('amount', '<', function (Builder $query) { + $query->selectRaw('avg(i.amount)')->from('incomes as i'); +})->get(); +``` ### Full Text Where Clauses @@ -935,9 +1087,11 @@ Or, you may need to construct a "where" clause that compares a column to the res The `whereFullText` and `orWhereFullText` methods may be used to add full text "where" clauses to a query for columns that have [full text indexes](/docs/{{version}}/migrations#available-index-types). These methods will be transformed into the appropriate SQL for the underlying database system by Laravel. For example, a `MATCH AGAINST` clause will be generated for applications utilizing MariaDB or MySQL: - $users = DB::table('users') - ->whereFullText('bio', 'web developer') - ->get(); +```php +$users = DB::table('users') + ->whereFullText('bio', 'web developer') + ->get(); +``` ## Ordering, Grouping, Limit and Offset @@ -950,49 +1104,61 @@ The `whereFullText` and `orWhereFullText` methods may be used to add full text " The `orderBy` method allows you to sort the results of the query by a given column. The first argument accepted by the `orderBy` method should be the column you wish to sort by, while the second argument determines the direction of the sort and may be either `asc` or `desc`: - $users = DB::table('users') - ->orderBy('name', 'desc') - ->get(); +```php +$users = DB::table('users') + ->orderBy('name', 'desc') + ->get(); +``` To sort by multiple columns, you may simply invoke `orderBy` as many times as necessary: - $users = DB::table('users') - ->orderBy('name', 'desc') - ->orderBy('email', 'asc') - ->get(); +```php +$users = DB::table('users') + ->orderBy('name', 'desc') + ->orderBy('email', 'asc') + ->get(); +``` #### The `latest` and `oldest` Methods The `latest` and `oldest` methods allow you to easily order results by date. By default, the result will be ordered by the table's `created_at` column. Or, you may pass the column name that you wish to sort by: - $user = DB::table('users') - ->latest() - ->first(); +```php +$user = DB::table('users') + ->latest() + ->first(); +``` #### Random Ordering The `inRandomOrder` method may be used to sort the query results randomly. For example, you may use this method to fetch a random user: - $randomUser = DB::table('users') - ->inRandomOrder() - ->first(); +```php +$randomUser = DB::table('users') + ->inRandomOrder() + ->first(); +``` #### Removing Existing Orderings The `reorder` method removes all of the "order by" clauses that have previously been applied to the query: - $query = DB::table('users')->orderBy('name'); +```php +$query = DB::table('users')->orderBy('name'); - $unorderedUsers = $query->reorder()->get(); +$unorderedUsers = $query->reorder()->get(); +``` You may pass a column and direction when calling the `reorder` method in order to remove all existing "order by" clauses and apply an entirely new order to the query: - $query = DB::table('users')->orderBy('name'); +```php +$query = DB::table('users')->orderBy('name'); - $usersOrderedByEmail = $query->reorder('email', 'desc')->get(); +$usersOrderedByEmail = $query->reorder('email', 'desc')->get(); +``` ### Grouping @@ -1002,25 +1168,31 @@ You may pass a column and direction when calling the `reorder` method in order t As you might expect, the `groupBy` and `having` methods may be used to group the query results. The `having` method's signature is similar to that of the `where` method: - $users = DB::table('users') - ->groupBy('account_id') - ->having('account_id', '>', 100) - ->get(); +```php +$users = DB::table('users') + ->groupBy('account_id') + ->having('account_id', '>', 100) + ->get(); +``` You can use the `havingBetween` method to filter the results within a given range: - $report = DB::table('orders') - ->selectRaw('count(id) as number_of_orders, customer_id') - ->groupBy('customer_id') - ->havingBetween('number_of_orders', [5, 15]) - ->get(); +```php +$report = DB::table('orders') + ->selectRaw('count(id) as number_of_orders, customer_id') + ->groupBy('customer_id') + ->havingBetween('number_of_orders', [5, 15]) + ->get(); +``` You may pass multiple arguments to the `groupBy` method to group by multiple columns: - $users = DB::table('users') - ->groupBy('first_name', 'status') - ->having('account_id', '>', 100) - ->get(); +```php +$users = DB::table('users') + ->groupBy('first_name', 'status') + ->having('account_id', '>', 100) + ->get(); +``` To build more advanced `having` statements, see the [`havingRaw`](#raw-methods) method. @@ -1032,82 +1204,100 @@ To build more advanced `having` statements, see the [`havingRaw`](#raw-methods) You may use the `skip` and `take` methods to limit the number of results returned from the query or to skip a given number of results in the query: - $users = DB::table('users')->skip(10)->take(5)->get(); +```php +$users = DB::table('users')->skip(10)->take(5)->get(); +``` Alternatively, you may use the `limit` and `offset` methods. These methods are functionally equivalent to the `take` and `skip` methods, respectively: - $users = DB::table('users') - ->offset(10) - ->limit(5) - ->get(); +```php +$users = DB::table('users') + ->offset(10) + ->limit(5) + ->get(); +``` ## Conditional Clauses Sometimes you may want certain query clauses to apply to a query based on another condition. For instance, you may only want to apply a `where` statement if a given input value is present on the incoming HTTP request. You may accomplish this using the `when` method: - $role = $request->input('role'); +```php +$role = $request->input('role'); - $users = DB::table('users') - ->when($role, function (Builder $query, string $role) { - $query->where('role_id', $role); - }) - ->get(); +$users = DB::table('users') + ->when($role, function (Builder $query, string $role) { + $query->where('role_id', $role); + }) + ->get(); +``` The `when` method only executes the given closure when the first argument is `true`. If the first argument is `false`, the closure will not be executed. So, in the example above, the closure given to the `when` method will only be invoked if the `role` field is present on the incoming request and evaluates to `true`. You may pass another closure as the third argument to the `when` method. This closure will only execute if the first argument evaluates as `false`. To illustrate how this feature may be used, we will use it to configure the default ordering of a query: - $sortByVotes = $request->boolean('sort_by_votes'); - - $users = DB::table('users') - ->when($sortByVotes, function (Builder $query, bool $sortByVotes) { - $query->orderBy('votes'); - }, function (Builder $query) { - $query->orderBy('name'); - }) - ->get(); +```php +$sortByVotes = $request->boolean('sort_by_votes'); + +$users = DB::table('users') + ->when($sortByVotes, function (Builder $query, bool $sortByVotes) { + $query->orderBy('votes'); + }, function (Builder $query) { + $query->orderBy('name'); + }) + ->get(); +``` ## Insert Statements The query builder also provides an `insert` method that may be used to insert records into the database table. The `insert` method accepts an array of column names and values: - DB::table('users')->insert([ - 'email' => 'kayla@example.com', - 'votes' => 0 - ]); +```php +DB::table('users')->insert([ + 'email' => 'kayla@example.com', + 'votes' => 0 +]); +``` You may insert several records at once by passing an array of arrays. Each array represents a record that should be inserted into the table: - DB::table('users')->insert([ - ['email' => 'picard@example.com', 'votes' => 0], - ['email' => 'janeway@example.com', 'votes' => 0], - ]); +```php +DB::table('users')->insert([ + ['email' => 'picard@example.com', 'votes' => 0], + ['email' => 'janeway@example.com', 'votes' => 0], +]); +``` The `insertOrIgnore` method will ignore errors while inserting records into the database. When using this method, you should be aware that duplicate record errors will be ignored and other types of errors may also be ignored depending on the database engine. For example, `insertOrIgnore` will [bypass MySQL's strict mode](https://dev.mysql.com/doc/refman/en/sql-mode.html#ignore-effect-on-execution): - DB::table('users')->insertOrIgnore([ - ['id' => 1, 'email' => 'sisko@example.com'], - ['id' => 2, 'email' => 'archer@example.com'], - ]); +```php +DB::table('users')->insertOrIgnore([ + ['id' => 1, 'email' => 'sisko@example.com'], + ['id' => 2, 'email' => 'archer@example.com'], +]); +``` The `insertUsing` method will insert new records into the table while using a subquery to determine the data that should be inserted: - DB::table('pruned_users')->insertUsing([ - 'id', 'name', 'email', 'email_verified_at' - ], DB::table('users')->select( - 'id', 'name', 'email', 'email_verified_at' - )->where('updated_at', '<=', now()->subMonth())); +```php +DB::table('pruned_users')->insertUsing([ + 'id', 'name', 'email', 'email_verified_at' +], DB::table('users')->select( + 'id', 'name', 'email', 'email_verified_at' +)->where('updated_at', '<=', now()->subMonth())); +``` #### Auto-Incrementing IDs If the table has an auto-incrementing id, use the `insertGetId` method to insert a record and then retrieve the ID: - $id = DB::table('users')->insertGetId( - ['email' => 'john@example.com', 'votes' => 0] - ); +```php +$id = DB::table('users')->insertGetId( + ['email' => 'john@example.com', 'votes' => 0] +); +``` > [!WARNING] > When using PostgreSQL the `insertGetId` method expects the auto-incrementing column to be named `id`. If you would like to retrieve the ID from a different "sequence", you may pass the column name as the second parameter to the `insertGetId` method. @@ -1117,14 +1307,16 @@ If the table has an auto-incrementing id, use the `insertGetId` method to insert The `upsert` method will insert records that do not exist and update the records that already exist with new values that you may specify. The method's first argument consists of the values to insert or update, while the second argument lists the column(s) that uniquely identify records within the associated table. The method's third and final argument is an array of columns that should be updated if a matching record already exists in the database: - DB::table('flights')->upsert( - [ - ['departure' => 'Oakland', 'destination' => 'San Diego', 'price' => 99], - ['departure' => 'Chicago', 'destination' => 'New York', 'price' => 150] - ], - ['departure', 'destination'], - ['price'] - ); +```php +DB::table('flights')->upsert( + [ + ['departure' => 'Oakland', 'destination' => 'San Diego', 'price' => 99], + ['departure' => 'Chicago', 'destination' => 'New York', 'price' => 150] + ], + ['departure', 'destination'], + ['price'] +); +``` In the example above, Laravel will attempt to insert two records. If a record already exists with the same `departure` and `destination` column values, Laravel will update that record's `price` column. @@ -1136,9 +1328,11 @@ In the example above, Laravel will attempt to insert two records. If a record al In addition to inserting records into the database, the query builder can also update existing records using the `update` method. The `update` method, like the `insert` method, accepts an array of column and value pairs indicating the columns to be updated. The `update` method returns the number of affected rows. You may constrain the `update` query using `where` clauses: - $affected = DB::table('users') - ->where('id', 1) - ->update(['votes' => 1]); +```php +$affected = DB::table('users') + ->where('id', 1) + ->update(['votes' => 1]); +``` #### Update or Insert @@ -1147,11 +1341,13 @@ Sometimes you may want to update an existing record in the database or create it The `updateOrInsert` method will attempt to locate a matching database record using the first argument's column and value pairs. If the record exists, it will be updated with the values in the second argument. If the record cannot be found, a new record will be inserted with the merged attributes of both arguments: - DB::table('users') - ->updateOrInsert( - ['email' => 'john@example.com', 'name' => 'John'], - ['votes' => '2'] - ); +```php +DB::table('users') + ->updateOrInsert( + ['email' => 'john@example.com', 'name' => 'John'], + ['votes' => '2'] + ); +``` You may provide a closure to the `updateOrInsert` method to customize the attributes that are updated or inserted into the database based on the existence of a matching record: @@ -1174,99 +1370,119 @@ DB::table('users')->updateOrInsert( When updating a JSON column, you should use `->` syntax to update the appropriate key in the JSON object. This operation is supported on MariaDB 10.3+, MySQL 5.7+, and PostgreSQL 9.5+: - $affected = DB::table('users') - ->where('id', 1) - ->update(['options->enabled' => true]); +```php +$affected = DB::table('users') + ->where('id', 1) + ->update(['options->enabled' => true]); +``` ### Increment and Decrement The query builder also provides convenient methods for incrementing or decrementing the value of a given column. Both of these methods accept at least one argument: the column to modify. A second argument may be provided to specify the amount by which the column should be incremented or decremented: - DB::table('users')->increment('votes'); +```php +DB::table('users')->increment('votes'); - DB::table('users')->increment('votes', 5); +DB::table('users')->increment('votes', 5); - DB::table('users')->decrement('votes'); +DB::table('users')->decrement('votes'); - DB::table('users')->decrement('votes', 5); +DB::table('users')->decrement('votes', 5); +``` If needed, you may also specify additional columns to update during the increment or decrement operation: - DB::table('users')->increment('votes', 1, ['name' => 'John']); +```php +DB::table('users')->increment('votes', 1, ['name' => 'John']); +``` In addition, you may increment or decrement multiple columns at once using the `incrementEach` and `decrementEach` methods: - DB::table('users')->incrementEach([ - 'votes' => 5, - 'balance' => 100, - ]); +```php +DB::table('users')->incrementEach([ + 'votes' => 5, + 'balance' => 100, +]); +``` ## Delete Statements The query builder's `delete` method may be used to delete records from the table. The `delete` method returns the number of affected rows. You may constrain `delete` statements by adding "where" clauses before calling the `delete` method: - $deleted = DB::table('users')->delete(); +```php +$deleted = DB::table('users')->delete(); - $deleted = DB::table('users')->where('votes', '>', 100)->delete(); +$deleted = DB::table('users')->where('votes', '>', 100)->delete(); +``` ## Pessimistic Locking The query builder also includes a few functions to help you achieve "pessimistic locking" when executing your `select` statements. To execute a statement with a "shared lock", you may call the `sharedLock` method. A shared lock prevents the selected rows from being modified until your transaction is committed: - DB::table('users') - ->where('votes', '>', 100) - ->sharedLock() - ->get(); +```php +DB::table('users') + ->where('votes', '>', 100) + ->sharedLock() + ->get(); +``` Alternatively, you may use the `lockForUpdate` method. A "for update" lock prevents the selected records from being modified or from being selected with another shared lock: - DB::table('users') - ->where('votes', '>', 100) - ->lockForUpdate() - ->get(); +```php +DB::table('users') + ->where('votes', '>', 100) + ->lockForUpdate() + ->get(); +``` While not obligatory, it is recommended to wrap pessimistic locks within a [transaction](/docs/{{version}}/database#database-transactions). This ensures that the data retrieved remains unaltered in the database until the entire operation completes. In case of a failure, the transaction will roll back any changes and release the locks automatically: - DB::transaction(function () { - $sender = DB::table('users') - ->lockForUpdate() - ->find(1); +```php +DB::transaction(function () { + $sender = DB::table('users') + ->lockForUpdate() + ->find(1); - $receiver = DB::table('users') - ->lockForUpdate(); - ->find(2); + $receiver = DB::table('users') + ->lockForUpdate(); + ->find(2); - if ($sender->balance < 100) { - throw new RuntimeException('Balance too low.'); - } - - DB::table('users') - ->where('id', $sender->id) - ->update([ - 'balance' => $sender->balance - 100 - ]); + if ($sender->balance < 100) { + throw new RuntimeException('Balance too low.'); + } + + DB::table('users') + ->where('id', $sender->id) + ->update([ + 'balance' => $sender->balance - 100 + ]); - DB::table('users') - ->where('id', $receiver->id) - ->update([ - 'balance' => $receiver->balance + 100 - ]); - }); + DB::table('users') + ->where('id', $receiver->id) + ->update([ + 'balance' => $receiver->balance + 100 + ]); +}); +``` ## Debugging You may use the `dd` and `dump` methods while building a query to dump the current query bindings and SQL. The `dd` method will display the debug information and then stop executing the request. The `dump` method will display the debug information but allow the request to continue executing: - DB::table('users')->where('votes', '>', 100)->dd(); +```php +DB::table('users')->where('votes', '>', 100)->dd(); - DB::table('users')->where('votes', '>', 100)->dump(); +DB::table('users')->where('votes', '>', 100)->dump(); +``` The `dumpRawSql` and `ddRawSql` methods may be invoked on a query to dump the query's SQL with all parameter bindings properly substituted: - DB::table('users')->where('votes', '>', 100)->dumpRawSql(); +```php +DB::table('users')->where('votes', '>', 100)->dumpRawSql(); - DB::table('users')->where('votes', '>', 100)->ddRawSql(); +DB::table('users')->where('votes', '>', 100)->ddRawSql(); +``` diff --git a/queues.md b/queues.md index edef55942a1..7f17862d426 100644 --- a/queues.md +++ b/queues.md @@ -74,13 +74,15 @@ Before getting started with Laravel queues, it is important to understand the di Note that each connection configuration example in the `queue` configuration file contains a `queue` attribute. This is the default queue that jobs will be dispatched to when they are sent to a given connection. In other words, if you dispatch a job without explicitly defining which queue it should be dispatched to, the job will be placed on the queue that is defined in the `queue` attribute of the connection configuration: - use App\Jobs\ProcessPodcast; +```php +use App\Jobs\ProcessPodcast; - // This job is sent to the default connection's default queue... - ProcessPodcast::dispatch(); +// This job is sent to the default connection's default queue... +ProcessPodcast::dispatch(); - // This job is sent to the default connection's "emails" queue... - ProcessPodcast::dispatch()->onQueue('emails'); +// This job is sent to the default connection's "emails" queue... +ProcessPodcast::dispatch()->onQueue('emails'); +``` Some applications may not need to ever push jobs onto multiple queues, instead preferring to have one simple queue. However, pushing jobs to multiple queues can be especially useful for applications that wish to prioritize or segment how jobs are processed, since the Laravel queue worker allows you to specify which queues it should process by priority. For example, if you push jobs to a `high` queue, you may run a worker that gives them higher processing priority: @@ -114,14 +116,16 @@ In order to use the `redis` queue driver, you should configure a Redis database If your Redis queue connection uses a Redis Cluster, your queue names must contain a [key hash tag](https://redis.io/docs/reference/cluster-spec/#hash-tags). This is required in order to ensure all of the Redis keys for a given queue are placed into the same hash slot: - 'redis' => [ - 'driver' => 'redis', - 'connection' => env('REDIS_QUEUE_CONNECTION', 'default'), - 'queue' => env('REDIS_QUEUE', '{default}'), - 'retry_after' => env('REDIS_QUEUE_RETRY_AFTER', 90), - 'block_for' => null, - 'after_commit' => false, - ], +```php +'redis' => [ + 'driver' => 'redis', + 'connection' => env('REDIS_QUEUE_CONNECTION', 'default'), + 'queue' => env('REDIS_QUEUE', '{default}'), + 'retry_after' => env('REDIS_QUEUE_RETRY_AFTER', 90), + 'block_for' => null, + 'after_commit' => false, +], +``` **Blocking** @@ -129,14 +133,16 @@ When using the Redis queue, you may use the `block_for` configuration option to Adjusting this value based on your queue load can be more efficient than continually polling the Redis database for new jobs. For instance, you may set the value to `5` to indicate that the driver should block for five seconds while waiting for a job to become available: - 'redis' => [ - 'driver' => 'redis', - 'connection' => env('REDIS_QUEUE_CONNECTION', 'default'), - 'queue' => env('REDIS_QUEUE', 'default'), - 'retry_after' => env('REDIS_QUEUE_RETRY_AFTER', 90), - 'block_for' => 5, - 'after_commit' => false, - ], +```php +'redis' => [ + 'driver' => 'redis', + 'connection' => env('REDIS_QUEUE_CONNECTION', 'default'), + 'queue' => env('REDIS_QUEUE', 'default'), + 'retry_after' => env('REDIS_QUEUE_RETRY_AFTER', 90), + 'block_for' => 5, + 'after_commit' => false, +], +``` > [!WARNING] > Setting `block_for` to `0` will cause queue workers to block indefinitely until a job is available. This will also prevent signals such as `SIGTERM` from being handled until the next job has been processed. @@ -177,34 +183,36 @@ The generated class will implement the `Illuminate\Contracts\Queue\ShouldQueue` Job classes are very simple, normally containing only a `handle` method that is invoked when the job is processed by the queue. To get started, let's take a look at an example job class. In this example, we'll pretend we manage a podcast publishing service and need to process the uploaded podcast files before they are published: - app->bindMethod([ProcessPodcast::class, 'handle'], function (ProcessPodcast $job, Application $app) { - return $job->handle($app->make(AudioProcessor::class)); - }); +$this->app->bindMethod([ProcessPodcast::class, 'handle'], function (ProcessPodcast $job, Application $app) { + return $job->handle($app->make(AudioProcessor::class)); +}); +``` > [!WARNING] > Binary data, such as raw image contents, should be passed through the `base64_encode` function before being passed to a queued job. Otherwise, the job may not properly serialize to JSON when being placed on the queue. @@ -235,26 +245,30 @@ Because all loaded Eloquent model relationships also get serialized when a job i Or, to prevent relations from being serialized, you can call the `withoutRelations` method on the model when setting a property value. This method will return an instance of the model without its loaded relationships: - /** - * Create a new job instance. - */ - public function __construct( - Podcast $podcast, - ) { - $this->podcast = $podcast->withoutRelations(); - } +```php +/** + * Create a new job instance. + */ +public function __construct( + Podcast $podcast, +) { + $this->podcast = $podcast->withoutRelations(); +} +``` If you are using PHP constructor property promotion and would like to indicate that an Eloquent model should not have its relations serialized, you may use the `WithoutRelations` attribute: - use Illuminate\Queue\Attributes\WithoutRelations; +```php +use Illuminate\Queue\Attributes\WithoutRelations; - /** - * Create a new job instance. - */ - public function __construct( - #[WithoutRelations] - public Podcast $podcast, - ) {} +/** + * Create a new job instance. + */ +public function __construct( + #[WithoutRelations] + public Podcast $podcast, +) {} +``` If a job receives a collection or array of Eloquent models instead of a single model, the models within that collection will not have their relationships restored when the job is deserialized and executed. This is to prevent excessive resource usage on jobs that deal with large numbers of models. @@ -266,50 +280,54 @@ If a job receives a collection or array of Eloquent models instead of a single m Sometimes, you may want to ensure that only one instance of a specific job is on the queue at any point in time. You may do so by implementing the `ShouldBeUnique` interface on your job class. This interface does not require you to define any additional methods on your class: - product->id; - } + return $this->product->id; } +} +``` In the example above, the `UpdateSearchIndex` job is unique by a product ID. So, any new dispatches of the job with the same product ID will be ignored until the existing job has completed processing. In addition, if the existing job is not processed within one hour, the unique lock will be released and another job with the same unique key can be dispatched to the queue. @@ -321,37 +339,41 @@ In the example above, the `UpdateSearchIndex` job is unique by a product ID. So, By default, unique jobs are "unlocked" after a job completes processing or fails all of its retry attempts. However, there may be situations where you would like your job to unlock immediately before it is processed. To accomplish this, your job should implement the `ShouldBeUniqueUntilProcessing` contract instead of the `ShouldBeUnique` contract: - #### Unique Job Locks Behind the scenes, when a `ShouldBeUnique` job is dispatched, Laravel attempts to acquire a [lock](/docs/{{version}}/cache#atomic-locks) with the `uniqueId` key. If the lock is not acquired, the job is not dispatched. This lock is released when the job completes processing or fails all of its retry attempts. By default, Laravel will use the default cache driver to obtain this lock. However, if you wish to use another driver for acquiring the lock, you may define a `uniqueVia` method that returns the cache driver that should be used: - use Illuminate\Contracts\Cache\Repository; - use Illuminate\Support\Facades\Cache; +```php +use Illuminate\Contracts\Cache\Repository; +use Illuminate\Support\Facades\Cache; - class UpdateSearchIndex implements ShouldQueue, ShouldBeUnique +class UpdateSearchIndex implements ShouldQueue, ShouldBeUnique +{ + ... + + /** + * Get the cache driver for the unique job lock. + */ + public function uniqueVia(): Repository { - ... - - /** - * Get the cache driver for the unique job lock. - */ - public function uniqueVia(): Repository - { - return Cache::driver('redis'); - } + return Cache::driver('redis'); } +} +``` > [!NOTE] > If you only need to limit the concurrent processing of a job, use the [`WithoutOverlapping`](/docs/{{version}}/queues#preventing-job-overlaps) job middleware instead. @@ -361,88 +383,96 @@ Behind the scenes, when a `ShouldBeUnique` job is dispatched, Laravel attempts t Laravel allows you to ensure the privacy and integrity of a job's data via [encryption](/docs/{{version}}/encryption). To get started, simply add the `ShouldBeEncrypted` interface to the job class. Once this interface has been added to the class, Laravel will automatically encrypt your job before pushing it onto a queue: - ## Job Middleware Job middleware allow you to wrap custom logic around the execution of queued jobs, reducing boilerplate in the jobs themselves. For example, consider the following `handle` method which leverages Laravel's Redis rate limiting features to allow only one job to process every five seconds: - use Illuminate\Support\Facades\Redis; +```php +use Illuminate\Support\Facades\Redis; - /** - * Execute the job. - */ - public function handle(): void - { - Redis::throttle('key')->block(0)->allow(1)->every(5)->then(function () { - info('Lock obtained...'); +/** + * Execute the job. + */ +public function handle(): void +{ + Redis::throttle('key')->block(0)->allow(1)->every(5)->then(function () { + info('Lock obtained...'); - // Handle job... - }, function () { - // Could not obtain lock... + // Handle job... + }, function () { + // Could not obtain lock... - return $this->release(5); - }); - } + return $this->release(5); + }); +} +``` While this code is valid, the implementation of the `handle` method becomes noisy since it is cluttered with Redis rate limiting logic. In addition, this rate limiting logic must be duplicated for any other jobs that we want to rate limit. Instead of rate limiting in the handle method, we could define a job middleware that handles rate limiting. Laravel does not have a default location for job middleware, so you are welcome to place job middleware anywhere in your application. In this example, we will place the middleware in an `app/Jobs/Middleware` directory: - block(0)->allow(1)->every(5) - ->then(function () use ($job, $next) { - // Lock obtained... - - $next($job); - }, function () use ($job) { - // Could not obtain lock... - - $job->release(5); - }); - } + Redis::throttle('key') + ->block(0)->allow(1)->every(5) + ->then(function () use ($job, $next) { + // Lock obtained... + + $next($job); + }, function () use ($job) { + // Could not obtain lock... + + $job->release(5); + }); } +} +``` As you can see, like [route middleware](/docs/{{version}}/middleware), job middleware receive the job being processed and a callback that should be invoked to continue processing the job. After creating job middleware, they may be attached to a job by returning them from the job's `middleware` method. This method does not exist on jobs scaffolded by the `make:job` Artisan command, so you will need to manually add it to your job class: - use App\Jobs\Middleware\RateLimited; +```php +use App\Jobs\Middleware\RateLimited; - /** - * Get the middleware the job should pass through. - * - * @return array - */ - public function middleware(): array - { - return [new RateLimited]; - } +/** + * Get the middleware the job should pass through. + * + * @return array + */ +public function middleware(): array +{ + return [new RateLimited]; +} +``` > [!NOTE] > Job middleware can also be assigned to queueable event listeners, mailables, and notifications. @@ -454,52 +484,60 @@ Although we just demonstrated how to write your own rate limiting job middleware For example, you may wish to allow users to backup their data once per hour while imposing no such limit on premium customers. To accomplish this, you may define a `RateLimiter` in the `boot` method of your `AppServiceProvider`: - use Illuminate\Cache\RateLimiting\Limit; - use Illuminate\Support\Facades\RateLimiter; +```php +use Illuminate\Cache\RateLimiting\Limit; +use Illuminate\Support\Facades\RateLimiter; - /** - * Bootstrap any application services. - */ - public function boot(): void - { - RateLimiter::for('backups', function (object $job) { - return $job->user->vipCustomer() - ? Limit::none() - : Limit::perHour(1)->by($job->user->id); - }); - } +/** + * Bootstrap any application services. + */ +public function boot(): void +{ + RateLimiter::for('backups', function (object $job) { + return $job->user->vipCustomer() + ? Limit::none() + : Limit::perHour(1)->by($job->user->id); + }); +} +``` In the example above, we defined an hourly rate limit; however, you may easily define a rate limit based on minutes using the `perMinute` method. In addition, you may pass any value you wish to the `by` method of the rate limit; however, this value is most often used to segment rate limits by customer: - return Limit::perMinute(50)->by($job->user->id); +```php +return Limit::perMinute(50)->by($job->user->id); +``` Once you have defined your rate limit, you may attach the rate limiter to your job using the `Illuminate\Queue\Middleware\RateLimited` middleware. Each time the job exceeds the rate limit, this middleware will release the job back to the queue with an appropriate delay based on the rate limit duration. - use Illuminate\Queue\Middleware\RateLimited; +```php +use Illuminate\Queue\Middleware\RateLimited; - /** - * Get the middleware the job should pass through. - * - * @return array - */ - public function middleware(): array - { - return [new RateLimited('backups')]; - } +/** + * Get the middleware the job should pass through. + * + * @return array + */ +public function middleware(): array +{ + return [new RateLimited('backups')]; +} +``` Releasing a rate limited job back onto the queue will still increment the job's total number of `attempts`. You may wish to tune your `tries` and `maxExceptions` properties on your job class accordingly. Or, you may wish to use the [`retryUntil` method](#time-based-attempts) to define the amount of time until the job should no longer be attempted. If you do not want a job to be retried when it is rate limited, you may use the `dontRelease` method: - /** - * Get the middleware the job should pass through. - * - * @return array - */ - public function middleware(): array - { - return [(new RateLimited('backups'))->dontRelease()]; - } +```php +/** + * Get the middleware the job should pass through. + * + * @return array + */ +public function middleware(): array +{ + return [(new RateLimited('backups'))->dontRelease()]; +} +``` > [!NOTE] > If you are using Redis, you may use the `Illuminate\Queue\Middleware\RateLimitedWithRedis` middleware, which is fine-tuned for Redis and more efficient than the basic rate limiting middleware. @@ -511,53 +549,61 @@ Laravel includes an `Illuminate\Queue\Middleware\WithoutOverlapping` middleware For example, let's imagine you have a queued job that updates a user's credit score and you want to prevent credit score update job overlaps for the same user ID. To accomplish this, you can return the `WithoutOverlapping` middleware from your job's `middleware` method: - use Illuminate\Queue\Middleware\WithoutOverlapping; +```php +use Illuminate\Queue\Middleware\WithoutOverlapping; - /** - * Get the middleware the job should pass through. - * - * @return array - */ - public function middleware(): array - { - return [new WithoutOverlapping($this->user->id)]; - } +/** + * Get the middleware the job should pass through. + * + * @return array + */ +public function middleware(): array +{ + return [new WithoutOverlapping($this->user->id)]; +} +``` Any overlapping jobs of the same type will be released back to the queue. You may also specify the number of seconds that must elapse before the released job will be attempted again: - /** - * Get the middleware the job should pass through. - * - * @return array - */ - public function middleware(): array - { - return [(new WithoutOverlapping($this->order->id))->releaseAfter(60)]; - } +```php +/** + * Get the middleware the job should pass through. + * + * @return array + */ +public function middleware(): array +{ + return [(new WithoutOverlapping($this->order->id))->releaseAfter(60)]; +} +``` If you wish to immediately delete any overlapping jobs so that they will not be retried, you may use the `dontRelease` method: - /** - * Get the middleware the job should pass through. - * - * @return array - */ - public function middleware(): array - { - return [(new WithoutOverlapping($this->order->id))->dontRelease()]; - } +```php +/** + * Get the middleware the job should pass through. + * + * @return array + */ +public function middleware(): array +{ + return [(new WithoutOverlapping($this->order->id))->dontRelease()]; +} +``` The `WithoutOverlapping` middleware is powered by Laravel's atomic lock feature. Sometimes, your job may unexpectedly fail or timeout in such a way that the lock is not released. Therefore, you may explicitly define a lock expiration time using the `expireAfter` method. For example, the example below will instruct Laravel to release the `WithoutOverlapping` lock three minutes after the job has started processing: - /** - * Get the middleware the job should pass through. - * - * @return array - */ - public function middleware(): array - { - return [(new WithoutOverlapping($this->order->id))->expireAfter(180)]; - } +```php +/** + * Get the middleware the job should pass through. + * + * @return array + */ +public function middleware(): array +{ + return [(new WithoutOverlapping($this->order->id))->expireAfter(180)]; +} +``` > [!WARNING] > The `WithoutOverlapping` middleware requires a cache driver that supports [locks](/docs/{{version}}/cache#atomic-locks). Currently, the `memcached`, `redis`, `dynamodb`, `database`, `file`, and `array` cache drivers support atomic locks. @@ -602,90 +648,100 @@ Laravel includes a `Illuminate\Queue\Middleware\ThrottlesExceptions` middleware For example, let's imagine a queued job that interacts with a third-party API that begins throwing exceptions. To throttle exceptions, you can return the `ThrottlesExceptions` middleware from your job's `middleware` method. Typically, this middleware should be paired with a job that implements [time based attempts](#time-based-attempts): - use DateTime; - use Illuminate\Queue\Middleware\ThrottlesExceptions; +```php +use DateTime; +use Illuminate\Queue\Middleware\ThrottlesExceptions; - /** - * Get the middleware the job should pass through. - * - * @return array - */ - public function middleware(): array - { - return [new ThrottlesExceptions(10, 5 * 60)]; - } +/** + * Get the middleware the job should pass through. + * + * @return array + */ +public function middleware(): array +{ + return [new ThrottlesExceptions(10, 5 * 60)]; +} - /** - * Determine the time at which the job should timeout. - */ - public function retryUntil(): DateTime - { - return now()->addMinutes(30); - } +/** + * Determine the time at which the job should timeout. + */ +public function retryUntil(): DateTime +{ + return now()->addMinutes(30); +} +``` The first constructor argument accepted by the middleware is the number of exceptions the job can throw before being throttled, while the second constructor argument is the number of seconds that should elapse before the job is attempted again once it has been throttled. In the code example above, if the job throws 10 consecutive exceptions, we will wait 5 minutes before attempting the job again, constrained by the 30-minute time limit. When a job throws an exception but the exception threshold has not yet been reached, the job will typically be retried immediately. However, you may specify the number of minutes such a job should be delayed by calling the `backoff` method when attaching the middleware to the job: - use Illuminate\Queue\Middleware\ThrottlesExceptions; +```php +use Illuminate\Queue\Middleware\ThrottlesExceptions; - /** - * Get the middleware the job should pass through. - * - * @return array - */ - public function middleware(): array - { - return [(new ThrottlesExceptions(10, 5 * 60))->backoff(5)]; - } +/** + * Get the middleware the job should pass through. + * + * @return array + */ +public function middleware(): array +{ + return [(new ThrottlesExceptions(10, 5 * 60))->backoff(5)]; +} +``` Internally, this middleware uses Laravel's cache system to implement rate limiting, and the job's class name is utilized as the cache "key". You may override this key by calling the `by` method when attaching the middleware to your job. This may be useful if you have multiple jobs interacting with the same third-party service and you would like them to share a common throttling "bucket": - use Illuminate\Queue\Middleware\ThrottlesExceptions; +```php +use Illuminate\Queue\Middleware\ThrottlesExceptions; - /** - * Get the middleware the job should pass through. - * - * @return array - */ - public function middleware(): array - { - return [(new ThrottlesExceptions(10, 10 * 60))->by('key')]; - } +/** + * Get the middleware the job should pass through. + * + * @return array + */ +public function middleware(): array +{ + return [(new ThrottlesExceptions(10, 10 * 60))->by('key')]; +} +``` By default, this middleware will throttle every exception. You can modify this behaviour by invoking the `when` method when attaching the middleware to your job. The exception will then only be throttled if closure provided to the `when` method returns `true`: - use Illuminate\Http\Client\HttpClientException; - use Illuminate\Queue\Middleware\ThrottlesExceptions; - - /** - * Get the middleware the job should pass through. - * - * @return array - */ - public function middleware(): array - { - return [(new ThrottlesExceptions(10, 10 * 60))->when( - fn (Throwable $throwable) => $throwable instanceof HttpClientException - )]; - } +```php +use Illuminate\Http\Client\HttpClientException; +use Illuminate\Queue\Middleware\ThrottlesExceptions; + +/** + * Get the middleware the job should pass through. + * + * @return array + */ +public function middleware(): array +{ + return [(new ThrottlesExceptions(10, 10 * 60))->when( + fn (Throwable $throwable) => $throwable instanceof HttpClientException + )]; +} +``` If you would like to have the throttled exceptions reported to your application's exception handler, you can do so by invoking the `report` method when attaching the middleware to your job. Optionally, you may provide a closure to the `report` method and the exception will only be reported if the given closure returns `true`: - use Illuminate\Http\Client\HttpClientException; - use Illuminate\Queue\Middleware\ThrottlesExceptions; +```php +use Illuminate\Http\Client\HttpClientException; +use Illuminate\Queue\Middleware\ThrottlesExceptions; - /** - * Get the middleware the job should pass through. - * - * @return array - */ - public function middleware(): array - { - return [(new ThrottlesExceptions(10, 10 * 60))->report( - fn (Throwable $throwable) => $throwable instanceof HttpClientException - )]; - } +/** + * Get the middleware the job should pass through. + * + * @return array + */ +public function middleware(): array +{ + return [(new ThrottlesExceptions(10, 10 * 60))->report( + fn (Throwable $throwable) => $throwable instanceof HttpClientException + )]; +} +``` > [!NOTE] > If you are using Redis, you may use the `Illuminate\Queue\Middleware\ThrottlesExceptionsWithRedis` middleware, which is fine-tuned for Redis and more efficient than the basic exception throttling middleware. @@ -695,71 +751,79 @@ If you would like to have the throttled exceptions reported to your application' The `Skip` middleware allows you to specify that a job should be skipped / deleted without needing to modify the job's logic. The `Skip::when` method will delete the job if the given condition evaluates to `true`, while the `Skip::unless` method will delete the job if the condition evaluates to `false`: - use Illuminate\Queue\Middleware\Skip; +```php +use Illuminate\Queue\Middleware\Skip; - /** - * Get the middleware the job should pass through. - */ - public function middleware(): array - { - return [ - Skip::when($someCondition), - ]; - } +/** + * Get the middleware the job should pass through. + */ +public function middleware(): array +{ + return [ + Skip::when($someCondition), + ]; +} +``` You can also pass a `Closure` to the `when` and `unless` methods for more complex conditional evaluation: - use Illuminate\Queue\Middleware\Skip; +```php +use Illuminate\Queue\Middleware\Skip; - /** - * Get the middleware the job should pass through. - */ - public function middleware(): array - { - return [ - Skip::when(function (): bool { - return $this->shouldSkip(); - }), - ]; - } +/** + * Get the middleware the job should pass through. + */ +public function middleware(): array +{ + return [ + Skip::when(function (): bool { + return $this->shouldSkip(); + }), + ]; +} +``` ## Dispatching Jobs Once you have written your job class, you may dispatch it using the `dispatch` method on the job itself. The arguments passed to the `dispatch` method will be given to the job's constructor: - delay(now()->addMinutes(10)); + ProcessPodcast::dispatch($podcast) + ->delay(now()->addMinutes(10)); - return redirect('/podcasts'); - } + return redirect('/podcasts'); } +} +``` In some cases, jobs may have a default delay configured. If you need to bypass this delay and dispatch a job for immediate processing, you may use the `withoutDelay` method: - ProcessPodcast::dispatch($podcast)->withoutDelay(); +```php +ProcessPodcast::dispatch($podcast)->withoutDelay(); +``` > [!WARNING] > The Amazon SQS queue service has a maximum delay time of 15 minutes. @@ -808,50 +876,56 @@ In some cases, jobs may have a default delay configured. If you need to bypass t Alternatively, the `dispatchAfterResponse` method delays dispatching a job until after the HTTP response is sent to the user's browser if your web server is using FastCGI. This will still allow the user to begin using the application even though a queued job is still executing. This should typically only be used for jobs that take about a second, such as sending an email. Since they are processed within the current HTTP request, jobs dispatched in this fashion do not require a queue worker to be running in order for them to be processed: - use App\Jobs\SendNotification; +```php +use App\Jobs\SendNotification; - SendNotification::dispatchAfterResponse(); +SendNotification::dispatchAfterResponse(); +``` You may also `dispatch` a closure and chain the `afterResponse` method onto the `dispatch` helper to execute a closure after the HTTP response has been sent to the browser: - use App\Mail\WelcomeMessage; - use Illuminate\Support\Facades\Mail; +```php +use App\Mail\WelcomeMessage; +use Illuminate\Support\Facades\Mail; - dispatch(function () { - Mail::to('taylor@example.com')->send(new WelcomeMessage); - })->afterResponse(); +dispatch(function () { + Mail::to('taylor@example.com')->send(new WelcomeMessage); +})->afterResponse(); +``` ### Synchronous Dispatching If you would like to dispatch a job immediately (synchronously), you may use the `dispatchSync` method. When using this method, the job will not be queued and will be executed immediately within the current process: - ### Jobs & Database Transactions @@ -860,11 +934,13 @@ While it is perfectly fine to dispatch jobs within database transactions, you sh Thankfully, Laravel provides several methods of working around this problem. First, you may set the `after_commit` connection option in your queue connection's configuration array: - 'redis' => [ - 'driver' => 'redis', - // ... - 'after_commit' => true, - ], +```php +'redis' => [ + 'driver' => 'redis', + // ... + 'after_commit' => true, +], +``` When the `after_commit` option is `true`, you may dispatch jobs within database transactions; however, Laravel will wait until the open parent database transactions have been committed before actually dispatching the job. Of course, if no database transactions are currently open, the job will be dispatched immediately. @@ -878,39 +954,47 @@ If a transaction is rolled back due to an exception that occurs during the trans If you do not set the `after_commit` queue connection configuration option to `true`, you may still indicate that a specific job should be dispatched after all open database transactions have been committed. To accomplish this, you may chain the `afterCommit` method onto your dispatch operation: - use App\Jobs\ProcessPodcast; +```php +use App\Jobs\ProcessPodcast; - ProcessPodcast::dispatch($podcast)->afterCommit(); +ProcessPodcast::dispatch($podcast)->afterCommit(); +``` Likewise, if the `after_commit` configuration option is set to `true`, you may indicate that a specific job should be dispatched immediately without waiting for any open database transactions to commit: - ProcessPodcast::dispatch($podcast)->beforeCommit(); +```php +ProcessPodcast::dispatch($podcast)->beforeCommit(); +``` ### Job Chaining Job chaining allows you to specify a list of queued jobs that should be run in sequence after the primary job has executed successfully. If one job in the sequence fails, the rest of the jobs will not be run. To execute a queued job chain, you may use the `chain` method provided by the `Bus` facade. Laravel's command bus is a lower level component that queued job dispatching is built on top of: - use App\Jobs\OptimizePodcast; - use App\Jobs\ProcessPodcast; - use App\Jobs\ReleasePodcast; - use Illuminate\Support\Facades\Bus; +```php +use App\Jobs\OptimizePodcast; +use App\Jobs\ProcessPodcast; +use App\Jobs\ReleasePodcast; +use Illuminate\Support\Facades\Bus; - Bus::chain([ - new ProcessPodcast, - new OptimizePodcast, - new ReleasePodcast, - ])->dispatch(); +Bus::chain([ + new ProcessPodcast, + new OptimizePodcast, + new ReleasePodcast, +])->dispatch(); +``` In addition to chaining job class instances, you may also chain closures: - Bus::chain([ - new ProcessPodcast, - new OptimizePodcast, - function () { - Podcast::update(/* ... */); - }, - ])->dispatch(); +```php +Bus::chain([ + new ProcessPodcast, + new OptimizePodcast, + function () { + Podcast::update(/* ... */); + }, +])->dispatch(); +``` > [!WARNING] > Deleting jobs using the `$this->delete()` method within the job will not prevent chained jobs from being processed. The chain will only stop executing if a job in the chain fails. @@ -920,11 +1004,13 @@ In addition to chaining job class instances, you may also chain closures: If you would like to specify the connection and queue that should be used for the chained jobs, you may use the `onConnection` and `onQueue` methods. These methods specify the queue connection and queue name that should be used unless the queued job is explicitly assigned a different connection / queue: - Bus::chain([ - new ProcessPodcast, - new OptimizePodcast, - new ReleasePodcast, - ])->onConnection('redis')->onQueue('podcasts')->dispatch(); +```php +Bus::chain([ + new ProcessPodcast, + new OptimizePodcast, + new ReleasePodcast, +])->onConnection('redis')->onQueue('podcasts')->dispatch(); +``` #### Adding Jobs to the Chain @@ -952,16 +1038,18 @@ public function handle(): void When chaining jobs, you may use the `catch` method to specify a closure that should be invoked if a job within the chain fails. The given callback will receive the `Throwable` instance that caused the job failure: - use Illuminate\Support\Facades\Bus; - use Throwable; +```php +use Illuminate\Support\Facades\Bus; +use Throwable; - Bus::chain([ - new ProcessPodcast, - new OptimizePodcast, - new ReleasePodcast, - ])->catch(function (Throwable $e) { - // A job within the chain has failed... - })->dispatch(); +Bus::chain([ + new ProcessPodcast, + new OptimizePodcast, + new ReleasePodcast, +])->catch(function (Throwable $e) { + // A job within the chain has failed... +})->dispatch(); +``` > [!WARNING] > Since chain callbacks are serialized and executed at a later time by the Laravel queue, you should not use the `$this` variable within chain callbacks. @@ -974,114 +1062,124 @@ When chaining jobs, you may use the `catch` method to specify a closure that sho By pushing jobs to different queues, you may "categorize" your queued jobs and even prioritize how many workers you assign to various queues. Keep in mind, this does not push jobs to different queue "connections" as defined by your queue configuration file, but only to specific queues within a single connection. To specify the queue, use the `onQueue` method when dispatching the job: - onQueue('processing'); + ProcessPodcast::dispatch($podcast)->onQueue('processing'); - return redirect('/podcasts'); - } + return redirect('/podcasts'); } +} +``` Alternatively, you may specify the job's queue by calling the `onQueue` method within the job's constructor: - onQueue('processing'); - } + $this->onQueue('processing'); } +} +``` #### Dispatching to a Particular Connection If your application interacts with multiple queue connections, you may specify which connection to push a job to using the `onConnection` method: - onConnection('sqs'); + ProcessPodcast::dispatch($podcast)->onConnection('sqs'); - return redirect('/podcasts'); - } + return redirect('/podcasts'); } +} +``` You may chain the `onConnection` and `onQueue` methods together to specify the connection and the queue for a job: - ProcessPodcast::dispatch($podcast) - ->onConnection('sqs') - ->onQueue('processing'); +```php +ProcessPodcast::dispatch($podcast) + ->onConnection('sqs') + ->onQueue('processing'); +``` Alternatively, you may specify the job's connection by calling the `onConnection` method within the job's constructor: - onConnection('sqs'); - } + $this->onConnection('sqs'); } +} +``` ### Specifying Max Job Attempts / Timeout Values @@ -1101,44 +1199,50 @@ If a job exceeds its maximum number of attempts, it will be considered a "failed You may take a more granular approach by defining the maximum number of times a job may be attempted on the job class itself. If the maximum number of attempts is specified on the job, it will take precedence over the `--tries` value provided on the command line: - #### Time Based Attempts As an alternative to defining how many times a job may be attempted before it fails, you may define a time at which the job should no longer be attempted. This allows a job to be attempted any number of times within a given time frame. To define the time at which a job should no longer be attempted, add a `retryUntil` method to your job class. This method should return a `DateTime` instance: - use DateTime; +```php +use DateTime; - /** - * Determine the time at which the job should timeout. - */ - public function retryUntil(): DateTime - { - return now()->addMinutes(10); - } +/** + * Determine the time at which the job should timeout. + */ +public function retryUntil(): DateTime +{ + return now()->addMinutes(10); +} +``` > [!NOTE] > You may also define a `tries` property or `retryUntil` method on your [queued event listeners](/docs/{{version}}/events#queued-event-listeners). @@ -1148,41 +1252,43 @@ As an alternative to defining how many times a job may be attempted before it fa Sometimes you may wish to specify that a job may be attempted many times, but should fail if the retries are triggered by a given number of unhandled exceptions (as opposed to being released by the `release` method directly). To accomplish this, you may define a `maxExceptions` property on your job class: - allow(10)->every(60)->then(function () { - // Lock obtained, process the podcast... - }, function () { - // Unable to obtain lock... - return $this->release(10); - }); - } + Redis::throttle('key')->allow(10)->every(60)->then(function () { + // Lock obtained, process the podcast... + }, function () { + // Unable to obtain lock... + return $this->release(10); + }); } +} +``` In this example, the job is released for ten seconds if the application is unable to obtain a Redis lock and will continue to be retried up to 25 times. However, the job will fail if three unhandled exceptions are thrown by the job. @@ -1201,19 +1307,21 @@ If the job exceeds its maximum attempts by continually timing out, it will be ma You may also define the maximum number of seconds a job should be allowed to run on the job class itself. If the timeout is specified on the job, it will take precedence over any timeout specified on the command line: - release(); - } + $this->release(); +} +``` By default, the `release` method will release the job back onto the queue for immediate processing. However, you may instruct the queue to not make the job available for processing until a given number of seconds has elapsed by passing an integer or date instance to the `release` method: - $this->release(10); +```php +$this->release(10); - $this->release(now()->addSeconds(10)); +$this->release(now()->addSeconds(10)); +``` #### Manually Failing a Job Occasionally you may need to manually mark a job as "failed". To do so, you may call the `fail` method: - /** - * Execute the job. - */ - public function handle(): void - { - // ... +```php +/** + * Execute the job. + */ +public function handle(): void +{ + // ... - $this->fail(); - } + $this->fail(); +} +``` If you would like to mark your job as failed because of an exception that you have caught, you may pass the exception to the `fail` method. Or, for convenience, you may pass a string error message which will be converted to an exception for you: - $this->fail($exception); +```php +$this->fail($exception); - $this->fail('Something went wrong.'); +$this->fail('Something went wrong.'); +``` > [!NOTE] > For more information on failed jobs, check out the [documentation on dealing with job failures](#dealing-with-failed-jobs). @@ -1300,62 +1416,66 @@ php artisan migrate To define a batchable job, you should [create a queueable job](#creating-jobs) as normal; however, you should add the `Illuminate\Bus\Batchable` trait to the job class. This trait provides access to a `batch` method which may be used to retrieve the current batch that the job is executing within: - batch()->cancelled()) { - // Determine if the batch has been cancelled... - - return; - } + if ($this->batch()->cancelled()) { + // Determine if the batch has been cancelled... - // Import a portion of the CSV file... + return; } + + // Import a portion of the CSV file... } +} +``` ### Dispatching Batches To dispatch a batch of jobs, you should use the `batch` method of the `Bus` facade. Of course, batching is primarily useful when combined with completion callbacks. So, you may use the `then`, `catch`, and `finally` methods to define completion callbacks for the batch. Each of these callbacks will receive an `Illuminate\Bus\Batch` instance when they are invoked. In this example, we will imagine we are queueing a batch of jobs that each process a given number of rows from a CSV file: - use App\Jobs\ImportCsv; - use Illuminate\Bus\Batch; - use Illuminate\Support\Facades\Bus; - use Throwable; - - $batch = Bus::batch([ - new ImportCsv(1, 100), - new ImportCsv(101, 200), - new ImportCsv(201, 300), - new ImportCsv(301, 400), - new ImportCsv(401, 500), - ])->before(function (Batch $batch) { - // The batch has been created but no jobs have been added... - })->progress(function (Batch $batch) { - // A single job has completed successfully... - })->then(function (Batch $batch) { - // All jobs completed successfully... - })->catch(function (Batch $batch, Throwable $e) { - // First batch job failure detected... - })->finally(function (Batch $batch) { - // The batch has finished executing... - })->dispatch(); - - return $batch->id; +```php +use App\Jobs\ImportCsv; +use Illuminate\Bus\Batch; +use Illuminate\Support\Facades\Bus; +use Throwable; + +$batch = Bus::batch([ + new ImportCsv(1, 100), + new ImportCsv(101, 200), + new ImportCsv(201, 300), + new ImportCsv(301, 400), + new ImportCsv(401, 500), +])->before(function (Batch $batch) { + // The batch has been created but no jobs have been added... +})->progress(function (Batch $batch) { + // A single job has completed successfully... +})->then(function (Batch $batch) { + // All jobs completed successfully... +})->catch(function (Batch $batch, Throwable $e) { + // First batch job failure detected... +})->finally(function (Batch $batch) { + // The batch has finished executing... +})->dispatch(); + +return $batch->id; +``` The batch's ID, which may be accessed via the `$batch->id` property, may be used to [query the Laravel command bus](#inspecting-batches) for information about the batch after it has been dispatched. @@ -1367,97 +1487,109 @@ The batch's ID, which may be accessed via the `$batch->id` property, may be used Some tools such as Laravel Horizon and Laravel Telescope may provide more user-friendly debug information for batches if batches are named. To assign an arbitrary name to a batch, you may call the `name` method while defining the batch: - $batch = Bus::batch([ - // ... - ])->then(function (Batch $batch) { - // All jobs completed successfully... - })->name('Import CSV')->dispatch(); +```php +$batch = Bus::batch([ + // ... +])->then(function (Batch $batch) { + // All jobs completed successfully... +})->name('Import CSV')->dispatch(); +``` #### Batch Connection and Queue If you would like to specify the connection and queue that should be used for the batched jobs, you may use the `onConnection` and `onQueue` methods. All batched jobs must execute within the same connection and queue: - $batch = Bus::batch([ - // ... - ])->then(function (Batch $batch) { - // All jobs completed successfully... - })->onConnection('redis')->onQueue('imports')->dispatch(); +```php +$batch = Bus::batch([ + // ... +])->then(function (Batch $batch) { + // All jobs completed successfully... +})->onConnection('redis')->onQueue('imports')->dispatch(); +``` ### Chains and Batches You may define a set of [chained jobs](#job-chaining) within a batch by placing the chained jobs within an array. For example, we may execute two job chains in parallel and execute a callback when both job chains have finished processing: - use App\Jobs\ReleasePodcast; - use App\Jobs\SendPodcastReleaseNotification; - use Illuminate\Bus\Batch; - use Illuminate\Support\Facades\Bus; - - Bus::batch([ - [ - new ReleasePodcast(1), - new SendPodcastReleaseNotification(1), - ], - [ - new ReleasePodcast(2), - new SendPodcastReleaseNotification(2), - ], - ])->then(function (Batch $batch) { - // ... - })->dispatch(); +```php +use App\Jobs\ReleasePodcast; +use App\Jobs\SendPodcastReleaseNotification; +use Illuminate\Bus\Batch; +use Illuminate\Support\Facades\Bus; + +Bus::batch([ + [ + new ReleasePodcast(1), + new SendPodcastReleaseNotification(1), + ], + [ + new ReleasePodcast(2), + new SendPodcastReleaseNotification(2), + ], +])->then(function (Batch $batch) { + // ... +})->dispatch(); +``` Conversely, you may run batches of jobs within a [chain](#job-chaining) by defining batches within the chain. For example, you could first run a batch of jobs to release multiple podcasts then a batch of jobs to send the release notifications: - use App\Jobs\FlushPodcastCache; - use App\Jobs\ReleasePodcast; - use App\Jobs\SendPodcastReleaseNotification; - use Illuminate\Support\Facades\Bus; - - Bus::chain([ - new FlushPodcastCache, - Bus::batch([ - new ReleasePodcast(1), - new ReleasePodcast(2), - ]), - Bus::batch([ - new SendPodcastReleaseNotification(1), - new SendPodcastReleaseNotification(2), - ]), - ])->dispatch(); +```php +use App\Jobs\FlushPodcastCache; +use App\Jobs\ReleasePodcast; +use App\Jobs\SendPodcastReleaseNotification; +use Illuminate\Support\Facades\Bus; + +Bus::chain([ + new FlushPodcastCache, + Bus::batch([ + new ReleasePodcast(1), + new ReleasePodcast(2), + ]), + Bus::batch([ + new SendPodcastReleaseNotification(1), + new SendPodcastReleaseNotification(2), + ]), +])->dispatch(); +``` ### Adding Jobs to Batches Sometimes it may be useful to add additional jobs to a batch from within a batched job. This pattern can be useful when you need to batch thousands of jobs which may take too long to dispatch during a web request. So, instead, you may wish to dispatch an initial batch of "loader" jobs that hydrate the batch with even more jobs: - $batch = Bus::batch([ - new LoadImportBatch, - new LoadImportBatch, - new LoadImportBatch, - ])->then(function (Batch $batch) { - // All jobs completed successfully... - })->name('Import Contacts')->dispatch(); +```php +$batch = Bus::batch([ + new LoadImportBatch, + new LoadImportBatch, + new LoadImportBatch, +])->then(function (Batch $batch) { + // All jobs completed successfully... +})->name('Import Contacts')->dispatch(); +``` In this example, we will use the `LoadImportBatch` job to hydrate the batch with additional jobs. To accomplish this, we may use the `add` method on the batch instance that may be accessed via the job's `batch` method: - use App\Jobs\ImportContacts; - use Illuminate\Support\Collection; - - /** - * Execute the job. - */ - public function handle(): void - { - if ($this->batch()->cancelled()) { - return; - } +```php +use App\Jobs\ImportContacts; +use Illuminate\Support\Collection; - $this->batch()->add(Collection::times(1000, function () { - return new ImportContacts; - })); +/** + * Execute the job. + */ +public function handle(): void +{ + if ($this->batch()->cancelled()) { + return; } + $this->batch()->add(Collection::times(1000, function () { + return new ImportContacts; + })); +} +``` + > [!WARNING] > You may only add jobs to a batch from within a job that belongs to the same batch. @@ -1466,35 +1598,37 @@ In this example, we will use the `LoadImportBatch` job to hydrate the batch with The `Illuminate\Bus\Batch` instance that is provided to batch completion callbacks has a variety of properties and methods to assist you in interacting with and inspecting a given batch of jobs: - // The UUID of the batch... - $batch->id; +```php +// The UUID of the batch... +$batch->id; - // The name of the batch (if applicable)... - $batch->name; +// The name of the batch (if applicable)... +$batch->name; - // The number of jobs assigned to the batch... - $batch->totalJobs; +// The number of jobs assigned to the batch... +$batch->totalJobs; - // The number of jobs that have not been processed by the queue... - $batch->pendingJobs; +// The number of jobs that have not been processed by the queue... +$batch->pendingJobs; - // The number of jobs that have failed... - $batch->failedJobs; +// The number of jobs that have failed... +$batch->failedJobs; - // The number of jobs that have been processed thus far... - $batch->processedJobs(); +// The number of jobs that have been processed thus far... +$batch->processedJobs(); - // The completion percentage of the batch (0-100)... - $batch->progress(); +// The completion percentage of the batch (0-100)... +$batch->progress(); - // Indicates if the batch has finished executing... - $batch->finished(); +// Indicates if the batch has finished executing... +$batch->finished(); - // Cancel the execution of the batch... - $batch->cancel(); +// Cancel the execution of the batch... +$batch->cancel(); - // Indicates if the batch has been cancelled... - $batch->cancelled(); +// Indicates if the batch has been cancelled... +$batch->cancelled(); +``` #### Returning Batches From Routes @@ -1503,43 +1637,49 @@ All `Illuminate\Bus\Batch` instances are JSON serializable, meaning you can retu To retrieve a batch by its ID, you may use the `Bus` facade's `findBatch` method: - use Illuminate\Support\Facades\Bus; - use Illuminate\Support\Facades\Route; +```php +use Illuminate\Support\Facades\Bus; +use Illuminate\Support\Facades\Route; - Route::get('/batch/{batchId}', function (string $batchId) { - return Bus::findBatch($batchId); - }); +Route::get('/batch/{batchId}', function (string $batchId) { + return Bus::findBatch($batchId); +}); +``` ### Cancelling Batches Sometimes you may need to cancel a given batch's execution. This can be accomplished by calling the `cancel` method on the `Illuminate\Bus\Batch` instance: - /** - * Execute the job. - */ - public function handle(): void - { - if ($this->user->exceedsImportLimit()) { - return $this->batch()->cancel(); - } +```php +/** + * Execute the job. + */ +public function handle(): void +{ + if ($this->user->exceedsImportLimit()) { + return $this->batch()->cancel(); + } - if ($this->batch()->cancelled()) { - return; - } + if ($this->batch()->cancelled()) { + return; } +} +``` As you may have noticed in the previous examples, batched jobs should typically determine if their corresponding batch has been cancelled before continuing execution. However, for convenience, you may assign the `SkipIfBatchCancelled` [middleware](#job-middleware) to the job instead. As its name indicates, this middleware will instruct Laravel to not process the job if its corresponding batch has been cancelled: - use Illuminate\Queue\Middleware\SkipIfBatchCancelled; +```php +use Illuminate\Queue\Middleware\SkipIfBatchCancelled; - /** - * Get the middleware the job should pass through. - */ - public function middleware(): array - { - return [new SkipIfBatchCancelled]; - } +/** + * Get the middleware the job should pass through. + */ +public function middleware(): array +{ + return [new SkipIfBatchCancelled]; +} +``` ### Batch Failures @@ -1551,11 +1691,13 @@ When a batched job fails, the `catch` callback (if assigned) will be invoked. Th When a job within a batch fails, Laravel will automatically mark the batch as "cancelled". If you wish, you may disable this behavior so that a job failure does not automatically mark the batch as cancelled. This may be accomplished by calling the `allowFailures` method while dispatching the batch: - $batch = Bus::batch([ - // ... - ])->then(function (Batch $batch) { - // All jobs completed successfully... - })->allowFailures()->dispatch(); +```php +$batch = Bus::batch([ + // ... +])->then(function (Batch $batch) { + // All jobs completed successfully... +})->allowFailures()->dispatch(); +``` #### Retrying Failed Batch Jobs @@ -1571,27 +1713,35 @@ php artisan queue:retry-batch 32dbc76c-4f82-4749-b610-a639fe0099b5 Without pruning, the `job_batches` table can accumulate records very quickly. To mitigate this, you should [schedule](/docs/{{version}}/scheduling) the `queue:prune-batches` Artisan command to run daily: - use Illuminate\Support\Facades\Schedule; +```php +use Illuminate\Support\Facades\Schedule; - Schedule::command('queue:prune-batches')->daily(); +Schedule::command('queue:prune-batches')->daily(); +``` By default, all finished batches that are more than 24 hours old will be pruned. You may use the `hours` option when calling the command to determine how long to retain batch data. For example, the following command will delete all batches that finished over 48 hours ago: - use Illuminate\Support\Facades\Schedule; +```php +use Illuminate\Support\Facades\Schedule; - Schedule::command('queue:prune-batches --hours=48')->daily(); +Schedule::command('queue:prune-batches --hours=48')->daily(); +``` Sometimes, your `jobs_batches` table may accumulate batch records for batches that never completed successfully, such as batches where a job failed and that job was never retried successfully. You may instruct the `queue:prune-batches` command to prune these unfinished batch records using the `unfinished` option: - use Illuminate\Support\Facades\Schedule; +```php +use Illuminate\Support\Facades\Schedule; - Schedule::command('queue:prune-batches --hours=48 --unfinished=72')->daily(); +Schedule::command('queue:prune-batches --hours=48 --unfinished=72')->daily(); +``` Likewise, your `jobs_batches` table may also accumulate batch records for cancelled batches. You may instruct the `queue:prune-batches` command to prune these cancelled batch records using the `cancelled` option: - use Illuminate\Support\Facades\Schedule; +```php +use Illuminate\Support\Facades\Schedule; - Schedule::command('queue:prune-batches --hours=48 --cancelled=72')->daily(); +Schedule::command('queue:prune-batches --hours=48 --cancelled=72')->daily(); +``` ### Storing Batches in DynamoDB @@ -1652,21 +1802,25 @@ If you defined your DynamoDB table with a `ttl` attribute, you may define config Instead of dispatching a job class to the queue, you may also dispatch a closure. This is great for quick, simple tasks that need to be executed outside of the current request cycle. When dispatching closures to the queue, the closure's code content is cryptographically signed so that it cannot be modified in transit: - $podcast = App\Podcast::find(1); +```php +$podcast = App\Podcast::find(1); - dispatch(function () use ($podcast) { - $podcast->publish(); - }); +dispatch(function () use ($podcast) { + $podcast->publish(); +}); +``` Using the `catch` method, you may provide a closure that should be executed if the queued closure fails to complete successfully after exhausting all of your queue's [configured retry attempts](#max-job-attempts-and-timeout): - use Throwable; +```php +use Throwable; - dispatch(function () use ($podcast) { - $podcast->publish(); - })->catch(function (Throwable $e) { - // This job has failed... - }); +dispatch(function () use ($podcast) { + $podcast->publish(); +})->catch(function (Throwable $e) { + // This job has failed... +}); +``` > [!WARNING] > Since `catch` callbacks are serialized and executed at a later time by the Laravel queue, you should not use the `$this` variable within `catch` callbacks. @@ -1784,7 +1938,9 @@ Daemon queue workers do not "reboot" the framework before processing each job. T Sometimes you may wish to prioritize how your queues are processed. For example, in your `config/queue.php` configuration file, you may set the default `queue` for your `redis` connection to `low`. However, occasionally you may wish to push a job to a `high` priority queue like so: - dispatch((new Job)->onQueue('high')); +```php +dispatch((new Job)->onQueue('high')); +``` To start a worker that verifies that all of the `high` queue jobs are processed before continuing to any jobs on the `low` queue, pass a comma-delimited list of queue names to the `work` command: @@ -1917,77 +2073,85 @@ php artisan queue:work redis --tries=3 --backoff=3 If you would like to configure how many seconds Laravel should wait before retrying a job that has encountered an exception on a per-job basis, you may do so by defining a `backoff` property on your job class: - /** - * The number of seconds to wait before retrying the job. - * - * @var int - */ - public $backoff = 3; +```php +/** + * The number of seconds to wait before retrying the job. + * + * @var int + */ +public $backoff = 3; +``` If you require more complex logic for determining the job's backoff time, you may define a `backoff` method on your job class: - /** - * Calculate the number of seconds to wait before retrying the job. - */ - public function backoff(): int - { - return 3; - } +```php +/** + * Calculate the number of seconds to wait before retrying the job. + */ +public function backoff(): int +{ + return 3; +} +``` You may easily configure "exponential" backoffs by returning an array of backoff values from the `backoff` method. In this example, the retry delay will be 1 second for the first retry, 5 seconds for the second retry, 10 seconds for the third retry, and 10 seconds for every subsequent retry if there are more attempts remaining: - /** - * Calculate the number of seconds to wait before retrying the job. - * - * @return array - */ - public function backoff(): array - { - return [1, 5, 10]; - } +```php +/** + * Calculate the number of seconds to wait before retrying the job. + * + * @return array + */ +public function backoff(): array +{ + return [1, 5, 10]; +} +``` ### Cleaning Up After Failed Jobs When a particular job fails, you may want to send an alert to your users or revert any actions that were partially completed by the job. To accomplish this, you may define a `failed` method on your job class. The `Throwable` instance that caused the job to fail will be passed to the `failed` method: - [!WARNING] > A new instance of the job is instantiated before invoking the `failed` method; therefore, any class property modifications that may have occurred within the `handle` method will be lost. @@ -2047,12 +2211,14 @@ When injecting an Eloquent model into a job, the model is automatically serializ For convenience, you may choose to automatically delete jobs with missing models by setting your job's `deleteWhenMissingModels` property to `true`. When this property is set to `true`, Laravel will quietly discard the job without raising an exception: - /** - * Delete the job if its models no longer exist. - * - * @var bool - */ - public $deleteWhenMissingModels = true; +```php +/** + * Delete the job if its models no longer exist. + * + * @var bool + */ +public $deleteWhenMissingModels = true; +``` ### Pruning Failed Jobs @@ -2108,36 +2274,38 @@ QUEUE_FAILED_DRIVER=null If you would like to register an event listener that will be invoked when a job fails, you may use the `Queue` facade's `failing` method. For example, we may attach a closure to this event from the `boot` method of the `AppServiceProvider` that is included with Laravel: - connectionName - // $event->job - // $event->exception - }); - } + /** + * Bootstrap any application services. + */ + public function boot(): void + { + Queue::failing(function (JobFailed $event) { + // $event->connectionName + // $event->job + // $event->exception + }); } +} +``` ## Clearing Jobs From Queues @@ -2277,9 +2445,11 @@ class ExampleTest extends TestCase You may pass a closure to the `assertPushed` or `assertNotPushed` methods in order to assert that a job was pushed that passes a given "truth test". If at least one job was pushed that passes the given truth test then the assertion will be successful: - Queue::assertPushed(function (ShipOrder $job) use ($order) { - return $job->order->id === $order->id; - }); +```php +Queue::assertPushed(function (ShipOrder $job) use ($order) { + return $job->order->id === $order->id; +}); +``` ### Faking a Subset of Jobs @@ -2315,41 +2485,49 @@ public function test_orders_can_be_shipped(): void You may fake all jobs except for a set of specified jobs using the `except` method: - Queue::fake()->except([ - ShipOrder::class, - ]); +```php +Queue::fake()->except([ + ShipOrder::class, +]); +``` ### Testing Job Chains To test job chains, you will need to utilize the `Bus` facade's faking capabilities. The `Bus` facade's `assertChained` method may be used to assert that a [chain of jobs](/docs/{{version}}/queues#job-chaining) was dispatched. The `assertChained` method accepts an array of chained jobs as its first argument: - use App\Jobs\RecordShipment; - use App\Jobs\ShipOrder; - use App\Jobs\UpdateInventory; - use Illuminate\Support\Facades\Bus; +```php +use App\Jobs\RecordShipment; +use App\Jobs\ShipOrder; +use App\Jobs\UpdateInventory; +use Illuminate\Support\Facades\Bus; - Bus::fake(); +Bus::fake(); - // ... +// ... - Bus::assertChained([ - ShipOrder::class, - RecordShipment::class, - UpdateInventory::class - ]); +Bus::assertChained([ + ShipOrder::class, + RecordShipment::class, + UpdateInventory::class +]); +``` As you can see in the example above, the array of chained jobs may be an array of the job's class names. However, you may also provide an array of actual job instances. When doing so, Laravel will ensure that the job instances are of the same class and have the same property values of the chained jobs dispatched by your application: - Bus::assertChained([ - new ShipOrder, - new RecordShipment, - new UpdateInventory, - ]); +```php +Bus::assertChained([ + new ShipOrder, + new RecordShipment, + new UpdateInventory, +]); +``` You may use the `assertDispatchedWithoutChain` method to assert that a job was pushed without a chain of jobs: - Bus::assertDispatchedWithoutChain(ShipOrder::class); +```php +Bus::assertDispatchedWithoutChain(ShipOrder::class); +``` #### Testing Chain Modifications @@ -2379,55 +2557,65 @@ $job->assertDoesntHaveChain(); If your job chain [contains a batch of jobs](#chains-and-batches), you may assert that the chained batch matches your expectations by inserting a `Bus::chainedBatch` definition within your chain assertion: - use App\Jobs\ShipOrder; - use App\Jobs\UpdateInventory; - use Illuminate\Bus\PendingBatch; - use Illuminate\Support\Facades\Bus; - - Bus::assertChained([ - new ShipOrder, - Bus::chainedBatch(function (PendingBatch $batch) { - return $batch->jobs->count() === 3; - }), - new UpdateInventory, - ]); +```php +use App\Jobs\ShipOrder; +use App\Jobs\UpdateInventory; +use Illuminate\Bus\PendingBatch; +use Illuminate\Support\Facades\Bus; + +Bus::assertChained([ + new ShipOrder, + Bus::chainedBatch(function (PendingBatch $batch) { + return $batch->jobs->count() === 3; + }), + new UpdateInventory, +]); +``` ### Testing Job Batches The `Bus` facade's `assertBatched` method may be used to assert that a [batch of jobs](/docs/{{version}}/queues#job-batching) was dispatched. The closure given to the `assertBatched` method receives an instance of `Illuminate\Bus\PendingBatch`, which may be used to inspect the jobs within the batch: - use Illuminate\Bus\PendingBatch; - use Illuminate\Support\Facades\Bus; +```php +use Illuminate\Bus\PendingBatch; +use Illuminate\Support\Facades\Bus; - Bus::fake(); +Bus::fake(); - // ... +// ... - Bus::assertBatched(function (PendingBatch $batch) { - return $batch->name == 'import-csv' && - $batch->jobs->count() === 10; - }); +Bus::assertBatched(function (PendingBatch $batch) { + return $batch->name == 'import-csv' && + $batch->jobs->count() === 10; +}); +``` You may use the `assertBatchCount` method to assert that a given number of batches were dispatched: - Bus::assertBatchCount(3); +```php +Bus::assertBatchCount(3); +``` You may use `assertNothingBatched` to assert that no batches were dispatched: - Bus::assertNothingBatched(); +```php +Bus::assertNothingBatched(); +``` #### Testing Job / Batch Interaction In addition, you may occasionally need to test an individual job's interaction with its underlying batch. For example, you may need to test if a job cancelled further processing for its batch. To accomplish this, you need to assign a fake batch to the job via the `withFakeBatch` method. The `withFakeBatch` method returns a tuple containing the job instance and the fake batch: - [$job, $batch] = (new ShipOrder)->withFakeBatch(); +```php +[$job, $batch] = (new ShipOrder)->withFakeBatch(); - $job->handle(); +$job->handle(); - $this->assertTrue($batch->cancelled()); - $this->assertEmpty($batch->added); +$this->assertTrue($batch->cancelled()); +$this->assertEmpty($batch->added); +``` ### Testing Job / Queue Interactions @@ -2457,51 +2645,55 @@ $job->assertNotFailed(); Using the `before` and `after` methods on the `Queue` [facade](/docs/{{version}}/facades), you may specify callbacks to be executed before or after a queued job is processed. These callbacks are a great opportunity to perform additional logging or increment statistics for a dashboard. Typically, you should call these methods from the `boot` method of a [service provider](/docs/{{version}}/providers). For example, we may use the `AppServiceProvider` that is included with Laravel: - connectionName - // $event->job - // $event->job->payload() - }); + /** + * Bootstrap any application services. + */ + public function boot(): void + { + Queue::before(function (JobProcessing $event) { + // $event->connectionName + // $event->job + // $event->job->payload() + }); - Queue::after(function (JobProcessed $event) { - // $event->connectionName - // $event->job - // $event->job->payload() - }); - } + Queue::after(function (JobProcessed $event) { + // $event->connectionName + // $event->job + // $event->job->payload() + }); } +} +``` Using the `looping` method on the `Queue` [facade](/docs/{{version}}/facades), you may specify callbacks that execute before the worker attempts to fetch a job from a queue. For example, you might register a closure to rollback any transactions that were left open by a previously failed job: - use Illuminate\Support\Facades\DB; - use Illuminate\Support\Facades\Queue; +```php +use Illuminate\Support\Facades\DB; +use Illuminate\Support\Facades\Queue; - Queue::looping(function () { - while (DB::transactionLevel() > 0) { - DB::rollBack(); - } - }); +Queue::looping(function () { + while (DB::transactionLevel() > 0) { + DB::rollBack(); + } +}); +``` diff --git a/rate-limiting.md b/rate-limiting.md index 8492dda2231..414fd162441 100644 --- a/rate-limiting.md +++ b/rate-limiting.md @@ -19,9 +19,11 @@ Laravel includes a simple to use rate limiting abstraction which, in conjunction Typically, the rate limiter utilizes your default application cache as defined by the `default` key within your application's `cache` configuration file. However, you may specify which cache driver the rate limiter should use by defining a `limiter` key within your application's `cache` configuration file: - 'default' => env('CACHE_STORE', 'database'), +```php +'default' => env('CACHE_STORE', 'database'), - 'limiter' => 'redis', +'limiter' => 'redis', +``` ## Basic Usage @@ -30,93 +32,107 @@ The `Illuminate\Support\Facades\RateLimiter` facade may be used to interact with The `attempt` method returns `false` when the callback has no remaining attempts available; otherwise, the `attempt` method will return the callback's result or `true`. The first argument accepted by the `attempt` method is a rate limiter "key", which may be any string of your choosing that represents the action being rate limited: - use Illuminate\Support\Facades\RateLimiter; +```php +use Illuminate\Support\Facades\RateLimiter; - $executed = RateLimiter::attempt( - 'send-message:'.$user->id, - $perMinute = 5, - function() { - // Send message... - } - ); - - if (! $executed) { - return 'Too many messages sent!'; +$executed = RateLimiter::attempt( + 'send-message:'.$user->id, + $perMinute = 5, + function() { + // Send message... } +); + +if (! $executed) { + return 'Too many messages sent!'; +} +``` If necessary, you may provide a fourth argument to the `attempt` method, which is the "decay rate", or the number of seconds until the available attempts are reset. For example, we can modify the example above to allow five attempts every two minutes: - $executed = RateLimiter::attempt( - 'send-message:'.$user->id, - $perTwoMinutes = 5, - function() { - // Send message... - }, - $decayRate = 120, - ); +```php +$executed = RateLimiter::attempt( + 'send-message:'.$user->id, + $perTwoMinutes = 5, + function() { + // Send message... + }, + $decayRate = 120, +); +``` ### Manually Incrementing Attempts If you would like to manually interact with the rate limiter, a variety of other methods are available. For example, you may invoke the `tooManyAttempts` method to determine if a given rate limiter key has exceeded its maximum number of allowed attempts per minute: - use Illuminate\Support\Facades\RateLimiter; +```php +use Illuminate\Support\Facades\RateLimiter; - if (RateLimiter::tooManyAttempts('send-message:'.$user->id, $perMinute = 5)) { - return 'Too many attempts!'; - } +if (RateLimiter::tooManyAttempts('send-message:'.$user->id, $perMinute = 5)) { + return 'Too many attempts!'; +} - RateLimiter::increment('send-message:'.$user->id); +RateLimiter::increment('send-message:'.$user->id); - // Send message... +// Send message... +``` Alternatively, you may use the `remaining` method to retrieve the number of attempts remaining for a given key. If a given key has retries remaining, you may invoke the `increment` method to increment the number of total attempts: - use Illuminate\Support\Facades\RateLimiter; +```php +use Illuminate\Support\Facades\RateLimiter; - if (RateLimiter::remaining('send-message:'.$user->id, $perMinute = 5)) { - RateLimiter::increment('send-message:'.$user->id); +if (RateLimiter::remaining('send-message:'.$user->id, $perMinute = 5)) { + RateLimiter::increment('send-message:'.$user->id); - // Send message... - } + // Send message... +} +``` If you would like to increment the value for a given rate limiter key by more than one, you may provide the desired amount to the `increment` method: - RateLimiter::increment('send-message:'.$user->id, amount: 5); +```php +RateLimiter::increment('send-message:'.$user->id, amount: 5); +``` #### Determining Limiter Availability When a key has no more attempts left, the `availableIn` method returns the number of seconds remaining until more attempts will be available: - use Illuminate\Support\Facades\RateLimiter; +```php +use Illuminate\Support\Facades\RateLimiter; - if (RateLimiter::tooManyAttempts('send-message:'.$user->id, $perMinute = 5)) { - $seconds = RateLimiter::availableIn('send-message:'.$user->id); +if (RateLimiter::tooManyAttempts('send-message:'.$user->id, $perMinute = 5)) { + $seconds = RateLimiter::availableIn('send-message:'.$user->id); - return 'You may try again in '.$seconds.' seconds.'; - } + return 'You may try again in '.$seconds.' seconds.'; +} - RateLimiter::increment('send-message:'.$user->id); +RateLimiter::increment('send-message:'.$user->id); - // Send message... +// Send message... +``` ### Clearing Attempts You may reset the number of attempts for a given rate limiter key using the `clear` method. For example, you may reset the number of attempts when a given message is read by the receiver: - use App\Models\Message; - use Illuminate\Support\Facades\RateLimiter; +```php +use App\Models\Message; +use Illuminate\Support\Facades\RateLimiter; - /** - * Mark the message as read. - */ - public function read(Message $message): Message - { - $message->markAsRead(); +/** + * Mark the message as read. + */ +public function read(Message $message): Message +{ + $message->markAsRead(); - RateLimiter::clear('send-message:'.$message->user_id); + RateLimiter::clear('send-message:'.$message->user_id); - return $message; - } + return $message; +} +``` diff --git a/redirects.md b/redirects.md index 00f5e814230..a4fbf8772d2 100644 --- a/redirects.md +++ b/redirects.md @@ -10,88 +10,112 @@ Redirect responses are instances of the `Illuminate\Http\RedirectResponse` class, and contain the proper headers needed to redirect the user to another URL. There are several ways to generate a `RedirectResponse` instance. The simplest method is to use the global `redirect` helper: - Route::get('/dashboard', function () { - return redirect('/home/dashboard'); - }); +```php +Route::get('/dashboard', function () { + return redirect('/home/dashboard'); +}); +``` Sometimes you may wish to redirect the user to their previous location, such as when a submitted form is invalid. You may do so by using the global `back` helper function. Since this feature utilizes the [session](/docs/{{version}}/session), make sure the route calling the `back` function is using the `web` middleware group or has all of the session middleware applied: - Route::post('/user/profile', function () { - // Validate the request... +```php +Route::post('/user/profile', function () { + // Validate the request... - return back()->withInput(); - }); + return back()->withInput(); +}); +``` ## Redirecting To Named Routes When you call the `redirect` helper with no parameters, an instance of `Illuminate\Routing\Redirector` is returned, allowing you to call any method on the `Redirector` instance. For example, to generate a `RedirectResponse` to a named route, you may use the `route` method: - return redirect()->route('login'); +```php +return redirect()->route('login'); +``` If your route has parameters, you may pass them as the second argument to the `route` method: - // For a route with the following URI: profile/{id} +```php +// For a route with the following URI: profile/{id} - return redirect()->route('profile', ['id' => 1]); +return redirect()->route('profile', ['id' => 1]); +``` For convenience, Laravel also offers the global `to_route` function: - return to_route('profile', ['id' => 1]); +```php +return to_route('profile', ['id' => 1]); +``` #### Populating Parameters Via Eloquent Models If you are redirecting to a route with an "ID" parameter that is being populated from an Eloquent model, you may pass the model itself. The ID will be extracted automatically: - // For a route with the following URI: profile/{id} +```php +// For a route with the following URI: profile/{id} - return redirect()->route('profile', [$user]); +return redirect()->route('profile', [$user]); +``` If you would like to customize the value that is placed in the route parameter, you should override the `getRouteKey` method on your Eloquent model: - /** - * Get the value of the model's route key. - */ - public function getRouteKey(): mixed - { - return $this->slug; - } +```php +/** + * Get the value of the model's route key. + */ +public function getRouteKey(): mixed +{ + return $this->slug; +} +``` ## Redirecting To Controller Actions You may also generate redirects to [controller actions](/docs/{{version}}/controllers). To do so, pass the controller and action name to the `action` method: - use App\Http\Controllers\HomeController; +```php +use App\Http\Controllers\HomeController; - return redirect()->action([HomeController::class, 'index']); +return redirect()->action([HomeController::class, 'index']); +``` If your controller route requires parameters, you may pass them as the second argument to the `action` method: - return redirect()->action( - [UserController::class, 'profile'], ['id' => 1] - ); +```php +return redirect()->action( + [UserController::class, 'profile'], ['id' => 1] +); +``` ## Redirecting With Flashed Session Data Redirecting to a new URL and [flashing data to the session](/docs/{{version}}/session#flash-data) are usually done at the same time. Typically, this is done after successfully performing an action when you flash a success message to the session. For convenience, you may create a `RedirectResponse` instance and flash data to the session in a single, fluent method chain: - Route::post('/user/profile', function () { - // Update the user's profile... +```php +Route::post('/user/profile', function () { + // Update the user's profile... - return redirect('/dashboard')->with('status', 'Profile updated!'); - }); + return redirect('/dashboard')->with('status', 'Profile updated!'); +}); +``` You may use the `withInput` method provided by the `RedirectResponse` instance to flash the current request's input data to the session before redirecting the user to a new location. Once the input has been flashed to the session, you may easily [retrieve it](/docs/{{version}}/requests#retrieving-old-input) during the next request: - return back()->withInput(); +```php +return back()->withInput(); +``` After the user is redirected, you may display the flashed message from the [session](/docs/{{version}}/session). For example, using [Blade syntax](/docs/{{version}}/blade): - @if (session('status')) -
- {{ session('status') }} -
- @endif +```blade +@if (session('status')) +
+ {{ session('status') }} +
+@endif +``` diff --git a/redis.md b/redis.md index 7615f351090..ecd3fde4c57 100644 --- a/redis.md +++ b/redis.md @@ -28,189 +28,209 @@ composer require predis/predis:^2.0 You may configure your application's Redis settings via the `config/database.php` configuration file. Within this file, you will see a `redis` array containing the Redis servers utilized by your application: - 'redis' => [ +```php +'redis' => [ - 'client' => env('REDIS_CLIENT', 'phpredis'), + 'client' => env('REDIS_CLIENT', 'phpredis'), - 'options' => [ - 'cluster' => env('REDIS_CLUSTER', 'redis'), - 'prefix' => env('REDIS_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_database_'), - ], - - 'default' => [ - 'url' => env('REDIS_URL'), - 'host' => env('REDIS_HOST', '127.0.0.1'), - 'username' => env('REDIS_USERNAME'), - 'password' => env('REDIS_PASSWORD'), - 'port' => env('REDIS_PORT', '6379'), - 'database' => env('REDIS_DB', '0'), - ], + 'options' => [ + 'cluster' => env('REDIS_CLUSTER', 'redis'), + 'prefix' => env('REDIS_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_database_'), + ], - 'cache' => [ - 'url' => env('REDIS_URL'), - 'host' => env('REDIS_HOST', '127.0.0.1'), - 'username' => env('REDIS_USERNAME'), - 'password' => env('REDIS_PASSWORD'), - 'port' => env('REDIS_PORT', '6379'), - 'database' => env('REDIS_CACHE_DB', '1'), - ], + 'default' => [ + 'url' => env('REDIS_URL'), + 'host' => env('REDIS_HOST', '127.0.0.1'), + 'username' => env('REDIS_USERNAME'), + 'password' => env('REDIS_PASSWORD'), + 'port' => env('REDIS_PORT', '6379'), + 'database' => env('REDIS_DB', '0'), + ], + 'cache' => [ + 'url' => env('REDIS_URL'), + 'host' => env('REDIS_HOST', '127.0.0.1'), + 'username' => env('REDIS_USERNAME'), + 'password' => env('REDIS_PASSWORD'), + 'port' => env('REDIS_PORT', '6379'), + 'database' => env('REDIS_CACHE_DB', '1'), ], -Each Redis server defined in your configuration file is required to have a name, host, and a port unless you define a single URL to represent the Redis connection: +], +``` - 'redis' => [ +Each Redis server defined in your configuration file is required to have a name, host, and a port unless you define a single URL to represent the Redis connection: - 'client' => env('REDIS_CLIENT', 'phpredis'), +```php +'redis' => [ - 'options' => [ - 'cluster' => env('REDIS_CLUSTER', 'redis'), - 'prefix' => env('REDIS_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_database_'), - ], + 'client' => env('REDIS_CLIENT', 'phpredis'), - 'default' => [ - 'url' => 'tcp://127.0.0.1:6379?database=0', - ], + 'options' => [ + 'cluster' => env('REDIS_CLUSTER', 'redis'), + 'prefix' => env('REDIS_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_database_'), + ], - 'cache' => [ - 'url' => 'tls://user:password@127.0.0.1:6380?database=1', - ], + 'default' => [ + 'url' => 'tcp://127.0.0.1:6379?database=0', + ], + 'cache' => [ + 'url' => 'tls://user:password@127.0.0.1:6380?database=1', ], +], +``` + #### Configuring the Connection Scheme By default, Redis clients will use the `tcp` scheme when connecting to your Redis servers; however, you may use TLS / SSL encryption by specifying a `scheme` configuration option in your Redis server's configuration array: - 'default' => [ - 'scheme' => 'tls', - 'url' => env('REDIS_URL'), - 'host' => env('REDIS_HOST', '127.0.0.1'), - 'username' => env('REDIS_USERNAME'), - 'password' => env('REDIS_PASSWORD'), - 'port' => env('REDIS_PORT', '6379'), - 'database' => env('REDIS_DB', '0'), - ], +```php +'default' => [ + 'scheme' => 'tls', + 'url' => env('REDIS_URL'), + 'host' => env('REDIS_HOST', '127.0.0.1'), + 'username' => env('REDIS_USERNAME'), + 'password' => env('REDIS_PASSWORD'), + 'port' => env('REDIS_PORT', '6379'), + 'database' => env('REDIS_DB', '0'), +], +``` ### Clusters If your application is utilizing a cluster of Redis servers, you should define these clusters within a `clusters` key of your Redis configuration. This configuration key does not exist by default so you will need to create it within your application's `config/database.php` configuration file: - 'redis' => [ +```php +'redis' => [ - 'client' => env('REDIS_CLIENT', 'phpredis'), + 'client' => env('REDIS_CLIENT', 'phpredis'), - 'options' => [ - 'cluster' => env('REDIS_CLUSTER', 'redis'), - 'prefix' => env('REDIS_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_database_'), - ], + 'options' => [ + 'cluster' => env('REDIS_CLUSTER', 'redis'), + 'prefix' => env('REDIS_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_database_'), + ], - 'clusters' => [ - 'default' => [ - [ - 'url' => env('REDIS_URL'), - 'host' => env('REDIS_HOST', '127.0.0.1'), - 'username' => env('REDIS_USERNAME'), - 'password' => env('REDIS_PASSWORD'), - 'port' => env('REDIS_PORT', '6379'), - 'database' => env('REDIS_DB', '0'), - ], + 'clusters' => [ + 'default' => [ + [ + 'url' => env('REDIS_URL'), + 'host' => env('REDIS_HOST', '127.0.0.1'), + 'username' => env('REDIS_USERNAME'), + 'password' => env('REDIS_PASSWORD'), + 'port' => env('REDIS_PORT', '6379'), + 'database' => env('REDIS_DB', '0'), ], ], - - // ... ], + // ... +], +``` + By default, Laravel will use native Redis clustering since the `options.cluster` configuration value is set to `redis`. Redis clustering is a great default option, as it gracefully handles failover. Laravel also supports client-side sharding when using Predis. However, client-side sharding does not handle failover; therefore, it is primarily suited for transient cached data that is available from another primary data store. If you would like to use client-side sharding instead of native Redis clustering, you may remove the `options.cluster` configuration value within your application's `config/database.php` configuration file: - 'redis' => [ - - 'client' => env('REDIS_CLIENT', 'phpredis'), +```php +'redis' => [ - 'clusters' => [ - // ... - ], + 'client' => env('REDIS_CLIENT', 'phpredis'), + 'clusters' => [ // ... ], + // ... +], +``` + ### Predis If you would like your application to interact with Redis via the Predis package, you should ensure the `REDIS_CLIENT` environment variable's value is `predis`: - 'redis' => [ +```php +'redis' => [ - 'client' => env('REDIS_CLIENT', 'predis'), + 'client' => env('REDIS_CLIENT', 'predis'), - // ... - ], + // ... +], +``` In addition to the default configuration options, Predis supports additional [connection parameters](https://github.com/nrk/predis/wiki/Connection-Parameters) that may be defined for each of your Redis servers. To utilize these additional configuration options, add them to your Redis server configuration in your application's `config/database.php` configuration file: - 'default' => [ - 'url' => env('REDIS_URL'), - 'host' => env('REDIS_HOST', '127.0.0.1'), - 'username' => env('REDIS_USERNAME'), - 'password' => env('REDIS_PASSWORD'), - 'port' => env('REDIS_PORT', '6379'), - 'database' => env('REDIS_DB', '0'), - 'read_write_timeout' => 60, - ], +```php +'default' => [ + 'url' => env('REDIS_URL'), + 'host' => env('REDIS_HOST', '127.0.0.1'), + 'username' => env('REDIS_USERNAME'), + 'password' => env('REDIS_PASSWORD'), + 'port' => env('REDIS_PORT', '6379'), + 'database' => env('REDIS_DB', '0'), + 'read_write_timeout' => 60, +], +``` ### PhpRedis By default, Laravel will use the PhpRedis extension to communicate with Redis. The client that Laravel will use to communicate with Redis is dictated by the value of the `redis.client` configuration option, which typically reflects the value of the `REDIS_CLIENT` environment variable: - 'redis' => [ +```php +'redis' => [ - 'client' => env('REDIS_CLIENT', 'phpredis'), + 'client' => env('REDIS_CLIENT', 'phpredis'), - // ... - ], + // ... +], +``` In addition to the default configuration options, PhpRedis supports the following additional connection parameters: `name`, `persistent`, `persistent_id`, `prefix`, `read_timeout`, `retry_interval`, `max_retries`, `backoff_algorithm`, `backoff_base`, `backoff_cap`, `timeout`, and `context`. You may add any of these options to your Redis server configuration in the `config/database.php` configuration file: - 'default' => [ - 'url' => env('REDIS_URL'), - 'host' => env('REDIS_HOST', '127.0.0.1'), - 'username' => env('REDIS_USERNAME'), - 'password' => env('REDIS_PASSWORD'), - 'port' => env('REDIS_PORT', '6379'), - 'database' => env('REDIS_DB', '0'), - 'read_timeout' => 60, - 'context' => [ - // 'auth' => ['username', 'secret'], - // 'stream' => ['verify_peer' => false], - ], +```php +'default' => [ + 'url' => env('REDIS_URL'), + 'host' => env('REDIS_HOST', '127.0.0.1'), + 'username' => env('REDIS_USERNAME'), + 'password' => env('REDIS_PASSWORD'), + 'port' => env('REDIS_PORT', '6379'), + 'database' => env('REDIS_DB', '0'), + 'read_timeout' => 60, + 'context' => [ + // 'auth' => ['username', 'secret'], + // 'stream' => ['verify_peer' => false], ], +], +``` #### PhpRedis Serialization and Compression The PhpRedis extension may also be configured to use a variety of serializers and compression algorithms. These algorithms can be configured via the `options` array of your Redis configuration: - 'redis' => [ +```php +'redis' => [ - 'client' => env('REDIS_CLIENT', 'phpredis'), + 'client' => env('REDIS_CLIENT', 'phpredis'), - 'options' => [ - 'cluster' => env('REDIS_CLUSTER', 'redis'), - 'prefix' => env('REDIS_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_database_'), - 'serializer' => Redis::SERIALIZER_MSGPACK, - 'compression' => Redis::COMPRESSION_LZ4, - ], - - // ... + 'options' => [ + 'cluster' => env('REDIS_CLUSTER', 'redis'), + 'prefix' => env('REDIS_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_database_'), + 'serializer' => Redis::SERIALIZER_MSGPACK, + 'compression' => Redis::COMPRESSION_LZ4, ], + // ... +], +``` + Currently supported serializers include: `Redis::SERIALIZER_NONE` (default), `Redis::SERIALIZER_PHP`, `Redis::SERIALIZER_JSON`, `Redis::SERIALIZER_IGBINARY`, and `Redis::SERIALIZER_MSGPACK`. Supported compression algorithms include: `Redis::COMPRESSION_NONE` (default), `Redis::COMPRESSION_LZF`, `Redis::COMPRESSION_ZSTD`, and `Redis::COMPRESSION_LZ4`. @@ -220,62 +240,74 @@ Supported compression algorithms include: `Redis::COMPRESSION_NONE` (default), ` You may interact with Redis by calling various methods on the `Redis` [facade](/docs/{{version}}/facades). The `Redis` facade supports dynamic methods, meaning you may call any [Redis command](https://redis.io/commands) on the facade and the command will be passed directly to Redis. In this example, we will call the Redis `GET` command by calling the `get` method on the `Redis` facade: - Redis::get('user:profile:'.$id) - ]); - } + return view('user.profile', [ + 'user' => Redis::get('user:profile:'.$id) + ]); } +} +``` As mentioned above, you may call any of Redis' commands on the `Redis` facade. Laravel uses magic methods to pass the commands to the Redis server. If a Redis command expects arguments, you should pass those to the facade's corresponding method: - use Illuminate\Support\Facades\Redis; +```php +use Illuminate\Support\Facades\Redis; - Redis::set('name', 'Taylor'); +Redis::set('name', 'Taylor'); - $values = Redis::lrange('names', 5, 10); +$values = Redis::lrange('names', 5, 10); +``` Alternatively, you may pass commands to the server using the `Redis` facade's `command` method, which accepts the name of the command as its first argument and an array of values as its second argument: - $values = Redis::command('lrange', ['name', 5, 10]); +```php +$values = Redis::command('lrange', ['name', 5, 10]); +``` #### Using Multiple Redis Connections Your application's `config/database.php` configuration file allows you to define multiple Redis connections / servers. You may obtain a connection to a specific Redis connection using the `Redis` facade's `connection` method: - $redis = Redis::connection('connection-name'); +```php +$redis = Redis::connection('connection-name'); +``` To obtain an instance of the default Redis connection, you may call the `connection` method without any additional arguments: - $redis = Redis::connection(); +```php +$redis = Redis::connection(); +``` ### Transactions The `Redis` facade's `transaction` method provides a convenient wrapper around Redis' native `MULTI` and `EXEC` commands. The `transaction` method accepts a closure as its only argument. This closure will receive a Redis connection instance and may issue any commands it would like to this instance. All of the Redis commands issued within the closure will be executed in a single, atomic transaction: - use Redis; - use Illuminate\Support\Facades; +```php +use Redis; +use Illuminate\Support\Facades; - Facades\Redis::transaction(function (Redis $redis) { - $redis->incr('user_visits', 1); - $redis->incr('total_visits', 1); - }); +Facades\Redis::transaction(function (Redis $redis) { + $redis->incr('user_visits', 1); + $redis->incr('total_visits', 1); +}); +``` > [!WARNING] > When defining a Redis transaction, you may not retrieve any values from the Redis connection. Remember, your transaction is executed as a single, atomic operation and that operation is not executed until your entire closure has finished executing its commands. @@ -288,15 +320,17 @@ The `eval` method can be a bit scary at first, but we'll explore a basic example In this example, we will increment a counter, inspect its new value, and increment a second counter if the first counter's value is greater than five. Finally, we will return the value of the first counter: - $value = Redis::eval(<<<'LUA' - local counter = redis.call("incr", KEYS[1]) +```php +$value = Redis::eval(<<<'LUA' + local counter = redis.call("incr", KEYS[1]) - if counter > 5 then - redis.call("incr", KEYS[2]) - end + if counter > 5 then + redis.call("incr", KEYS[2]) + end - return counter - LUA, 2, 'first-counter', 'second-counter'); + return counter +LUA, 2, 'first-counter', 'second-counter'); +``` > [!WARNING] > Please consult the [Redis documentation](https://redis.io/commands/eval) for more information on Redis scripting. @@ -306,14 +340,16 @@ In this example, we will increment a counter, inspect its new value, and increme Sometimes you may need to execute dozens of Redis commands. Instead of making a network trip to your Redis server for each command, you may use the `pipeline` method. The `pipeline` method accepts one argument: a closure that receives a Redis instance. You may issue all of your commands to this Redis instance and they will all be sent to the Redis server at the same time to reduce network trips to the server. The commands will still be executed in the order they were issued: - use Redis; - use Illuminate\Support\Facades; +```php +use Redis; +use Illuminate\Support\Facades; - Facades\Redis::pipeline(function (Redis $pipe) { - for ($i = 0; $i < 1000; $i++) { - $pipe->set("key:$i", $i); - } - }); +Facades\Redis::pipeline(function (Redis $pipe) { + for ($i = 0; $i < 1000; $i++) { + $pipe->set("key:$i", $i); + } +}); +``` ## Pub / Sub @@ -322,61 +358,67 @@ Laravel provides a convenient interface to the Redis `publish` and `subscribe` c First, let's setup a channel listener using the `subscribe` method. We'll place this method call within an [Artisan command](/docs/{{version}}/artisan) since calling the `subscribe` method begins a long-running process: - 'Adam Wathan' - ])); - }); + Redis::publish('test-channel', json_encode([ + 'name' => 'Adam Wathan' + ])); +}); +``` #### Wildcard Subscriptions Using the `psubscribe` method, you may subscribe to a wildcard channel, which may be useful for catching all messages on all channels. The channel name will be passed as the second argument to the provided closure: - Redis::psubscribe(['*'], function (string $message, string $channel) { - echo $message; - }); +```php +Redis::psubscribe(['*'], function (string $message, string $channel) { + echo $message; +}); - Redis::psubscribe(['users.*'], function (string $message, string $channel) { - echo $message; - }); +Redis::psubscribe(['users.*'], function (string $message, string $channel) { + echo $message; +}); +``` diff --git a/requests.md b/requests.md index 4b4cc9e412d..97c77b2e10a 100644 --- a/requests.md +++ b/requests.md @@ -34,66 +34,74 @@ Laravel's `Illuminate\Http\Request` class provides an object-oriented way to int To obtain an instance of the current HTTP request via dependency injection, you should type-hint the `Illuminate\Http\Request` class on your route closure or controller method. The incoming request instance will automatically be injected by the Laravel [service container](/docs/{{version}}/container): - input('name'); + $name = $request->input('name'); - // Store the user... + // Store the user... - return redirect('/users'); - } + return redirect('/users'); } +} +``` As mentioned, you may also type-hint the `Illuminate\Http\Request` class on a route closure. The service container will automatically inject the incoming request into the closure when it is executed: - use Illuminate\Http\Request; +```php +use Illuminate\Http\Request; - Route::get('/', function (Request $request) { - // ... - }); +Route::get('/', function (Request $request) { + // ... +}); +``` #### Dependency Injection and Route Parameters If your controller method is also expecting input from a route parameter you should list your route parameters after your other dependencies. For example, if your route is defined like so: - use App\Http\Controllers\UserController; +```php +use App\Http\Controllers\UserController; - Route::put('/user/{id}', [UserController::class, 'update']); +Route::put('/user/{id}', [UserController::class, 'update']); +``` You may still type-hint the `Illuminate\Http\Request` and access your `id` route parameter by defining your controller method as follows: - ### Request Path, Host, and Method @@ -105,35 +113,45 @@ The `Illuminate\Http\Request` instance provides a variety of methods for examini The `path` method returns the request's path information. So, if the incoming request is targeted at `http://example.com/foo/bar`, the `path` method will return `foo/bar`: - $uri = $request->path(); +```php +$uri = $request->path(); +``` #### Inspecting the Request Path / Route The `is` method allows you to verify that the incoming request path matches a given pattern. You may use the `*` character as a wildcard when utilizing this method: - if ($request->is('admin/*')) { - // ... - } +```php +if ($request->is('admin/*')) { + // ... +} +``` Using the `routeIs` method, you may determine if the incoming request has matched a [named route](/docs/{{version}}/routing#named-routes): - if ($request->routeIs('admin.*')) { - // ... - } +```php +if ($request->routeIs('admin.*')) { + // ... +} +``` #### Retrieving the Request URL To retrieve the full URL for the incoming request you may use the `url` or `fullUrl` methods. The `url` method will return the URL without the query string, while the `fullUrl` method includes the query string: - $url = $request->url(); +```php +$url = $request->url(); - $urlWithQueryString = $request->fullUrl(); +$urlWithQueryString = $request->fullUrl(); +``` If you would like to append query string data to the current URL, you may call the `fullUrlWithQuery` method. This method merges the given array of query string variables with the current query string: - $request->fullUrlWithQuery(['type' => 'phone']); +```php +$request->fullUrlWithQuery(['type' => 'phone']); +``` If you would like to get the current URL without a given query string parameter, you may utilize the `fullUrlWithoutQuery` method: @@ -146,50 +164,64 @@ $request->fullUrlWithoutQuery(['type']); You may retrieve the "host" of the incoming request via the `host`, `httpHost`, and `schemeAndHttpHost` methods: - $request->host(); - $request->httpHost(); - $request->schemeAndHttpHost(); +```php +$request->host(); +$request->httpHost(); +$request->schemeAndHttpHost(); +``` #### Retrieving the Request Method The `method` method will return the HTTP verb for the request. You may use the `isMethod` method to verify that the HTTP verb matches a given string: - $method = $request->method(); +```php +$method = $request->method(); - if ($request->isMethod('post')) { - // ... - } +if ($request->isMethod('post')) { + // ... +} +``` ### Request Headers You may retrieve a request header from the `Illuminate\Http\Request` instance using the `header` method. If the header is not present on the request, `null` will be returned. However, the `header` method accepts an optional second argument that will be returned if the header is not present on the request: - $value = $request->header('X-Header-Name'); +```php +$value = $request->header('X-Header-Name'); - $value = $request->header('X-Header-Name', 'default'); +$value = $request->header('X-Header-Name', 'default'); +``` The `hasHeader` method may be used to determine if the request contains a given header: - if ($request->hasHeader('X-Header-Name')) { - // ... - } +```php +if ($request->hasHeader('X-Header-Name')) { + // ... +} +``` For convenience, the `bearerToken` method may be used to retrieve a bearer token from the `Authorization` header. If no such header is present, an empty string will be returned: - $token = $request->bearerToken(); +```php +$token = $request->bearerToken(); +``` ### Request IP Address The `ip` method may be used to retrieve the IP address of the client that made the request to your application: - $ipAddress = $request->ip(); +```php +$ipAddress = $request->ip(); +``` If you would like to retrieve an array of IP addresses, including all of the client IP addresses that were forwarded by proxies, you may use the `ips` method. The "original" client IP address will be at the end of the array: - $ipAddresses = $request->ips(); +```php +$ipAddresses = $request->ips(); +``` In general, IP addresses should be considered untrusted, user-controlled input and be used for informational purposes only. @@ -198,23 +230,31 @@ In general, IP addresses should be considered untrusted, user-controlled input a Laravel provides several methods for inspecting the incoming request's requested content types via the `Accept` header. First, the `getAcceptableContentTypes` method will return an array containing all of the content types accepted by the request: - $contentTypes = $request->getAcceptableContentTypes(); +```php +$contentTypes = $request->getAcceptableContentTypes(); +``` The `accepts` method accepts an array of content types and returns `true` if any of the content types are accepted by the request. Otherwise, `false` will be returned: - if ($request->accepts(['text/html', 'application/json'])) { - // ... - } +```php +if ($request->accepts(['text/html', 'application/json'])) { + // ... +} +``` You may use the `prefers` method to determine which content type out of a given array of content types is most preferred by the request. If none of the provided content types are accepted by the request, `null` will be returned: - $preferred = $request->prefers(['text/html', 'application/json']); +```php +$preferred = $request->prefers(['text/html', 'application/json']); +``` Since many applications only serve HTML or JSON, you may use the `expectsJson` method to quickly determine if the incoming request expects a JSON response: - if ($request->expectsJson()) { - // ... - } +```php +if ($request->expectsJson()) { + // ... +} +``` ### PSR-7 Requests @@ -228,11 +268,13 @@ composer require nyholm/psr7 Once you have installed these libraries, you may obtain a PSR-7 request by type-hinting the request interface on your route closure or controller method: - use Psr\Http\Message\ServerRequestInterface; +```php +use Psr\Http\Message\ServerRequestInterface; - Route::get('/', function (ServerRequestInterface $request) { - // ... - }); +Route::get('/', function (ServerRequestInterface $request) { + // ... +}); +``` > [!NOTE] > If you return a PSR-7 response instance from a route or controller, it will automatically be converted back to a Laravel response instance and be displayed by the framework. @@ -248,92 +290,124 @@ Once you have installed these libraries, you may obtain a PSR-7 request by type- You may retrieve all of the incoming request's input data as an `array` using the `all` method. This method may be used regardless of whether the incoming request is from an HTML form or is an XHR request: - $input = $request->all(); +```php +$input = $request->all(); +``` Using the `collect` method, you may retrieve all of the incoming request's input data as a [collection](/docs/{{version}}/collections): - $input = $request->collect(); +```php +$input = $request->collect(); +``` The `collect` method also allows you to retrieve a subset of the incoming request's input as a collection: - $request->collect('users')->each(function (string $user) { - // ... - }); +```php +$request->collect('users')->each(function (string $user) { + // ... +}); +``` #### Retrieving an Input Value Using a few simple methods, you may access all of the user input from your `Illuminate\Http\Request` instance without worrying about which HTTP verb was used for the request. Regardless of the HTTP verb, the `input` method may be used to retrieve user input: - $name = $request->input('name'); +```php +$name = $request->input('name'); +``` You may pass a default value as the second argument to the `input` method. This value will be returned if the requested input value is not present on the request: - $name = $request->input('name', 'Sally'); +```php +$name = $request->input('name', 'Sally'); +``` When working with forms that contain array inputs, use "dot" notation to access the arrays: - $name = $request->input('products.0.name'); +```php +$name = $request->input('products.0.name'); - $names = $request->input('products.*.name'); +$names = $request->input('products.*.name'); +``` You may call the `input` method without any arguments in order to retrieve all of the input values as an associative array: - $input = $request->input(); +```php +$input = $request->input(); +``` #### Retrieving Input From the Query String While the `input` method retrieves values from the entire request payload (including the query string), the `query` method will only retrieve values from the query string: - $name = $request->query('name'); +```php +$name = $request->query('name'); +``` If the requested query string value data is not present, the second argument to this method will be returned: - $name = $request->query('name', 'Helen'); +```php +$name = $request->query('name', 'Helen'); +``` You may call the `query` method without any arguments in order to retrieve all of the query string values as an associative array: - $query = $request->query(); +```php +$query = $request->query(); +``` #### Retrieving JSON Input Values When sending JSON requests to your application, you may access the JSON data via the `input` method as long as the `Content-Type` header of the request is properly set to `application/json`. You may even use "dot" syntax to retrieve values that are nested within JSON arrays / objects: - $name = $request->input('user.name'); +```php +$name = $request->input('user.name'); +``` #### Retrieving Stringable Input Values Instead of retrieving the request's input data as a primitive `string`, you may use the `string` method to retrieve the request data as an instance of [`Illuminate\Support\Stringable`](/docs/{{version}}/strings): - $name = $request->string('name')->trim(); +```php +$name = $request->string('name')->trim(); +``` #### Retrieving Integer Input Values To retrieve input values as integers, you may use the `integer` method. This method will attempt to cast the input value to an integer. If the input is not present or the cast fails, it will return the default value you specify. This is particularly useful for pagination or other numeric inputs: - $perPage = $request->integer('per_page'); +```php +$perPage = $request->integer('per_page'); +``` #### Retrieving Boolean Input Values When dealing with HTML elements like checkboxes, your application may receive "truthy" values that are actually strings. For example, "true" or "on". For convenience, you may use the `boolean` method to retrieve these values as booleans. The `boolean` method returns `true` for 1, "1", true, "true", "on", and "yes". All other values will return `false`: - $archived = $request->boolean('archived'); +```php +$archived = $request->boolean('archived'); +``` #### Retrieving Date Input Values For convenience, input values containing dates / times may be retrieved as Carbon instances using the `date` method. If the request does not contain an input value with the given name, `null` will be returned: - $birthday = $request->date('birthday'); +```php +$birthday = $request->date('birthday'); +``` The second and third arguments accepted by the `date` method may be used to specify the date's format and timezone, respectively: - $elapsed = $request->date('elapsed', '!H:i', 'Europe/Madrid'); +```php +$elapsed = $request->date('elapsed', '!H:i', 'Europe/Madrid'); +``` If the input value is present but has an invalid format, an `InvalidArgumentException` will be thrown; therefore, it is recommended that you validate the input before invoking the `date` method. @@ -342,22 +416,28 @@ If the input value is present but has an invalid format, an `InvalidArgumentExce Input values that correspond to [PHP enums](https://www.php.net/manual/en/language.types.enumerations.php) may also be retrieved from the request. If the request does not contain an input value with the given name or the enum does not have a backing value that matches the input value, `null` will be returned. The `enum` method accepts the name of the input value and the enum class as its first and second arguments: - use App\Enums\Status; +```php +use App\Enums\Status; - $status = $request->enum('status', Status::class); +$status = $request->enum('status', Status::class); +``` If the input value is an array of values that correspond to a PHP enum, you may use the `enums` method to retrieve the array of values as enum instances: - use App\Enums\Product; +```php +use App\Enums\Product; - $products = $request->enums('products', Product::class); +$products = $request->enums('products', Product::class); +``` #### Retrieving Input via Dynamic Properties You may also access user input using dynamic properties on the `Illuminate\Http\Request` instance. For example, if one of your application's forms contains a `name` field, you may access the value of the field like so: - $name = $request->name; +```php +$name = $request->name; +``` When using dynamic properties, Laravel will first look for the parameter's value in the request payload. If it is not present, Laravel will search for the field in the matched route's parameters. @@ -366,13 +446,15 @@ When using dynamic properties, Laravel will first look for the parameter's value If you need to retrieve a subset of the input data, you may use the `only` and `except` methods. Both of these methods accept a single `array` or a dynamic list of arguments: - $input = $request->only(['username', 'password']); +```php +$input = $request->only(['username', 'password']); - $input = $request->only('username', 'password'); +$input = $request->only('username', 'password'); - $input = $request->except(['credit_card']); +$input = $request->except(['credit_card']); - $input = $request->except('credit_card'); +$input = $request->except('credit_card'); +``` > [!WARNING] > The `only` method returns all of the key / value pairs that you request; however, it will not return key / value pairs that are not present on the request. @@ -382,96 +464,124 @@ If you need to retrieve a subset of the input data, you may use the `only` and ` You may use the `has` method to determine if a value is present on the request. The `has` method returns `true` if the value is present on the request: - if ($request->has('name')) { - // ... - } +```php +if ($request->has('name')) { + // ... +} +``` When given an array, the `has` method will determine if all of the specified values are present: - if ($request->has(['name', 'email'])) { - // ... - } +```php +if ($request->has(['name', 'email'])) { + // ... +} +``` The `hasAny` method returns `true` if any of the specified values are present: - if ($request->hasAny(['name', 'email'])) { - // ... - } +```php +if ($request->hasAny(['name', 'email'])) { + // ... +} +``` The `whenHas` method will execute the given closure if a value is present on the request: - $request->whenHas('name', function (string $input) { - // ... - }); +```php +$request->whenHas('name', function (string $input) { + // ... +}); +``` A second closure may be passed to the `whenHas` method that will be executed if the specified value is not present on the request: - $request->whenHas('name', function (string $input) { - // The "name" value is present... - }, function () { - // The "name" value is not present... - }); +```php +$request->whenHas('name', function (string $input) { + // The "name" value is present... +}, function () { + // The "name" value is not present... +}); +``` If you would like to determine if a value is present on the request and is not an empty string, you may use the `filled` method: - if ($request->filled('name')) { - // ... - } +```php +if ($request->filled('name')) { + // ... +} +``` If you would like to determine if a value is missing from the request or is an empty string, you may use the `isNotFilled` method: - if ($request->isNotFilled('name')) { - // ... - } +```php +if ($request->isNotFilled('name')) { + // ... +} +``` When given an array, the `isNotFilled` method will determine if all of the specified values are missing or empty: - if ($request->isNotFilled(['name', 'email'])) { - // ... - } +```php +if ($request->isNotFilled(['name', 'email'])) { + // ... +} +``` The `anyFilled` method returns `true` if any of the specified values is not an empty string: - if ($request->anyFilled(['name', 'email'])) { - // ... - } +```php +if ($request->anyFilled(['name', 'email'])) { + // ... +} +``` The `whenFilled` method will execute the given closure if a value is present on the request and is not an empty string: - $request->whenFilled('name', function (string $input) { - // ... - }); +```php +$request->whenFilled('name', function (string $input) { + // ... +}); +``` A second closure may be passed to the `whenFilled` method that will be executed if the specified value is not "filled": - $request->whenFilled('name', function (string $input) { - // The "name" value is filled... - }, function () { - // The "name" value is not filled... - }); +```php +$request->whenFilled('name', function (string $input) { + // The "name" value is filled... +}, function () { + // The "name" value is not filled... +}); +``` To determine if a given key is absent from the request, you may use the `missing` and `whenMissing` methods: - if ($request->missing('name')) { - // ... - } - - $request->whenMissing('name', function () { - // The "name" value is missing... - }, function () { - // The "name" value is present... - }); +```php +if ($request->missing('name')) { + // ... +} + +$request->whenMissing('name', function () { + // The "name" value is missing... +}, function () { + // The "name" value is present... +}); +``` ### Merging Additional Input Sometimes you may need to manually merge additional input into the request's existing input data. To accomplish this, you may use the `merge` method. If a given input key already exists on the request, it will be overwritten by the data provided to the `merge` method: - $request->merge(['votes' => 0]); +```php +$request->merge(['votes' => 0]); +``` The `mergeIfMissing` method may be used to merge input into the request if the corresponding keys do not already exist within the request's input data: - $request->mergeIfMissing(['votes' => 0]); +```php +$request->mergeIfMissing(['votes' => 0]); +``` ### Old Input @@ -483,37 +593,47 @@ Laravel allows you to keep input from one request during the next request. This The `flash` method on the `Illuminate\Http\Request` class will flash the current input to the [session](/docs/{{version}}/session) so that it is available during the user's next request to the application: - $request->flash(); +```php +$request->flash(); +``` You may also use the `flashOnly` and `flashExcept` methods to flash a subset of the request data to the session. These methods are useful for keeping sensitive information such as passwords out of the session: - $request->flashOnly(['username', 'email']); +```php +$request->flashOnly(['username', 'email']); - $request->flashExcept('password'); +$request->flashExcept('password'); +``` #### Flashing Input Then Redirecting Since you often will want to flash input to the session and then redirect to the previous page, you may easily chain input flashing onto a redirect using the `withInput` method: - return redirect('/form')->withInput(); +```php +return redirect('/form')->withInput(); - return redirect()->route('user.create')->withInput(); +return redirect()->route('user.create')->withInput(); - return redirect('/form')->withInput( - $request->except('password') - ); +return redirect('/form')->withInput( + $request->except('password') +); +``` #### Retrieving Old Input To retrieve flashed input from the previous request, invoke the `old` method on an instance of `Illuminate\Http\Request`. The `old` method will pull the previously flashed input data from the [session](/docs/{{version}}/session): - $username = $request->old('username'); +```php +$username = $request->old('username'); +``` Laravel also provides a global `old` helper. If you are displaying old input within a [Blade template](/docs/{{version}}/blade), it is more convenient to use the `old` helper to repopulate the form. If no old input exists for the given field, `null` will be returned: - +```blade + +``` ### Cookies @@ -523,7 +643,9 @@ Laravel also provides a global `old` helper. If you are displaying old input wit All cookies created by the Laravel framework are encrypted and signed with an authentication code, meaning they will be considered invalid if they have been changed by the client. To retrieve a cookie value from the request, use the `cookie` method on an `Illuminate\Http\Request` instance: - $value = $request->cookie('name'); +```php +$value = $request->cookie('name'); +``` ## Input Trimming and Normalization @@ -534,27 +656,31 @@ By default, Laravel includes the `Illuminate\Foundation\Http\Middleware\TrimStri If you would like to disable this behavior for all requests, you may remove the two middleware from your application's middleware stack by invoking the `$middleware->remove` method in your application's `bootstrap/app.php` file: - use Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull; - use Illuminate\Foundation\Http\Middleware\TrimStrings; - - ->withMiddleware(function (Middleware $middleware) { - $middleware->remove([ - ConvertEmptyStringsToNull::class, - TrimStrings::class, - ]); - }) +```php +use Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull; +use Illuminate\Foundation\Http\Middleware\TrimStrings; + +->withMiddleware(function (Middleware $middleware) { + $middleware->remove([ + ConvertEmptyStringsToNull::class, + TrimStrings::class, + ]); +}) +``` If you would like to disable string trimming and empty string conversion for a subset of requests to your application, you may use the `trimStrings` and `convertEmptyStringsToNull` middleware methods within your application's `bootstrap/app.php` file. Both methods accept an array of closures, which should return `true` or `false` to indicate whether input normalization should be skipped: - ->withMiddleware(function (Middleware $middleware) { - $middleware->convertEmptyStringsToNull(except: [ - fn (Request $request) => $request->is('admin/*'), - ]); - - $middleware->trimStrings(except: [ - fn (Request $request) => $request->is('admin/*'), - ]); - }) +```php +->withMiddleware(function (Middleware $middleware) { + $middleware->convertEmptyStringsToNull(except: [ + fn (Request $request) => $request->is('admin/*'), + ]); + + $middleware->trimStrings(except: [ + fn (Request $request) => $request->is('admin/*'), + ]); +}) +``` ## Files @@ -564,33 +690,41 @@ If you would like to disable string trimming and empty string conversion for a s You may retrieve uploaded files from an `Illuminate\Http\Request` instance using the `file` method or using dynamic properties. The `file` method returns an instance of the `Illuminate\Http\UploadedFile` class, which extends the PHP `SplFileInfo` class and provides a variety of methods for interacting with the file: - $file = $request->file('photo'); +```php +$file = $request->file('photo'); - $file = $request->photo; +$file = $request->photo; +``` You may determine if a file is present on the request using the `hasFile` method: - if ($request->hasFile('photo')) { - // ... - } +```php +if ($request->hasFile('photo')) { + // ... +} +``` #### Validating Successful Uploads In addition to checking if the file is present, you may verify that there were no problems uploading the file via the `isValid` method: - if ($request->file('photo')->isValid()) { - // ... - } +```php +if ($request->file('photo')->isValid()) { + // ... +} +``` #### File Paths and Extensions The `UploadedFile` class also contains methods for accessing the file's fully-qualified path and its extension. The `extension` method will attempt to guess the file's extension based on its contents. This extension may be different from the extension that was supplied by the client: - $path = $request->photo->path(); +```php +$path = $request->photo->path(); - $extension = $request->photo->extension(); +$extension = $request->photo->extension(); +``` #### Other File Methods @@ -606,15 +740,19 @@ The `store` method accepts the path where the file should be stored relative to The `store` method also accepts an optional second argument for the name of the disk that should be used to store the file. The method will return the path of the file relative to the disk's root: - $path = $request->photo->store('images'); +```php +$path = $request->photo->store('images'); - $path = $request->photo->store('images', 's3'); +$path = $request->photo->store('images', 's3'); +``` If you do not want a filename to be automatically generated, you may use the `storeAs` method, which accepts the path, filename, and disk name as its arguments: - $path = $request->photo->storeAs('images', 'filename.jpg'); +```php +$path = $request->photo->storeAs('images', 'filename.jpg'); - $path = $request->photo->storeAs('images', 'filename.jpg', 's3'); +$path = $request->photo->storeAs('images', 'filename.jpg', 's3'); +``` > [!NOTE] > For more information about file storage in Laravel, check out the complete [file storage documentation](/docs/{{version}}/filesystem). @@ -626,23 +764,27 @@ When running your applications behind a load balancer that terminates TLS / SSL To solve this, you may enable the `Illuminate\Http\Middleware\TrustProxies` middleware that is included in your Laravel application, which allows you to quickly customize the load balancers or proxies that should be trusted by your application. Your trusted proxies should be specified using the `trustProxies` middleware method in your application's `bootstrap/app.php` file: - ->withMiddleware(function (Middleware $middleware) { - $middleware->trustProxies(at: [ - '192.168.1.1', - '10.0.0.0/8', - ]); - }) +```php +->withMiddleware(function (Middleware $middleware) { + $middleware->trustProxies(at: [ + '192.168.1.1', + '10.0.0.0/8', + ]); +}) +``` In addition to configuring the trusted proxies, you may also configure the proxy headers that should be trusted: - ->withMiddleware(function (Middleware $middleware) { - $middleware->trustProxies(headers: Request::HEADER_X_FORWARDED_FOR | - Request::HEADER_X_FORWARDED_HOST | - Request::HEADER_X_FORWARDED_PORT | - Request::HEADER_X_FORWARDED_PROTO | - Request::HEADER_X_FORWARDED_AWS_ELB - ); - }) +```php +->withMiddleware(function (Middleware $middleware) { + $middleware->trustProxies(headers: Request::HEADER_X_FORWARDED_FOR | + Request::HEADER_X_FORWARDED_HOST | + Request::HEADER_X_FORWARDED_PORT | + Request::HEADER_X_FORWARDED_PROTO | + Request::HEADER_X_FORWARDED_AWS_ELB + ); +}) +``` > [!NOTE] > If you are using AWS Elastic Load Balancing, the `headers` value should be `Request::HEADER_X_FORWARDED_AWS_ELB`. If your load balancer uses the standard `Forwarded` header from [RFC 7239](https://www.rfc-editor.org/rfc/rfc7239#section-4), the `headers` value should be `Request::HEADER_FORWARDED`. For more information on the constants that may be used in the `headers` value, check out Symfony's documentation on [trusting proxies](https://symfony.com/doc/7.0/deployment/proxies.html). @@ -652,9 +794,11 @@ In addition to configuring the trusted proxies, you may also configure the proxy If you are using Amazon AWS or another "cloud" load balancer provider, you may not know the IP addresses of your actual balancers. In this case, you may use `*` to trust all proxies: - ->withMiddleware(function (Middleware $middleware) { - $middleware->trustProxies(at: '*'); - }) +```php +->withMiddleware(function (Middleware $middleware) { + $middleware->trustProxies(at: '*'); +}) +``` ## Configuring Trusted Hosts @@ -665,18 +809,24 @@ Typically, you should configure your web server, such as Nginx or Apache, to onl To enable the `TrustHosts` middleware, you should invoke the `trustHosts` middleware method in your application's `bootstrap/app.php` file. Using the `at` argument of this method, you may specify the hostnames that your application should respond to. Incoming requests with other `Host` headers will be rejected: - ->withMiddleware(function (Middleware $middleware) { - $middleware->trustHosts(at: ['laravel.test']); - }) +```php +->withMiddleware(function (Middleware $middleware) { + $middleware->trustHosts(at: ['laravel.test']); +}) +``` By default, requests coming from subdomains of the application's URL are also automatically trusted. If you would like to disable this behavior, you may use the `subdomains` argument: - ->withMiddleware(function (Middleware $middleware) { - $middleware->trustHosts(at: ['laravel.test'], subdomains: false); - }) +```php +->withMiddleware(function (Middleware $middleware) { + $middleware->trustHosts(at: ['laravel.test'], subdomains: false); +}) +``` If you need to access your application's configuration files or database to determine your trusted hosts, you may provide a closure to the `at` argument: - ->withMiddleware(function (Middleware $middleware) { - $middleware->trustHosts(at: fn () => config('app.trusted_hosts')); - }) +```php +->withMiddleware(function (Middleware $middleware) { + $middleware->trustHosts(at: fn () => config('app.trusted_hosts')); +}) +``` diff --git a/responses.md b/responses.md index 68ef7b3bcdc..98e0948a185 100644 --- a/responses.md +++ b/responses.md @@ -25,15 +25,19 @@ All routes and controllers should return a response to be sent back to the user's browser. Laravel provides several different ways to return responses. The most basic response is returning a string from a route or controller. The framework will automatically convert the string into a full HTTP response: - Route::get('/', function () { - return 'Hello World'; - }); +```php +Route::get('/', function () { + return 'Hello World'; +}); +``` In addition to returning strings from your routes and controllers, you may also return arrays. The framework will automatically convert the array into a JSON response: - Route::get('/', function () { - return [1, 2, 3]; - }); +```php +Route::get('/', function () { + return [1, 2, 3]; +}); +``` > [!NOTE] > Did you know you can also return [Eloquent collections](/docs/{{version}}/eloquent-collections) from your routes or controllers? They will automatically be converted to JSON. Give it a shot! @@ -45,204 +49,252 @@ Typically, you won't just be returning simple strings or arrays from your route Returning a full `Response` instance allows you to customize the response's HTTP status code and headers. A `Response` instance inherits from the `Symfony\Component\HttpFoundation\Response` class, which provides a variety of methods for building HTTP responses: - Route::get('/home', function () { - return response('Hello World', 200) - ->header('Content-Type', 'text/plain'); - }); +```php +Route::get('/home', function () { + return response('Hello World', 200) + ->header('Content-Type', 'text/plain'); +}); +``` #### Eloquent Models and Collections You may also return [Eloquent ORM](/docs/{{version}}/eloquent) models and collections directly from your routes and controllers. When you do, Laravel will automatically convert the models and collections to JSON responses while respecting the model's [hidden attributes](/docs/{{version}}/eloquent-serialization#hiding-attributes-from-json): - use App\Models\User; +```php +use App\Models\User; - Route::get('/user/{user}', function (User $user) { - return $user; - }); +Route::get('/user/{user}', function (User $user) { + return $user; +}); +``` ### Attaching Headers to Responses Keep in mind that most response methods are chainable, allowing for the fluent construction of response instances. For example, you may use the `header` method to add a series of headers to the response before sending it back to the user: - return response($content) - ->header('Content-Type', $type) - ->header('X-Header-One', 'Header Value') - ->header('X-Header-Two', 'Header Value'); +```php +return response($content) + ->header('Content-Type', $type) + ->header('X-Header-One', 'Header Value') + ->header('X-Header-Two', 'Header Value'); +``` Or, you may use the `withHeaders` method to specify an array of headers to be added to the response: - return response($content) - ->withHeaders([ - 'Content-Type' => $type, - 'X-Header-One' => 'Header Value', - 'X-Header-Two' => 'Header Value', - ]); +```php +return response($content) + ->withHeaders([ + 'Content-Type' => $type, + 'X-Header-One' => 'Header Value', + 'X-Header-Two' => 'Header Value', + ]); +``` #### Cache Control Middleware Laravel includes a `cache.headers` middleware, which may be used to quickly set the `Cache-Control` header for a group of routes. Directives should be provided using the "snake case" equivalent of the corresponding cache-control directive and should be separated by a semicolon. If `etag` is specified in the list of directives, an MD5 hash of the response content will automatically be set as the ETag identifier: - Route::middleware('cache.headers:public;max_age=2628000;etag')->group(function () { - Route::get('/privacy', function () { - // ... - }); +```php +Route::middleware('cache.headers:public;max_age=2628000;etag')->group(function () { + Route::get('/privacy', function () { + // ... + }); - Route::get('/terms', function () { - // ... - }); + Route::get('/terms', function () { + // ... }); +}); +``` ### Attaching Cookies to Responses You may attach a cookie to an outgoing `Illuminate\Http\Response` instance using the `cookie` method. You should pass the name, value, and the number of minutes the cookie should be considered valid to this method: - return response('Hello World')->cookie( - 'name', 'value', $minutes - ); +```php +return response('Hello World')->cookie( + 'name', 'value', $minutes +); +``` The `cookie` method also accepts a few more arguments which are used less frequently. Generally, these arguments have the same purpose and meaning as the arguments that would be given to PHP's native [setcookie](https://secure.php.net/manual/en/function.setcookie.php) method: - return response('Hello World')->cookie( - 'name', 'value', $minutes, $path, $domain, $secure, $httpOnly - ); +```php +return response('Hello World')->cookie( + 'name', 'value', $minutes, $path, $domain, $secure, $httpOnly +); +``` If you would like to ensure that a cookie is sent with the outgoing response but you do not yet have an instance of that response, you can use the `Cookie` facade to "queue" cookies for attachment to the response when it is sent. The `queue` method accepts the arguments needed to create a cookie instance. These cookies will be attached to the outgoing response before it is sent to the browser: - use Illuminate\Support\Facades\Cookie; +```php +use Illuminate\Support\Facades\Cookie; - Cookie::queue('name', 'value', $minutes); +Cookie::queue('name', 'value', $minutes); +``` #### Generating Cookie Instances If you would like to generate a `Symfony\Component\HttpFoundation\Cookie` instance that can be attached to a response instance at a later time, you may use the global `cookie` helper. This cookie will not be sent back to the client unless it is attached to a response instance: - $cookie = cookie('name', 'value', $minutes); +```php +$cookie = cookie('name', 'value', $minutes); - return response('Hello World')->cookie($cookie); +return response('Hello World')->cookie($cookie); +``` #### Expiring Cookies Early You may remove a cookie by expiring it via the `withoutCookie` method of an outgoing response: - return response('Hello World')->withoutCookie('name'); +```php +return response('Hello World')->withoutCookie('name'); +``` If you do not yet have an instance of the outgoing response, you may use the `Cookie` facade's `expire` method to expire a cookie: - Cookie::expire('name'); +```php +Cookie::expire('name'); +``` ### Cookies and Encryption By default, thanks to the `Illuminate\Cookie\Middleware\EncryptCookies` middleware, all cookies generated by Laravel are encrypted and signed so that they can't be modified or read by the client. If you would like to disable encryption for a subset of cookies generated by your application, you may use the `encryptCookies` method in your application's `bootstrap/app.php` file: - ->withMiddleware(function (Middleware $middleware) { - $middleware->encryptCookies(except: [ - 'cookie_name', - ]); - }) +```php +->withMiddleware(function (Middleware $middleware) { + $middleware->encryptCookies(except: [ + 'cookie_name', + ]); +}) +``` ## Redirects Redirect responses are instances of the `Illuminate\Http\RedirectResponse` class, and contain the proper headers needed to redirect the user to another URL. There are several ways to generate a `RedirectResponse` instance. The simplest method is to use the global `redirect` helper: - Route::get('/dashboard', function () { - return redirect('/home/dashboard'); - }); +```php +Route::get('/dashboard', function () { + return redirect('/home/dashboard'); +}); +``` Sometimes you may wish to redirect the user to their previous location, such as when a submitted form is invalid. You may do so by using the global `back` helper function. Since this feature utilizes the [session](/docs/{{version}}/session), make sure the route calling the `back` function is using the `web` middleware group: - Route::post('/user/profile', function () { - // Validate the request... +```php +Route::post('/user/profile', function () { + // Validate the request... - return back()->withInput(); - }); + return back()->withInput(); +}); +``` ### Redirecting to Named Routes When you call the `redirect` helper with no parameters, an instance of `Illuminate\Routing\Redirector` is returned, allowing you to call any method on the `Redirector` instance. For example, to generate a `RedirectResponse` to a named route, you may use the `route` method: - return redirect()->route('login'); +```php +return redirect()->route('login'); +``` If your route has parameters, you may pass them as the second argument to the `route` method: - // For a route with the following URI: /profile/{id} +```php +// For a route with the following URI: /profile/{id} - return redirect()->route('profile', ['id' => 1]); +return redirect()->route('profile', ['id' => 1]); +``` #### Populating Parameters via Eloquent Models If you are redirecting to a route with an "ID" parameter that is being populated from an Eloquent model, you may pass the model itself. The ID will be extracted automatically: - // For a route with the following URI: /profile/{id} +```php +// For a route with the following URI: /profile/{id} - return redirect()->route('profile', [$user]); +return redirect()->route('profile', [$user]); +``` If you would like to customize the value that is placed in the route parameter, you can specify the column in the route parameter definition (`/profile/{id:slug}`) or you can override the `getRouteKey` method on your Eloquent model: - /** - * Get the value of the model's route key. - */ - public function getRouteKey(): mixed - { - return $this->slug; - } +```php +/** + * Get the value of the model's route key. + */ +public function getRouteKey(): mixed +{ + return $this->slug; +} +``` ### Redirecting to Controller Actions You may also generate redirects to [controller actions](/docs/{{version}}/controllers). To do so, pass the controller and action name to the `action` method: - use App\Http\Controllers\UserController; +```php +use App\Http\Controllers\UserController; - return redirect()->action([UserController::class, 'index']); +return redirect()->action([UserController::class, 'index']); +``` If your controller route requires parameters, you may pass them as the second argument to the `action` method: - return redirect()->action( - [UserController::class, 'profile'], ['id' => 1] - ); +```php +return redirect()->action( + [UserController::class, 'profile'], ['id' => 1] +); +``` ### Redirecting to External Domains Sometimes you may need to redirect to a domain outside of your application. You may do so by calling the `away` method, which creates a `RedirectResponse` without any additional URL encoding, validation, or verification: - return redirect()->away('https://www.google.com'); +```php +return redirect()->away('https://www.google.com'); +``` ### Redirecting With Flashed Session Data Redirecting to a new URL and [flashing data to the session](/docs/{{version}}/session#flash-data) are usually done at the same time. Typically, this is done after successfully performing an action when you flash a success message to the session. For convenience, you may create a `RedirectResponse` instance and flash data to the session in a single, fluent method chain: - Route::post('/user/profile', function () { - // ... +```php +Route::post('/user/profile', function () { + // ... - return redirect('/dashboard')->with('status', 'Profile updated!'); - }); + return redirect('/dashboard')->with('status', 'Profile updated!'); +}); +``` After the user is redirected, you may display the flashed message from the [session](/docs/{{version}}/session). For example, using [Blade syntax](/docs/{{version}}/blade): - @if (session('status')) -
- {{ session('status') }} -
- @endif +```blade +@if (session('status')) +
+ {{ session('status') }} +
+@endif +``` #### Redirecting With Input You may use the `withInput` method provided by the `RedirectResponse` instance to flash the current request's input data to the session before redirecting the user to a new location. This is typically done if the user has encountered a validation error. Once the input has been flashed to the session, you may easily [retrieve it](/docs/{{version}}/requests#retrieving-old-input) during the next request to repopulate the form: - return back()->withInput(); +```php +return back()->withInput(); +``` ## Other Response Types @@ -254,9 +306,11 @@ The `response` helper may be used to generate other types of response instances. If you need control over the response's status and headers but also need to return a [view](/docs/{{version}}/views) as the response's content, you should use the `view` method: - return response() - ->view('hello', $data, 200) - ->header('Content-Type', $type); +```php +return response() + ->view('hello', $data, 200) + ->header('Content-Type', $type); +``` Of course, if you do not need to pass a custom HTTP status code or custom headers, you may use the global `view` helper function. @@ -265,25 +319,31 @@ Of course, if you do not need to pass a custom HTTP status code or custom header The `json` method will automatically set the `Content-Type` header to `application/json`, as well as convert the given array to JSON using the `json_encode` PHP function: - return response()->json([ - 'name' => 'Abigail', - 'state' => 'CA', - ]); +```php +return response()->json([ + 'name' => 'Abigail', + 'state' => 'CA', +]); +``` If you would like to create a JSONP response, you may use the `json` method in combination with the `withCallback` method: - return response() - ->json(['name' => 'Abigail', 'state' => 'CA']) - ->withCallback($request->input('callback')); +```php +return response() + ->json(['name' => 'Abigail', 'state' => 'CA']) + ->withCallback($request->input('callback')); +``` ### File Downloads The `download` method may be used to generate a response that forces the user's browser to download the file at the given path. The `download` method accepts a filename as the second argument to the method, which will determine the filename that is seen by the user downloading the file. Finally, you may pass an array of HTTP headers as the third argument to the method: - return response()->download($pathToFile); +```php +return response()->download($pathToFile); - return response()->download($pathToFile, $name, $headers); +return response()->download($pathToFile, $name, $headers); +``` > [!WARNING] > Symfony HttpFoundation, which manages file downloads, requires the file being downloaded to have an ASCII filename. @@ -293,30 +353,34 @@ The `download` method may be used to generate a response that forces the user's The `file` method may be used to display a file, such as an image or PDF, directly in the user's browser instead of initiating a download. This method accepts the absolute path to the file as its first argument and an array of headers as its second argument: - return response()->file($pathToFile); +```php +return response()->file($pathToFile); - return response()->file($pathToFile, $headers); +return response()->file($pathToFile, $headers); +``` ### Streamed Responses By streaming data to the client as it is generated, you can significantly reduce memory usage and improve performance, especially for very large responses. Streamed responses allow the client to begin processing data before the server has finished sending it: - function streamedContent(): Generator { - yield 'Hello, '; - yield 'World!'; - } - - Route::get('/stream', function () { - return response()->stream(function (): void { - foreach (streamedContent() as $chunk) { - echo $chunk; - ob_flush(); - flush(); - sleep(2); // Simulate delay between chunks... - } - }, 200, ['X-Accel-Buffering' => 'no']); - }); +```php +function streamedContent(): Generator { + yield 'Hello, '; + yield 'World!'; +} + +Route::get('/stream', function () { + return response()->stream(function (): void { + foreach (streamedContent() as $chunk) { + echo $chunk; + ob_flush(); + flush(); + sleep(2); // Simulate delay between chunks... + } + }, 200, ['X-Accel-Buffering' => 'no']); +}); +``` > [!NOTE] > Internally, Laravel utilizes PHP's output buffering functionality. As you can see in the example above, you should use the `ob_flush` and `flush` functions to push buffered content to the client. @@ -326,52 +390,60 @@ By streaming data to the client as it is generated, you can significantly reduce If you need to stream JSON data incrementally, you may utilize the `streamJson` method. This method is especially useful for large datasets that need to be sent progressively to the browser in a format that can be easily parsed by JavaScript: - use App\Models\User; +```php +use App\Models\User; - Route::get('/users.json', function () { - return response()->streamJson([ - 'users' => User::cursor(), - ]); - }); +Route::get('/users.json', function () { + return response()->streamJson([ + 'users' => User::cursor(), + ]); +}); +``` #### Streamed Downloads Sometimes you may wish to turn the string response of a given operation into a downloadable response without having to write the contents of the operation to disk. You may use the `streamDownload` method in this scenario. This method accepts a callback, filename, and an optional array of headers as its arguments: - use App\Services\GitHub; +```php +use App\Services\GitHub; - return response()->streamDownload(function () { - echo GitHub::api('repo') - ->contents() - ->readme('laravel', 'laravel')['contents']; - }, 'laravel-readme.md'); +return response()->streamDownload(function () { + echo GitHub::api('repo') + ->contents() + ->readme('laravel', 'laravel')['contents']; +}, 'laravel-readme.md'); +``` ## Response Macros If you would like to define a custom response that you can re-use in a variety of your routes and controllers, you may use the `macro` method on the `Response` facade. Typically, you should call this method from the `boot` method of one of your application's [service providers](/docs/{{version}}/providers), such as the `App\Providers\AppServiceProvider` service provider: - caps('foo'); +```php +return response()->caps('foo'); +``` diff --git a/routing.md b/routing.md index debcd9bd6f2..8b8935f3d1e 100644 --- a/routing.md +++ b/routing.md @@ -35,11 +35,13 @@ The most basic Laravel routes accept a URI and a closure, providing a very simple and expressive method of defining routes and behavior without complicated routing configuration files: - use Illuminate\Support\Facades\Route; +```php +use Illuminate\Support\Facades\Route; - Route::get('/greeting', function () { - return 'Hello World'; - }); +Route::get('/greeting', function () { + return 'Hello World'; +}); +``` ### The Default Route Files @@ -48,9 +50,11 @@ All Laravel routes are defined in your route files, which are located in the `ro For most applications, you will begin by defining routes in your `routes/web.php` file. The routes defined in `routes/web.php` may be accessed by entering the defined route's URL in your browser. For example, you may access the following route by navigating to `http://example.com/user` in your browser: - use App\Http\Controllers\UserController; +```php +use App\Http\Controllers\UserController; - Route::get('/user', [UserController::class, 'index']); +Route::get('/user', [UserController::class, 'index']); +``` #### API Routes @@ -63,39 +67,47 @@ php artisan install:api The `install:api` command installs [Laravel Sanctum](/docs/{{version}}/sanctum), which provides a robust, yet simple API token authentication guard which can be used to authenticate third-party API consumers, SPAs, or mobile applications. In addition, the `install:api` command creates the `routes/api.php` file: - Route::get('/user', function (Request $request) { - return $request->user(); - })->middleware('auth:sanctum'); +```php +Route::get('/user', function (Request $request) { + return $request->user(); +})->middleware('auth:sanctum'); +``` The routes in `routes/api.php` are stateless and are assigned to the `api` [middleware group](/docs/{{version}}/middleware#laravels-default-middleware-groups). Additionally, the `/api` URI prefix is automatically applied to these routes, so you do not need to manually apply it to every route in the file. You may change the prefix by modifying your application's `bootstrap/app.php` file: - ->withRouting( - api: __DIR__.'/../routes/api.php', - apiPrefix: 'api/admin', - // ... - ) +```php +->withRouting( + api: __DIR__.'/../routes/api.php', + apiPrefix: 'api/admin', + // ... +) +``` #### Available Router Methods The router allows you to register routes that respond to any HTTP verb: - Route::get($uri, $callback); - Route::post($uri, $callback); - Route::put($uri, $callback); - Route::patch($uri, $callback); - Route::delete($uri, $callback); - Route::options($uri, $callback); +```php +Route::get($uri, $callback); +Route::post($uri, $callback); +Route::put($uri, $callback); +Route::patch($uri, $callback); +Route::delete($uri, $callback); +Route::options($uri, $callback); +``` Sometimes you may need to register a route that responds to multiple HTTP verbs. You may do so using the `match` method. Or, you may even register a route that responds to all HTTP verbs using the `any` method: - Route::match(['get', 'post'], '/', function () { - // ... - }); +```php +Route::match(['get', 'post'], '/', function () { + // ... +}); - Route::any('/', function () { - // ... - }); +Route::any('/', function () { + // ... +}); +``` > [!NOTE] > When defining multiple routes that share the same URI, routes using the `get`, `post`, `put`, `patch`, `delete`, and `options` methods should be defined before routes using the `any`, `match`, and `redirect` methods. This ensures the incoming request is matched with the correct route. @@ -105,36 +117,46 @@ Sometimes you may need to register a route that responds to multiple HTTP verbs. You may type-hint any dependencies required by your route in your route's callback signature. The declared dependencies will automatically be resolved and injected into the callback by the Laravel [service container](/docs/{{version}}/container). For example, you may type-hint the `Illuminate\Http\Request` class to have the current HTTP request automatically injected into your route callback: - use Illuminate\Http\Request; +```php +use Illuminate\Http\Request; - Route::get('/users', function (Request $request) { - // ... - }); +Route::get('/users', function (Request $request) { + // ... +}); +``` #### CSRF Protection Remember, any HTML forms pointing to `POST`, `PUT`, `PATCH`, or `DELETE` routes that are defined in the `web` routes file should include a CSRF token field. Otherwise, the request will be rejected. You can read more about CSRF protection in the [CSRF documentation](/docs/{{version}}/csrf): -
- @csrf - ... -
+```blade +
+ @csrf + ... +
+``` ### Redirect Routes If you are defining a route that redirects to another URI, you may use the `Route::redirect` method. This method provides a convenient shortcut so that you do not have to define a full route or controller for performing a simple redirect: - Route::redirect('/here', '/there'); +```php +Route::redirect('/here', '/there'); +``` By default, `Route::redirect` returns a `302` status code. You may customize the status code using the optional third parameter: - Route::redirect('/here', '/there', 301); +```php +Route::redirect('/here', '/there', 301); +``` Or, you may use the `Route::permanentRedirect` method to return a `301` status code: - Route::permanentRedirect('/here', '/there'); +```php +Route::permanentRedirect('/here', '/there'); +``` > [!WARNING] > When using route parameters in redirect routes, the following parameters are reserved by Laravel and cannot be used: `destination` and `status`. @@ -144,9 +166,11 @@ Or, you may use the `Route::permanentRedirect` method to return a `301` status c If your route only needs to return a [view](/docs/{{version}}/views), you may use the `Route::view` method. Like the `redirect` method, this method provides a simple shortcut so that you do not have to define a full route or controller. The `view` method accepts a URI as its first argument and a view name as its second argument. In addition, you may provide an array of data to pass to the view as an optional third argument: - Route::view('/welcome', 'welcome'); +```php +Route::view('/welcome', 'welcome'); - Route::view('/welcome', 'welcome', ['name' => 'Taylor']); +Route::view('/welcome', 'welcome', ['name' => 'Taylor']); +``` > [!WARNING] > When using route parameters in view routes, the following parameters are reserved by Laravel and cannot be used: `view`, `data`, `status`, and `headers`. @@ -249,15 +273,19 @@ use Illuminate\Support\Facades\Route; Sometimes you will need to capture segments of the URI within your route. For example, you may need to capture a user's ID from the URL. You may do so by defining route parameters: - Route::get('/user/{id}', function (string $id) { - return 'User '.$id; - }); +```php +Route::get('/user/{id}', function (string $id) { + return 'User '.$id; +}); +``` You may define as many route parameters as required by your route: - Route::get('/posts/{post}/comments/{comment}', function (string $postId, string $commentId) { - // ... - }); +```php +Route::get('/posts/{post}/comments/{comment}', function (string $postId, string $commentId) { + // ... +}); +``` Route parameters are always encased within `{}` braces and should consist of alphabetic characters. Underscores (`_`) are also acceptable within route parameter names. Route parameters are injected into route callbacks / controllers based on their order - the names of the route callback / controller arguments do not matter. @@ -266,67 +294,75 @@ Route parameters are always encased within `{}` braces and should consist of alp If your route has dependencies that you would like the Laravel service container to automatically inject into your route's callback, you should list your route parameters after your dependencies: - use Illuminate\Http\Request; +```php +use Illuminate\Http\Request; - Route::get('/user/{id}', function (Request $request, string $id) { - return 'User '.$id; - }); +Route::get('/user/{id}', function (Request $request, string $id) { + return 'User '.$id; +}); +``` ### Optional Parameters Occasionally you may need to specify a route parameter that may not always be present in the URI. You may do so by placing a `?` mark after the parameter name. Make sure to give the route's corresponding variable a default value: - Route::get('/user/{name?}', function (?string $name = null) { - return $name; - }); +```php +Route::get('/user/{name?}', function (?string $name = null) { + return $name; +}); - Route::get('/user/{name?}', function (?string $name = 'John') { - return $name; - }); +Route::get('/user/{name?}', function (?string $name = 'John') { + return $name; +}); +``` ### Regular Expression Constraints You may constrain the format of your route parameters using the `where` method on a route instance. The `where` method accepts the name of the parameter and a regular expression defining how the parameter should be constrained: - Route::get('/user/{name}', function (string $name) { - // ... - })->where('name', '[A-Za-z]+'); +```php +Route::get('/user/{name}', function (string $name) { + // ... +})->where('name', '[A-Za-z]+'); - Route::get('/user/{id}', function (string $id) { - // ... - })->where('id', '[0-9]+'); +Route::get('/user/{id}', function (string $id) { + // ... +})->where('id', '[0-9]+'); - Route::get('/user/{id}/{name}', function (string $id, string $name) { - // ... - })->where(['id' => '[0-9]+', 'name' => '[a-z]+']); +Route::get('/user/{id}/{name}', function (string $id, string $name) { + // ... +})->where(['id' => '[0-9]+', 'name' => '[a-z]+']); +``` For convenience, some commonly used regular expression patterns have helper methods that allow you to quickly add pattern constraints to your routes: - Route::get('/user/{id}/{name}', function (string $id, string $name) { - // ... - })->whereNumber('id')->whereAlpha('name'); +```php +Route::get('/user/{id}/{name}', function (string $id, string $name) { + // ... +})->whereNumber('id')->whereAlpha('name'); - Route::get('/user/{name}', function (string $name) { - // ... - })->whereAlphaNumeric('name'); +Route::get('/user/{name}', function (string $name) { + // ... +})->whereAlphaNumeric('name'); - Route::get('/user/{id}', function (string $id) { - // ... - })->whereUuid('id'); +Route::get('/user/{id}', function (string $id) { + // ... +})->whereUuid('id'); - Route::get('/user/{id}', function (string $id) { - // ... - })->whereUlid('id'); +Route::get('/user/{id}', function (string $id) { + // ... +})->whereUlid('id'); - Route::get('/category/{category}', function (string $category) { - // ... - })->whereIn('category', ['movie', 'song', 'painting']); +Route::get('/category/{category}', function (string $category) { + // ... +})->whereIn('category', ['movie', 'song', 'painting']); - Route::get('/category/{category}', function (string $category) { - // ... - })->whereIn('category', CategoryEnum::cases()); +Route::get('/category/{category}', function (string $category) { + // ... +})->whereIn('category', CategoryEnum::cases()); +``` If the incoming request does not match the route pattern constraints, a 404 HTTP response will be returned. @@ -335,30 +371,36 @@ If the incoming request does not match the route pattern constraints, a 404 HTTP If you would like a route parameter to always be constrained by a given regular expression, you may use the `pattern` method. You should define these patterns in the `boot` method of your application's `App\Providers\AppServiceProvider` class: - use Illuminate\Support\Facades\Route; +```php +use Illuminate\Support\Facades\Route; - /** - * Bootstrap any application services. - */ - public function boot(): void - { - Route::pattern('id', '[0-9]+'); - } +/** + * Bootstrap any application services. + */ +public function boot(): void +{ + Route::pattern('id', '[0-9]+'); +} +``` Once the pattern has been defined, it is automatically applied to all routes using that parameter name: - Route::get('/user/{id}', function (string $id) { - // Only executed if {id} is numeric... - }); +```php +Route::get('/user/{id}', function (string $id) { + // Only executed if {id} is numeric... +}); +``` #### Encoded Forward Slashes The Laravel routing component allows all characters except `/` to be present within route parameter values. You must explicitly allow `/` to be part of your placeholder using a `where` condition regular expression: - Route::get('/search/{search}', function (string $search) { - return $search; - })->where('search', '.*'); +```php +Route::get('/search/{search}', function (string $search) { + return $search; +})->where('search', '.*'); +``` > [!WARNING] > Encoded forward slashes are only supported within the last route segment. @@ -368,16 +410,20 @@ The Laravel routing component allows all characters except `/` to be present wit Named routes allow the convenient generation of URLs or redirects for specific routes. You may specify a name for a route by chaining the `name` method onto the route definition: - Route::get('/user/profile', function () { - // ... - })->name('profile'); +```php +Route::get('/user/profile', function () { + // ... +})->name('profile'); +``` You may also specify route names for controller actions: - Route::get( - '/user/profile', - [UserProfileController::class, 'show'] - )->name('profile'); +```php +Route::get( + '/user/profile', + [UserProfileController::class, 'show'] +)->name('profile'); +``` > [!WARNING] > Route names should always be unique. @@ -387,31 +433,37 @@ You may also specify route names for controller actions: Once you have assigned a name to a given route, you may use the route's name when generating URLs or redirects via Laravel's `route` and `redirect` helper functions: - // Generating URLs... - $url = route('profile'); +```php +// Generating URLs... +$url = route('profile'); - // Generating Redirects... - return redirect()->route('profile'); +// Generating Redirects... +return redirect()->route('profile'); - return to_route('profile'); +return to_route('profile'); +``` If the named route defines parameters, you may pass the parameters as the second argument to the `route` function. The given parameters will automatically be inserted into the generated URL in their correct positions: - Route::get('/user/{id}/profile', function (string $id) { - // ... - })->name('profile'); +```php +Route::get('/user/{id}/profile', function (string $id) { + // ... +})->name('profile'); - $url = route('profile', ['id' => 1]); +$url = route('profile', ['id' => 1]); +``` If you pass additional parameters in the array, those key / value pairs will automatically be added to the generated URL's query string: - Route::get('/user/{id}/profile', function (string $id) { - // ... - })->name('profile'); +```php +Route::get('/user/{id}/profile', function (string $id) { + // ... +})->name('profile'); - $url = route('profile', ['id' => 1, 'photos' => 'yes']); +$url = route('profile', ['id' => 1, 'photos' => 'yes']); - // /user/1/profile?photos=yes +// /user/1/profile?photos=yes +``` > [!NOTE] > Sometimes, you may wish to specify request-wide default values for URL parameters, such as the current locale. To accomplish this, you may use the [`URL::defaults` method](/docs/{{version}}/urls#default-values). @@ -421,24 +473,26 @@ If you pass additional parameters in the array, those key / value pairs will aut If you would like to determine if the current request was routed to a given named route, you may use the `named` method on a Route instance. For example, you may check the current route name from a route middleware: - use Closure; - use Illuminate\Http\Request; - use Symfony\Component\HttpFoundation\Response; - - /** - * Handle an incoming request. - * - * @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next - */ - public function handle(Request $request, Closure $next): Response - { - if ($request->route()->named('profile')) { - // ... - } - - return $next($request); +```php +use Closure; +use Illuminate\Http\Request; +use Symfony\Component\HttpFoundation\Response; + +/** + * Handle an incoming request. + * + * @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next + */ +public function handle(Request $request, Closure $next): Response +{ + if ($request->route()->named('profile')) { + // ... } + return $next($request); +} +``` + ## Route Groups @@ -451,38 +505,44 @@ Nested groups attempt to intelligently "merge" attributes with their parent grou To assign [middleware](/docs/{{version}}/middleware) to all routes within a group, you may use the `middleware` method before defining the group. Middleware are executed in the order they are listed in the array: - Route::middleware(['first', 'second'])->group(function () { - Route::get('/', function () { - // Uses first & second middleware... - }); +```php +Route::middleware(['first', 'second'])->group(function () { + Route::get('/', function () { + // Uses first & second middleware... + }); - Route::get('/user/profile', function () { - // Uses first & second middleware... - }); + Route::get('/user/profile', function () { + // Uses first & second middleware... }); +}); +``` ### Controllers If a group of routes all utilize the same [controller](/docs/{{version}}/controllers), you may use the `controller` method to define the common controller for all of the routes within the group. Then, when defining the routes, you only need to provide the controller method that they invoke: - use App\Http\Controllers\OrderController; +```php +use App\Http\Controllers\OrderController; - Route::controller(OrderController::class)->group(function () { - Route::get('/orders/{id}', 'show'); - Route::post('/orders', 'store'); - }); +Route::controller(OrderController::class)->group(function () { + Route::get('/orders/{id}', 'show'); + Route::post('/orders', 'store'); +}); +``` ### Subdomain Routing Route groups may also be used to handle subdomain routing. Subdomains may be assigned route parameters just like route URIs, allowing you to capture a portion of the subdomain for usage in your route or controller. The subdomain may be specified by calling the `domain` method before defining the group: - Route::domain('{account}.example.com')->group(function () { - Route::get('/user/{id}', function (string $account, string $id) { - // ... - }); +```php +Route::domain('{account}.example.com')->group(function () { + Route::get('/user/{id}', function (string $account, string $id) { + // ... }); +}); +``` > [!WARNING] > In order to ensure your subdomain routes are reachable, you should register subdomain routes before registering root domain routes. This will prevent root domain routes from overwriting subdomain routes which have the same URI path. @@ -492,22 +552,26 @@ Route groups may also be used to handle subdomain routing. Subdomains may be ass The `prefix` method may be used to prefix each route in the group with a given URI. For example, you may want to prefix all route URIs within the group with `admin`: - Route::prefix('admin')->group(function () { - Route::get('/users', function () { - // Matches The "/admin/users" URL - }); +```php +Route::prefix('admin')->group(function () { + Route::get('/users', function () { + // Matches The "/admin/users" URL }); +}); +``` ### Route Name Prefixes The `name` method may be used to prefix each route name in the group with a given string. For example, you may want to prefix the names of all of the routes in the group with `admin`. The given string is prefixed to the route name exactly as it is specified, so we will be sure to provide the trailing `.` character in the prefix: - Route::name('admin.')->group(function () { - Route::get('/users', function () { - // Route assigned name "admin.users"... - })->name('users'); - }); +```php +Route::name('admin.')->group(function () { + Route::get('/users', function () { + // Route assigned name "admin.users"... + })->name('users'); +}); +``` ## Route Model Binding @@ -519,111 +583,131 @@ When injecting a model ID to a route or controller action, you will often query Laravel automatically resolves Eloquent models defined in routes or controller actions whose type-hinted variable names match a route segment name. For example: - use App\Models\User; +```php +use App\Models\User; - Route::get('/users/{user}', function (User $user) { - return $user->email; - }); +Route::get('/users/{user}', function (User $user) { + return $user->email; +}); +``` Since the `$user` variable is type-hinted as the `App\Models\User` Eloquent model and the variable name matches the `{user}` URI segment, Laravel will automatically inject the model instance that has an ID matching the corresponding value from the request URI. If a matching model instance is not found in the database, a 404 HTTP response will automatically be generated. Of course, implicit binding is also possible when using controller methods. Again, note the `{user}` URI segment matches the `$user` variable in the controller which contains an `App\Models\User` type-hint: - use App\Http\Controllers\UserController; - use App\Models\User; +```php +use App\Http\Controllers\UserController; +use App\Models\User; - // Route definition... - Route::get('/users/{user}', [UserController::class, 'show']); +// Route definition... +Route::get('/users/{user}', [UserController::class, 'show']); - // Controller method definition... - public function show(User $user) - { - return view('user.profile', ['user' => $user]); - } +// Controller method definition... +public function show(User $user) +{ + return view('user.profile', ['user' => $user]); +} +``` #### Soft Deleted Models Typically, implicit model binding will not retrieve models that have been [soft deleted](/docs/{{version}}/eloquent#soft-deleting). However, you may instruct the implicit binding to retrieve these models by chaining the `withTrashed` method onto your route's definition: - use App\Models\User; +```php +use App\Models\User; - Route::get('/users/{user}', function (User $user) { - return $user->email; - })->withTrashed(); +Route::get('/users/{user}', function (User $user) { + return $user->email; +})->withTrashed(); +``` #### Customizing the Key Sometimes you may wish to resolve Eloquent models using a column other than `id`. To do so, you may specify the column in the route parameter definition: - use App\Models\Post; +```php +use App\Models\Post; - Route::get('/posts/{post:slug}', function (Post $post) { - return $post; - }); +Route::get('/posts/{post:slug}', function (Post $post) { + return $post; +}); +``` If you would like model binding to always use a database column other than `id` when retrieving a given model class, you may override the `getRouteKeyName` method on the Eloquent model: - /** - * Get the route key for the model. - */ - public function getRouteKeyName(): string - { - return 'slug'; - } +```php +/** + * Get the route key for the model. + */ +public function getRouteKeyName(): string +{ + return 'slug'; +} +``` #### Custom Keys and Scoping When implicitly binding multiple Eloquent models in a single route definition, you may wish to scope the second Eloquent model such that it must be a child of the previous Eloquent model. For example, consider this route definition that retrieves a blog post by slug for a specific user: - use App\Models\Post; - use App\Models\User; +```php +use App\Models\Post; +use App\Models\User; - Route::get('/users/{user}/posts/{post:slug}', function (User $user, Post $post) { - return $post; - }); +Route::get('/users/{user}/posts/{post:slug}', function (User $user, Post $post) { + return $post; +}); +``` When using a custom keyed implicit binding as a nested route parameter, Laravel will automatically scope the query to retrieve the nested model by its parent using conventions to guess the relationship name on the parent. In this case, it will be assumed that the `User` model has a relationship named `posts` (the plural form of the route parameter name) which can be used to retrieve the `Post` model. If you wish, you may instruct Laravel to scope "child" bindings even when a custom key is not provided. To do so, you may invoke the `scopeBindings` method when defining your route: - use App\Models\Post; - use App\Models\User; +```php +use App\Models\Post; +use App\Models\User; - Route::get('/users/{user}/posts/{post}', function (User $user, Post $post) { - return $post; - })->scopeBindings(); +Route::get('/users/{user}/posts/{post}', function (User $user, Post $post) { + return $post; +})->scopeBindings(); +``` Or, you may instruct an entire group of route definitions to use scoped bindings: - Route::scopeBindings()->group(function () { - Route::get('/users/{user}/posts/{post}', function (User $user, Post $post) { - return $post; - }); +```php +Route::scopeBindings()->group(function () { + Route::get('/users/{user}/posts/{post}', function (User $user, Post $post) { + return $post; }); +}); +``` Similarly, you may explicitly instruct Laravel to not scope bindings by invoking the `withoutScopedBindings` method: - Route::get('/users/{user}/posts/{post:slug}', function (User $user, Post $post) { - return $post; - })->withoutScopedBindings(); +```php +Route::get('/users/{user}/posts/{post:slug}', function (User $user, Post $post) { + return $post; +})->withoutScopedBindings(); +``` #### Customizing Missing Model Behavior Typically, a 404 HTTP response will be generated if an implicitly bound model is not found. However, you may customize this behavior by calling the `missing` method when defining your route. The `missing` method accepts a closure that will be invoked if an implicitly bound model cannot be found: - use App\Http\Controllers\LocationsController; - use Illuminate\Http\Request; - use Illuminate\Support\Facades\Redirect; +```php +use App\Http\Controllers\LocationsController; +use Illuminate\Http\Request; +use Illuminate\Support\Facades\Redirect; - Route::get('/locations/{location:slug}', [LocationsController::class, 'show']) - ->name('locations.view') - ->missing(function (Request $request) { - return Redirect::route('locations.index'); - }); +Route::get('/locations/{location:slug}', [LocationsController::class, 'show']) + ->name('locations.view') + ->missing(function (Request $request) { + return Redirect::route('locations.index'); + }); +``` ### Implicit Enum Binding @@ -658,24 +742,28 @@ Route::get('/categories/{category}', function (Category $category) { You are not required to use Laravel's implicit, convention based model resolution in order to use model binding. You can also explicitly define how route parameters correspond to models. To register an explicit binding, use the router's `model` method to specify the class for a given parameter. You should define your explicit model bindings at the beginning of the `boot` method of your `AppServiceProvider` class: - use App\Models\User; - use Illuminate\Support\Facades\Route; +```php +use App\Models\User; +use Illuminate\Support\Facades\Route; - /** - * Bootstrap any application services. - */ - public function boot(): void - { - Route::model('user', User::class); - } +/** + * Bootstrap any application services. + */ +public function boot(): void +{ + Route::model('user', User::class); +} +``` Next, define a route that contains a `{user}` parameter: - use App\Models\User; +```php +use App\Models\User; - Route::get('/users/{user}', function (User $user) { - // ... - }); +Route::get('/users/{user}', function (User $user) { + // ... +}); +``` Since we have bound all `{user}` parameters to the `App\Models\User` model, an instance of that class will be injected into the route. So, for example, a request to `users/1` will inject the `User` instance from the database which has an ID of `1`. @@ -686,56 +774,64 @@ If a matching model instance is not found in the database, a 404 HTTP response w If you wish to define your own model binding resolution logic, you may use the `Route::bind` method. The closure you pass to the `bind` method will receive the value of the URI segment and should return the instance of the class that should be injected into the route. Again, this customization should take place in the `boot` method of your application's `AppServiceProvider`: - use App\Models\User; - use Illuminate\Support\Facades\Route; - - /** - * Bootstrap any application services. - */ - public function boot(): void - { - Route::bind('user', function (string $value) { - return User::where('name', $value)->firstOrFail(); - }); - } +```php +use App\Models\User; +use Illuminate\Support\Facades\Route; + +/** + * Bootstrap any application services. + */ +public function boot(): void +{ + Route::bind('user', function (string $value) { + return User::where('name', $value)->firstOrFail(); + }); +} +``` Alternatively, you may override the `resolveRouteBinding` method on your Eloquent model. This method will receive the value of the URI segment and should return the instance of the class that should be injected into the route: - /** - * Retrieve the model for a bound value. - * - * @param mixed $value - * @param string|null $field - * @return \Illuminate\Database\Eloquent\Model|null - */ - public function resolveRouteBinding($value, $field = null) - { - return $this->where('name', $value)->firstOrFail(); - } +```php +/** + * Retrieve the model for a bound value. + * + * @param mixed $value + * @param string|null $field + * @return \Illuminate\Database\Eloquent\Model|null + */ +public function resolveRouteBinding($value, $field = null) +{ + return $this->where('name', $value)->firstOrFail(); +} +``` If a route is utilizing [implicit binding scoping](#implicit-model-binding-scoping), the `resolveChildRouteBinding` method will be used to resolve the child binding of the parent model: - /** - * Retrieve the child model for a bound value. - * - * @param string $childType - * @param mixed $value - * @param string|null $field - * @return \Illuminate\Database\Eloquent\Model|null - */ - public function resolveChildRouteBinding($childType, $value, $field) - { - return parent::resolveChildRouteBinding($childType, $value, $field); - } +```php +/** + * Retrieve the child model for a bound value. + * + * @param string $childType + * @param mixed $value + * @param string|null $field + * @return \Illuminate\Database\Eloquent\Model|null + */ +public function resolveChildRouteBinding($childType, $value, $field) +{ + return parent::resolveChildRouteBinding($childType, $value, $field); +} +``` ## Fallback Routes Using the `Route::fallback` method, you may define a route that will be executed when no other route matches the incoming request. Typically, unhandled requests will automatically render a "404" page via your application's exception handler. However, since you would typically define the `fallback` route within your `routes/web.php` file, all middleware in the `web` middleware group will apply to the route. You are free to add additional middleware to this route as needed: - Route::fallback(function () { - // ... - }); +```php +Route::fallback(function () { + // ... +}); +``` ## Rate Limiting @@ -765,128 +861,152 @@ protected function boot(): void Rate limiters are defined using the `RateLimiter` facade's `for` method. The `for` method accepts a rate limiter name and a closure that returns the limit configuration that should apply to routes that are assigned to the rate limiter. Limit configuration are instances of the `Illuminate\Cache\RateLimiting\Limit` class. This class contains helpful "builder" methods so that you can quickly define your limit. The rate limiter name may be any string you wish: - use Illuminate\Cache\RateLimiting\Limit; - use Illuminate\Http\Request; - use Illuminate\Support\Facades\RateLimiter; - - /** - * Bootstrap any application services. - */ - protected function boot(): void - { - RateLimiter::for('global', function (Request $request) { - return Limit::perMinute(1000); - }); - } +```php +use Illuminate\Cache\RateLimiting\Limit; +use Illuminate\Http\Request; +use Illuminate\Support\Facades\RateLimiter; + +/** + * Bootstrap any application services. + */ +protected function boot(): void +{ + RateLimiter::for('global', function (Request $request) { + return Limit::perMinute(1000); + }); +} +``` If the incoming request exceeds the specified rate limit, a response with a 429 HTTP status code will automatically be returned by Laravel. If you would like to define your own response that should be returned by a rate limit, you may use the `response` method: - RateLimiter::for('global', function (Request $request) { - return Limit::perMinute(1000)->response(function (Request $request, array $headers) { - return response('Custom response...', 429, $headers); - }); +```php +RateLimiter::for('global', function (Request $request) { + return Limit::perMinute(1000)->response(function (Request $request, array $headers) { + return response('Custom response...', 429, $headers); }); +}); +``` Since rate limiter callbacks receive the incoming HTTP request instance, you may build the appropriate rate limit dynamically based on the incoming request or authenticated user: - RateLimiter::for('uploads', function (Request $request) { - return $request->user()->vipCustomer() - ? Limit::none() - : Limit::perMinute(100); - }); +```php +RateLimiter::for('uploads', function (Request $request) { + return $request->user()->vipCustomer() + ? Limit::none() + : Limit::perMinute(100); +}); +``` #### Segmenting Rate Limits Sometimes you may wish to segment rate limits by some arbitrary value. For example, you may wish to allow users to access a given route 100 times per minute per IP address. To accomplish this, you may use the `by` method when building your rate limit: - RateLimiter::for('uploads', function (Request $request) { - return $request->user()->vipCustomer() - ? Limit::none() - : Limit::perMinute(100)->by($request->ip()); - }); +```php +RateLimiter::for('uploads', function (Request $request) { + return $request->user()->vipCustomer() + ? Limit::none() + : Limit::perMinute(100)->by($request->ip()); +}); +``` To illustrate this feature using another example, we can limit access to the route to 100 times per minute per authenticated user ID or 10 times per minute per IP address for guests: - RateLimiter::for('uploads', function (Request $request) { - return $request->user() - ? Limit::perMinute(100)->by($request->user()->id) - : Limit::perMinute(10)->by($request->ip()); - }); +```php +RateLimiter::for('uploads', function (Request $request) { + return $request->user() + ? Limit::perMinute(100)->by($request->user()->id) + : Limit::perMinute(10)->by($request->ip()); +}); +``` #### Multiple Rate Limits If needed, you may return an array of rate limits for a given rate limiter configuration. Each rate limit will be evaluated for the route based on the order they are placed within the array: - RateLimiter::for('login', function (Request $request) { - return [ - Limit::perMinute(500), - Limit::perMinute(3)->by($request->input('email')), - ]; - }); +```php +RateLimiter::for('login', function (Request $request) { + return [ + Limit::perMinute(500), + Limit::perMinute(3)->by($request->input('email')), + ]; +}); +``` If you're assigning multiple rate limits segmented by identical `by` values, you should ensure that each `by` value is unique. The easiest way to achieve this is to prefix the values given to the `by` method: - RateLimiter::for('uploads', function (Request $request) { - return [ - Limit::perMinute(10)->by('minute:'.$request->user()->id), - Limit::perDay(1000)->by('day:'.$request->user()->id), - ]; - }); +```php +RateLimiter::for('uploads', function (Request $request) { + return [ + Limit::perMinute(10)->by('minute:'.$request->user()->id), + Limit::perDay(1000)->by('day:'.$request->user()->id), + ]; +}); +``` ### Attaching Rate Limiters to Routes Rate limiters may be attached to routes or route groups using the `throttle` [middleware](/docs/{{version}}/middleware). The throttle middleware accepts the name of the rate limiter you wish to assign to the route: - Route::middleware(['throttle:uploads'])->group(function () { - Route::post('/audio', function () { - // ... - }); +```php +Route::middleware(['throttle:uploads'])->group(function () { + Route::post('/audio', function () { + // ... + }); - Route::post('/video', function () { - // ... - }); + Route::post('/video', function () { + // ... }); +}); +``` #### Throttling With Redis By default, the `throttle` middleware is mapped to the `Illuminate\Routing\Middleware\ThrottleRequests` class. However, if you are using Redis as your application's cache driver, you may wish to instruct Laravel to use Redis to manage rate limiting. To do so, you should use the `throttleWithRedis` method in your application's `bootstrap/app.php` file. This method maps the `throttle` middleware to the `Illuminate\Routing\Middleware\ThrottleRequestsWithRedis` middleware class: - ->withMiddleware(function (Middleware $middleware) { - $middleware->throttleWithRedis(); - // ... - }) +```php +->withMiddleware(function (Middleware $middleware) { + $middleware->throttleWithRedis(); + // ... +}) +``` ## Form Method Spoofing HTML forms do not support `PUT`, `PATCH`, or `DELETE` actions. So, when defining `PUT`, `PATCH`, or `DELETE` routes that are called from an HTML form, you will need to add a hidden `_method` field to the form. The value sent with the `_method` field will be used as the HTTP request method: -
- - -
+```blade +
+ + +
+``` For convenience, you may use the `@method` [Blade directive](/docs/{{version}}/blade) to generate the `_method` input field: -
- @method('PUT') - @csrf -
+```blade +
+ @method('PUT') + @csrf +
+``` ## Accessing the Current Route You may use the `current`, `currentRouteName`, and `currentRouteAction` methods on the `Route` facade to access information about the route handling the incoming request: - use Illuminate\Support\Facades\Route; +```php +use Illuminate\Support\Facades\Route; - $route = Route::current(); // Illuminate\Routing\Route - $name = Route::currentRouteName(); // string - $action = Route::currentRouteAction(); // string +$route = Route::current(); // Illuminate\Routing\Route +$name = Route::currentRouteName(); // string +$action = Route::currentRouteAction(); // string +``` You may refer to the API documentation for both the [underlying class of the Route facade](https://laravel.com/api/{{version}}/Illuminate/Routing/Router.html) and [Route instance](https://laravel.com/api/{{version}}/Illuminate/Routing/Route.html) to review all of the methods that are available on the router and route classes. diff --git a/sanctum.md b/sanctum.md index 97cc7a6a079..3a3fe9f4a99 100644 --- a/sanctum.md +++ b/sanctum.md @@ -70,25 +70,29 @@ Next, if you plan to utilize Sanctum to authenticate an SPA, please refer to the Although not typically required, you are free to extend the `PersonalAccessToken` model used internally by Sanctum: - use Laravel\Sanctum\PersonalAccessToken as SanctumPersonalAccessToken; +```php +use Laravel\Sanctum\PersonalAccessToken as SanctumPersonalAccessToken; - class PersonalAccessToken extends SanctumPersonalAccessToken - { - // ... - } +class PersonalAccessToken extends SanctumPersonalAccessToken +{ + // ... +} +``` Then, you may instruct Sanctum to use your custom model via the `usePersonalAccessTokenModel` method provided by Sanctum. Typically, you should call this method in the `boot` method of your application's `AppServiceProvider` file: - use App\Models\Sanctum\PersonalAccessToken; - use Laravel\Sanctum\Sanctum; +```php +use App\Models\Sanctum\PersonalAccessToken; +use Laravel\Sanctum\Sanctum; - /** - * Bootstrap any application services. - */ - public function boot(): void - { - Sanctum::usePersonalAccessTokenModel(PersonalAccessToken::class); - } +/** + * Bootstrap any application services. + */ +public function boot(): void +{ + Sanctum::usePersonalAccessTokenModel(PersonalAccessToken::class); +} +``` ## API Token Authentication @@ -103,72 +107,88 @@ Sanctum allows you to issue API tokens / personal access tokens that may be used To begin issuing tokens for users, your User model should use the `Laravel\Sanctum\HasApiTokens` trait: - use Laravel\Sanctum\HasApiTokens; +```php +use Laravel\Sanctum\HasApiTokens; - class User extends Authenticatable - { - use HasApiTokens, HasFactory, Notifiable; - } +class User extends Authenticatable +{ + use HasApiTokens, HasFactory, Notifiable; +} +``` To issue a token, you may use the `createToken` method. The `createToken` method returns a `Laravel\Sanctum\NewAccessToken` instance. API tokens are hashed using SHA-256 hashing before being stored in your database, but you may access the plain-text value of the token using the `plainTextToken` property of the `NewAccessToken` instance. You should display this value to the user immediately after the token has been created: - use Illuminate\Http\Request; +```php +use Illuminate\Http\Request; - Route::post('/tokens/create', function (Request $request) { - $token = $request->user()->createToken($request->token_name); +Route::post('/tokens/create', function (Request $request) { + $token = $request->user()->createToken($request->token_name); - return ['token' => $token->plainTextToken]; - }); + return ['token' => $token->plainTextToken]; +}); +``` You may access all of the user's tokens using the `tokens` Eloquent relationship provided by the `HasApiTokens` trait: - foreach ($user->tokens as $token) { - // ... - } +```php +foreach ($user->tokens as $token) { + // ... +} +``` ### Token Abilities Sanctum allows you to assign "abilities" to tokens. Abilities serve a similar purpose as OAuth's "scopes". You may pass an array of string abilities as the second argument to the `createToken` method: - return $user->createToken('token-name', ['server:update'])->plainTextToken; +```php +return $user->createToken('token-name', ['server:update'])->plainTextToken; +``` When handling an incoming request authenticated by Sanctum, you may determine if the token has a given ability using the `tokenCan` or `tokenCant` methods: - if ($user->tokenCan('server:update')) { - // ... - } +```php +if ($user->tokenCan('server:update')) { + // ... +} - if ($user->tokenCant('server:update')) { - // ... - } +if ($user->tokenCant('server:update')) { + // ... +} +``` #### Token Ability Middleware Sanctum also includes two middleware that may be used to verify that an incoming request is authenticated with a token that has been granted a given ability. To get started, define the following middleware aliases in your application's `bootstrap/app.php` file: - use Laravel\Sanctum\Http\Middleware\CheckAbilities; - use Laravel\Sanctum\Http\Middleware\CheckForAnyAbility; - - ->withMiddleware(function (Middleware $middleware) { - $middleware->alias([ - 'abilities' => CheckAbilities::class, - 'ability' => CheckForAnyAbility::class, - ]); - }) +```php +use Laravel\Sanctum\Http\Middleware\CheckAbilities; +use Laravel\Sanctum\Http\Middleware\CheckForAnyAbility; + +->withMiddleware(function (Middleware $middleware) { + $middleware->alias([ + 'abilities' => CheckAbilities::class, + 'ability' => CheckForAnyAbility::class, + ]); +}) +``` The `abilities` middleware may be assigned to a route to verify that the incoming request's token has all of the listed abilities: - Route::get('/orders', function () { - // Token has both "check-status" and "place-orders" abilities... - })->middleware(['auth:sanctum', 'abilities:check-status,place-orders']); +```php +Route::get('/orders', function () { + // Token has both "check-status" and "place-orders" abilities... +})->middleware(['auth:sanctum', 'abilities:check-status,place-orders']); +``` The `ability` middleware may be assigned to a route to verify that the incoming request's token has *at least one* of the listed abilities: - Route::get('/orders', function () { - // Token has the "check-status" or "place-orders" ability... - })->middleware(['auth:sanctum', 'ability:check-status,place-orders']); +```php +Route::get('/orders', function () { + // Token has the "check-status" or "place-orders" ability... +})->middleware(['auth:sanctum', 'ability:check-status,place-orders']); +``` #### First-Party UI Initiated Requests @@ -193,25 +213,29 @@ To protect routes so that all incoming requests must be authenticated, you shoul You may be wondering why we suggest that you authenticate the routes within your application's `routes/web.php` file using the `sanctum` guard. Remember, Sanctum will first attempt to authenticate incoming requests using Laravel's typical session authentication cookie. If that cookie is not present then Sanctum will attempt to authenticate the request using a token in the request's `Authorization` header. In addition, authenticating all requests using Sanctum ensures that we may always call the `tokenCan` method on the currently authenticated user instance: - use Illuminate\Http\Request; +```php +use Illuminate\Http\Request; - Route::get('/user', function (Request $request) { - return $request->user(); - })->middleware('auth:sanctum'); +Route::get('/user', function (Request $request) { + return $request->user(); +})->middleware('auth:sanctum'); +``` ### Revoking Tokens You may "revoke" tokens by deleting them from your database using the `tokens` relationship that is provided by the `Laravel\Sanctum\HasApiTokens` trait: - // Revoke all tokens... - $user->tokens()->delete(); +```php +// Revoke all tokens... +$user->tokens()->delete(); - // Revoke the token that was used to authenticate the current request... - $request->user()->currentAccessToken()->delete(); +// Revoke the token that was used to authenticate the current request... +$request->user()->currentAccessToken()->delete(); - // Revoke a specific token... - $user->tokens()->where('id', $tokenId)->delete(); +// Revoke a specific token... +$user->tokens()->where('id', $tokenId)->delete(); +``` ### Token Expiration @@ -264,9 +288,11 @@ First, you should configure which domains your SPA will be making requests from. Next, you should instruct Laravel that incoming requests from your SPA can authenticate using Laravel's session cookies, while still allowing requests from third parties or mobile applications to authenticate using API tokens. This can be easily accomplished by invoking the `statefulApi` middleware method in your application's `bootstrap/app.php` file: - ->withMiddleware(function (Middleware $middleware) { - $middleware->statefulApi(); - }) +```php +->withMiddleware(function (Middleware $middleware) { + $middleware->statefulApi(); +}) +``` #### CORS and Cookies @@ -290,7 +316,9 @@ axios.defaults.withXSRFToken = true; Finally, you should ensure your application's session cookie domain configuration supports any subdomain of your root domain. You may accomplish this by prefixing the domain with a leading `.` within your application's `config/session.php` configuration file: - 'domain' => '.domain.com', +```php +'domain' => '.domain.com', +``` ### Authenticating @@ -325,26 +353,30 @@ Of course, if your user's session expires due to lack of activity, subsequent re To protect routes so that all incoming requests must be authenticated, you should attach the `sanctum` authentication guard to your API routes within your `routes/api.php` file. This guard will ensure that incoming requests are authenticated as either stateful authenticated requests from your SPA or contain a valid API token header if the request is from a third party: - use Illuminate\Http\Request; +```php +use Illuminate\Http\Request; - Route::get('/user', function (Request $request) { - return $request->user(); - })->middleware('auth:sanctum'); +Route::get('/user', function (Request $request) { + return $request->user(); +})->middleware('auth:sanctum'); +``` ### Authorizing Private Broadcast Channels If your SPA needs to authenticate with [private / presence broadcast channels](/docs/{{version}}/broadcasting#authorizing-channels), you should remove the `channels` entry from the `withRouting` method contained in your application's `bootstrap/app.php` file. Instead, you should invoke the `withBroadcasting` method so that you may specify the correct middleware for your application's broadcasting routes: - return Application::configure(basePath: dirname(__DIR__)) - ->withRouting( - web: __DIR__.'/../routes/web.php', - // ... - ) - ->withBroadcasting( - __DIR__.'/../routes/channels.php', - ['prefix' => 'api', 'middleware' => ['api', 'auth:sanctum']], - ) +```php +return Application::configure(basePath: dirname(__DIR__)) + ->withRouting( + web: __DIR__.'/../routes/web.php', + // ... + ) + ->withBroadcasting( + __DIR__.'/../routes/channels.php', + ['prefix' => 'api', 'middleware' => ['api', 'auth:sanctum']], + ) +``` Next, in order for Pusher's authorization requests to succeed, you will need to provide a custom Pusher `authorizer` when initializing [Laravel Echo](/docs/{{version}}/broadcasting#client-side-installation). This allows your application to configure Pusher to use the `axios` instance that is [properly configured for cross-domain requests](#cors-and-cookies): @@ -385,28 +417,30 @@ To get started, create a route that accepts the user's email / username, passwor Typically, you will make a request to the token endpoint from your mobile application's "login" screen. The endpoint will return the plain-text API token which may then be stored on the mobile device and used to make additional API requests: - use App\Models\User; - use Illuminate\Http\Request; - use Illuminate\Support\Facades\Hash; - use Illuminate\Validation\ValidationException; - - Route::post('/sanctum/token', function (Request $request) { - $request->validate([ - 'email' => 'required|email', - 'password' => 'required', - 'device_name' => 'required', +```php +use App\Models\User; +use Illuminate\Http\Request; +use Illuminate\Support\Facades\Hash; +use Illuminate\Validation\ValidationException; + +Route::post('/sanctum/token', function (Request $request) { + $request->validate([ + 'email' => 'required|email', + 'password' => 'required', + 'device_name' => 'required', + ]); + + $user = User::where('email', $request->email)->first(); + + if (! $user || ! Hash::check($request->password, $user->password)) { + throw ValidationException::withMessages([ + 'email' => ['The provided credentials are incorrect.'], ]); + } - $user = User::where('email', $request->email)->first(); - - if (! $user || ! Hash::check($request->password, $user->password)) { - throw ValidationException::withMessages([ - 'email' => ['The provided credentials are incorrect.'], - ]); - } - - return $user->createToken($request->device_name)->plainTextToken; - }); + return $user->createToken($request->device_name)->plainTextToken; +}); +``` When the mobile application uses the token to make an API request to your application, it should pass the token in the `Authorization` header as a `Bearer` token. @@ -418,20 +452,24 @@ When the mobile application uses the token to make an API request to your applic As previously documented, you may protect routes so that all incoming requests must be authenticated by attaching the `sanctum` authentication guard to the routes: - Route::get('/user', function (Request $request) { - return $request->user(); - })->middleware('auth:sanctum'); +```php +Route::get('/user', function (Request $request) { + return $request->user(); +})->middleware('auth:sanctum'); +``` ### Revoking Tokens To allow users to revoke API tokens issued to mobile devices, you may list them by name, along with a "Revoke" button, within an "account settings" portion of your web application's UI. When the user clicks the "Revoke" button, you can delete the token from the database. Remember, you can access a user's API tokens via the `tokens` relationship provided by the `Laravel\Sanctum\HasApiTokens` trait: - // Revoke all tokens... - $user->tokens()->delete(); +```php +// Revoke all tokens... +$user->tokens()->delete(); - // Revoke a specific token... - $user->tokens()->where('id', $tokenId)->delete(); +// Revoke a specific token... +$user->tokens()->where('id', $tokenId)->delete(); +``` ## Testing @@ -473,7 +511,9 @@ public function test_task_list_can_be_retrieved(): void If you would like to grant all abilities to the token, you should include `*` in the ability list provided to the `actingAs` method: - Sanctum::actingAs( - User::factory()->create(), - ['*'] - ); +```php +Sanctum::actingAs( + User::factory()->create(), + ['*'] +); +``` diff --git a/scheduling.md b/scheduling.md index 912f8e2070a..3b63243b16a 100644 --- a/scheduling.md +++ b/scheduling.md @@ -31,26 +31,32 @@ Laravel's command scheduler offers a fresh approach to managing scheduled tasks You may define all of your scheduled tasks in your application's `routes/console.php` file. To get started, let's take a look at an example. In this example, we will schedule a closure to be called every day at midnight. Within the closure we will execute a database query to clear a table: - delete(); - })->daily(); +Schedule::call(function () { + DB::table('recent_users')->delete(); +})->daily(); +``` In addition to scheduling using closures, you may also schedule [invokable objects](https://secure.php.net/manual/en/language.oop5.magic.php#object.invoke). Invokable objects are simple PHP classes that contain an `__invoke` method: - Schedule::call(new DeleteRecentUsers)->daily(); +```php +Schedule::call(new DeleteRecentUsers)->daily(); +``` If you prefer to reserve your `routes/console.php` file for command definitions only, you may use the `withSchedule` method in your application's `bootstrap/app.php` file to define your scheduled tasks. This method accepts a closure that receives an instance of the scheduler: - use Illuminate\Console\Scheduling\Schedule; +```php +use Illuminate\Console\Scheduling\Schedule; - ->withSchedule(function (Schedule $schedule) { - $schedule->call(new DeleteRecentUsers)->daily(); - }) +->withSchedule(function (Schedule $schedule) { + $schedule->call(new DeleteRecentUsers)->daily(); +}) +``` If you would like to view an overview of your scheduled tasks and the next time they are scheduled to run, you may use the `schedule:list` Artisan command: @@ -65,54 +71,66 @@ In addition to scheduling closures, you may also schedule [Artisan commands](/do When scheduling Artisan commands using the command's class name, you may pass an array of additional command-line arguments that should be provided to the command when it is invoked: - use App\Console\Commands\SendEmailsCommand; - use Illuminate\Support\Facades\Schedule; +```php +use App\Console\Commands\SendEmailsCommand; +use Illuminate\Support\Facades\Schedule; - Schedule::command('emails:send Taylor --force')->daily(); +Schedule::command('emails:send Taylor --force')->daily(); - Schedule::command(SendEmailsCommand::class, ['Taylor', '--force'])->daily(); +Schedule::command(SendEmailsCommand::class, ['Taylor', '--force'])->daily(); +``` #### Scheduling Artisan Closure Commands If you want to schedule an Artisan command defined by a closure, you may chain the scheduling related methods after the command's definition: - Artisan::command('delete:recent-users', function () { - DB::table('recent_users')->delete(); - })->purpose('Delete recent users')->daily(); +```php +Artisan::command('delete:recent-users', function () { + DB::table('recent_users')->delete(); +})->purpose('Delete recent users')->daily(); +``` If you need to pass arguments to the closure command, you may provide them to the `schedule` method: - Artisan::command('emails:send {user} {--force}', function ($user) { - // ... - })->purpose('Send emails to the specified user')->schedule(['Taylor', '--force'])->daily(); +```php +Artisan::command('emails:send {user} {--force}', function ($user) { + // ... +})->purpose('Send emails to the specified user')->schedule(['Taylor', '--force'])->daily(); +``` ### Scheduling Queued Jobs The `job` method may be used to schedule a [queued job](/docs/{{version}}/queues). This method provides a convenient way to schedule queued jobs without using the `call` method to define closures to queue the job: - use App\Jobs\Heartbeat; - use Illuminate\Support\Facades\Schedule; +```php +use App\Jobs\Heartbeat; +use Illuminate\Support\Facades\Schedule; - Schedule::job(new Heartbeat)->everyFiveMinutes(); +Schedule::job(new Heartbeat)->everyFiveMinutes(); +``` Optional second and third arguments may be provided to the `job` method which specifies the queue name and queue connection that should be used to queue the job: - use App\Jobs\Heartbeat; - use Illuminate\Support\Facades\Schedule; +```php +use App\Jobs\Heartbeat; +use Illuminate\Support\Facades\Schedule; - // Dispatch the job to the "heartbeats" queue on the "sqs" connection... - Schedule::job(new Heartbeat, 'heartbeats', 'sqs')->everyFiveMinutes(); +// Dispatch the job to the "heartbeats" queue on the "sqs" connection... +Schedule::job(new Heartbeat, 'heartbeats', 'sqs')->everyFiveMinutes(); +``` ### Scheduling Shell Commands The `exec` method may be used to issue a command to the operating system: - use Illuminate\Support\Facades\Schedule; +```php +use Illuminate\Support\Facades\Schedule; - Schedule::exec('node /home/forge/script.js')->daily(); +Schedule::exec('node /home/forge/script.js')->daily(); +``` ### Schedule Frequency Options @@ -166,19 +184,21 @@ We've already seen a few examples of how you may configure a task to run at spec These methods may be combined with additional constraints to create even more finely tuned schedules that only run on certain days of the week. For example, you may schedule a command to run weekly on Monday: - use Illuminate\Support\Facades\Schedule; - - // Run once per week on Monday at 1 PM... - Schedule::call(function () { - // ... - })->weekly()->mondays()->at('13:00'); +```php +use Illuminate\Support\Facades\Schedule; - // Run hourly from 8 AM to 5 PM on weekdays... - Schedule::command('foo') - ->weekdays() - ->hourly() - ->timezone('America/Chicago') - ->between('8:00', '17:00'); +// Run once per week on Monday at 1 PM... +Schedule::call(function () { + // ... +})->weekly()->mondays()->at('13:00'); + +// Run hourly from 8 AM to 5 PM on weekdays... +Schedule::command('foo') + ->weekdays() + ->hourly() + ->timezone('America/Chicago') + ->between('8:00', '17:00'); +``` A list of additional schedule constraints may be found below: @@ -208,50 +228,62 @@ A list of additional schedule constraints may be found below: The `days` method may be used to limit the execution of a task to specific days of the week. For example, you may schedule a command to run hourly on Sundays and Wednesdays: - use Illuminate\Support\Facades\Schedule; +```php +use Illuminate\Support\Facades\Schedule; - Schedule::command('emails:send') - ->hourly() - ->days([0, 3]); +Schedule::command('emails:send') + ->hourly() + ->days([0, 3]); +``` Alternatively, you may use the constants available on the `Illuminate\Console\Scheduling\Schedule` class when defining the days on which a task should run: - use Illuminate\Support\Facades; - use Illuminate\Console\Scheduling\Schedule; +```php +use Illuminate\Support\Facades; +use Illuminate\Console\Scheduling\Schedule; - Facades\Schedule::command('emails:send') - ->hourly() - ->days([Schedule::SUNDAY, Schedule::WEDNESDAY]); +Facades\Schedule::command('emails:send') + ->hourly() + ->days([Schedule::SUNDAY, Schedule::WEDNESDAY]); +``` #### Between Time Constraints The `between` method may be used to limit the execution of a task based on the time of day: - Schedule::command('emails:send') - ->hourly() - ->between('7:00', '22:00'); +```php +Schedule::command('emails:send') + ->hourly() + ->between('7:00', '22:00'); +``` Similarly, the `unlessBetween` method can be used to exclude the execution of a task for a period of time: - Schedule::command('emails:send') - ->hourly() - ->unlessBetween('23:00', '4:00'); +```php +Schedule::command('emails:send') + ->hourly() + ->unlessBetween('23:00', '4:00'); +``` #### Truth Test Constraints The `when` method may be used to limit the execution of a task based on the result of a given truth test. In other words, if the given closure returns `true`, the task will execute as long as no other constraining conditions prevent the task from running: - Schedule::command('emails:send')->daily()->when(function () { - return true; - }); +```php +Schedule::command('emails:send')->daily()->when(function () { + return true; +}); +``` The `skip` method may be seen as the inverse of `when`. If the `skip` method returns `true`, the scheduled task will not be executed: - Schedule::command('emails:send')->daily()->skip(function () { - return true; - }); +```php +Schedule::command('emails:send')->daily()->skip(function () { + return true; +}); +``` When using chained `when` methods, the scheduled command will only execute if all `when` conditions return `true`. @@ -260,26 +292,32 @@ When using chained `when` methods, the scheduled command will only execute if al The `environments` method may be used to execute tasks only on the given environments (as defined by the `APP_ENV` [environment variable](/docs/{{version}}/configuration#environment-configuration)): - Schedule::command('emails:send') - ->daily() - ->environments(['staging', 'production']); +```php +Schedule::command('emails:send') + ->daily() + ->environments(['staging', 'production']); +``` ### Timezones Using the `timezone` method, you may specify that a scheduled task's time should be interpreted within a given timezone: - use Illuminate\Support\Facades\Schedule; +```php +use Illuminate\Support\Facades\Schedule; - Schedule::command('report:generate') - ->timezone('America/New_York') - ->at('2:00') +Schedule::command('report:generate') + ->timezone('America/New_York') + ->at('2:00') +``` If you are repeatedly assigning the same timezone to all of your scheduled tasks, you can specify which timezone should be assigned to all schedules by defining a `schedule_timezone` option within your application's `app` configuration file: - 'timezone' => 'UTC', +```php +'timezone' => 'UTC', - 'schedule_timezone' => 'America/Chicago', +'schedule_timezone' => 'America/Chicago', +``` > [!WARNING] > Remember that some timezones utilize daylight savings time. When daylight saving time changes occur, your scheduled task may run twice or even not run at all. For this reason, we recommend avoiding timezone scheduling when possible. @@ -289,15 +327,19 @@ If you are repeatedly assigning the same timezone to all of your scheduled tasks By default, scheduled tasks will be run even if the previous instance of the task is still running. To prevent this, you may use the `withoutOverlapping` method: - use Illuminate\Support\Facades\Schedule; +```php +use Illuminate\Support\Facades\Schedule; - Schedule::command('emails:send')->withoutOverlapping(); +Schedule::command('emails:send')->withoutOverlapping(); +``` In this example, the `emails:send` [Artisan command](/docs/{{version}}/artisan) will be run every minute if it is not already running. The `withoutOverlapping` method is especially useful if you have tasks that vary drastically in their execution time, preventing you from predicting exactly how long a given task will take. If needed, you may specify how many minutes must pass before the "without overlapping" lock expires. By default, the lock will expire after 24 hours: - Schedule::command('emails:send')->withoutOverlapping(10); +```php +Schedule::command('emails:send')->withoutOverlapping(10); +``` Behind the scenes, the `withoutOverlapping` method utilizes your application's [cache](/docs/{{version}}/cache) to obtain locks. If necessary, you can clear these cache locks using the `schedule:clear-cache` Artisan command. This is typically only necessary if a task becomes stuck due to an unexpected server problem. @@ -311,12 +353,14 @@ If your application's scheduler is running on multiple servers, you may limit a To indicate that the task should run on only one server, use the `onOneServer` method when defining the scheduled task. The first server to obtain the task will secure an atomic lock on the job to prevent other servers from running the same task at the same time: - use Illuminate\Support\Facades\Schedule; +```php +use Illuminate\Support\Facades\Schedule; - Schedule::command('report:generate') - ->fridays() - ->at('17:00') - ->onOneServer(); +Schedule::command('report:generate') + ->fridays() + ->at('17:00') + ->onOneServer(); +``` #### Naming Single Server Jobs @@ -349,11 +393,13 @@ Schedule::call(fn () => User::resetApiRequestCount()) By default, multiple tasks scheduled at the same time will execute sequentially based on the order they are defined in your `schedule` method. If you have long-running tasks, this may cause subsequent tasks to start much later than anticipated. If you would like to run tasks in the background so that they may all run simultaneously, you may use the `runInBackground` method: - use Illuminate\Support\Facades\Schedule; +```php +use Illuminate\Support\Facades\Schedule; - Schedule::command('analytics:report') - ->daily() - ->runInBackground(); +Schedule::command('analytics:report') + ->daily() + ->runInBackground(); +``` > [!WARNING] > The `runInBackground` method may only be used when scheduling tasks via the `command` and `exec` methods. @@ -363,7 +409,9 @@ By default, multiple tasks scheduled at the same time will execute sequentially Your application's scheduled tasks will not run when the application is in [maintenance mode](/docs/{{version}}/configuration#maintenance-mode), since we don't want your tasks to interfere with any unfinished maintenance you may be performing on your server. However, if you would like to force a task to run even in maintenance mode, you may call the `evenInMaintenanceMode` method when defining the task: - Schedule::command('emails:send')->evenInMaintenanceMode(); +```php +Schedule::command('emails:send')->evenInMaintenanceMode(); +``` ### Schedule Groups @@ -400,21 +448,25 @@ So, when using Laravel's scheduler, we only need to add a single cron configurat On most operating systems, cron jobs are limited to running a maximum of once per minute. However, Laravel's scheduler allows you to schedule tasks to run at more frequent intervals, even as often as once per second: - use Illuminate\Support\Facades\Schedule; +```php +use Illuminate\Support\Facades\Schedule; - Schedule::call(function () { - DB::table('recent_users')->delete(); - })->everySecond(); +Schedule::call(function () { + DB::table('recent_users')->delete(); +})->everySecond(); +``` When sub-minute tasks are defined within your application, the `schedule:run` command will continue running until the end of the current minute instead of exiting immediately. This allows the command to invoke all required sub-minute tasks throughout the minute. Since sub-minute tasks that take longer than expected to run could delay the execution of later sub-minute tasks, it is recommended that all sub-minute tasks dispatch queued jobs or background commands to handle the actual task processing: - use App\Jobs\DeleteRecentUsers; +```php +use App\Jobs\DeleteRecentUsers; - Schedule::job(new DeleteRecentUsers)->everyTenSeconds(); +Schedule::job(new DeleteRecentUsers)->everyTenSeconds(); - Schedule::command('users:delete')->everyTenSeconds()->runInBackground(); +Schedule::command('users:delete')->everyTenSeconds()->runInBackground(); +``` #### Interrupting Sub-Minute Tasks @@ -441,30 +493,38 @@ php artisan schedule:work The Laravel scheduler provides several convenient methods for working with the output generated by scheduled tasks. First, using the `sendOutputTo` method, you may send the output to a file for later inspection: - use Illuminate\Support\Facades\Schedule; +```php +use Illuminate\Support\Facades\Schedule; - Schedule::command('emails:send') - ->daily() - ->sendOutputTo($filePath); +Schedule::command('emails:send') + ->daily() + ->sendOutputTo($filePath); +``` If you would like to append the output to a given file, you may use the `appendOutputTo` method: - Schedule::command('emails:send') - ->daily() - ->appendOutputTo($filePath); +```php +Schedule::command('emails:send') + ->daily() + ->appendOutputTo($filePath); +``` Using the `emailOutputTo` method, you may email the output to an email address of your choice. Before emailing the output of a task, you should configure Laravel's [email services](/docs/{{version}}/mail): - Schedule::command('report:generate') - ->daily() - ->sendOutputTo($filePath) - ->emailOutputTo('taylor@example.com'); +```php +Schedule::command('report:generate') + ->daily() + ->sendOutputTo($filePath) + ->emailOutputTo('taylor@example.com'); +``` If you only want to email the output if the scheduled Artisan or system command terminates with a non-zero exit code, use the `emailOutputOnFailure` method: - Schedule::command('report:generate') - ->daily() - ->emailOutputOnFailure('taylor@example.com'); +```php +Schedule::command('report:generate') + ->daily() + ->emailOutputOnFailure('taylor@example.com'); +``` > [!WARNING] > The `emailOutputTo`, `emailOutputOnFailure`, `sendOutputTo`, and `appendOutputTo` methods are exclusive to the `command` and `exec` methods. @@ -474,69 +534,81 @@ If you only want to email the output if the scheduled Artisan or system command Using the `before` and `after` methods, you may specify code to be executed before and after the scheduled task is executed: - use Illuminate\Support\Facades\Schedule; +```php +use Illuminate\Support\Facades\Schedule; - Schedule::command('emails:send') - ->daily() - ->before(function () { - // The task is about to execute... - }) - ->after(function () { - // The task has executed... - }); +Schedule::command('emails:send') + ->daily() + ->before(function () { + // The task is about to execute... + }) + ->after(function () { + // The task has executed... + }); +``` The `onSuccess` and `onFailure` methods allow you to specify code to be executed if the scheduled task succeeds or fails. A failure indicates that the scheduled Artisan or system command terminated with a non-zero exit code: - Schedule::command('emails:send') - ->daily() - ->onSuccess(function () { - // The task succeeded... - }) - ->onFailure(function () { - // The task failed... - }); +```php +Schedule::command('emails:send') + ->daily() + ->onSuccess(function () { + // The task succeeded... + }) + ->onFailure(function () { + // The task failed... + }); +``` If output is available from your command, you may access it in your `after`, `onSuccess` or `onFailure` hooks by type-hinting an `Illuminate\Support\Stringable` instance as the `$output` argument of your hook's closure definition: - use Illuminate\Support\Stringable; +```php +use Illuminate\Support\Stringable; - Schedule::command('emails:send') - ->daily() - ->onSuccess(function (Stringable $output) { - // The task succeeded... - }) - ->onFailure(function (Stringable $output) { - // The task failed... - }); +Schedule::command('emails:send') + ->daily() + ->onSuccess(function (Stringable $output) { + // The task succeeded... + }) + ->onFailure(function (Stringable $output) { + // The task failed... + }); +``` #### Pinging URLs Using the `pingBefore` and `thenPing` methods, the scheduler can automatically ping a given URL before or after a task is executed. This method is useful for notifying an external service, such as [Envoyer](https://envoyer.io), that your scheduled task is beginning or has finished execution: - Schedule::command('emails:send') - ->daily() - ->pingBefore($url) - ->thenPing($url); +```php +Schedule::command('emails:send') + ->daily() + ->pingBefore($url) + ->thenPing($url); +``` The `pingOnSuccess` and `pingOnFailure` methods may be used to ping a given URL only if the task succeeds or fails. A failure indicates that the scheduled Artisan or system command terminated with a non-zero exit code: - Schedule::command('emails:send') - ->daily() - ->pingOnSuccess($successUrl) - ->pingOnFailure($failureUrl); +```php +Schedule::command('emails:send') + ->daily() + ->pingOnSuccess($successUrl) + ->pingOnFailure($failureUrl); +``` The `pingBeforeIf`,`thenPingIf`,`pingOnSuccessIf`, and `pingOnFailureIf` methods may be used to ping a given URL only if a given condition is `true`: - Schedule::command('emails:send') - ->daily() - ->pingBeforeIf($condition, $url) - ->thenPingIf($condition, $url); +```php +Schedule::command('emails:send') + ->daily() + ->pingBeforeIf($condition, $url) + ->thenPingIf($condition, $url); - Schedule::command('emails:send') - ->daily() - ->pingOnSuccessIf($condition, $successUrl) - ->pingOnFailureIf($condition, $failureUrl); +Schedule::command('emails:send') + ->daily() + ->pingOnSuccessIf($condition, $successUrl) + ->pingOnFailureIf($condition, $failureUrl); +``` ## Events diff --git a/scout.md b/scout.md index 6146ea36e4c..346be125376 100644 --- a/scout.md +++ b/scout.md @@ -54,17 +54,19 @@ php artisan vendor:publish --provider="Laravel\Scout\ScoutServiceProvider" Finally, add the `Laravel\Scout\Searchable` trait to the model you would like to make searchable. This trait will register a model observer that will automatically keep the model in sync with your search driver: - ### Queueing @@ -73,20 +75,26 @@ While not strictly required to use Scout, you should strongly consider configuri Once you have configured a queue driver, set the value of the `queue` option in your `config/scout.php` configuration file to `true`: - 'queue' => true, +```php +'queue' => true, +``` Even when the `queue` option is set to `false`, it's important to remember that some Scout drivers like Algolia and Meilisearch always index records asynchronously. Meaning, even though the index operation has completed within your Laravel application, the search engine itself may not reflect the new and updated records immediately. To specify the connection and queue that your Scout jobs utilize, you may define the `queue` configuration option as an array: - 'queue' => [ - 'connection' => 'redis', - 'queue' => 'scout' - ], +```php +'queue' => [ + 'connection' => 'redis', + 'queue' => 'scout' +], +``` Of course, if you customize the connection and queue that Scout jobs utilize, you should run a queue worker to process jobs on that connection and queue: - php artisan queue:work redis --queue=scout +```shell +php artisan queue:work redis --queue=scout +``` ## Driver Prerequisites @@ -219,67 +227,73 @@ Todo::search('Groceries')->options([ Each Eloquent model is synced with a given search "index", which contains all of the searchable records for that model. In other words, you can think of each index like a MySQL table. By default, each model will be persisted to an index matching the model's typical "table" name. Typically, this is the plural form of the model name; however, you are free to customize the model's index by overriding the `searchableAs` method on the model: - ### Configuring Searchable Data By default, the entire `toArray` form of a given model will be persisted to its search index. If you would like to customize the data that is synchronized to the search index, you may override the `toSearchableArray` method on the model: - - */ - public function toSearchableArray(): array - { - $array = $this->toArray(); + /** + * Get the indexable data array for the model. + * + * @return array + */ + public function toSearchableArray(): array + { + $array = $this->toArray(); - // Customize the data array... + // Customize the data array... - return $array; - } + return $array; } +} +``` Some search engines such as Meilisearch will only perform filter operations (`>`, `<`, etc.) on data of the correct type. So, when using these search engines and customizing your searchable data, you should ensure that numeric values are cast to their correct type: - public function toSearchableArray() - { - return [ - 'id' => (int) $this->id, - 'name' => $this->name, - 'price' => (float) $this->price, - ]; - } +```php +public function toSearchableArray() +{ + return [ + 'id' => (int) $this->id, + 'name' => $this->name, + 'price' => (float) $this->price, + ]; +} +``` #### Configuring Index Settings (Algolia) @@ -371,60 +385,64 @@ php artisan scout:sync-index-settings By default, Scout will use the primary key of the model as the model's unique ID / key that is stored in the search index. If you need to customize this behavior, you may override the `getScoutKey` and the `getScoutKeyName` methods on the model: - email; + } - class User extends Model + /** + * Get the key name used to index the model. + */ + public function getScoutKeyName(): mixed { - use Searchable; - - /** - * Get the value used to index the model. - */ - public function getScoutKey(): mixed - { - return $this->email; - } - - /** - * Get the key name used to index the model. - */ - public function getScoutKeyName(): mixed - { - return 'email'; - } + return 'email'; } +} +``` ### Configuring Search Engines per Model When searching, Scout will typically use the default search engine specified in your application's `scout` configuration file. However, the search engine for a particular model can be changed by overriding the `searchableUsing` method on the model: - engine('meilisearch'); - } + return app(EngineManager::class)->engine('meilisearch'); } +} +``` ### Identifying Users @@ -529,15 +547,17 @@ php artisan scout:flush "App\Models\Post" If you would like to modify the query that is used to retrieve all of your models for batch importing, you may define a `makeAllSearchableUsing` method on your model. This is a great place to add any eager relationship loading that may be necessary before importing your models: - use Illuminate\Database\Eloquent\Builder; +```php +use Illuminate\Database\Eloquent\Builder; - /** - * Modify the query used to retrieve models when making all of the models searchable. - */ - protected function makeAllSearchableUsing(Builder $query): Builder - { - return $query->with('author'); - } +/** + * Modify the query used to retrieve models when making all of the models searchable. + */ +protected function makeAllSearchableUsing(Builder $query): Builder +{ + return $query->with('author'); +} +``` > [!WARNING] > The `makeAllSearchableUsing` method may not be applicable when using a queue to batch import models. Relationships are [not restored](/docs/{{version}}/queues#handling-relationships) when model collections are processed by jobs. @@ -547,30 +567,38 @@ If you would like to modify the query that is used to retrieve all of your model Once you have added the `Laravel\Scout\Searchable` trait to a model, all you need to do is `save` or `create` a model instance and it will automatically be added to your search index. If you have configured Scout to [use queues](#queueing) this operation will be performed in the background by your queue worker: - use App\Models\Order; +```php +use App\Models\Order; - $order = new Order; +$order = new Order; - // ... +// ... - $order->save(); +$order->save(); +``` #### Adding Records via Query If you would like to add a collection of models to your search index via an Eloquent query, you may chain the `searchable` method onto the Eloquent query. The `searchable` method will [chunk the results](/docs/{{version}}/eloquent#chunking-results) of the query and add the records to your search index. Again, if you have configured Scout to use queues, all of the chunks will be imported in the background by your queue workers: - use App\Models\Order; +```php +use App\Models\Order; - Order::where('price', '>', 100)->searchable(); +Order::where('price', '>', 100)->searchable(); +``` You may also call the `searchable` method on an Eloquent relationship instance: - $user->orders()->searchable(); +```php +$user->orders()->searchable(); +``` Or, if you already have a collection of Eloquent models in memory, you may call the `searchable` method on the collection instance to add the model instances to their corresponding index: - $orders->searchable(); +```php +$orders->searchable(); +``` > [!NOTE] > The `searchable` method can be considered an "upsert" operation. In other words, if the model record is already in your index, it will be updated. If it does not exist in the search index, it will be added to the index. @@ -580,91 +608,115 @@ Or, if you already have a collection of Eloquent models in memory, you may call To update a searchable model, you only need to update the model instance's properties and `save` the model to your database. Scout will automatically persist the changes to your search index: - use App\Models\Order; +```php +use App\Models\Order; - $order = Order::find(1); +$order = Order::find(1); - // Update the order... +// Update the order... - $order->save(); +$order->save(); +``` You may also invoke the `searchable` method on an Eloquent query instance to update a collection of models. If the models do not exist in your search index, they will be created: - Order::where('price', '>', 100)->searchable(); +```php +Order::where('price', '>', 100)->searchable(); +``` If you would like to update the search index records for all of the models in a relationship, you may invoke the `searchable` on the relationship instance: - $user->orders()->searchable(); +```php +$user->orders()->searchable(); +``` Or, if you already have a collection of Eloquent models in memory, you may call the `searchable` method on the collection instance to update the model instances in their corresponding index: - $orders->searchable(); +```php +$orders->searchable(); +``` #### Modifying Records Before Importing Sometimes you may need to prepare the collection of models before they are made searchable. For instance, you may want to eager load a relationship so that the relationship data can be efficiently added to your search index. To accomplish this, define a `makeSearchableUsing` method on the corresponding model: - use Illuminate\Database\Eloquent\Collection; +```php +use Illuminate\Database\Eloquent\Collection; - /** - * Modify the collection of models being made searchable. - */ - public function makeSearchableUsing(Collection $models): Collection - { - return $models->load('author'); - } +/** + * Modify the collection of models being made searchable. + */ +public function makeSearchableUsing(Collection $models): Collection +{ + return $models->load('author'); +} +``` ### Removing Records To remove a record from your index you may simply `delete` the model from the database. This may be done even if you are using [soft deleted](/docs/{{version}}/eloquent#soft-deleting) models: - use App\Models\Order; +```php +use App\Models\Order; - $order = Order::find(1); +$order = Order::find(1); - $order->delete(); +$order->delete(); +``` If you do not want to retrieve the model before deleting the record, you may use the `unsearchable` method on an Eloquent query instance: - Order::where('price', '>', 100)->unsearchable(); +```php +Order::where('price', '>', 100)->unsearchable(); +``` If you would like to remove the search index records for all of the models in a relationship, you may invoke the `unsearchable` on the relationship instance: - $user->orders()->unsearchable(); +```php +$user->orders()->unsearchable(); +``` Or, if you already have a collection of Eloquent models in memory, you may call the `unsearchable` method on the collection instance to remove the model instances from their corresponding index: - $orders->unsearchable(); +```php +$orders->unsearchable(); +``` To remove all of the model records from their corresponding index, you may invoke the `removeAllFromSearch` method: - Order::removeAllFromSearch(); +```php +Order::removeAllFromSearch(); +``` ### Pausing Indexing Sometimes you may need to perform a batch of Eloquent operations on a model without syncing the model data to your search index. You may do this using the `withoutSyncingToSearch` method. This method accepts a single closure which will be immediately executed. Any model operations that occur within the closure will not be synced to the model's index: - use App\Models\Order; +```php +use App\Models\Order; - Order::withoutSyncingToSearch(function () { - // Perform model actions... - }); +Order::withoutSyncingToSearch(function () { + // Perform model actions... +}); +``` ### Conditionally Searchable Model Instances Sometimes you may need to only make a model searchable under certain conditions. For example, imagine you have `App\Models\Post` model that may be in one of two states: "draft" and "published". You may only want to allow "published" posts to be searchable. To accomplish this, you may define a `shouldBeSearchable` method on your model: - /** - * Determine if the model should be searchable. - */ - public function shouldBeSearchable(): bool - { - return $this->isPublished(); - } +```php +/** + * Determine if the model should be searchable. + */ +public function shouldBeSearchable(): bool +{ + return $this->isPublished(); +} +``` The `shouldBeSearchable` method is only applied when manipulating models through the `save` and `create` methods, queries, or relationships. Directly making models or collections searchable using the `searchable` method will override the result of the `shouldBeSearchable` method. @@ -676,52 +728,66 @@ The `shouldBeSearchable` method is only applied when manipulating models through You may begin searching a model using the `search` method. The search method accepts a single string that will be used to search your models. You should then chain the `get` method onto the search query to retrieve the Eloquent models that match the given search query: - use App\Models\Order; +```php +use App\Models\Order; - $orders = Order::search('Star Trek')->get(); +$orders = Order::search('Star Trek')->get(); +``` Since Scout searches return a collection of Eloquent models, you may even return the results directly from a route or controller and they will automatically be converted to JSON: - use App\Models\Order; - use Illuminate\Http\Request; +```php +use App\Models\Order; +use Illuminate\Http\Request; - Route::get('/search', function (Request $request) { - return Order::search($request->search)->get(); - }); +Route::get('/search', function (Request $request) { + return Order::search($request->search)->get(); +}); +``` If you would like to get the raw search results before they are converted to Eloquent models, you may use the `raw` method: - $orders = Order::search('Star Trek')->raw(); +```php +$orders = Order::search('Star Trek')->raw(); +``` #### Custom Indexes Search queries will typically be performed on the index specified by the model's [`searchableAs`](#configuring-model-indexes) method. However, you may use the `within` method to specify a custom index that should be searched instead: - $orders = Order::search('Star Trek') - ->within('tv_shows_popularity_desc') - ->get(); +```php +$orders = Order::search('Star Trek') + ->within('tv_shows_popularity_desc') + ->get(); +``` ### Where Clauses Scout allows you to add simple "where" clauses to your search queries. Currently, these clauses only support basic numeric equality checks and are primarily useful for scoping search queries by an owner ID: - use App\Models\Order; +```php +use App\Models\Order; - $orders = Order::search('Star Trek')->where('user_id', 1)->get(); +$orders = Order::search('Star Trek')->where('user_id', 1)->get(); +``` In addition, the `whereIn` method may be used to verify that a given column's value is contained within the given array: - $orders = Order::search('Star Trek')->whereIn( - 'status', ['open', 'paid'] - )->get(); +```php +$orders = Order::search('Star Trek')->whereIn( + 'status', ['open', 'paid'] +)->get(); +``` The `whereNotIn` method verifies that the given column's value is not contained in the given array: - $orders = Order::search('Star Trek')->whereNotIn( - 'status', ['closed'] - )->get(); +```php +$orders = Order::search('Star Trek')->whereNotIn( + 'status', ['closed'] +)->get(); +``` Since a search index is not a relational database, more advanced "where" clauses are not currently supported. @@ -733,13 +799,17 @@ Since a search index is not a relational database, more advanced "where" clauses In addition to retrieving a collection of models, you may paginate your search results using the `paginate` method. This method will return an `Illuminate\Pagination\LengthAwarePaginator` instance just as if you had [paginated a traditional Eloquent query](/docs/{{version}}/pagination): - use App\Models\Order; +```php +use App\Models\Order; - $orders = Order::search('Star Trek')->paginate(); +$orders = Order::search('Star Trek')->paginate(); +``` You may specify how many models to retrieve per page by passing the amount as the first argument to the `paginate` method: - $orders = Order::search('Star Trek')->paginate(15); +```php +$orders = Order::search('Star Trek')->paginate(15); +``` Once you have retrieved the results, you may display the results and render the page links using [Blade](/docs/{{version}}/blade) just as if you had paginated a traditional Eloquent query: @@ -755,12 +825,14 @@ Once you have retrieved the results, you may display the results and render the Of course, if you would like to retrieve the pagination results as JSON, you may return the paginator instance directly from a route or controller: - use App\Models\Order; - use Illuminate\Http\Request; +```php +use App\Models\Order; +use Illuminate\Http\Request; - Route::get('/orders', function (Request $request) { - return Order::search($request->input('query'))->paginate(15); - }); +Route::get('/orders', function (Request $request) { + return Order::search($request->input('query'))->paginate(15); +}); +``` > [!WARNING] > Since search engines are not aware of your Eloquent model's global scope definitions, you should not utilize global scopes in applications that utilize Scout pagination. Or, you should recreate the global scope's constraints when searching via Scout. @@ -770,17 +842,21 @@ Of course, if you would like to retrieve the pagination results as JSON, you may If your indexed models are [soft deleting](/docs/{{version}}/eloquent#soft-deleting) and you need to search your soft deleted models, set the `soft_delete` option of the `config/scout.php` configuration file to `true`: - 'soft_delete' => true, +```php +'soft_delete' => true, +``` When this configuration option is `true`, Scout will not remove soft deleted models from the search index. Instead, it will set a hidden `__soft_deleted` attribute on the indexed record. Then, you may use the `withTrashed` or `onlyTrashed` methods to retrieve the soft deleted records when searching: - use App\Models\Order; +```php +use App\Models\Order; - // Include trashed records when retrieving results... - $orders = Order::search('Star Trek')->withTrashed()->get(); +// Include trashed records when retrieving results... +$orders = Order::search('Star Trek')->withTrashed()->get(); - // Only include trashed records when retrieving results... - $orders = Order::search('Star Trek')->onlyTrashed()->get(); +// Only include trashed records when retrieving results... +$orders = Order::search('Star Trek')->onlyTrashed()->get(); +``` > [!NOTE] > When a soft deleted model is permanently deleted using `forceDelete`, Scout will remove it from the search index automatically. @@ -790,20 +866,22 @@ When this configuration option is `true`, Scout will not remove soft deleted mod If you need to perform advanced customization of the search behavior of an engine you may pass a closure as the second argument to the `search` method. For example, you could use this callback to add geo-location data to your search options before the search query is passed to Algolia: - use Algolia\AlgoliaSearch\SearchIndex; - use App\Models\Order; +```php +use Algolia\AlgoliaSearch\SearchIndex; +use App\Models\Order; - Order::search( - 'Star Trek', - function (SearchIndex $algolia, string $query, array $options) { - $options['body']['query']['bool']['filter']['geo_distance'] = [ - 'distance' => '1000km', - 'location' => ['lat' => 36, 'lon' => 111], - ]; +Order::search( + 'Star Trek', + function (SearchIndex $algolia, string $query, array $options) { + $options['body']['query']['bool']['filter']['geo_distance'] = [ + 'distance' => '1000km', + 'location' => ['lat' => 36, 'lon' => 111], + ]; - return $algolia->search($query, $options); - } - )->get(); + return $algolia->search($query, $options); + } +)->get(); +``` #### Customizing the Eloquent Results Query @@ -829,16 +907,18 @@ Since this callback is invoked after the relevant models have already been retri If one of the built-in Scout search engines doesn't fit your needs, you may write your own custom engine and register it with Scout. Your engine should extend the `Laravel\Scout\Engines\Engine` abstract class. This abstract class contains eight methods your custom engine must implement: - use Laravel\Scout\Builder; - - abstract public function update($models); - abstract public function delete($models); - abstract public function search(Builder $builder); - abstract public function paginate(Builder $builder, $perPage, $page); - abstract public function mapIds($results); - abstract public function map(Builder $builder, $results, $model); - abstract public function getTotalCount($results); - abstract public function flush($model); +```php +use Laravel\Scout\Builder; + +abstract public function update($models); +abstract public function delete($models); +abstract public function search(Builder $builder); +abstract public function paginate(Builder $builder, $perPage, $page); +abstract public function mapIds($results); +abstract public function map(Builder $builder, $results, $model); +abstract public function getTotalCount($results); +abstract public function flush($model); +``` You may find it helpful to review the implementations of these methods on the `Laravel\Scout\Engines\AlgoliaEngine` class. This class will provide you with a good starting point for learning how to implement each of these methods in your own engine. @@ -847,19 +927,23 @@ You may find it helpful to review the implementations of these methods on the `L Once you have written your custom engine, you may register it with Scout using the `extend` method of the Scout engine manager. Scout's engine manager may be resolved from the Laravel service container. You should call the `extend` method from the `boot` method of your `App\Providers\AppServiceProvider` class or any other service provider used by your application: - use App\ScoutExtensions\MySqlSearchEngine; - use Laravel\Scout\EngineManager; +```php +use App\ScoutExtensions\MySqlSearchEngine; +use Laravel\Scout\EngineManager; - /** - * Bootstrap any application services. - */ - public function boot(): void - { - resolve(EngineManager::class)->extend('mysql', function () { - return new MySqlSearchEngine; - }); - } +/** + * Bootstrap any application services. + */ +public function boot(): void +{ + resolve(EngineManager::class)->extend('mysql', function () { + return new MySqlSearchEngine; + }); +} +``` Once your engine has been registered, you may specify it as your default Scout `driver` in your application's `config/scout.php` configuration file: - 'driver' => 'mysql', +```php +'driver' => 'mysql', +``` diff --git a/seeding.md b/seeding.md index 94433c619be..28f6970af67 100644 --- a/seeding.md +++ b/seeding.md @@ -28,29 +28,31 @@ A seeder class only contains one method by default: `run`. This method is called As an example, let's modify the default `DatabaseSeeder` class and add a database insert statement to the `run` method: - insert([ - 'name' => Str::random(10), - 'email' => Str::random(10).'@example.com', - 'password' => Hash::make('password'), - ]); - } + DB::table('users')->insert([ + 'name' => Str::random(10), + 'email' => Str::random(10).'@example.com', + 'password' => Hash::make('password'), + ]); } +} +``` > [!NOTE] > You may type-hint any dependencies you need within the `run` method's signature. They will automatically be resolved via the Laravel [service container](/docs/{{version}}/container). @@ -62,62 +64,68 @@ Of course, manually specifying the attributes for each model seed is cumbersome. For example, let's create 50 users that each has one related post: - use App\Models\User; - - /** - * Run the database seeders. - */ - public function run(): void - { - User::factory() - ->count(50) - ->hasPosts(1) - ->create(); - } +```php +use App\Models\User; + +/** + * Run the database seeders. + */ +public function run(): void +{ + User::factory() + ->count(50) + ->hasPosts(1) + ->create(); +} +``` ### Calling Additional Seeders Within the `DatabaseSeeder` class, you may use the `call` method to execute additional seed classes. Using the `call` method allows you to break up your database seeding into multiple files so that no single seeder class becomes too large. The `call` method accepts an array of seeder classes that should be executed: - /** - * Run the database seeders. - */ - public function run(): void - { - $this->call([ - UserSeeder::class, - PostSeeder::class, - CommentSeeder::class, - ]); - } +```php +/** + * Run the database seeders. + */ +public function run(): void +{ + $this->call([ + UserSeeder::class, + PostSeeder::class, + CommentSeeder::class, + ]); +} +``` ### Muting Model Events While running seeds, you may want to prevent models from dispatching events. You may achieve this using the `WithoutModelEvents` trait. When used, the `WithoutModelEvents` trait ensures no model events are dispatched, even if additional seed classes are executed via the `call` method: - call([ - UserSeeder::class, - ]); - } + $this->call([ + UserSeeder::class, + ]); } +} +``` ## Running Seeders diff --git a/session.md b/session.md index 55bb3797557..543cac5a0a6 100644 --- a/session.md +++ b/session.md @@ -72,53 +72,59 @@ Before using Redis sessions with Laravel, you will need to either install the Ph There are two primary ways of working with session data in Laravel: the global `session` helper and via a `Request` instance. First, let's look at accessing the session via a `Request` instance, which can be type-hinted on a route closure or controller method. Remember, controller method dependencies are automatically injected via the Laravel [service container](/docs/{{version}}/container): - session()->get('key'); + $value = $request->session()->get('key'); - // ... + // ... - $user = $this->users->find($id); + $user = $this->users->find($id); - return view('user.profile', ['user' => $user]); - } + return view('user.profile', ['user' => $user]); } +} +``` When you retrieve an item from the session, you may also pass a default value as the second argument to the `get` method. This default value will be returned if the specified key does not exist in the session. If you pass a closure as the default value to the `get` method and the requested key does not exist, the closure will be executed and its result returned: - $value = $request->session()->get('key', 'default'); +```php +$value = $request->session()->get('key', 'default'); - $value = $request->session()->get('key', function () { - return 'default'; - }); +$value = $request->session()->get('key', function () { + return 'default'; +}); +``` #### The Global Session Helper You may also use the global `session` PHP function to retrieve and store data in the session. When the `session` helper is called with a single, string argument, it will return the value of that session key. When the helper is called with an array of key / value pairs, those values will be stored in the session: - Route::get('/home', function () { - // Retrieve a piece of data from the session... - $value = session('key'); +```php +Route::get('/home', function () { + // Retrieve a piece of data from the session... + $value = session('key'); - // Specifying a default value... - $value = session('key', 'default'); + // Specifying a default value... + $value = session('key', 'default'); - // Store a piece of data in the session... - session(['key' => 'value']); - }); + // Store a piece of data in the session... + session(['key' => 'value']); +}); +``` > [!NOTE] > There is little practical difference between using the session via an HTTP request instance versus using the global `session` helper. Both methods are [testable](/docs/{{version}}/testing) via the `assertSessionHas` method which is available in all of your test cases. @@ -128,105 +134,131 @@ You may also use the global `session` PHP function to retrieve and store data in If you would like to retrieve all the data in the session, you may use the `all` method: - $data = $request->session()->all(); +```php +$data = $request->session()->all(); +``` #### Retrieving a Portion of the Session Data The `only` and `except` methods may be used to retrieve a subset of the session data: - $data = $request->session()->only(['username', 'email']); +```php +$data = $request->session()->only(['username', 'email']); - $data = $request->session()->except(['username', 'email']); +$data = $request->session()->except(['username', 'email']); +``` #### Determining if an Item Exists in the Session To determine if an item is present in the session, you may use the `has` method. The `has` method returns `true` if the item is present and is not `null`: - if ($request->session()->has('users')) { - // ... - } +```php +if ($request->session()->has('users')) { + // ... +} +``` To determine if an item is present in the session, even if its value is `null`, you may use the `exists` method: - if ($request->session()->exists('users')) { - // ... - } +```php +if ($request->session()->exists('users')) { + // ... +} +``` To determine if an item is not present in the session, you may use the `missing` method. The `missing` method returns `true` if the item is not present: - if ($request->session()->missing('users')) { - // ... - } +```php +if ($request->session()->missing('users')) { + // ... +} +``` ### Storing Data To store data in the session, you will typically use the request instance's `put` method or the global `session` helper: - // Via a request instance... - $request->session()->put('key', 'value'); +```php +// Via a request instance... +$request->session()->put('key', 'value'); - // Via the global "session" helper... - session(['key' => 'value']); +// Via the global "session" helper... +session(['key' => 'value']); +``` #### Pushing to Array Session Values The `push` method may be used to push a new value onto a session value that is an array. For example, if the `user.teams` key contains an array of team names, you may push a new value onto the array like so: - $request->session()->push('user.teams', 'developers'); +```php +$request->session()->push('user.teams', 'developers'); +``` #### Retrieving and Deleting an Item The `pull` method will retrieve and delete an item from the session in a single statement: - $value = $request->session()->pull('key', 'default'); +```php +$value = $request->session()->pull('key', 'default'); +``` #### Incrementing and Decrementing Session Values If your session data contains an integer you wish to increment or decrement, you may use the `increment` and `decrement` methods: - $request->session()->increment('count'); +```php +$request->session()->increment('count'); - $request->session()->increment('count', $incrementBy = 2); +$request->session()->increment('count', $incrementBy = 2); - $request->session()->decrement('count'); +$request->session()->decrement('count'); - $request->session()->decrement('count', $decrementBy = 2); +$request->session()->decrement('count', $decrementBy = 2); +``` ### Flash Data Sometimes you may wish to store items in the session for the next request. You may do so using the `flash` method. Data stored in the session using this method will be available immediately and during the subsequent HTTP request. After the subsequent HTTP request, the flashed data will be deleted. Flash data is primarily useful for short-lived status messages: - $request->session()->flash('status', 'Task was successful!'); +```php +$request->session()->flash('status', 'Task was successful!'); +``` If you need to persist your flash data for several requests, you may use the `reflash` method, which will keep all of the flash data for an additional request. If you only need to keep specific flash data, you may use the `keep` method: - $request->session()->reflash(); +```php +$request->session()->reflash(); - $request->session()->keep(['username', 'email']); +$request->session()->keep(['username', 'email']); +``` To persist your flash data only for the current request, you may use the `now` method: - $request->session()->now('status', 'Task was successful!'); +```php +$request->session()->now('status', 'Task was successful!'); +``` ### Deleting Data The `forget` method will remove a piece of data from the session. If you would like to remove all data from the session, you may use the `flush` method: - // Forget a single key... - $request->session()->forget('name'); +```php +// Forget a single key... +$request->session()->forget('name'); - // Forget multiple keys... - $request->session()->forget(['name', 'status']); +// Forget multiple keys... +$request->session()->forget(['name', 'status']); - $request->session()->flush(); +$request->session()->flush(); +``` ### Regenerating the Session ID @@ -235,11 +267,15 @@ Regenerating the session ID is often done in order to prevent malicious users fr Laravel automatically regenerates the session ID during authentication if you are using one of the Laravel [application starter kits](/docs/{{version}}/starter-kits) or [Laravel Fortify](/docs/{{version}}/fortify); however, if you need to manually regenerate the session ID, you may use the `regenerate` method: - $request->session()->regenerate(); +```php +$request->session()->regenerate(); +``` If you need to regenerate the session ID and remove all data from the session in a single statement, you may use the `invalidate` method: - $request->session()->invalidate(); +```php +$request->session()->invalidate(); +``` ## Session Blocking @@ -251,13 +287,15 @@ By default, Laravel allows requests using the same session to execute concurrent To mitigate this, Laravel provides functionality that allows you to limit concurrent requests for a given session. To get started, you may simply chain the `block` method onto your route definition. In this example, an incoming request to the `/profile` endpoint would acquire a session lock. While this lock is being held, any incoming requests to the `/profile` or `/order` endpoints which share the same session ID will wait for the first request to finish executing before continuing their execution: - Route::post('/profile', function () { - // ... - })->block($lockSeconds = 10, $waitSeconds = 10); +```php +Route::post('/profile', function () { + // ... +})->block($lockSeconds = 10, $waitSeconds = 10); - Route::post('/order', function () { - // ... - })->block($lockSeconds = 10, $waitSeconds = 10); +Route::post('/order', function () { + // ... +})->block($lockSeconds = 10, $waitSeconds = 10); +``` The `block` method accepts two optional arguments. The first argument accepted by the `block` method is the maximum number of seconds the session lock should be held for before it is released. Of course, if the request finishes executing before this time the lock will be released earlier. @@ -265,9 +303,11 @@ The second argument accepted by the `block` method is the number of seconds a re If neither of these arguments is passed, the lock will be obtained for a maximum of 10 seconds and requests will wait a maximum of 10 seconds while attempting to obtain a lock: - Route::post('/profile', function () { - // ... - })->block(); +```php +Route::post('/profile', function () { + // ... +})->block(); +``` ## Adding Custom Session Drivers @@ -277,19 +317,21 @@ If neither of these arguments is passed, the lock will be obtained for a maximum If none of the existing session drivers fit your application's needs, Laravel makes it possible to write your own session handler. Your custom session driver should implement PHP's built-in `SessionHandlerInterface`. This interface contains just a few simple methods. A stubbed MongoDB implementation looks like the following: - [ - 'client_id' => env('GITHUB_CLIENT_ID'), - 'client_secret' => env('GITHUB_CLIENT_SECRET'), - 'redirect' => 'http://example.com/callback-url', - ], +```php +'github' => [ + 'client_id' => env('GITHUB_CLIENT_ID'), + 'client_secret' => env('GITHUB_CLIENT_SECRET'), + 'redirect' => 'http://example.com/callback-url', +], +``` > [!NOTE] > If the `redirect` option contains a relative path, it will automatically be resolved to a fully qualified URL. @@ -58,17 +60,19 @@ These credentials should be placed in your application's `config/services.php` c To authenticate users using an OAuth provider, you will need two routes: one for redirecting the user to the OAuth provider, and another for receiving the callback from the provider after authentication. The example routes below demonstrate the implementation of both routes: - use Laravel\Socialite\Facades\Socialite; +```php +use Laravel\Socialite\Facades\Socialite; - Route::get('/auth/redirect', function () { - return Socialite::driver('github')->redirect(); - }); +Route::get('/auth/redirect', function () { + return Socialite::driver('github')->redirect(); +}); - Route::get('/auth/callback', function () { - $user = Socialite::driver('github')->user(); +Route::get('/auth/callback', function () { + $user = Socialite::driver('github')->user(); - // $user->token - }); + // $user->token +}); +``` The `redirect` method provided by the `Socialite` facade takes care of redirecting the user to the OAuth provider, while the `user` method will examine the incoming request and retrieve the user's information from the provider after they have approved the authentication request. @@ -77,26 +81,28 @@ The `redirect` method provided by the `Socialite` facade takes care of redirecti Once the user has been retrieved from the OAuth provider, you may determine if the user exists in your application's database and [authenticate the user](/docs/{{version}}/authentication#authenticate-a-user-instance). If the user does not exist in your application's database, you will typically create a new record in your database to represent the user: - use App\Models\User; - use Illuminate\Support\Facades\Auth; - use Laravel\Socialite\Facades\Socialite; +```php +use App\Models\User; +use Illuminate\Support\Facades\Auth; +use Laravel\Socialite\Facades\Socialite; - Route::get('/auth/callback', function () { - $githubUser = Socialite::driver('github')->user(); +Route::get('/auth/callback', function () { + $githubUser = Socialite::driver('github')->user(); - $user = User::updateOrCreate([ - 'github_id' => $githubUser->id, - ], [ - 'name' => $githubUser->name, - 'email' => $githubUser->email, - 'github_token' => $githubUser->token, - 'github_refresh_token' => $githubUser->refreshToken, - ]); + $user = User::updateOrCreate([ + 'github_id' => $githubUser->id, + ], [ + 'name' => $githubUser->name, + 'email' => $githubUser->email, + 'github_token' => $githubUser->token, + 'github_refresh_token' => $githubUser->refreshToken, + ]); - Auth::login($user); + Auth::login($user); - return redirect('/dashboard'); - }); + return redirect('/dashboard'); +}); +``` > [!NOTE] > For more information regarding what user information is available from specific OAuth providers, please consult the documentation on [retrieving user details](#retrieving-user-details). @@ -106,17 +112,21 @@ Once the user has been retrieved from the OAuth provider, you may determine if t Before redirecting the user, you may use the `scopes` method to specify the "scopes" that should be included in the authentication request. This method will merge all previously specified scopes with the scopes that you specify: - use Laravel\Socialite\Facades\Socialite; +```php +use Laravel\Socialite\Facades\Socialite; - return Socialite::driver('github') - ->scopes(['read:user', 'public_repo']) - ->redirect(); +return Socialite::driver('github') + ->scopes(['read:user', 'public_repo']) + ->redirect(); +``` You can overwrite all existing scopes on the authentication request using the `setScopes` method: - return Socialite::driver('github') - ->setScopes(['read:user', 'public_repo']) - ->redirect(); +```php +return Socialite::driver('github') + ->setScopes(['read:user', 'public_repo']) + ->redirect(); +``` ### Slack Bot Scopes @@ -134,14 +144,18 @@ By default, the `slack` driver will generate a `user` token and invoking the dri Bot tokens are primarily useful if your application will be sending notifications to external Slack workspaces that are owned by your application's users. To generate a bot token, invoke the `asBotUser` method before redirecting the user to Slack for authentication: - return Socialite::driver('slack') - ->asBotUser() - ->setScopes(['chat:write', 'chat:write.public', 'chat:write.customize']) - ->redirect(); +```php +return Socialite::driver('slack') + ->asBotUser() + ->setScopes(['chat:write', 'chat:write.public', 'chat:write.customize']) + ->redirect(); +``` In addition, you must invoke the `asBotUser` method before invoking the `user` method after Slack redirects the user back to your application after authentication: - $user = Socialite::driver('slack')->asBotUser()->user(); +```php +$user = Socialite::driver('slack')->asBotUser()->user(); +``` When generating a bot token, the `user` method will still return a `Laravel\Socialite\Two\User` instance; however, only the `token` property will be hydrated. This token may be stored in order to [send notifications to the authenticated user's Slack workspaces](/docs/{{version}}/notifications#notifying-external-slack-workspaces). @@ -150,11 +164,13 @@ When generating a bot token, the `user` method will still return a `Laravel\Soci A number of OAuth providers support other optional parameters on the redirect request. To include any optional parameters in the request, call the `with` method with an associative array: - use Laravel\Socialite\Facades\Socialite; +```php +use Laravel\Socialite\Facades\Socialite; - return Socialite::driver('google') - ->with(['hd' => 'example.com']) - ->redirect(); +return Socialite::driver('google') + ->with(['hd' => 'example.com']) + ->redirect(); +``` > [!WARNING] > When using the `with` method, be careful not to pass any reserved keywords such as `state` or `response_type`. @@ -166,36 +182,40 @@ After the user is redirected back to your application's authentication callback Differing properties and methods may be available on this object depending on whether the OAuth provider you are authenticating with supports OAuth 1.0 or OAuth 2.0: - use Laravel\Socialite\Facades\Socialite; +```php +use Laravel\Socialite\Facades\Socialite; - Route::get('/auth/callback', function () { - $user = Socialite::driver('github')->user(); +Route::get('/auth/callback', function () { + $user = Socialite::driver('github')->user(); - // OAuth 2.0 providers... - $token = $user->token; - $refreshToken = $user->refreshToken; - $expiresIn = $user->expiresIn; + // OAuth 2.0 providers... + $token = $user->token; + $refreshToken = $user->refreshToken; + $expiresIn = $user->expiresIn; - // OAuth 1.0 providers... - $token = $user->token; - $tokenSecret = $user->tokenSecret; + // OAuth 1.0 providers... + $token = $user->token; + $tokenSecret = $user->tokenSecret; - // All providers... - $user->getId(); - $user->getNickname(); - $user->getName(); - $user->getEmail(); - $user->getAvatar(); - }); + // All providers... + $user->getId(); + $user->getNickname(); + $user->getName(); + $user->getEmail(); + $user->getAvatar(); +}); +``` #### Retrieving User Details From a Token If you already have a valid access token for a user, you can retrieve their user details using Socialite's `userFromToken` method: - use Laravel\Socialite\Facades\Socialite; +```php +use Laravel\Socialite\Facades\Socialite; - $user = Socialite::driver('github')->userFromToken($token); +$user = Socialite::driver('github')->userFromToken($token); +``` If you are using Facebook Limited Login via an iOS application, Facebook will return an OIDC token instead of an access token. Like an access token, the OIDC token can be provided to the `userFromToken` method in order to retrieve user details. @@ -204,6 +224,8 @@ If you are using Facebook Limited Login via an iOS application, Facebook will re The `stateless` method may be used to disable session state verification. This is useful when adding social authentication to a stateless API that does not utilize cookie based sessions: - use Laravel\Socialite\Facades\Socialite; +```php +use Laravel\Socialite\Facades\Socialite; - return Socialite::driver('google')->stateless()->user(); +return Socialite::driver('google')->stateless()->user(); +``` diff --git a/strings.md b/strings.md index 15166f189fd..1a2986f95b8 100644 --- a/strings.md +++ b/strings.md @@ -241,9 +241,11 @@ Laravel includes a variety of functions for manipulating string values. Many of The `__` function translates the given translation string or translation key using your [language files](/docs/{{version}}/localization): - echo __('Welcome to our application'); +```php +echo __('Welcome to our application'); - echo __('messages.welcome'); +echo __('messages.welcome'); +``` If the specified translation string or key does not exist, the `__` function will return the given value. So, using the example above, the `__` function would return `messages.welcome` if that translation key does not exist. @@ -252,762 +254,898 @@ If the specified translation string or key does not exist, the `__` function wil The `class_basename` function returns the class name of the given class with the class's namespace removed: - $class = class_basename('Foo\Bar\Baz'); +```php +$class = class_basename('Foo\Bar\Baz'); - // Baz +// Baz +``` #### `e()` {.collection-method} The `e` function runs PHP's `htmlspecialchars` function with the `double_encode` option set to `true` by default: - echo e('foo'); +```php +echo e('foo'); - // <html>foo</html> +// <html>foo</html> +``` #### `preg_replace_array()` {.collection-method} The `preg_replace_array` function replaces a given pattern in the string sequentially using an array: - $string = 'The event will take place between :start and :end'; +```php +$string = 'The event will take place between :start and :end'; - $replaced = preg_replace_array('/:[a-z_]+/', ['8:30', '9:00'], $string); +$replaced = preg_replace_array('/:[a-z_]+/', ['8:30', '9:00'], $string); - // The event will take place between 8:30 and 9:00 +// The event will take place between 8:30 and 9:00 +``` #### `Str::after()` {.collection-method} The `Str::after` method returns everything after the given value in a string. The entire string will be returned if the value does not exist within the string: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $slice = Str::after('This is my name', 'This is'); +$slice = Str::after('This is my name', 'This is'); - // ' my name' +// ' my name' +``` #### `Str::afterLast()` {.collection-method} The `Str::afterLast` method returns everything after the last occurrence of the given value in a string. The entire string will be returned if the value does not exist within the string: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $slice = Str::afterLast('App\Http\Controllers\Controller', '\\'); +$slice = Str::afterLast('App\Http\Controllers\Controller', '\\'); - // 'Controller' +// 'Controller' +``` #### `Str::apa()` {.collection-method} The `Str::apa` method converts the given string to title case following the [APA guidelines](https://apastyle.apa.org/style-grammar-guidelines/capitalization/title-case): - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $title = Str::apa('Creating A Project'); +$title = Str::apa('Creating A Project'); - // 'Creating a Project' +// 'Creating a Project' +``` #### `Str::ascii()` {.collection-method} The `Str::ascii` method will attempt to transliterate the string into an ASCII value: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $slice = Str::ascii('û'); +$slice = Str::ascii('û'); - // 'u' +// 'u' +``` #### `Str::before()` {.collection-method} The `Str::before` method returns everything before the given value in a string: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $slice = Str::before('This is my name', 'my name'); +$slice = Str::before('This is my name', 'my name'); - // 'This is ' +// 'This is ' +``` #### `Str::beforeLast()` {.collection-method} The `Str::beforeLast` method returns everything before the last occurrence of the given value in a string: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $slice = Str::beforeLast('This is my name', 'is'); +$slice = Str::beforeLast('This is my name', 'is'); - // 'This ' +// 'This ' +``` #### `Str::between()` {.collection-method} The `Str::between` method returns the portion of a string between two values: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $slice = Str::between('This is my name', 'This', 'name'); +$slice = Str::between('This is my name', 'This', 'name'); - // ' is my ' +// ' is my ' +``` #### `Str::betweenFirst()` {.collection-method} The `Str::betweenFirst` method returns the smallest possible portion of a string between two values: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $slice = Str::betweenFirst('[a] bc [d]', '[', ']'); +$slice = Str::betweenFirst('[a] bc [d]', '[', ']'); - // 'a' +// 'a' +``` #### `Str::camel()` {.collection-method} The `Str::camel` method converts the given string to `camelCase`: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $converted = Str::camel('foo_bar'); +$converted = Str::camel('foo_bar'); - // 'fooBar' +// 'fooBar' +``` #### `Str::charAt()` {.collection-method} The `Str::charAt` method returns the character at the specified index. If the index is out of bounds, `false` is returned: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $character = Str::charAt('This is my name.', 6); +$character = Str::charAt('This is my name.', 6); - // 's' +// 's' +``` #### `Str::chopStart()` {.collection-method} The `Str::chopStart` method removes the first occurrence of the given value only if the value appears at the start of the string: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $url = Str::chopStart('https://laravel.com', 'https://'); +$url = Str::chopStart('https://laravel.com', 'https://'); - // 'laravel.com' +// 'laravel.com' +``` You may also pass an array as the second argument. If the string starts with any of the values in the array then that value will be removed from string: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $url = Str::chopStart('http://laravel.com', ['https://', 'http://']); +$url = Str::chopStart('http://laravel.com', ['https://', 'http://']); - // 'laravel.com' +// 'laravel.com' +``` #### `Str::chopEnd()` {.collection-method} The `Str::chopEnd` method removes the last occurrence of the given value only if the value appears at the end of the string: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $url = Str::chopEnd('app/Models/Photograph.php', '.php'); +$url = Str::chopEnd('app/Models/Photograph.php', '.php'); - // 'app/Models/Photograph' +// 'app/Models/Photograph' +``` You may also pass an array as the second argument. If the string ends with any of the values in the array then that value will be removed from string: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $url = Str::chopEnd('laravel.com/index.php', ['/index.html', '/index.php']); +$url = Str::chopEnd('laravel.com/index.php', ['/index.html', '/index.php']); - // 'laravel.com' +// 'laravel.com' +``` #### `Str::contains()` {.collection-method} The `Str::contains` method determines if the given string contains the given value. By default this method is case sensitive: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $contains = Str::contains('This is my name', 'my'); +$contains = Str::contains('This is my name', 'my'); - // true +// true +``` You may also pass an array of values to determine if the given string contains any of the values in the array: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $contains = Str::contains('This is my name', ['my', 'foo']); +$contains = Str::contains('This is my name', ['my', 'foo']); - // true +// true +``` You may disable case sensitivity by setting the `ignoreCase` argument to `true`: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $contains = Str::contains('This is my name', 'MY', ignoreCase: true); +$contains = Str::contains('This is my name', 'MY', ignoreCase: true); - // true +// true +``` #### `Str::containsAll()` {.collection-method} The `Str::containsAll` method determines if the given string contains all of the values in a given array: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $containsAll = Str::containsAll('This is my name', ['my', 'name']); +$containsAll = Str::containsAll('This is my name', ['my', 'name']); - // true +// true +``` You may disable case sensitivity by setting the `ignoreCase` argument to `true`: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $containsAll = Str::containsAll('This is my name', ['MY', 'NAME'], ignoreCase: true); +$containsAll = Str::containsAll('This is my name', ['MY', 'NAME'], ignoreCase: true); - // true +// true +``` #### `Str::doesntContain()` {.collection-method} The `Str::doesntContain` method determines if the given string doesn't contain the given value. By default this method is case sensitive: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $doesntContain = Str::doesntContain('This is name', 'my'); +$doesntContain = Str::doesntContain('This is name', 'my'); - // true +// true +``` You may also pass an array of values to determine if the given string doesn't contain any of the values in the array: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $doesntContain = Str::doesntContain('This is name', ['my', 'foo']); +$doesntContain = Str::doesntContain('This is name', ['my', 'foo']); - // true +// true +``` You may disable case sensitivity by setting the `ignoreCase` argument to `true`: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $doesntContain = Str::doesntContain('This is name', 'MY', ignoreCase: true); +$doesntContain = Str::doesntContain('This is name', 'MY', ignoreCase: true); - // true +// true +``` #### `Str::deduplicate()` {.collection-method} The `Str::deduplicate` method replaces consecutive instances of a character with a single instance of that character in the given string. By default, the method deduplicates spaces: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $result = Str::deduplicate('The Laravel Framework'); +$result = Str::deduplicate('The Laravel Framework'); - // The Laravel Framework +// The Laravel Framework +``` You may specify a different character to deduplicate by passing it in as the second argument to the method: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $result = Str::deduplicate('The---Laravel---Framework', '-'); +$result = Str::deduplicate('The---Laravel---Framework', '-'); - // The-Laravel-Framework +// The-Laravel-Framework +``` #### `Str::endsWith()` {.collection-method} The `Str::endsWith` method determines if the given string ends with the given value: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $result = Str::endsWith('This is my name', 'name'); +$result = Str::endsWith('This is my name', 'name'); - // true +// true +``` You may also pass an array of values to determine if the given string ends with any of the values in the array: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $result = Str::endsWith('This is my name', ['name', 'foo']); +$result = Str::endsWith('This is my name', ['name', 'foo']); - // true +// true - $result = Str::endsWith('This is my name', ['this', 'foo']); +$result = Str::endsWith('This is my name', ['this', 'foo']); - // false +// false +``` #### `Str::excerpt()` {.collection-method} The `Str::excerpt` method extracts an excerpt from a given string that matches the first instance of a phrase within that string: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $excerpt = Str::excerpt('This is my name', 'my', [ - 'radius' => 3 - ]); +$excerpt = Str::excerpt('This is my name', 'my', [ + 'radius' => 3 +]); - // '...is my na...' +// '...is my na...' +``` The `radius` option, which defaults to `100`, allows you to define the number of characters that should appear on each side of the truncated string. In addition, you may use the `omission` option to define the string that will be prepended and appended to the truncated string: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $excerpt = Str::excerpt('This is my name', 'name', [ - 'radius' => 3, - 'omission' => '(...) ' - ]); +$excerpt = Str::excerpt('This is my name', 'name', [ + 'radius' => 3, + 'omission' => '(...) ' +]); - // '(...) my name' +// '(...) my name' +``` #### `Str::finish()` {.collection-method} The `Str::finish` method adds a single instance of the given value to a string if it does not already end with that value: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $adjusted = Str::finish('this/string', '/'); +$adjusted = Str::finish('this/string', '/'); - // this/string/ +// this/string/ - $adjusted = Str::finish('this/string/', '/'); +$adjusted = Str::finish('this/string/', '/'); - // this/string/ +// this/string/ +``` #### `Str::headline()` {.collection-method} The `Str::headline` method will convert strings delimited by casing, hyphens, or underscores into a space delimited string with each word's first letter capitalized: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $headline = Str::headline('steve_jobs'); +$headline = Str::headline('steve_jobs'); - // Steve Jobs +// Steve Jobs - $headline = Str::headline('EmailNotificationSent'); +$headline = Str::headline('EmailNotificationSent'); - // Email Notification Sent +// Email Notification Sent +``` #### `Str::inlineMarkdown()` {.collection-method} The `Str::inlineMarkdown` method converts GitHub flavored Markdown into inline HTML using [CommonMark](https://commonmark.thephpleague.com/). However, unlike the `markdown` method, it does not wrap all generated HTML in a block-level element: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $html = Str::inlineMarkdown('**Laravel**'); +$html = Str::inlineMarkdown('**Laravel**'); - // Laravel +// Laravel +``` #### Markdown Security By default, Markdown supports raw HTML, which will expose Cross-Site Scripting (XSS) vulnerabilities when used with raw user input. As per the [CommonMark Security documentation](https://commonmark.thephpleague.com/security/), you may use the `html_input` option to either escape or strip raw HTML, and the `allow_unsafe_links` option to specify whether to allow unsafe links. If you need to allow some raw HTML, you should pass your compiled Markdown through an HTML Purifier: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - Str::inlineMarkdown('Inject: ', [ - 'html_input' => 'strip', - 'allow_unsafe_links' => false, - ]); +Str::inlineMarkdown('Inject: ', [ + 'html_input' => 'strip', + 'allow_unsafe_links' => false, +]); - // Inject: alert("Hello XSS!"); +// Inject: alert("Hello XSS!"); +``` #### `Str::is()` {.collection-method} The `Str::is` method determines if a given string matches a given pattern. Asterisks may be used as wildcard values: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $matches = Str::is('foo*', 'foobar'); +$matches = Str::is('foo*', 'foobar'); - // true +// true - $matches = Str::is('baz*', 'foobar'); +$matches = Str::is('baz*', 'foobar'); - // false +// false +``` You may disable case sensitivity by setting the `ignoreCase` argument to `true`: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $matches = Str::is('*.jpg', 'photo.JPG', ignoreCase: true); +$matches = Str::is('*.jpg', 'photo.JPG', ignoreCase: true); - // true +// true +``` #### `Str::isAscii()` {.collection-method} The `Str::isAscii` method determines if a given string is 7 bit ASCII: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $isAscii = Str::isAscii('Taylor'); +$isAscii = Str::isAscii('Taylor'); - // true +// true - $isAscii = Str::isAscii('ü'); +$isAscii = Str::isAscii('ü'); - // false +// false +``` #### `Str::isJson()` {.collection-method} The `Str::isJson` method determines if the given string is valid JSON: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $result = Str::isJson('[1,2,3]'); +$result = Str::isJson('[1,2,3]'); - // true +// true - $result = Str::isJson('{"first": "John", "last": "Doe"}'); +$result = Str::isJson('{"first": "John", "last": "Doe"}'); - // true +// true - $result = Str::isJson('{first: "John", last: "Doe"}'); +$result = Str::isJson('{first: "John", last: "Doe"}'); - // false +// false +``` #### `Str::isUrl()` {.collection-method} The `Str::isUrl` method determines if the given string is a valid URL: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $isUrl = Str::isUrl('http://example.com'); +$isUrl = Str::isUrl('http://example.com'); - // true +// true - $isUrl = Str::isUrl('laravel'); +$isUrl = Str::isUrl('laravel'); - // false +// false +``` The `isUrl` method considers a wide range of protocols as valid. However, you may specify the protocols that should be considered valid by providing them to the `isUrl` method: - $isUrl = Str::isUrl('http://example.com', ['http', 'https']); +```php +$isUrl = Str::isUrl('http://example.com', ['http', 'https']); +``` #### `Str::isUlid()` {.collection-method} The `Str::isUlid` method determines if the given string is a valid ULID: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $isUlid = Str::isUlid('01gd6r360bp37zj17nxb55yv40'); +$isUlid = Str::isUlid('01gd6r360bp37zj17nxb55yv40'); - // true +// true - $isUlid = Str::isUlid('laravel'); +$isUlid = Str::isUlid('laravel'); - // false +// false +``` #### `Str::isUuid()` {.collection-method} The `Str::isUuid` method determines if the given string is a valid UUID: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $isUuid = Str::isUuid('a0a2a2d2-0b87-4a18-83f2-2529882be2de'); +$isUuid = Str::isUuid('a0a2a2d2-0b87-4a18-83f2-2529882be2de'); - // true +// true - $isUuid = Str::isUuid('laravel'); +$isUuid = Str::isUuid('laravel'); - // false +// false +``` #### `Str::kebab()` {.collection-method} The `Str::kebab` method converts the given string to `kebab-case`: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $converted = Str::kebab('fooBar'); +$converted = Str::kebab('fooBar'); - // foo-bar +// foo-bar +``` #### `Str::lcfirst()` {.collection-method} The `Str::lcfirst` method returns the given string with the first character lowercased: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $string = Str::lcfirst('Foo Bar'); +$string = Str::lcfirst('Foo Bar'); - // foo Bar +// foo Bar +``` #### `Str::length()` {.collection-method} The `Str::length` method returns the length of the given string: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $length = Str::length('Laravel'); +$length = Str::length('Laravel'); - // 7 +// 7 +``` #### `Str::limit()` {.collection-method} The `Str::limit` method truncates the given string to the specified length: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $truncated = Str::limit('The quick brown fox jumps over the lazy dog', 20); +$truncated = Str::limit('The quick brown fox jumps over the lazy dog', 20); - // The quick brown fox... +// The quick brown fox... +``` You may pass a third argument to the method to change the string that will be appended to the end of the truncated string: - $truncated = Str::limit('The quick brown fox jumps over the lazy dog', 20, ' (...)'); +```php +$truncated = Str::limit('The quick brown fox jumps over the lazy dog', 20, ' (...)'); - // The quick brown fox (...) +// The quick brown fox (...) +``` If you would like to preserve complete words when truncating the string, you may utilize the `preserveWords` argument. When this argument is `true`, the string will be truncated to the nearest complete word boundary: - $truncated = Str::limit('The quick brown fox', 12, preserveWords: true); +```php +$truncated = Str::limit('The quick brown fox', 12, preserveWords: true); - // The quick... +// The quick... +``` #### `Str::lower()` {.collection-method} The `Str::lower` method converts the given string to lowercase: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $converted = Str::lower('LARAVEL'); +$converted = Str::lower('LARAVEL'); - // laravel +// laravel +``` #### `Str::markdown()` {.collection-method} The `Str::markdown` method converts GitHub flavored Markdown into HTML using [CommonMark](https://commonmark.thephpleague.com/): - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $html = Str::markdown('# Laravel'); +$html = Str::markdown('# Laravel'); - //

Laravel

+//

Laravel

- $html = Str::markdown('# Taylor Otwell', [ - 'html_input' => 'strip', - ]); +$html = Str::markdown('# Taylor Otwell', [ + 'html_input' => 'strip', +]); - //

Taylor Otwell

+//

Taylor Otwell

+``` #### Markdown Security By default, Markdown supports raw HTML, which will expose Cross-Site Scripting (XSS) vulnerabilities when used with raw user input. As per the [CommonMark Security documentation](https://commonmark.thephpleague.com/security/), you may use the `html_input` option to either escape or strip raw HTML, and the `allow_unsafe_links` option to specify whether to allow unsafe links. If you need to allow some raw HTML, you should pass your compiled Markdown through an HTML Purifier: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - Str::markdown('Inject: ', [ - 'html_input' => 'strip', - 'allow_unsafe_links' => false, - ]); +Str::markdown('Inject: ', [ + 'html_input' => 'strip', + 'allow_unsafe_links' => false, +]); - //

Inject: alert("Hello XSS!");

+//

Inject: alert("Hello XSS!");

+``` #### `Str::mask()` {.collection-method} The `Str::mask` method masks a portion of a string with a repeated character, and may be used to obfuscate segments of strings such as email addresses and phone numbers: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $string = Str::mask('taylor@example.com', '*', 3); +$string = Str::mask('taylor@example.com', '*', 3); - // tay*************** +// tay*************** +``` If needed, you provide a negative number as the third argument to the `mask` method, which will instruct the method to begin masking at the given distance from the end of the string: - $string = Str::mask('taylor@example.com', '*', -15, 3); +```php +$string = Str::mask('taylor@example.com', '*', -15, 3); - // tay***@example.com +// tay***@example.com +``` #### `Str::orderedUuid()` {.collection-method} The `Str::orderedUuid` method generates a "timestamp first" UUID that may be efficiently stored in an indexed database column. Each UUID that is generated using this method will be sorted after UUIDs previously generated using the method: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - return (string) Str::orderedUuid(); +return (string) Str::orderedUuid(); +``` #### `Str::padBoth()` {.collection-method} The `Str::padBoth` method wraps PHP's `str_pad` function, padding both sides of a string with another string until the final string reaches a desired length: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $padded = Str::padBoth('James', 10, '_'); +$padded = Str::padBoth('James', 10, '_'); - // '__James___' +// '__James___' - $padded = Str::padBoth('James', 10); +$padded = Str::padBoth('James', 10); - // ' James ' +// ' James ' +``` #### `Str::padLeft()` {.collection-method} The `Str::padLeft` method wraps PHP's `str_pad` function, padding the left side of a string with another string until the final string reaches a desired length: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $padded = Str::padLeft('James', 10, '-='); +$padded = Str::padLeft('James', 10, '-='); - // '-=-=-James' +// '-=-=-James' - $padded = Str::padLeft('James', 10); +$padded = Str::padLeft('James', 10); - // ' James' +// ' James' +``` #### `Str::padRight()` {.collection-method} The `Str::padRight` method wraps PHP's `str_pad` function, padding the right side of a string with another string until the final string reaches a desired length: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $padded = Str::padRight('James', 10, '-'); +$padded = Str::padRight('James', 10, '-'); - // 'James-----' +// 'James-----' - $padded = Str::padRight('James', 10); +$padded = Str::padRight('James', 10); - // 'James ' +// 'James ' +``` #### `Str::password()` {.collection-method} The `Str::password` method may be used to generate a secure, random password of a given length. The password will consist of a combination of letters, numbers, symbols, and spaces. By default, passwords are 32 characters long: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $password = Str::password(); +$password = Str::password(); - // 'EbJo2vE-AS:U,$%_gkrV4n,q~1xy/-_4' +// 'EbJo2vE-AS:U,$%_gkrV4n,q~1xy/-_4' - $password = Str::password(12); +$password = Str::password(12); - // 'qwuar>#V|i]N' +// 'qwuar>#V|i]N' +``` #### `Str::plural()` {.collection-method} The `Str::plural` method converts a singular word string to its plural form. This function supports [any of the languages support by Laravel's pluralizer](/docs/{{version}}/localization#pluralization-language): - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $plural = Str::plural('car'); +$plural = Str::plural('car'); - // cars +// cars - $plural = Str::plural('child'); +$plural = Str::plural('child'); - // children +// children +``` You may provide an integer as a second argument to the function to retrieve the singular or plural form of the string: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $plural = Str::plural('child', 2); +$plural = Str::plural('child', 2); - // children +// children - $singular = Str::plural('child', 1); +$singular = Str::plural('child', 1); - // child +// child +``` #### `Str::pluralStudly()` {.collection-method} The `Str::pluralStudly` method converts a singular word string formatted in studly caps case to its plural form. This function supports [any of the languages support by Laravel's pluralizer](/docs/{{version}}/localization#pluralization-language): - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $plural = Str::pluralStudly('VerifiedHuman'); +$plural = Str::pluralStudly('VerifiedHuman'); - // VerifiedHumans +// VerifiedHumans - $plural = Str::pluralStudly('UserFeedback'); +$plural = Str::pluralStudly('UserFeedback'); - // UserFeedback +// UserFeedback +``` You may provide an integer as a second argument to the function to retrieve the singular or plural form of the string: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $plural = Str::pluralStudly('VerifiedHuman', 2); +$plural = Str::pluralStudly('VerifiedHuman', 2); - // VerifiedHumans +// VerifiedHumans - $singular = Str::pluralStudly('VerifiedHuman', 1); +$singular = Str::pluralStudly('VerifiedHuman', 1); - // VerifiedHuman +// VerifiedHuman +``` #### `Str::position()` {.collection-method} The `Str::position` method returns the position of the first occurrence of a substring in a string. If the substring does not exist in the given string, `false` is returned: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $position = Str::position('Hello, World!', 'Hello'); +$position = Str::position('Hello, World!', 'Hello'); - // 0 +// 0 - $position = Str::position('Hello, World!', 'W'); +$position = Str::position('Hello, World!', 'W'); - // 7 +// 7 +``` #### `Str::random()` {.collection-method} The `Str::random` method generates a random string of the specified length. This function uses PHP's `random_bytes` function: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $random = Str::random(40); +$random = Str::random(40); +``` During testing, it may be useful to "fake" the value that is returned by the `Str::random` method. To accomplish this, you may use the `createRandomStringsUsing` method: - Str::createRandomStringsUsing(function () { - return 'fake-random-string'; - }); +```php +Str::createRandomStringsUsing(function () { + return 'fake-random-string'; +}); +``` To instruct the `random` method to return to generating random strings normally, you may invoke the `createRandomStringsNormally` method: - Str::createRandomStringsNormally(); +```php +Str::createRandomStringsNormally(); +``` #### `Str::remove()` {.collection-method} The `Str::remove` method removes the given value or array of values from the string: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $string = 'Peter Piper picked a peck of pickled peppers.'; +$string = 'Peter Piper picked a peck of pickled peppers.'; - $removed = Str::remove('e', $string); +$removed = Str::remove('e', $string); - // Ptr Pipr pickd a pck of pickld ppprs. +// Ptr Pipr pickd a pck of pickld ppprs. +``` You may also pass `false` as a third argument to the `remove` method to ignore case when removing strings. @@ -1031,383 +1169,449 @@ $repeat = Str::repeat($string, 5); The `Str::replace` method replaces a given string within the string: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $string = 'Laravel 10.x'; +$string = 'Laravel 10.x'; - $replaced = Str::replace('10.x', '11.x', $string); +$replaced = Str::replace('10.x', '11.x', $string); - // Laravel 11.x +// Laravel 11.x +``` The `replace` method also accepts a `caseSensitive` argument. By default, the `replace` method is case sensitive: - Str::replace('Framework', 'Laravel', caseSensitive: false); +```php +Str::replace('Framework', 'Laravel', caseSensitive: false); +``` #### `Str::replaceArray()` {.collection-method} The `Str::replaceArray` method replaces a given value in the string sequentially using an array: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $string = 'The event will take place between ? and ?'; +$string = 'The event will take place between ? and ?'; - $replaced = Str::replaceArray('?', ['8:30', '9:00'], $string); +$replaced = Str::replaceArray('?', ['8:30', '9:00'], $string); - // The event will take place between 8:30 and 9:00 +// The event will take place between 8:30 and 9:00 +``` #### `Str::replaceFirst()` {.collection-method} The `Str::replaceFirst` method replaces the first occurrence of a given value in a string: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $replaced = Str::replaceFirst('the', 'a', 'the quick brown fox jumps over the lazy dog'); +$replaced = Str::replaceFirst('the', 'a', 'the quick brown fox jumps over the lazy dog'); - // a quick brown fox jumps over the lazy dog +// a quick brown fox jumps over the lazy dog +``` #### `Str::replaceLast()` {.collection-method} The `Str::replaceLast` method replaces the last occurrence of a given value in a string: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $replaced = Str::replaceLast('the', 'a', 'the quick brown fox jumps over the lazy dog'); +$replaced = Str::replaceLast('the', 'a', 'the quick brown fox jumps over the lazy dog'); - // the quick brown fox jumps over a lazy dog +// the quick brown fox jumps over a lazy dog +``` #### `Str::replaceMatches()` {.collection-method} The `Str::replaceMatches` method replaces all portions of a string matching a pattern with the given replacement string: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $replaced = Str::replaceMatches( - pattern: '/[^A-Za-z0-9]++/', - replace: '', - subject: '(+1) 501-555-1000' - ) +$replaced = Str::replaceMatches( + pattern: '/[^A-Za-z0-9]++/', + replace: '', + subject: '(+1) 501-555-1000' +) - // '15015551000' +// '15015551000' +``` The `replaceMatches` method also accepts a closure that will be invoked with each portion of the string matching the given pattern, allowing you to perform the replacement logic within the closure and return the replaced value: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $replaced = Str::replaceMatches('/\d/', function (array $matches) { - return '['.$matches[0].']'; - }, '123'); +$replaced = Str::replaceMatches('/\d/', function (array $matches) { + return '['.$matches[0].']'; +}, '123'); - // '[1][2][3]' +// '[1][2][3]' +``` #### `Str::replaceStart()` {.collection-method} The `Str::replaceStart` method replaces the first occurrence of the given value only if the value appears at the start of the string: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $replaced = Str::replaceStart('Hello', 'Laravel', 'Hello World'); +$replaced = Str::replaceStart('Hello', 'Laravel', 'Hello World'); - // Laravel World +// Laravel World - $replaced = Str::replaceStart('World', 'Laravel', 'Hello World'); +$replaced = Str::replaceStart('World', 'Laravel', 'Hello World'); - // Hello World +// Hello World +``` #### `Str::replaceEnd()` {.collection-method} The `Str::replaceEnd` method replaces the last occurrence of the given value only if the value appears at the end of the string: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $replaced = Str::replaceEnd('World', 'Laravel', 'Hello World'); +$replaced = Str::replaceEnd('World', 'Laravel', 'Hello World'); - // Hello Laravel +// Hello Laravel - $replaced = Str::replaceEnd('Hello', 'Laravel', 'Hello World'); +$replaced = Str::replaceEnd('Hello', 'Laravel', 'Hello World'); - // Hello World +// Hello World +``` #### `Str::reverse()` {.collection-method} The `Str::reverse` method reverses the given string: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $reversed = Str::reverse('Hello World'); +$reversed = Str::reverse('Hello World'); - // dlroW olleH +// dlroW olleH +``` #### `Str::singular()` {.collection-method} The `Str::singular` method converts a string to its singular form. This function supports [any of the languages support by Laravel's pluralizer](/docs/{{version}}/localization#pluralization-language): - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $singular = Str::singular('cars'); +$singular = Str::singular('cars'); - // car +// car - $singular = Str::singular('children'); +$singular = Str::singular('children'); - // child +// child +``` #### `Str::slug()` {.collection-method} The `Str::slug` method generates a URL friendly "slug" from the given string: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $slug = Str::slug('Laravel 5 Framework', '-'); +$slug = Str::slug('Laravel 5 Framework', '-'); - // laravel-5-framework +// laravel-5-framework +``` #### `Str::snake()` {.collection-method} The `Str::snake` method converts the given string to `snake_case`: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $converted = Str::snake('fooBar'); +$converted = Str::snake('fooBar'); - // foo_bar +// foo_bar - $converted = Str::snake('fooBar', '-'); +$converted = Str::snake('fooBar', '-'); - // foo-bar +// foo-bar +``` #### `Str::squish()` {.collection-method} The `Str::squish` method removes all extraneous white space from a string, including extraneous white space between words: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $string = Str::squish(' laravel framework '); +$string = Str::squish(' laravel framework '); - // laravel framework +// laravel framework +``` #### `Str::start()` {.collection-method} The `Str::start` method adds a single instance of the given value to a string if it does not already start with that value: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $adjusted = Str::start('this/string', '/'); +$adjusted = Str::start('this/string', '/'); - // /this/string +// /this/string - $adjusted = Str::start('/this/string', '/'); +$adjusted = Str::start('/this/string', '/'); - // /this/string +// /this/string +``` #### `Str::startsWith()` {.collection-method} The `Str::startsWith` method determines if the given string begins with the given value: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $result = Str::startsWith('This is my name', 'This'); +$result = Str::startsWith('This is my name', 'This'); - // true +// true +``` If an array of possible values is passed, the `startsWith` method will return `true` if the string begins with any of the given values: - $result = Str::startsWith('This is my name', ['This', 'That', 'There']); +```php +$result = Str::startsWith('This is my name', ['This', 'That', 'There']); - // true +// true +``` #### `Str::studly()` {.collection-method} The `Str::studly` method converts the given string to `StudlyCase`: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $converted = Str::studly('foo_bar'); +$converted = Str::studly('foo_bar'); - // FooBar +// FooBar +``` #### `Str::substr()` {.collection-method} The `Str::substr` method returns the portion of string specified by the start and length parameters: - use Illuminate\Support\Str; - - $converted = Str::substr('The Laravel Framework', 4, 7); +```php +use Illuminate\Support\Str; - // Laravel +$converted = Str::substr('The Laravel Framework', 4, 7); + +// Laravel +``` #### `Str::substrCount()` {.collection-method} The `Str::substrCount` method returns the number of occurrences of a given value in the given string: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $count = Str::substrCount('If you like ice cream, you will like snow cones.', 'like'); +$count = Str::substrCount('If you like ice cream, you will like snow cones.', 'like'); - // 2 +// 2 +``` #### `Str::substrReplace()` {.collection-method} The `Str::substrReplace` method replaces text within a portion of a string, starting at the position specified by the third argument and replacing the number of characters specified by the fourth argument. Passing `0` to the method's fourth argument will insert the string at the specified position without replacing any of the existing characters in the string: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $result = Str::substrReplace('1300', ':', 2); - // 13: +$result = Str::substrReplace('1300', ':', 2); +// 13: - $result = Str::substrReplace('1300', ':', 2, 0); - // 13:00 +$result = Str::substrReplace('1300', ':', 2, 0); +// 13:00 +``` #### `Str::swap()` {.collection-method} The `Str::swap` method replaces multiple values in the given string using PHP's `strtr` function: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $string = Str::swap([ - 'Tacos' => 'Burritos', - 'great' => 'fantastic', - ], 'Tacos are great!'); +$string = Str::swap([ + 'Tacos' => 'Burritos', + 'great' => 'fantastic', +], 'Tacos are great!'); - // Burritos are fantastic! +// Burritos are fantastic! +``` #### `Str::take()` {.collection-method} The `Str::take` method returns a specified number of characters from the beginning of a string: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $taken = Str::take('Build something amazing!', 5); +$taken = Str::take('Build something amazing!', 5); - // Build +// Build +``` #### `Str::title()` {.collection-method} The `Str::title` method converts the given string to `Title Case`: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $converted = Str::title('a nice title uses the correct case'); +$converted = Str::title('a nice title uses the correct case'); - // A Nice Title Uses The Correct Case +// A Nice Title Uses The Correct Case +``` #### `Str::toBase64()` {.collection-method} The `Str::toBase64` method converts the given string to Base64: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $base64 = Str::toBase64('Laravel'); +$base64 = Str::toBase64('Laravel'); - // TGFyYXZlbA== +// TGFyYXZlbA== +``` #### `Str::transliterate()` {.collection-method} The `Str::transliterate` method will attempt to convert a given string into its closest ASCII representation: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $email = Str::transliterate('ⓣⓔⓢⓣ@ⓛⓐⓡⓐⓥⓔⓛ.ⓒⓞⓜ'); +$email = Str::transliterate('ⓣⓔⓢⓣ@ⓛⓐⓡⓐⓥⓔⓛ.ⓒⓞⓜ'); - // 'test@laravel.com' +// 'test@laravel.com' +``` #### `Str::trim()` {.collection-method} The `Str::trim` method strips whitespace (or other characters) from the beginning and end of the given string. Unlike PHP's native `trim` function, the `Str::trim` method also removes unicode whitespace characters: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $string = Str::trim(' foo bar '); +$string = Str::trim(' foo bar '); - // 'foo bar' +// 'foo bar' +``` #### `Str::ltrim()` {.collection-method} The `Str::ltrim` method strips whitespace (or other characters) from the beginning of the given string. Unlike PHP's native `ltrim` function, the `Str::ltrim` method also removes unicode whitespace characters: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $string = Str::ltrim(' foo bar '); +$string = Str::ltrim(' foo bar '); - // 'foo bar ' +// 'foo bar ' +``` #### `Str::rtrim()` {.collection-method} The `Str::rtrim` method strips whitespace (or other characters) from the end of the given string. Unlike PHP's native `rtrim` function, the `Str::rtrim` method also removes unicode whitespace characters: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $string = Str::rtrim(' foo bar '); +$string = Str::rtrim(' foo bar '); - // ' foo bar' +// ' foo bar' +``` #### `Str::ucfirst()` {.collection-method} The `Str::ucfirst` method returns the given string with the first character capitalized: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $string = Str::ucfirst('foo bar'); +$string = Str::ucfirst('foo bar'); - // Foo bar +// Foo bar +``` #### `Str::ucsplit()` {.collection-method} The `Str::ucsplit` method splits the given string into an array by uppercase characters: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $segments = Str::ucsplit('FooBar'); +$segments = Str::ucsplit('FooBar'); - // [0 => 'Foo', 1 => 'Bar'] +// [0 => 'Foo', 1 => 'Bar'] +``` #### `Str::upper()` {.collection-method} The `Str::upper` method converts the given string to uppercase: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $string = Str::upper('laravel'); +$string = Str::upper('laravel'); - // LARAVEL +// LARAVEL +``` #### `Str::ulid()` {.collection-method} The `Str::ulid` method generates a ULID, which is a compact, time-ordered unique identifier: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - return (string) Str::ulid(); +return (string) Str::ulid(); - // 01gd6r360bp37zj17nxb55yv40 +// 01gd6r360bp37zj17nxb55yv40 +``` If you would like to retrieve a `Illuminate\Support\Carbon` date instance representing the date and time that a given ULID was created, you may use the `createFromId` method provided by Laravel's Carbon integration: @@ -1420,51 +1624,63 @@ $date = Carbon::createFromId((string) Str::ulid()); During testing, it may be useful to "fake" the value that is returned by the `Str::ulid` method. To accomplish this, you may use the `createUlidsUsing` method: - use Symfony\Component\Uid\Ulid; +```php +use Symfony\Component\Uid\Ulid; - Str::createUlidsUsing(function () { - return new Ulid('01HRDBNHHCKNW2AK4Z29SN82T9'); - }); +Str::createUlidsUsing(function () { + return new Ulid('01HRDBNHHCKNW2AK4Z29SN82T9'); +}); +``` To instruct the `ulid` method to return to generating ULIDs normally, you may invoke the `createUlidsNormally` method: - Str::createUlidsNormally(); +```php +Str::createUlidsNormally(); +``` #### `Str::unwrap()` {.collection-method} The `Str::unwrap` method removes the specified strings from the beginning and end of a given string: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - Str::unwrap('-Laravel-', '-'); +Str::unwrap('-Laravel-', '-'); - // Laravel +// Laravel - Str::unwrap('{framework: "Laravel"}', '{', '}'); +Str::unwrap('{framework: "Laravel"}', '{', '}'); - // framework: "Laravel" +// framework: "Laravel" +``` #### `Str::uuid()` {.collection-method} The `Str::uuid` method generates a UUID (version 4): - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - return (string) Str::uuid(); +return (string) Str::uuid(); +``` During testing, it may be useful to "fake" the value that is returned by the `Str::uuid` method. To accomplish this, you may use the `createUuidsUsing` method: - use Ramsey\Uuid\Uuid; +```php +use Ramsey\Uuid\Uuid; - Str::createUuidsUsing(function () { - return Uuid::fromString('eadbfeac-5258-45c2-bab7-ccb9b5ef74f9'); - }); +Str::createUuidsUsing(function () { + return Uuid::fromString('eadbfeac-5258-45c2-bab7-ccb9b5ef74f9'); +}); +``` To instruct the `uuid` method to return to generating UUIDs normally, you may invoke the `createUuidsNormally` method: - Str::createUuidsNormally(); +```php +Str::createUuidsNormally(); +``` #### `Str::wordCount()` {.collection-method} @@ -1482,65 +1698,89 @@ Str::wordCount('Hello, world!'); // 2 The `Str::wordWrap` method wraps a string to a given number of characters: - use Illuminate\Support\Str; +```php + +``````php +use Illuminate\Support\Str; - $text = "The quick brown fox jumped over the lazy dog." +$text = "The quick brown fox jumped over the lazy dog." - Str::wordWrap($text, characters: 20, break: "
\n"); +Str::wordWrap($text, characters: 20, break: "
\n"); - /* - The quick brown fox
- jumped over the lazy
- dog. - */ +/* +The quick brown fox
+jumped over the lazy
+dog. +*/ +``` #### `Str::words()` {.collection-method} The `Str::words` method limits the number of words in a string. An additional string may be passed to this method via its third argument to specify which string should be appended to the end of the truncated string: - use Illuminate\Support\Str; +```php + +``````php +use Illuminate\Support\Str; - return Str::words('Perfectly balanced, as all things should be.', 3, ' >>>'); +return Str::words('Perfectly balanced, as all things should be.', 3, ' >>>'); - // Perfectly balanced, as >>> +// Perfectly balanced, as >>> +``` #### `Str::wrap()` {.collection-method} The `Str::wrap` method wraps the given string with an additional string or pair of strings: - use Illuminate\Support\Str; +```php + +``````php +use Illuminate\Support\Str; - Str::wrap('Laravel', '"'); +Str::wrap('Laravel', '"'); - // "Laravel" +// "Laravel" - Str::wrap('is', before: 'This ', after: ' Laravel!'); +Str::wrap('is', before: 'This ', after: ' Laravel!'); - // This is Laravel! +// This is Laravel! +``` #### `str()` {.collection-method} The `str` function returns a new `Illuminate\Support\Stringable` instance of the given string. This function is equivalent to the `Str::of` method: - $string = str('Taylor')->append(' Otwell'); +```php - // 'Taylor Otwell' +``````php +$string = str('Taylor')->append(' Otwell'); + +// 'Taylor Otwell' +``` If no argument is provided to the `str` function, the function returns an instance of `Illuminate\Support\Str`: - $snake = str()->snake('FooBar'); +```php + +``````php +$snake = str()->snake('FooBar'); - // 'foo_bar' +// 'foo_bar' +``` #### `trans()` {.collection-method} The `trans` function translates the given translation key using your [language files](/docs/{{version}}/localization): - echo trans('messages.welcome'); +```php + +``````php +echo trans('messages.welcome'); +``` If the specified translation key does not exist, the `trans` function will return the given key. So, using the example above, the `trans` function would return `messages.welcome` if the translation key does not exist. @@ -1549,7 +1789,9 @@ If the specified translation key does not exist, the `trans` function will retur The `trans_choice` function translates the given translation key with inflection: - echo trans_choice('messages.notifications', $unreadCount); +```php +echo trans_choice('messages.notifications', $unreadCount); +``` If the specified translation key does not exist, the `trans_choice` function will return the given key. So, using the example above, the `trans_choice` function would return `messages.notifications` if the translation key does not exist. @@ -1563,678 +1805,798 @@ Fluent strings provide a more fluent, object-oriented interface for working with The `after` method returns everything after the given value in a string. The entire string will be returned if the value does not exist within the string: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $slice = Str::of('This is my name')->after('This is'); +$slice = Str::of('This is my name')->after('This is'); - // ' my name' +// ' my name' +``` #### `afterLast` {.collection-method} The `afterLast` method returns everything after the last occurrence of the given value in a string. The entire string will be returned if the value does not exist within the string: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $slice = Str::of('App\Http\Controllers\Controller')->afterLast('\\'); +$slice = Str::of('App\Http\Controllers\Controller')->afterLast('\\'); - // 'Controller' +// 'Controller' +``` #### `apa` {.collection-method} The `apa` method converts the given string to title case following the [APA guidelines](https://apastyle.apa.org/style-grammar-guidelines/capitalization/title-case): - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $converted = Str::of('a nice title uses the correct case')->apa(); +$converted = Str::of('a nice title uses the correct case')->apa(); - // A Nice Title Uses the Correct Case +// A Nice Title Uses the Correct Case +``` #### `append` {.collection-method} The `append` method appends the given values to the string: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $string = Str::of('Taylor')->append(' Otwell'); +$string = Str::of('Taylor')->append(' Otwell'); - // 'Taylor Otwell' +// 'Taylor Otwell' +``` #### `ascii` {.collection-method} The `ascii` method will attempt to transliterate the string into an ASCII value: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $string = Str::of('ü')->ascii(); +$string = Str::of('ü')->ascii(); - // 'u' +// 'u' +``` #### `basename` {.collection-method} The `basename` method will return the trailing name component of the given string: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $string = Str::of('/foo/bar/baz')->basename(); +$string = Str::of('/foo/bar/baz')->basename(); - // 'baz' +// 'baz' +``` If needed, you may provide an "extension" that will be removed from the trailing component: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $string = Str::of('/foo/bar/baz.jpg')->basename('.jpg'); +$string = Str::of('/foo/bar/baz.jpg')->basename('.jpg'); - // 'baz' +// 'baz' +``` #### `before` {.collection-method} The `before` method returns everything before the given value in a string: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $slice = Str::of('This is my name')->before('my name'); +$slice = Str::of('This is my name')->before('my name'); - // 'This is ' +// 'This is ' +``` #### `beforeLast` {.collection-method} The `beforeLast` method returns everything before the last occurrence of the given value in a string: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $slice = Str::of('This is my name')->beforeLast('is'); +$slice = Str::of('This is my name')->beforeLast('is'); - // 'This ' +// 'This ' +``` #### `between` {.collection-method} The `between` method returns the portion of a string between two values: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $converted = Str::of('This is my name')->between('This', 'name'); +$converted = Str::of('This is my name')->between('This', 'name'); - // ' is my ' +// ' is my ' +``` #### `betweenFirst` {.collection-method} The `betweenFirst` method returns the smallest possible portion of a string between two values: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $converted = Str::of('[a] bc [d]')->betweenFirst('[', ']'); +$converted = Str::of('[a] bc [d]')->betweenFirst('[', ']'); - // 'a' +// 'a' +``` #### `camel` {.collection-method} The `camel` method converts the given string to `camelCase`: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $converted = Str::of('foo_bar')->camel(); +$converted = Str::of('foo_bar')->camel(); - // 'fooBar' +// 'fooBar' +``` #### `charAt` {.collection-method} The `charAt` method returns the character at the specified index. If the index is out of bounds, `false` is returned: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $character = Str::of('This is my name.')->charAt(6); +$character = Str::of('This is my name.')->charAt(6); - // 's' +// 's' +``` #### `classBasename` {.collection-method} The `classBasename` method returns the class name of the given class with the class's namespace removed: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $class = Str::of('Foo\Bar\Baz')->classBasename(); +$class = Str::of('Foo\Bar\Baz')->classBasename(); - // 'Baz' +// 'Baz' +``` #### `chopStart` {.collection-method} The `chopStart` method removes the first occurrence of the given value only if the value appears at the start of the string: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $url = Str::of('https://laravel.com')->chopStart('https://'); +$url = Str::of('https://laravel.com')->chopStart('https://'); - // 'laravel.com' +// 'laravel.com' +``` You may also pass an array. If the string starts with any of the values in the array then that value will be removed from string: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $url = Str::of('http://laravel.com')->chopStart(['https://', 'http://']); +$url = Str::of('http://laravel.com')->chopStart(['https://', 'http://']); - // 'laravel.com' +// 'laravel.com' +``` #### `chopEnd` {.collection-method} The `chopEnd` method removes the last occurrence of the given value only if the value appears at the end of the string: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $url = Str::of('https://laravel.com')->chopEnd('.com'); +$url = Str::of('https://laravel.com')->chopEnd('.com'); - // 'https://laravel' +// 'https://laravel' +``` You may also pass an array. If the string ends with any of the values in the array then that value will be removed from string: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $url = Str::of('http://laravel.com')->chopEnd(['.com', '.io']); +$url = Str::of('http://laravel.com')->chopEnd(['.com', '.io']); - // 'http://laravel' +// 'http://laravel' +``` #### `contains` {.collection-method} The `contains` method determines if the given string contains the given value. By default this method is case sensitive: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $contains = Str::of('This is my name')->contains('my'); +$contains = Str::of('This is my name')->contains('my'); - // true +// true +``` You may also pass an array of values to determine if the given string contains any of the values in the array: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $contains = Str::of('This is my name')->contains(['my', 'foo']); +$contains = Str::of('This is my name')->contains(['my', 'foo']); - // true +// true +``` You can disable case sensitivity by setting the `ignoreCase` argument to `true`: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $contains = Str::of('This is my name')->contains('MY', ignoreCase: true); +$contains = Str::of('This is my name')->contains('MY', ignoreCase: true); - // true +// true +``` #### `containsAll` {.collection-method} The `containsAll` method determines if the given string contains all of the values in the given array: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $containsAll = Str::of('This is my name')->containsAll(['my', 'name']); +$containsAll = Str::of('This is my name')->containsAll(['my', 'name']); - // true +// true +``` You can disable case sensitivity by setting the `ignoreCase` argument to `true`: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $containsAll = Str::of('This is my name')->containsAll(['MY', 'NAME'], ignoreCase: true); +$containsAll = Str::of('This is my name')->containsAll(['MY', 'NAME'], ignoreCase: true); - // true +// true +``` #### `deduplicate` {.collection-method} The `deduplicate` method replaces consecutive instances of a character with a single instance of that character in the given string. By default, the method deduplicates spaces: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $result = Str::of('The Laravel Framework')->deduplicate(); +$result = Str::of('The Laravel Framework')->deduplicate(); - // The Laravel Framework +// The Laravel Framework +``` You may specify a different character to deduplicate by passing it in as the second argument to the method: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $result = Str::of('The---Laravel---Framework')->deduplicate('-'); +$result = Str::of('The---Laravel---Framework')->deduplicate('-'); - // The-Laravel-Framework +// The-Laravel-Framework +``` #### `dirname` {.collection-method} The `dirname` method returns the parent directory portion of the given string: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $string = Str::of('/foo/bar/baz')->dirname(); +$string = Str::of('/foo/bar/baz')->dirname(); - // '/foo/bar' +// '/foo/bar' +``` If necessary, you may specify how many directory levels you wish to trim from the string: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $string = Str::of('/foo/bar/baz')->dirname(2); +$string = Str::of('/foo/bar/baz')->dirname(2); - // '/foo' +// '/foo' +``` #### `endsWith` {.collection-method} The `endsWith` method determines if the given string ends with the given value: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $result = Str::of('This is my name')->endsWith('name'); +$result = Str::of('This is my name')->endsWith('name'); - // true +// true +``` You may also pass an array of values to determine if the given string ends with any of the values in the array: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $result = Str::of('This is my name')->endsWith(['name', 'foo']); +$result = Str::of('This is my name')->endsWith(['name', 'foo']); - // true +// true - $result = Str::of('This is my name')->endsWith(['this', 'foo']); +$result = Str::of('This is my name')->endsWith(['this', 'foo']); - // false +// false +``` #### `exactly` {.collection-method} The `exactly` method determines if the given string is an exact match with another string: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $result = Str::of('Laravel')->exactly('Laravel'); +$result = Str::of('Laravel')->exactly('Laravel'); - // true +// true +``` #### `excerpt` {.collection-method} The `excerpt` method extracts an excerpt from the string that matches the first instance of a phrase within that string: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $excerpt = Str::of('This is my name')->excerpt('my', [ - 'radius' => 3 - ]); +$excerpt = Str::of('This is my name')->excerpt('my', [ + 'radius' => 3 +]); - // '...is my na...' +// '...is my na...' +``` The `radius` option, which defaults to `100`, allows you to define the number of characters that should appear on each side of the truncated string. In addition, you may use the `omission` option to change the string that will be prepended and appended to the truncated string: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $excerpt = Str::of('This is my name')->excerpt('name', [ - 'radius' => 3, - 'omission' => '(...) ' - ]); +$excerpt = Str::of('This is my name')->excerpt('name', [ + 'radius' => 3, + 'omission' => '(...) ' +]); - // '(...) my name' +// '(...) my name' +``` #### `explode` {.collection-method} The `explode` method splits the string by the given delimiter and returns a collection containing each section of the split string: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $collection = Str::of('foo bar baz')->explode(' '); +$collection = Str::of('foo bar baz')->explode(' '); - // collect(['foo', 'bar', 'baz']) +// collect(['foo', 'bar', 'baz']) +``` #### `finish` {.collection-method} The `finish` method adds a single instance of the given value to a string if it does not already end with that value: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $adjusted = Str::of('this/string')->finish('/'); +$adjusted = Str::of('this/string')->finish('/'); - // this/string/ +// this/string/ - $adjusted = Str::of('this/string/')->finish('/'); +$adjusted = Str::of('this/string/')->finish('/'); - // this/string/ +// this/string/ +``` #### `headline` {.collection-method} The `headline` method will convert strings delimited by casing, hyphens, or underscores into a space delimited string with each word's first letter capitalized: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $headline = Str::of('taylor_otwell')->headline(); +$headline = Str::of('taylor_otwell')->headline(); - // Taylor Otwell +// Taylor Otwell - $headline = Str::of('EmailNotificationSent')->headline(); +$headline = Str::of('EmailNotificationSent')->headline(); - // Email Notification Sent +// Email Notification Sent +``` #### `inlineMarkdown` {.collection-method} The `inlineMarkdown` method converts GitHub flavored Markdown into inline HTML using [CommonMark](https://commonmark.thephpleague.com/). However, unlike the `markdown` method, it does not wrap all generated HTML in a block-level element: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $html = Str::of('**Laravel**')->inlineMarkdown(); +$html = Str::of('**Laravel**')->inlineMarkdown(); - // Laravel +// Laravel +``` #### Markdown Security By default, Markdown supports raw HTML, which will expose Cross-Site Scripting (XSS) vulnerabilities when used with raw user input. As per the [CommonMark Security documentation](https://commonmark.thephpleague.com/security/), you may use the `html_input` option to either escape or strip raw HTML, and the `allow_unsafe_links` option to specify whether to allow unsafe links. If you need to allow some raw HTML, you should pass your compiled Markdown through an HTML Purifier: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - Str::of('Inject: ')->inlineMarkdown([ - 'html_input' => 'strip', - 'allow_unsafe_links' => false, - ]); +Str::of('Inject: ')->inlineMarkdown([ + 'html_input' => 'strip', + 'allow_unsafe_links' => false, +]); - // Inject: alert("Hello XSS!"); +// Inject: alert("Hello XSS!"); +``` #### `is` {.collection-method} The `is` method determines if a given string matches a given pattern. Asterisks may be used as wildcard values - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $matches = Str::of('foobar')->is('foo*'); +$matches = Str::of('foobar')->is('foo*'); - // true +// true - $matches = Str::of('foobar')->is('baz*'); +$matches = Str::of('foobar')->is('baz*'); - // false +// false +``` #### `isAscii` {.collection-method} The `isAscii` method determines if a given string is an ASCII string: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $result = Str::of('Taylor')->isAscii(); +$result = Str::of('Taylor')->isAscii(); - // true +// true - $result = Str::of('ü')->isAscii(); +$result = Str::of('ü')->isAscii(); - // false +// false +``` #### `isEmpty` {.collection-method} The `isEmpty` method determines if the given string is empty: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $result = Str::of(' ')->trim()->isEmpty(); +$result = Str::of(' ')->trim()->isEmpty(); - // true +// true - $result = Str::of('Laravel')->trim()->isEmpty(); +$result = Str::of('Laravel')->trim()->isEmpty(); - // false +// false +``` #### `isNotEmpty` {.collection-method} The `isNotEmpty` method determines if the given string is not empty: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $result = Str::of(' ')->trim()->isNotEmpty(); +$result = Str::of(' ')->trim()->isNotEmpty(); - // false +// false - $result = Str::of('Laravel')->trim()->isNotEmpty(); +$result = Str::of('Laravel')->trim()->isNotEmpty(); - // true +// true +``` #### `isJson` {.collection-method} The `isJson` method determines if a given string is valid JSON: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $result = Str::of('[1,2,3]')->isJson(); +$result = Str::of('[1,2,3]')->isJson(); - // true +// true - $result = Str::of('{"first": "John", "last": "Doe"}')->isJson(); +$result = Str::of('{"first": "John", "last": "Doe"}')->isJson(); - // true +// true - $result = Str::of('{first: "John", last: "Doe"}')->isJson(); +$result = Str::of('{first: "John", last: "Doe"}')->isJson(); - // false +// false +``` #### `isUlid` {.collection-method} The `isUlid` method determines if a given string is a ULID: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $result = Str::of('01gd6r360bp37zj17nxb55yv40')->isUlid(); +$result = Str::of('01gd6r360bp37zj17nxb55yv40')->isUlid(); - // true +// true - $result = Str::of('Taylor')->isUlid(); +$result = Str::of('Taylor')->isUlid(); - // false +// false +``` #### `isUrl` {.collection-method} The `isUrl` method determines if a given string is a URL: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $result = Str::of('http://example.com')->isUrl(); +$result = Str::of('http://example.com')->isUrl(); - // true +// true - $result = Str::of('Taylor')->isUrl(); +$result = Str::of('Taylor')->isUrl(); - // false +// false +``` The `isUrl` method considers a wide range of protocols as valid. However, you may specify the protocols that should be considered valid by providing them to the `isUrl` method: - $result = Str::of('http://example.com')->isUrl(['http', 'https']); +```php +$result = Str::of('http://example.com')->isUrl(['http', 'https']); +``` #### `isUuid` {.collection-method} The `isUuid` method determines if a given string is a UUID: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $result = Str::of('5ace9ab9-e9cf-4ec6-a19d-5881212a452c')->isUuid(); +$result = Str::of('5ace9ab9-e9cf-4ec6-a19d-5881212a452c')->isUuid(); - // true +// true - $result = Str::of('Taylor')->isUuid(); +$result = Str::of('Taylor')->isUuid(); - // false +// false +``` #### `kebab` {.collection-method} The `kebab` method converts the given string to `kebab-case`: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $converted = Str::of('fooBar')->kebab(); +$converted = Str::of('fooBar')->kebab(); - // foo-bar +// foo-bar +``` #### `lcfirst` {.collection-method} The `lcfirst` method returns the given string with the first character lowercased: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $string = Str::of('Foo Bar')->lcfirst(); +$string = Str::of('Foo Bar')->lcfirst(); - // foo Bar +// foo Bar +``` #### `length` {.collection-method} The `length` method returns the length of the given string: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $length = Str::of('Laravel')->length(); +$length = Str::of('Laravel')->length(); - // 7 +// 7 +``` #### `limit` {.collection-method} The `limit` method truncates the given string to the specified length: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $truncated = Str::of('The quick brown fox jumps over the lazy dog')->limit(20); +$truncated = Str::of('The quick brown fox jumps over the lazy dog')->limit(20); - // The quick brown fox... +// The quick brown fox... +``` You may also pass a second argument to change the string that will be appended to the end of the truncated string: - $truncated = Str::of('The quick brown fox jumps over the lazy dog')->limit(20, ' (...)'); +```php +$truncated = Str::of('The quick brown fox jumps over the lazy dog')->limit(20, ' (...)'); - // The quick brown fox (...) +// The quick brown fox (...) +``` If you would like to preserve complete words when truncating the string, you may utilize the `preserveWords` argument. When this argument is `true`, the string will be truncated to the nearest complete word boundary: - $truncated = Str::of('The quick brown fox')->limit(12, preserveWords: true); +```php +$truncated = Str::of('The quick brown fox')->limit(12, preserveWords: true); - // The quick... +// The quick... +``` #### `lower` {.collection-method} The `lower` method converts the given string to lowercase: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $result = Str::of('LARAVEL')->lower(); +$result = Str::of('LARAVEL')->lower(); - // 'laravel' +// 'laravel' +``` #### `markdown` {.collection-method} The `markdown` method converts GitHub flavored Markdown into HTML: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $html = Str::of('# Laravel')->markdown(); +$html = Str::of('# Laravel')->markdown(); - //

Laravel

+//

Laravel

- $html = Str::of('# Taylor Otwell')->markdown([ - 'html_input' => 'strip', - ]); +$html = Str::of('# Taylor Otwell')->markdown([ + 'html_input' => 'strip', +]); - //

Taylor Otwell

+//

Taylor Otwell

+``` #### Markdown Security By default, Markdown supports raw HTML, which will expose Cross-Site Scripting (XSS) vulnerabilities when used with raw user input. As per the [CommonMark Security documentation](https://commonmark.thephpleague.com/security/), you may use the `html_input` option to either escape or strip raw HTML, and the `allow_unsafe_links` option to specify whether to allow unsafe links. If you need to allow some raw HTML, you should pass your compiled Markdown through an HTML Purifier: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - Str::of('Inject: ')->markdown([ - 'html_input' => 'strip', - 'allow_unsafe_links' => false, - ]); +Str::of('Inject: ')->markdown([ + 'html_input' => 'strip', + 'allow_unsafe_links' => false, +]); - //

Inject: alert("Hello XSS!");

+//

Inject: alert("Hello XSS!");

+``` #### `mask` {.collection-method} The `mask` method masks a portion of a string with a repeated character, and may be used to obfuscate segments of strings such as email addresses and phone numbers: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $string = Str::of('taylor@example.com')->mask('*', 3); +$string = Str::of('taylor@example.com')->mask('*', 3); - // tay*************** +// tay*************** +``` If needed, you may provide negative numbers as the third or fourth argument to the `mask` method, which will instruct the method to begin masking at the given distance from the end of the string: - $string = Str::of('taylor@example.com')->mask('*', -15, 3); +```php +$string = Str::of('taylor@example.com')->mask('*', -15, 3); - // tay***@example.com +// tay***@example.com - $string = Str::of('taylor@example.com')->mask('*', 4, -4); +$string = Str::of('taylor@example.com')->mask('*', 4, -4); - // tayl**********.com +// tayl**********.com +``` #### `match` {.collection-method} The `match` method will return the portion of a string that matches a given regular expression pattern: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $result = Str::of('foo bar')->match('/bar/'); +$result = Str::of('foo bar')->match('/bar/'); - // 'bar' +// 'bar' - $result = Str::of('foo bar')->match('/foo (.*)/'); +$result = Str::of('foo bar')->match('/foo (.*)/'); - // 'bar' +// 'bar' +``` #### `matchAll` {.collection-method} The `matchAll` method will return a collection containing the portions of a string that match a given regular expression pattern: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $result = Str::of('bar foo bar')->matchAll('/bar/'); +$result = Str::of('bar foo bar')->matchAll('/bar/'); - // collect(['bar', 'bar']) +// collect(['bar', 'bar']) +``` If you specify a matching group within the expression, Laravel will return a collection of the first matching group's matches: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $result = Str::of('bar fun bar fly')->matchAll('/f(\w*)/'); +$result = Str::of('bar fun bar fly')->matchAll('/f(\w*)/'); - // collect(['un', 'ly']); +// collect(['un', 'ly']); +``` If no matches are found, an empty collection will be returned. @@ -2243,154 +2605,176 @@ If no matches are found, an empty collection will be returned. The `isMatch` method will return `true` if the string matches a given regular expression: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $result = Str::of('foo bar')->isMatch('/foo (.*)/'); +$result = Str::of('foo bar')->isMatch('/foo (.*)/'); - // true +// true - $result = Str::of('laravel')->isMatch('/foo (.*)/'); +$result = Str::of('laravel')->isMatch('/foo (.*)/'); - // false +// false +``` #### `newLine` {.collection-method} The `newLine` method appends an "end of line" character to a string: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $padded = Str::of('Laravel')->newLine()->append('Framework'); +$padded = Str::of('Laravel')->newLine()->append('Framework'); - // 'Laravel - // Framework' +// 'Laravel +// Framework' +``` #### `padBoth` {.collection-method} The `padBoth` method wraps PHP's `str_pad` function, padding both sides of a string with another string until the final string reaches the desired length: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $padded = Str::of('James')->padBoth(10, '_'); +$padded = Str::of('James')->padBoth(10, '_'); - // '__James___' +// '__James___' - $padded = Str::of('James')->padBoth(10); +$padded = Str::of('James')->padBoth(10); - // ' James ' +// ' James ' +``` #### `padLeft` {.collection-method} The `padLeft` method wraps PHP's `str_pad` function, padding the left side of a string with another string until the final string reaches the desired length: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $padded = Str::of('James')->padLeft(10, '-='); +$padded = Str::of('James')->padLeft(10, '-='); - // '-=-=-James' +// '-=-=-James' - $padded = Str::of('James')->padLeft(10); +$padded = Str::of('James')->padLeft(10); - // ' James' +// ' James' +``` #### `padRight` {.collection-method} The `padRight` method wraps PHP's `str_pad` function, padding the right side of a string with another string until the final string reaches the desired length: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $padded = Str::of('James')->padRight(10, '-'); +$padded = Str::of('James')->padRight(10, '-'); - // 'James-----' +// 'James-----' - $padded = Str::of('James')->padRight(10); +$padded = Str::of('James')->padRight(10); - // 'James ' +// 'James ' +``` #### `pipe` {.collection-method} The `pipe` method allows you to transform the string by passing its current value to the given callable: - use Illuminate\Support\Str; - use Illuminate\Support\Stringable; +```php +use Illuminate\Support\Str; +use Illuminate\Support\Stringable; - $hash = Str::of('Laravel')->pipe('md5')->prepend('Checksum: '); +$hash = Str::of('Laravel')->pipe('md5')->prepend('Checksum: '); - // 'Checksum: a5c95b86291ea299fcbe64458ed12702' +// 'Checksum: a5c95b86291ea299fcbe64458ed12702' - $closure = Str::of('foo')->pipe(function (Stringable $str) { - return 'bar'; - }); +$closure = Str::of('foo')->pipe(function (Stringable $str) { + return 'bar'; +}); - // 'bar' +// 'bar' +``` #### `plural` {.collection-method} The `plural` method converts a singular word string to its plural form. This function supports [any of the languages support by Laravel's pluralizer](/docs/{{version}}/localization#pluralization-language): - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $plural = Str::of('car')->plural(); +$plural = Str::of('car')->plural(); - // cars +// cars - $plural = Str::of('child')->plural(); +$plural = Str::of('child')->plural(); - // children +// children +``` You may provide an integer as a second argument to the function to retrieve the singular or plural form of the string: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $plural = Str::of('child')->plural(2); +$plural = Str::of('child')->plural(2); - // children +// children - $plural = Str::of('child')->plural(1); +$plural = Str::of('child')->plural(1); - // child +// child +``` #### `position` {.collection-method} The `position` method returns the position of the first occurrence of a substring in a string. If the substring does not exist within the string, `false` is returned: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $position = Str::of('Hello, World!')->position('Hello'); +$position = Str::of('Hello, World!')->position('Hello'); - // 0 +// 0 - $position = Str::of('Hello, World!')->position('W'); +$position = Str::of('Hello, World!')->position('W'); - // 7 +// 7 +``` #### `prepend` {.collection-method} The `prepend` method prepends the given values onto the string: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $string = Str::of('Framework')->prepend('Laravel '); +$string = Str::of('Framework')->prepend('Laravel '); - // Laravel Framework +// Laravel Framework +``` #### `remove` {.collection-method} The `remove` method removes the given value or array of values from the string: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $string = Str::of('Arkansas is quite beautiful!')->remove('quite'); +$string = Str::of('Arkansas is quite beautiful!')->remove('quite'); - // Arkansas is beautiful! +// Arkansas is beautiful! +``` You may also pass `false` as a second parameter to ignore case when removing strings. @@ -2412,459 +2796,533 @@ $repeated = Str::of('a')->repeat(5); The `replace` method replaces a given string within the string: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $replaced = Str::of('Laravel 6.x')->replace('6.x', '7.x'); +$replaced = Str::of('Laravel 6.x')->replace('6.x', '7.x'); - // Laravel 7.x +// Laravel 7.x +``` The `replace` method also accepts a `caseSensitive` argument. By default, the `replace` method is case sensitive: - $replaced = Str::of('macOS 13.x')->replace( - 'macOS', 'iOS', caseSensitive: false - ); +```php +$replaced = Str::of('macOS 13.x')->replace( + 'macOS', 'iOS', caseSensitive: false +); +``` #### `replaceArray` {.collection-method} The `replaceArray` method replaces a given value in the string sequentially using an array: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $string = 'The event will take place between ? and ?'; +$string = 'The event will take place between ? and ?'; - $replaced = Str::of($string)->replaceArray('?', ['8:30', '9:00']); +$replaced = Str::of($string)->replaceArray('?', ['8:30', '9:00']); - // The event will take place between 8:30 and 9:00 +// The event will take place between 8:30 and 9:00 +``` #### `replaceFirst` {.collection-method} The `replaceFirst` method replaces the first occurrence of a given value in a string: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $replaced = Str::of('the quick brown fox jumps over the lazy dog')->replaceFirst('the', 'a'); +$replaced = Str::of('the quick brown fox jumps over the lazy dog')->replaceFirst('the', 'a'); - // a quick brown fox jumps over the lazy dog +// a quick brown fox jumps over the lazy dog +``` #### `replaceLast` {.collection-method} The `replaceLast` method replaces the last occurrence of a given value in a string: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $replaced = Str::of('the quick brown fox jumps over the lazy dog')->replaceLast('the', 'a'); +$replaced = Str::of('the quick brown fox jumps over the lazy dog')->replaceLast('the', 'a'); - // the quick brown fox jumps over a lazy dog +// the quick brown fox jumps over a lazy dog +``` #### `replaceMatches` {.collection-method} The `replaceMatches` method replaces all portions of a string matching a pattern with the given replacement string: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $replaced = Str::of('(+1) 501-555-1000')->replaceMatches('/[^A-Za-z0-9]++/', '') +$replaced = Str::of('(+1) 501-555-1000')->replaceMatches('/[^A-Za-z0-9]++/', '') - // '15015551000' +// '15015551000' +``` The `replaceMatches` method also accepts a closure that will be invoked with each portion of the string matching the given pattern, allowing you to perform the replacement logic within the closure and return the replaced value: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $replaced = Str::of('123')->replaceMatches('/\d/', function (array $matches) { - return '['.$matches[0].']'; - }); +$replaced = Str::of('123')->replaceMatches('/\d/', function (array $matches) { + return '['.$matches[0].']'; +}); - // '[1][2][3]' +// '[1][2][3]' +``` #### `replaceStart` {.collection-method} The `replaceStart` method replaces the first occurrence of the given value only if the value appears at the start of the string: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $replaced = Str::of('Hello World')->replaceStart('Hello', 'Laravel'); +$replaced = Str::of('Hello World')->replaceStart('Hello', 'Laravel'); - // Laravel World +// Laravel World - $replaced = Str::of('Hello World')->replaceStart('World', 'Laravel'); +$replaced = Str::of('Hello World')->replaceStart('World', 'Laravel'); - // Hello World +// Hello World +``` #### `replaceEnd` {.collection-method} The `replaceEnd` method replaces the last occurrence of the given value only if the value appears at the end of the string: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $replaced = Str::of('Hello World')->replaceEnd('World', 'Laravel'); +$replaced = Str::of('Hello World')->replaceEnd('World', 'Laravel'); - // Hello Laravel +// Hello Laravel - $replaced = Str::of('Hello World')->replaceEnd('Hello', 'Laravel'); +$replaced = Str::of('Hello World')->replaceEnd('Hello', 'Laravel'); - // Hello World +// Hello World +``` #### `scan` {.collection-method} The `scan` method parses input from a string into a collection according to a format supported by the [`sscanf` PHP function](https://www.php.net/manual/en/function.sscanf.php): - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $collection = Str::of('filename.jpg')->scan('%[^.].%s'); +$collection = Str::of('filename.jpg')->scan('%[^.].%s'); - // collect(['filename', 'jpg']) +// collect(['filename', 'jpg']) +``` #### `singular` {.collection-method} The `singular` method converts a string to its singular form. This function supports [any of the languages support by Laravel's pluralizer](/docs/{{version}}/localization#pluralization-language): - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $singular = Str::of('cars')->singular(); +$singular = Str::of('cars')->singular(); - // car +// car - $singular = Str::of('children')->singular(); +$singular = Str::of('children')->singular(); - // child +// child +``` #### `slug` {.collection-method} The `slug` method generates a URL friendly "slug" from the given string: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $slug = Str::of('Laravel Framework')->slug('-'); +$slug = Str::of('Laravel Framework')->slug('-'); - // laravel-framework +// laravel-framework +``` #### `snake` {.collection-method} The `snake` method converts the given string to `snake_case`: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $converted = Str::of('fooBar')->snake(); +$converted = Str::of('fooBar')->snake(); - // foo_bar +// foo_bar +``` #### `split` {.collection-method} The `split` method splits a string into a collection using a regular expression: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $segments = Str::of('one, two, three')->split('/[\s,]+/'); +$segments = Str::of('one, two, three')->split('/[\s,]+/'); - // collect(["one", "two", "three"]) +// collect(["one", "two", "three"]) +``` #### `squish` {.collection-method} The `squish` method removes all extraneous white space from a string, including extraneous white space between words: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $string = Str::of(' laravel framework ')->squish(); +$string = Str::of(' laravel framework ')->squish(); - // laravel framework +// laravel framework +``` #### `start` {.collection-method} The `start` method adds a single instance of the given value to a string if it does not already start with that value: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $adjusted = Str::of('this/string')->start('/'); +$adjusted = Str::of('this/string')->start('/'); - // /this/string +// /this/string - $adjusted = Str::of('/this/string')->start('/'); +$adjusted = Str::of('/this/string')->start('/'); - // /this/string +// /this/string +``` #### `startsWith` {.collection-method} The `startsWith` method determines if the given string begins with the given value: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $result = Str::of('This is my name')->startsWith('This'); +$result = Str::of('This is my name')->startsWith('This'); - // true +// true +``` #### `stripTags` {.collection-method} The `stripTags` method removes all HTML and PHP tags from a string: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $result = Str::of('Taylor Otwell')->stripTags(); +$result = Str::of('Taylor Otwell')->stripTags(); - // Taylor Otwell +// Taylor Otwell - $result = Str::of('Taylor Otwell')->stripTags(''); +$result = Str::of('Taylor Otwell')->stripTags(''); - // Taylor Otwell +// Taylor Otwell +``` #### `studly` {.collection-method} The `studly` method converts the given string to `StudlyCase`: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $converted = Str::of('foo_bar')->studly(); +$converted = Str::of('foo_bar')->studly(); - // FooBar +// FooBar +``` #### `substr` {.collection-method} The `substr` method returns the portion of the string specified by the given start and length parameters: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $string = Str::of('Laravel Framework')->substr(8); +$string = Str::of('Laravel Framework')->substr(8); - // Framework +// Framework - $string = Str::of('Laravel Framework')->substr(8, 5); +$string = Str::of('Laravel Framework')->substr(8, 5); - // Frame +// Frame +``` #### `substrReplace` {.collection-method} The `substrReplace` method replaces text within a portion of a string, starting at the position specified by the second argument and replacing the number of characters specified by the third argument. Passing `0` to the method's third argument will insert the string at the specified position without replacing any of the existing characters in the string: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $string = Str::of('1300')->substrReplace(':', 2); +$string = Str::of('1300')->substrReplace(':', 2); - // 13: +// 13: - $string = Str::of('The Framework')->substrReplace(' Laravel', 3, 0); +$string = Str::of('The Framework')->substrReplace(' Laravel', 3, 0); - // The Laravel Framework +// The Laravel Framework +``` #### `swap` {.collection-method} The `swap` method replaces multiple values in the string using PHP's `strtr` function: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $string = Str::of('Tacos are great!') - ->swap([ - 'Tacos' => 'Burritos', - 'great' => 'fantastic', - ]); +$string = Str::of('Tacos are great!') + ->swap([ + 'Tacos' => 'Burritos', + 'great' => 'fantastic', + ]); - // Burritos are fantastic! +// Burritos are fantastic! +``` #### `take` {.collection-method} The `take` method returns a specified number of characters from the beginning of the string: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $taken = Str::of('Build something amazing!')->take(5); +$taken = Str::of('Build something amazing!')->take(5); - // Build +// Build +``` #### `tap` {.collection-method} The `tap` method passes the string to the given closure, allowing you to examine and interact with the string while not affecting the string itself. The original string is returned by the `tap` method regardless of what is returned by the closure: - use Illuminate\Support\Str; - use Illuminate\Support\Stringable; +```php +use Illuminate\Support\Str; +use Illuminate\Support\Stringable; - $string = Str::of('Laravel') - ->append(' Framework') - ->tap(function (Stringable $string) { - dump('String after append: '.$string); - }) - ->upper(); +$string = Str::of('Laravel') + ->append(' Framework') + ->tap(function (Stringable $string) { + dump('String after append: '.$string); + }) + ->upper(); - // LARAVEL FRAMEWORK +// LARAVEL FRAMEWORK +``` #### `test` {.collection-method} The `test` method determines if a string matches the given regular expression pattern: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $result = Str::of('Laravel Framework')->test('/Laravel/'); +$result = Str::of('Laravel Framework')->test('/Laravel/'); - // true +// true +``` #### `title` {.collection-method} The `title` method converts the given string to `Title Case`: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $converted = Str::of('a nice title uses the correct case')->title(); +$converted = Str::of('a nice title uses the correct case')->title(); - // A Nice Title Uses The Correct Case +// A Nice Title Uses The Correct Case +``` #### `toBase64` {.collection-method} The `toBase64` method converts the given string to Base64: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $base64 = Str::of('Laravel')->toBase64(); +$base64 = Str::of('Laravel')->toBase64(); - // TGFyYXZlbA== +// TGFyYXZlbA== +``` #### `toHtmlString` {.collection-method} The `toHtmlString` method converts the given string to an instance of `Illuminate\Support\HtmlString`, which will not be escaped when rendered in Blade templates: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $htmlString = Str::of('Nuno Maduro')->toHtmlString(); +$htmlString = Str::of('Nuno Maduro')->toHtmlString(); +``` #### `transliterate` {.collection-method} The `transliterate` method will attempt to convert a given string into its closest ASCII representation: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $email = Str::of('ⓣⓔⓢⓣ@ⓛⓐⓡⓐⓥⓔⓛ.ⓒⓞⓜ')->transliterate() +$email = Str::of('ⓣⓔⓢⓣ@ⓛⓐⓡⓐⓥⓔⓛ.ⓒⓞⓜ')->transliterate() - // 'test@laravel.com' +// 'test@laravel.com' +``` #### `trim` {.collection-method} The `trim` method trims the given string. Unlike PHP's native `trim` function, Laravel's `trim` method also removes unicode whitespace characters: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $string = Str::of(' Laravel ')->trim(); +$string = Str::of(' Laravel ')->trim(); - // 'Laravel' +// 'Laravel' - $string = Str::of('/Laravel/')->trim('/'); +$string = Str::of('/Laravel/')->trim('/'); - // 'Laravel' +// 'Laravel' +``` #### `ltrim` {.collection-method} The `ltrim` method trims the left side of the string. Unlike PHP's native `ltrim` function, Laravel's `ltrim` method also removes unicode whitespace characters: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $string = Str::of(' Laravel ')->ltrim(); +$string = Str::of(' Laravel ')->ltrim(); - // 'Laravel ' +// 'Laravel ' - $string = Str::of('/Laravel/')->ltrim('/'); +$string = Str::of('/Laravel/')->ltrim('/'); - // 'Laravel/' +// 'Laravel/' +``` #### `rtrim` {.collection-method} The `rtrim` method trims the right side of the given string. Unlike PHP's native `rtrim` function, Laravel's `rtrim` method also removes unicode whitespace characters: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $string = Str::of(' Laravel ')->rtrim(); +$string = Str::of(' Laravel ')->rtrim(); - // ' Laravel' +// ' Laravel' - $string = Str::of('/Laravel/')->rtrim('/'); +$string = Str::of('/Laravel/')->rtrim('/'); - // '/Laravel' +// '/Laravel' +``` #### `ucfirst` {.collection-method} The `ucfirst` method returns the given string with the first character capitalized: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $string = Str::of('foo bar')->ucfirst(); +$string = Str::of('foo bar')->ucfirst(); - // Foo bar +// Foo bar +``` #### `ucsplit` {.collection-method} The `ucsplit` method splits the given string into a collection by uppercase characters: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $string = Str::of('Foo Bar')->ucsplit(); +$string = Str::of('Foo Bar')->ucsplit(); - // collect(['Foo', 'Bar']) +// collect(['Foo', 'Bar']) +``` #### `unwrap` {.collection-method} The `unwrap` method removes the specified strings from the beginning and end of a given string: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - Str::of('-Laravel-')->unwrap('-'); +Str::of('-Laravel-')->unwrap('-'); - // Laravel +// Laravel - Str::of('{framework: "Laravel"}')->unwrap('{', '}'); +Str::of('{framework: "Laravel"}')->unwrap('{', '}'); - // framework: "Laravel" +// framework: "Laravel" +``` #### `upper` {.collection-method} The `upper` method converts the given string to uppercase: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $adjusted = Str::of('laravel')->upper(); +$adjusted = Str::of('laravel')->upper(); - // LARAVEL +// LARAVEL +``` #### `when` {.collection-method} The `when` method invokes the given closure if a given condition is `true`. The closure will receive the fluent string instance: - use Illuminate\Support\Str; - use Illuminate\Support\Stringable; +```php +use Illuminate\Support\Str; +use Illuminate\Support\Stringable; - $string = Str::of('Taylor') - ->when(true, function (Stringable $string) { - return $string->append(' Otwell'); - }); +$string = Str::of('Taylor') + ->when(true, function (Stringable $string) { + return $string->append(' Otwell'); + }); - // 'Taylor Otwell' +// 'Taylor Otwell' +``` If necessary, you may pass another closure as the third parameter to the `when` method. This closure will execute if the condition parameter evaluates to `false`. @@ -2873,44 +3331,50 @@ If necessary, you may pass another closure as the third parameter to the `when` The `whenContains` method invokes the given closure if the string contains the given value. The closure will receive the fluent string instance: - use Illuminate\Support\Str; - use Illuminate\Support\Stringable; +```php +use Illuminate\Support\Str; +use Illuminate\Support\Stringable; - $string = Str::of('tony stark') - ->whenContains('tony', function (Stringable $string) { - return $string->title(); - }); +$string = Str::of('tony stark') + ->whenContains('tony', function (Stringable $string) { + return $string->title(); + }); - // 'Tony Stark' +// 'Tony Stark' +``` If necessary, you may pass another closure as the third parameter to the `when` method. This closure will execute if the string does not contain the given value. You may also pass an array of values to determine if the given string contains any of the values in the array: - use Illuminate\Support\Str; - use Illuminate\Support\Stringable; +```php +use Illuminate\Support\Str; +use Illuminate\Support\Stringable; - $string = Str::of('tony stark') - ->whenContains(['tony', 'hulk'], function (Stringable $string) { - return $string->title(); - }); +$string = Str::of('tony stark') + ->whenContains(['tony', 'hulk'], function (Stringable $string) { + return $string->title(); + }); - // Tony Stark +// Tony Stark +``` #### `whenContainsAll` {.collection-method} The `whenContainsAll` method invokes the given closure if the string contains all of the given sub-strings. The closure will receive the fluent string instance: - use Illuminate\Support\Str; - use Illuminate\Support\Stringable; +```php +use Illuminate\Support\Str; +use Illuminate\Support\Stringable; - $string = Str::of('tony stark') - ->whenContainsAll(['tony', 'stark'], function (Stringable $string) { - return $string->title(); - }); +$string = Str::of('tony stark') + ->whenContainsAll(['tony', 'stark'], function (Stringable $string) { + return $string->title(); + }); - // 'Tony Stark' +// 'Tony Stark' +``` If necessary, you may pass another closure as the third parameter to the `when` method. This closure will execute if the condition parameter evaluates to `false`. @@ -2919,153 +3383,175 @@ If necessary, you may pass another closure as the third parameter to the `when` The `whenEmpty` method invokes the given closure if the string is empty. If the closure returns a value, that value will also be returned by the `whenEmpty` method. If the closure does not return a value, the fluent string instance will be returned: - use Illuminate\Support\Str; - use Illuminate\Support\Stringable; +```php +use Illuminate\Support\Str; +use Illuminate\Support\Stringable; - $string = Str::of(' ')->whenEmpty(function (Stringable $string) { - return $string->trim()->prepend('Laravel'); - }); +$string = Str::of(' ')->whenEmpty(function (Stringable $string) { + return $string->trim()->prepend('Laravel'); +}); - // 'Laravel' +// 'Laravel' +``` #### `whenNotEmpty` {.collection-method} The `whenNotEmpty` method invokes the given closure if the string is not empty. If the closure returns a value, that value will also be returned by the `whenNotEmpty` method. If the closure does not return a value, the fluent string instance will be returned: - use Illuminate\Support\Str; - use Illuminate\Support\Stringable; +```php +use Illuminate\Support\Str; +use Illuminate\Support\Stringable; - $string = Str::of('Framework')->whenNotEmpty(function (Stringable $string) { - return $string->prepend('Laravel '); - }); +$string = Str::of('Framework')->whenNotEmpty(function (Stringable $string) { + return $string->prepend('Laravel '); +}); - // 'Laravel Framework' +// 'Laravel Framework' +``` #### `whenStartsWith` {.collection-method} The `whenStartsWith` method invokes the given closure if the string starts with the given sub-string. The closure will receive the fluent string instance: - use Illuminate\Support\Str; - use Illuminate\Support\Stringable; +```php +use Illuminate\Support\Str; +use Illuminate\Support\Stringable; - $string = Str::of('disney world')->whenStartsWith('disney', function (Stringable $string) { - return $string->title(); - }); +$string = Str::of('disney world')->whenStartsWith('disney', function (Stringable $string) { + return $string->title(); +}); - // 'Disney World' +// 'Disney World' +``` #### `whenEndsWith` {.collection-method} The `whenEndsWith` method invokes the given closure if the string ends with the given sub-string. The closure will receive the fluent string instance: - use Illuminate\Support\Str; - use Illuminate\Support\Stringable; +```php +use Illuminate\Support\Str; +use Illuminate\Support\Stringable; - $string = Str::of('disney world')->whenEndsWith('world', function (Stringable $string) { - return $string->title(); - }); +$string = Str::of('disney world')->whenEndsWith('world', function (Stringable $string) { + return $string->title(); +}); - // 'Disney World' +// 'Disney World' +``` #### `whenExactly` {.collection-method} The `whenExactly` method invokes the given closure if the string exactly matches the given string. The closure will receive the fluent string instance: - use Illuminate\Support\Str; - use Illuminate\Support\Stringable; +```php +use Illuminate\Support\Str; +use Illuminate\Support\Stringable; - $string = Str::of('laravel')->whenExactly('laravel', function (Stringable $string) { - return $string->title(); - }); +$string = Str::of('laravel')->whenExactly('laravel', function (Stringable $string) { + return $string->title(); +}); - // 'Laravel' +// 'Laravel' +``` #### `whenNotExactly` {.collection-method} The `whenNotExactly` method invokes the given closure if the string does not exactly match the given string. The closure will receive the fluent string instance: - use Illuminate\Support\Str; - use Illuminate\Support\Stringable; +```php +use Illuminate\Support\Str; +use Illuminate\Support\Stringable; - $string = Str::of('framework')->whenNotExactly('laravel', function (Stringable $string) { - return $string->title(); - }); +$string = Str::of('framework')->whenNotExactly('laravel', function (Stringable $string) { + return $string->title(); +}); - // 'Framework' +// 'Framework' +``` #### `whenIs` {.collection-method} The `whenIs` method invokes the given closure if the string matches a given pattern. Asterisks may be used as wildcard values. The closure will receive the fluent string instance: - use Illuminate\Support\Str; - use Illuminate\Support\Stringable; +```php +use Illuminate\Support\Str; +use Illuminate\Support\Stringable; - $string = Str::of('foo/bar')->whenIs('foo/*', function (Stringable $string) { - return $string->append('/baz'); - }); +$string = Str::of('foo/bar')->whenIs('foo/*', function (Stringable $string) { + return $string->append('/baz'); +}); - // 'foo/bar/baz' +// 'foo/bar/baz' +``` #### `whenIsAscii` {.collection-method} The `whenIsAscii` method invokes the given closure if the string is 7 bit ASCII. The closure will receive the fluent string instance: - use Illuminate\Support\Str; - use Illuminate\Support\Stringable; +```php +use Illuminate\Support\Str; +use Illuminate\Support\Stringable; - $string = Str::of('laravel')->whenIsAscii(function (Stringable $string) { - return $string->title(); - }); +$string = Str::of('laravel')->whenIsAscii(function (Stringable $string) { + return $string->title(); +}); - // 'Laravel' +// 'Laravel' +``` #### `whenIsUlid` {.collection-method} The `whenIsUlid` method invokes the given closure if the string is a valid ULID. The closure will receive the fluent string instance: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $string = Str::of('01gd6r360bp37zj17nxb55yv40')->whenIsUlid(function (Stringable $string) { - return $string->substr(0, 8); - }); +$string = Str::of('01gd6r360bp37zj17nxb55yv40')->whenIsUlid(function (Stringable $string) { + return $string->substr(0, 8); +}); - // '01gd6r36' +// '01gd6r36' +``` #### `whenIsUuid` {.collection-method} The `whenIsUuid` method invokes the given closure if the string is a valid UUID. The closure will receive the fluent string instance: - use Illuminate\Support\Str; - use Illuminate\Support\Stringable; +```php +use Illuminate\Support\Str; +use Illuminate\Support\Stringable; - $string = Str::of('a0a2a2d2-0b87-4a18-83f2-2529882be2de')->whenIsUuid(function (Stringable $string) { - return $string->substr(0, 8); - }); +$string = Str::of('a0a2a2d2-0b87-4a18-83f2-2529882be2de')->whenIsUuid(function (Stringable $string) { + return $string->substr(0, 8); +}); - // 'a0a2a2d2' +// 'a0a2a2d2' +``` #### `whenTest` {.collection-method} The `whenTest` method invokes the given closure if the string matches the given regular expression. The closure will receive the fluent string instance: - use Illuminate\Support\Str; - use Illuminate\Support\Stringable; +```php +use Illuminate\Support\Str; +use Illuminate\Support\Stringable; - $string = Str::of('laravel framework')->whenTest('/laravel/', function (Stringable $string) { - return $string->title(); - }); +$string = Str::of('laravel framework')->whenTest('/laravel/', function (Stringable $string) { + return $string->title(); +}); - // 'Laravel Framework' +// 'Laravel Framework' +``` #### `wordCount` {.collection-method} @@ -3083,23 +3569,27 @@ Str::of('Hello, world!')->wordCount(); // 2 The `words` method limits the number of words in a string. If necessary, you may specify an additional string that will be appended to the truncated string: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - $string = Str::of('Perfectly balanced, as all things should be.')->words(3, ' >>>'); +$string = Str::of('Perfectly balanced, as all things should be.')->words(3, ' >>>'); - // Perfectly balanced, as >>> +// Perfectly balanced, as >>> +``` #### `wrap` {.collection-method} The `wrap` method wraps the given string with an additional string or pair of strings: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - Str::of('Laravel')->wrap('"'); +Str::of('Laravel')->wrap('"'); - // "Laravel" +// "Laravel" - Str::is('is')->wrap(before: 'This ', after: ' Laravel!'); +Str::is('is')->wrap(before: 'This ', after: ' Laravel!'); - // This is Laravel! +// This is Laravel! +``` diff --git a/telescope.md b/telescope.md index b008e5a1d48..4365ffcba65 100644 --- a/telescope.md +++ b/telescope.md @@ -73,16 +73,18 @@ php artisan migrate After running `telescope:install`, you should remove the `TelescopeServiceProvider` service provider registration from your application's `bootstrap/providers.php` configuration file. Instead, manually register Telescope's service providers in the `register` method of your `App\Providers\AppServiceProvider` class. We will ensure the current environment is `local` before registering the providers: - /** - * Register any application services. - */ - public function register(): void - { - if ($this->app->environment('local') && class_exists(\Laravel\Telescope\TelescopeServiceProvider::class)) { - $this->app->register(\Laravel\Telescope\TelescopeServiceProvider::class); - $this->app->register(TelescopeServiceProvider::class); - } +```php +/** + * Register any application services. + */ +public function register(): void +{ + if ($this->app->environment('local') && class_exists(\Laravel\Telescope\TelescopeServiceProvider::class)) { + $this->app->register(\Laravel\Telescope\TelescopeServiceProvider::class); + $this->app->register(TelescopeServiceProvider::class); } +} +``` Finally, you should also prevent the Telescope package from being [auto-discovered](/docs/{{version}}/packages#package-discovery) by adding the following to your `composer.json` file: @@ -103,43 +105,51 @@ After publishing Telescope's assets, its primary configuration file will be loca If desired, you may disable Telescope's data collection entirely using the `enabled` configuration option: - 'enabled' => env('TELESCOPE_ENABLED', true), +```php +'enabled' => env('TELESCOPE_ENABLED', true), +``` ### Data Pruning Without pruning, the `telescope_entries` table can accumulate records very quickly. To mitigate this, you should [schedule](/docs/{{version}}/scheduling) the `telescope:prune` Artisan command to run daily: - use Illuminate\Support\Facades\Schedule; +```php +use Illuminate\Support\Facades\Schedule; - Schedule::command('telescope:prune')->daily(); +Schedule::command('telescope:prune')->daily(); +``` By default, all entries older than 24 hours will be pruned. You may use the `hours` option when calling the command to determine how long to retain Telescope data. For example, the following command will delete all records created over 48 hours ago: - use Illuminate\Support\Facades\Schedule; +```php +use Illuminate\Support\Facades\Schedule; - Schedule::command('telescope:prune --hours=48')->daily(); +Schedule::command('telescope:prune --hours=48')->daily(); +``` ### Dashboard Authorization The Telescope dashboard may be accessed via the `/telescope` route. By default, you will only be able to access this dashboard in the `local` environment. Within your `app/Providers/TelescopeServiceProvider.php` file, there is an [authorization gate](/docs/{{version}}/authorization#gates) definition. This authorization gate controls access to Telescope in **non-local** environments. You are free to modify this gate as needed to restrict access to your Telescope installation: - use App\Models\User; - - /** - * Register the Telescope gate. - * - * This gate determines who can access Telescope in non-local environments. - */ - protected function gate(): void - { - Gate::define('viewTelescope', function (User $user) { - return in_array($user->email, [ - 'taylor@laravel.com', - ]); - }); - } +```php +use App\Models\User; + +/** + * Register the Telescope gate. + * + * This gate determines who can access Telescope in non-local environments. + */ +protected function gate(): void +{ + Gate::define('viewTelescope', function (User $user) { + return in_array($user->email, [ + 'taylor@laravel.com', + ]); + }); +} +``` > [!WARNING] > You should ensure you change your `APP_ENV` environment variable to `production` in your production environment. Otherwise, your Telescope installation will be publicly available. @@ -175,102 +185,112 @@ To keep the assets up-to-date and avoid issues in future updates, you may add th You may filter the data that is recorded by Telescope via the `filter` closure that is defined in your `App\Providers\TelescopeServiceProvider` class. By default, this closure records all data in the `local` environment and exceptions, failed jobs, scheduled tasks, and data with monitored tags in all other environments: - use Laravel\Telescope\IncomingEntry; - use Laravel\Telescope\Telescope; +```php +use Laravel\Telescope\IncomingEntry; +use Laravel\Telescope\Telescope; - /** - * Register any application services. - */ - public function register(): void - { - $this->hideSensitiveRequestDetails(); +/** + * Register any application services. + */ +public function register(): void +{ + $this->hideSensitiveRequestDetails(); - Telescope::filter(function (IncomingEntry $entry) { - if ($this->app->environment('local')) { - return true; - } + Telescope::filter(function (IncomingEntry $entry) { + if ($this->app->environment('local')) { + return true; + } - return $entry->isReportableException() || - $entry->isFailedJob() || - $entry->isScheduledTask() || - $entry->isSlowQuery() || - $entry->hasMonitoredTag(); - }); - } + return $entry->isReportableException() || + $entry->isFailedJob() || + $entry->isScheduledTask() || + $entry->isSlowQuery() || + $entry->hasMonitoredTag(); + }); +} +``` ### Batches While the `filter` closure filters data for individual entries, you may use the `filterBatch` method to register a closure that filters all data for a given request or console command. If the closure returns `true`, all of the entries are recorded by Telescope: - use Illuminate\Support\Collection; - use Laravel\Telescope\IncomingEntry; - use Laravel\Telescope\Telescope; - - /** - * Register any application services. - */ - public function register(): void - { - $this->hideSensitiveRequestDetails(); - - Telescope::filterBatch(function (Collection $entries) { - if ($this->app->environment('local')) { - return true; - } - - return $entries->contains(function (IncomingEntry $entry) { - return $entry->isReportableException() || - $entry->isFailedJob() || - $entry->isScheduledTask() || - $entry->isSlowQuery() || - $entry->hasMonitoredTag(); - }); - }); - } +```php +use Illuminate\Support\Collection; +use Laravel\Telescope\IncomingEntry; +use Laravel\Telescope\Telescope; + +/** + * Register any application services. + */ +public function register(): void +{ + $this->hideSensitiveRequestDetails(); + + Telescope::filterBatch(function (Collection $entries) { + if ($this->app->environment('local')) { + return true; + } + + return $entries->contains(function (IncomingEntry $entry) { + return $entry->isReportableException() || + $entry->isFailedJob() || + $entry->isScheduledTask() || + $entry->isSlowQuery() || + $entry->hasMonitoredTag(); + }); + }); +} +``` ## Tagging Telescope allows you to search entries by "tag". Often, tags are Eloquent model class names or authenticated user IDs which Telescope automatically adds to entries. Occasionally, you may want to attach your own custom tags to entries. To accomplish this, you may use the `Telescope::tag` method. The `tag` method accepts a closure which should return an array of tags. The tags returned by the closure will be merged with any tags Telescope would automatically attach to the entry. Typically, you should call the `tag` method within the `register` method of your `App\Providers\TelescopeServiceProvider` class: - use Laravel\Telescope\IncomingEntry; - use Laravel\Telescope\Telescope; +```php +use Laravel\Telescope\IncomingEntry; +use Laravel\Telescope\Telescope; - /** - * Register any application services. - */ - public function register(): void - { - $this->hideSensitiveRequestDetails(); +/** + * Register any application services. + */ +public function register(): void +{ + $this->hideSensitiveRequestDetails(); - Telescope::tag(function (IncomingEntry $entry) { - return $entry->type === 'request' - ? ['status:'.$entry->content['response_status']] - : []; - }); - } + Telescope::tag(function (IncomingEntry $entry) { + return $entry->type === 'request' + ? ['status:'.$entry->content['response_status']] + : []; + }); +} +``` ## Available Watchers Telescope "watchers" gather application data when a request or console command is executed. You may customize the list of watchers that you would like to enable within your `config/telescope.php` configuration file: - 'watchers' => [ - Watchers\CacheWatcher::class => true, - Watchers\CommandWatcher::class => true, - ... - ], +```php +'watchers' => [ + Watchers\CacheWatcher::class => true, + Watchers\CommandWatcher::class => true, + ... +], +``` Some watchers also allow you to provide additional customization options: - 'watchers' => [ - Watchers\QueryWatcher::class => [ - 'enabled' => env('TELESCOPE_QUERY_WATCHER', true), - 'slow' => 100, - ], - ... +```php +'watchers' => [ + Watchers\QueryWatcher::class => [ + 'enabled' => env('TELESCOPE_QUERY_WATCHER', true), + 'slow' => 100, ], + ... +], +``` ### Batch Watcher @@ -287,13 +307,15 @@ The cache watcher records data when a cache key is hit, missed, updated and forg The command watcher records the arguments, options, exit code, and output whenever an Artisan command is executed. If you would like to exclude certain commands from being recorded by the watcher, you may specify the command in the `ignore` option within your `config/telescope.php` file: - 'watchers' => [ - Watchers\CommandWatcher::class => [ - 'enabled' => env('TELESCOPE_COMMAND_WATCHER', true), - 'ignore' => ['key:generate'], - ], - ... +```php +'watchers' => [ + Watchers\CommandWatcher::class => [ + 'enabled' => env('TELESCOPE_COMMAND_WATCHER', true), + 'ignore' => ['key:generate'], ], + ... +], +``` ### Dump Watcher @@ -315,13 +337,15 @@ The exception watcher records the data and stack trace for any reportable except The gate watcher records the data and result of [gate and policy](/docs/{{version}}/authorization) checks by your application. If you would like to exclude certain abilities from being recorded by the watcher, you may specify those in the `ignore_abilities` option in your `config/telescope.php` file: - 'watchers' => [ - Watchers\GateWatcher::class => [ - 'enabled' => env('TELESCOPE_GATE_WATCHER', true), - 'ignore_abilities' => ['viewNova'], - ], - ... +```php +'watchers' => [ + Watchers\GateWatcher::class => [ + 'enabled' => env('TELESCOPE_GATE_WATCHER', true), + 'ignore_abilities' => ['viewNova'], ], + ... +], +``` ### HTTP Client Watcher @@ -340,15 +364,17 @@ The log watcher records the [log data](/docs/{{version}}/logging) for any logs w By default, Telescope will only record logs at the `error` level and above. However, you can modify the `level` option in your application's `config/telescope.php` configuration file to modify this behavior: - 'watchers' => [ - Watchers\LogWatcher::class => [ - 'enabled' => env('TELESCOPE_LOG_WATCHER', true), - 'level' => 'debug', - ], - - // ... +```php +'watchers' => [ + Watchers\LogWatcher::class => [ + 'enabled' => env('TELESCOPE_LOG_WATCHER', true), + 'level' => 'debug', ], + // ... +], +``` + ### Mail Watcher @@ -359,24 +385,28 @@ The mail watcher allows you to view an in-browser preview of [emails](/docs/{{ve The model watcher records model changes whenever an Eloquent [model event](/docs/{{version}}/eloquent#events) is dispatched. You may specify which model events should be recorded via the watcher's `events` option: - 'watchers' => [ - Watchers\ModelWatcher::class => [ - 'enabled' => env('TELESCOPE_MODEL_WATCHER', true), - 'events' => ['eloquent.created*', 'eloquent.updated*'], - ], - ... +```php +'watchers' => [ + Watchers\ModelWatcher::class => [ + 'enabled' => env('TELESCOPE_MODEL_WATCHER', true), + 'events' => ['eloquent.created*', 'eloquent.updated*'], ], + ... +], +``` If you would like to record the number of models hydrated during a given request, enable the `hydrations` option: - 'watchers' => [ - Watchers\ModelWatcher::class => [ - 'enabled' => env('TELESCOPE_MODEL_WATCHER', true), - 'events' => ['eloquent.created*', 'eloquent.updated*'], - 'hydrations' => true, - ], - ... +```php +'watchers' => [ + Watchers\ModelWatcher::class => [ + 'enabled' => env('TELESCOPE_MODEL_WATCHER', true), + 'events' => ['eloquent.created*', 'eloquent.updated*'], + 'hydrations' => true, ], + ... +], +``` ### Notification Watcher @@ -388,13 +418,15 @@ The notification watcher records all [notifications](/docs/{{version}}/notificat The query watcher records the raw SQL, bindings, and execution time for all queries that are executed by your application. The watcher also tags any queries slower than 100 milliseconds as `slow`. You may customize the slow query threshold using the watcher's `slow` option: - 'watchers' => [ - Watchers\QueryWatcher::class => [ - 'enabled' => env('TELESCOPE_QUERY_WATCHER', true), - 'slow' => 50, - ], - ... +```php +'watchers' => [ + Watchers\QueryWatcher::class => [ + 'enabled' => env('TELESCOPE_QUERY_WATCHER', true), + 'slow' => 50, ], + ... +], +``` ### Redis Watcher @@ -406,13 +438,15 @@ The Redis watcher records all [Redis](/docs/{{version}}/redis) commands executed The request watcher records the request, headers, session, and response data associated with any requests handled by the application. You may limit your recorded response data via the `size_limit` (in kilobytes) option: - 'watchers' => [ - Watchers\RequestWatcher::class => [ - 'enabled' => env('TELESCOPE_REQUEST_WATCHER', true), - 'size_limit' => env('TELESCOPE_RESPONSE_SIZE_LIMIT', 64), - ], - ... +```php +'watchers' => [ + Watchers\RequestWatcher::class => [ + 'enabled' => env('TELESCOPE_REQUEST_WATCHER', true), + 'size_limit' => env('TELESCOPE_RESPONSE_SIZE_LIMIT', 64), ], + ... +], +``` ### Schedule Watcher @@ -429,17 +463,19 @@ The view watcher records the [view](/docs/{{version}}/views) name, path, data, a The Telescope dashboard displays the user avatar for the user that was authenticated when a given entry was saved. By default, Telescope will retrieve avatars using the Gravatar web service. However, you may customize the avatar URL by registering a callback in your `App\Providers\TelescopeServiceProvider` class. The callback will receive the user's ID and email address and should return the user's avatar image URL: - use App\Models\User; - use Laravel\Telescope\Telescope; +```php +use App\Models\User; +use Laravel\Telescope\Telescope; - /** - * Register any application services. - */ - public function register(): void - { - // ... +/** + * Register any application services. + */ +public function register(): void +{ + // ... - Telescope::avatar(function (string $id, string $email) { - return '/avatars/'.User::find($id)->avatar_path; - }); - } + Telescope::avatar(function (string $id, string $email) { + return '/avatars/'.User::find($id)->avatar_path; + }); +} +``` diff --git a/testing.md b/testing.md index 84c7e216418..740b1c01b86 100644 --- a/testing.md +++ b/testing.md @@ -144,44 +144,46 @@ Occasionally, you may need to prepare certain resources used by your application Using the `ParallelTesting` facade, you may specify code to be executed on the `setUp` and `tearDown` of a process or test case. The given closures receive the `$token` and `$testCase` variables that contain the process token and the current test case, respectively: - #### Accessing the Parallel Testing Token diff --git a/urls.md b/urls.md index d3bf482ca81..765c839e73d 100644 --- a/urls.md +++ b/urls.md @@ -22,98 +22,120 @@ Laravel provides several helpers to assist you in generating URLs for your appli The `url` helper may be used to generate arbitrary URLs for your application. The generated URL will automatically use the scheme (HTTP or HTTPS) and host from the current request being handled by the application: - $post = App\Models\Post::find(1); +```php +$post = App\Models\Post::find(1); - echo url("/posts/{$post->id}"); +echo url("/posts/{$post->id}"); - // http://example.com/posts/1 +// http://example.com/posts/1 +``` To generate a URL with query string parameters, you may use the `query` method: - echo url()->query('/posts', ['search' => 'Laravel']); +```php +echo url()->query('/posts', ['search' => 'Laravel']); - // https://example.com/posts?search=Laravel +// https://example.com/posts?search=Laravel - echo url()->query('/posts?sort=latest', ['search' => 'Laravel']); +echo url()->query('/posts?sort=latest', ['search' => 'Laravel']); - // http://example.com/posts?sort=latest&search=Laravel +// http://example.com/posts?sort=latest&search=Laravel +``` Providing query string parameters that already exist in the path will overwrite their existing value: - echo url()->query('/posts?sort=latest', ['sort' => 'oldest']); +```php +echo url()->query('/posts?sort=latest', ['sort' => 'oldest']); - // http://example.com/posts?sort=oldest +// http://example.com/posts?sort=oldest +``` Arrays of values may also be passed as query parameters. These values will be properly keyed and encoded in the generated URL: - echo $url = url()->query('/posts', ['columns' => ['title', 'body']]); +```php +echo $url = url()->query('/posts', ['columns' => ['title', 'body']]); - // http://example.com/posts?columns%5B0%5D=title&columns%5B1%5D=body +// http://example.com/posts?columns%5B0%5D=title&columns%5B1%5D=body - echo urldecode($url); +echo urldecode($url); - // http://example.com/posts?columns[0]=title&columns[1]=body +// http://example.com/posts?columns[0]=title&columns[1]=body +``` ### Accessing the Current URL If no path is provided to the `url` helper, an `Illuminate\Routing\UrlGenerator` instance is returned, allowing you to access information about the current URL: - // Get the current URL without the query string... - echo url()->current(); +```php +// Get the current URL without the query string... +echo url()->current(); - // Get the current URL including the query string... - echo url()->full(); +// Get the current URL including the query string... +echo url()->full(); - // Get the full URL for the previous request... - echo url()->previous(); +// Get the full URL for the previous request... +echo url()->previous(); - // Get the path for the previous request... - echo url()->previousPath(); +// Get the path for the previous request... +echo url()->previousPath(); +``` Each of these methods may also be accessed via the `URL` [facade](/docs/{{version}}/facades): - use Illuminate\Support\Facades\URL; +```php +use Illuminate\Support\Facades\URL; - echo URL::current(); +echo URL::current(); +``` ## URLs for Named Routes The `route` helper may be used to generate URLs to [named routes](/docs/{{version}}/routing#named-routes). Named routes allow you to generate URLs without being coupled to the actual URL defined on the route. Therefore, if the route's URL changes, no changes need to be made to your calls to the `route` function. For example, imagine your application contains a route defined like the following: - Route::get('/post/{post}', function (Post $post) { - // ... - })->name('post.show'); +```php +Route::get('/post/{post}', function (Post $post) { + // ... +})->name('post.show'); +``` To generate a URL to this route, you may use the `route` helper like so: - echo route('post.show', ['post' => 1]); +```php +echo route('post.show', ['post' => 1]); - // http://example.com/post/1 +// http://example.com/post/1 +``` Of course, the `route` helper may also be used to generate URLs for routes with multiple parameters: - Route::get('/post/{post}/comment/{comment}', function (Post $post, Comment $comment) { - // ... - })->name('comment.show'); +```php +Route::get('/post/{post}/comment/{comment}', function (Post $post, Comment $comment) { + // ... +})->name('comment.show'); - echo route('comment.show', ['post' => 1, 'comment' => 3]); +echo route('comment.show', ['post' => 1, 'comment' => 3]); - // http://example.com/post/1/comment/3 +// http://example.com/post/1/comment/3 +``` Any additional array elements that do not correspond to the route's definition parameters will be added to the URL's query string: - echo route('post.show', ['post' => 1, 'search' => 'rocket']); +```php +echo route('post.show', ['post' => 1, 'search' => 'rocket']); - // http://example.com/post/1?search=rocket +// http://example.com/post/1?search=rocket +``` #### Eloquent Models You will often be generating URLs using the route key (typically the primary key) of [Eloquent models](/docs/{{version}}/eloquent). For this reason, you may pass Eloquent models as parameter values. The `route` helper will automatically extract the model's route key: - echo route('post.show', ['post' => $post]); +```php +echo route('post.show', ['post' => $post]); +``` ### Signed URLs @@ -122,115 +144,139 @@ Laravel allows you to easily create "signed" URLs to named routes. These URLs ha For example, you might use signed URLs to implement a public "unsubscribe" link that is emailed to your customers. To create a signed URL to a named route, use the `signedRoute` method of the `URL` facade: - use Illuminate\Support\Facades\URL; +```php +use Illuminate\Support\Facades\URL; - return URL::signedRoute('unsubscribe', ['user' => 1]); +return URL::signedRoute('unsubscribe', ['user' => 1]); +``` You may exclude the domain from the signed URL hash by providing the `absolute` argument to the `signedRoute` method: - return URL::signedRoute('unsubscribe', ['user' => 1], absolute: false); +```php +return URL::signedRoute('unsubscribe', ['user' => 1], absolute: false); +``` If you would like to generate a temporary signed route URL that expires after a specified amount of time, you may use the `temporarySignedRoute` method. When Laravel validates a temporary signed route URL, it will ensure that the expiration timestamp that is encoded into the signed URL has not elapsed: - use Illuminate\Support\Facades\URL; +```php +use Illuminate\Support\Facades\URL; - return URL::temporarySignedRoute( - 'unsubscribe', now()->addMinutes(30), ['user' => 1] - ); +return URL::temporarySignedRoute( + 'unsubscribe', now()->addMinutes(30), ['user' => 1] +); +``` #### Validating Signed Route Requests To verify that an incoming request has a valid signature, you should call the `hasValidSignature` method on the incoming `Illuminate\Http\Request` instance: - use Illuminate\Http\Request; +```php +use Illuminate\Http\Request; - Route::get('/unsubscribe/{user}', function (Request $request) { - if (! $request->hasValidSignature()) { - abort(401); - } +Route::get('/unsubscribe/{user}', function (Request $request) { + if (! $request->hasValidSignature()) { + abort(401); + } - // ... - })->name('unsubscribe'); + // ... +})->name('unsubscribe'); +``` Sometimes, you may need to allow your application's frontend to append data to a signed URL, such as when performing client-side pagination. Therefore, you can specify request query parameters that should be ignored when validating a signed URL using the `hasValidSignatureWhileIgnoring` method. Remember, ignoring parameters allows anyone to modify those parameters on the request: - if (! $request->hasValidSignatureWhileIgnoring(['page', 'order'])) { - abort(401); - } +```php +if (! $request->hasValidSignatureWhileIgnoring(['page', 'order'])) { + abort(401); +} +``` Instead of validating signed URLs using the incoming request instance, you may assign the `signed` (`Illuminate\Routing\Middleware\ValidateSignature`) [middleware](/docs/{{version}}/middleware) to the route. If the incoming request does not have a valid signature, the middleware will automatically return a `403` HTTP response: - Route::post('/unsubscribe/{user}', function (Request $request) { - // ... - })->name('unsubscribe')->middleware('signed'); +```php +Route::post('/unsubscribe/{user}', function (Request $request) { + // ... +})->name('unsubscribe')->middleware('signed'); +``` If your signed URLs do not include the domain in the URL hash, you should provide the `relative` argument to the middleware: - Route::post('/unsubscribe/{user}', function (Request $request) { - // ... - })->name('unsubscribe')->middleware('signed:relative'); +```php +Route::post('/unsubscribe/{user}', function (Request $request) { + // ... +})->name('unsubscribe')->middleware('signed:relative'); +``` #### Responding to Invalid Signed Routes When someone visits a signed URL that has expired, they will receive a generic error page for the `403` HTTP status code. However, you can customize this behavior by defining a custom "render" closure for the `InvalidSignatureException` exception in your application's `bootstrap/app.php` file: - use Illuminate\Routing\Exceptions\InvalidSignatureException; +```php +use Illuminate\Routing\Exceptions\InvalidSignatureException; - ->withExceptions(function (Exceptions $exceptions) { - $exceptions->render(function (InvalidSignatureException $e) { - return response()->view('errors.link-expired', status: 403); - }); - }) +->withExceptions(function (Exceptions $exceptions) { + $exceptions->render(function (InvalidSignatureException $e) { + return response()->view('errors.link-expired', status: 403); + }); +}) +``` ## URLs for Controller Actions The `action` function generates a URL for the given controller action: - use App\Http\Controllers\HomeController; +```php +use App\Http\Controllers\HomeController; - $url = action([HomeController::class, 'index']); +$url = action([HomeController::class, 'index']); +``` If the controller method accepts route parameters, you may pass an associative array of route parameters as the second argument to the function: - $url = action([UserController::class, 'profile'], ['id' => 1]); +```php +$url = action([UserController::class, 'profile'], ['id' => 1]); +``` ## Default Values For some applications, you may wish to specify request-wide default values for certain URL parameters. For example, imagine many of your routes define a `{locale}` parameter: - Route::get('/{locale}/posts', function () { - // ... - })->name('post.index'); +```php +Route::get('/{locale}/posts', function () { + // ... +})->name('post.index'); +``` It is cumbersome to always pass the `locale` every time you call the `route` helper. So, you may use the `URL::defaults` method to define a default value for this parameter that will always be applied during the current request. You may wish to call this method from a [route middleware](/docs/{{version}}/middleware#assigning-middleware-to-routes) so that you have access to the current request: - $request->user()->locale]); - - return $next($request); - } + URL::defaults(['locale' => $request->user()->locale]); + + return $next($request); } +} +``` Once the default value for the `locale` parameter has been set, you are no longer required to pass its value when generating URLs via the `route` helper. diff --git a/valet.md b/valet.md index 0a324237735..75e9704b05b 100644 --- a/valet.md +++ b/valet.md @@ -332,19 +332,21 @@ Once you have updated your Nginx configuration, run the `valet restart` command Some applications using other frameworks may depend on server environment variables but do not provide a way for those variables to be configured within your project. Valet allows you to configure site specific environment variables by adding a `.valet-env.php` file within the root of your project. This file should return an array of site / environment variable pairs which will be added to the global `$_SERVER` array for each site specified in the array: - [ - 'key' => 'value', - ], - - // Set $_SERVER['key'] to "value" for all sites... - '*' => [ - 'key' => 'value', - ], - ]; +```php + [ + 'key' => 'value', + ], + + // Set $_SERVER['key'] to "value" for all sites... + '*' => [ + 'key' => 'value', + ], +]; +``` ## Proxying Services @@ -391,33 +393,37 @@ The `serves` method should return `true` if your driver should handle the incomi For example, let's imagine we are writing a `WordPressValetDriver`. Our `serves` method might look something like this: - /** - * Determine if the driver serves the request. - */ - public function serves(string $sitePath, string $siteName, string $uri): bool - { - return is_dir($sitePath.'/wp-admin'); - } +```php +/** + * Determine if the driver serves the request. + */ +public function serves(string $sitePath, string $siteName, string $uri): bool +{ + return is_dir($sitePath.'/wp-admin'); +} +``` #### The `isStaticFile` Method The `isStaticFile` should determine if the incoming request is for a file that is "static", such as an image or a stylesheet. If the file is static, the method should return the fully qualified path to the static file on disk. If the incoming request is not for a static file, the method should return `false`: - /** - * Determine if the incoming request is for a static file. - * - * @return string|false - */ - public function isStaticFile(string $sitePath, string $siteName, string $uri) - { - if (file_exists($staticFilePath = $sitePath.'/public/'.$uri)) { - return $staticFilePath; - } - - return false; +```php +/** + * Determine if the incoming request is for a static file. + * + * @return string|false + */ +public function isStaticFile(string $sitePath, string $siteName, string $uri) +{ + if (file_exists($staticFilePath = $sitePath.'/public/'.$uri)) { + return $staticFilePath; } + return false; +} +``` + > [!WARNING] > The `isStaticFile` method will only be called if the `serves` method returns `true` for the incoming request and the request URI is not `/`. @@ -426,40 +432,44 @@ The `isStaticFile` should determine if the incoming request is for a file that i The `frontControllerPath` method should return the fully qualified path to your application's "front controller", which is typically an "index.php" file or equivalent: - /** - * Get the fully resolved path to the application's front controller. - */ - public function frontControllerPath(string $sitePath, string $siteName, string $uri): string - { - return $sitePath.'/public/index.php'; - } +```php +/** + * Get the fully resolved path to the application's front controller. + */ +public function frontControllerPath(string $sitePath, string $siteName, string $uri): string +{ + return $sitePath.'/public/index.php'; +} +``` ### Local Drivers If you would like to define a custom Valet driver for a single application, create a `LocalValetDriver.php` file in the application's root directory. Your custom driver may extend the base `ValetDriver` class or extend an existing application specific driver such as the `LaravelValetDriver`: - use Valet\Drivers\LaravelValetDriver; +```php +use Valet\Drivers\LaravelValetDriver; - class LocalValetDriver extends LaravelValetDriver +class LocalValetDriver extends LaravelValetDriver +{ + /** + * Determine if the driver serves the request. + */ + public function serves(string $sitePath, string $siteName, string $uri): bool { - /** - * Determine if the driver serves the request. - */ - public function serves(string $sitePath, string $siteName, string $uri): bool - { - return true; - } - - /** - * Get the fully resolved path to the application's front controller. - */ - public function frontControllerPath(string $sitePath, string $siteName, string $uri): string - { - return $sitePath.'/public_html/index.php'; - } + return true; } + /** + * Get the fully resolved path to the application's front controller. + */ + public function frontControllerPath(string $sitePath, string $siteName, string $uri): string + { + return $sitePath.'/public_html/index.php'; + } +} +``` + ## Other Valet Commands diff --git a/validation.md b/validation.md index 6faf67c72ca..891d50c881e 100644 --- a/validation.md +++ b/validation.md @@ -53,10 +53,12 @@ To learn about Laravel's powerful validation features, let's look at a complete First, let's assume we have the following routes defined in our `routes/web.php` file: - use App\Http\Controllers\PostController; +```php +use App\Http\Controllers\PostController; - Route::get('/post/create', [PostController::class, 'create']); - Route::post('/post', [PostController::class, 'store']); +Route::get('/post/create', [PostController::class, 'create']); +Route::post('/post', [PostController::class, 'store']); +``` The `GET` route will display a form for the user to create a new blog post, while the `POST` route will store the new blog post in the database. @@ -65,36 +67,38 @@ The `GET` route will display a form for the user to create a new blog post, whil Next, let's take a look at a simple controller that handles incoming requests to these routes. We'll leave the `store` method empty for now: - $post->id]); - } + return to_route('post.show', ['post' => $post->id]); } +} +``` ### Writing the Validation Logic @@ -105,46 +109,54 @@ If validation fails during a traditional HTTP request, a redirect response to th To get a better understanding of the `validate` method, let's jump back into the `store` method: - /** - * Store a new blog post. - */ - public function store(Request $request): RedirectResponse - { - $validated = $request->validate([ - 'title' => 'required|unique:posts|max:255', - 'body' => 'required', - ]); +```php +/** + * Store a new blog post. + */ +public function store(Request $request): RedirectResponse +{ + $validated = $request->validate([ + 'title' => 'required|unique:posts|max:255', + 'body' => 'required', + ]); - // The blog post is valid... + // The blog post is valid... - return redirect('/posts'); - } + return redirect('/posts'); +} +``` As you can see, the validation rules are passed into the `validate` method. Don't worry - all available validation rules are [documented](#available-validation-rules). Again, if the validation fails, the proper response will automatically be generated. If the validation passes, our controller will continue executing normally. Alternatively, validation rules may be specified as arrays of rules instead of a single `|` delimited string: - $validatedData = $request->validate([ - 'title' => ['required', 'unique:posts', 'max:255'], - 'body' => ['required'], - ]); +```php +$validatedData = $request->validate([ + 'title' => ['required', 'unique:posts', 'max:255'], + 'body' => ['required'], +]); +``` In addition, you may use the `validateWithBag` method to validate a request and store any error messages within a [named error bag](#named-error-bags): - $validatedData = $request->validateWithBag('post', [ - 'title' => ['required', 'unique:posts', 'max:255'], - 'body' => ['required'], - ]); +```php +$validatedData = $request->validateWithBag('post', [ + 'title' => ['required', 'unique:posts', 'max:255'], + 'body' => ['required'], +]); +``` #### Stopping on First Validation Failure Sometimes you may wish to stop running validation rules on an attribute after the first validation failure. To do so, assign the `bail` rule to the attribute: - $request->validate([ - 'title' => 'bail|required|unique:posts|max:255', - 'body' => 'required', - ]); +```php +$request->validate([ + 'title' => 'bail|required|unique:posts|max:255', + 'body' => 'required', +]); +``` In this example, if the `unique` rule on the `title` attribute fails, the `max` rule will not be checked. Rules will be validated in the order they are assigned. @@ -153,18 +165,22 @@ In this example, if the `unique` rule on the `title` attribute fails, the `max` If the incoming HTTP request contains "nested" field data, you may specify these fields in your validation rules using "dot" syntax: - $request->validate([ - 'title' => 'required|unique:posts|max:255', - 'author.name' => 'required', - 'author.description' => 'required', - ]); +```php +$request->validate([ + 'title' => 'required|unique:posts|max:255', + 'author.name' => 'required', + 'author.description' => 'required', +]); +``` On the other hand, if your field name contains a literal period, you can explicitly prevent this from being interpreted as "dot" syntax by escaping the period with a backslash: - $request->validate([ - 'title' => 'required|unique:posts|max:255', - 'v1\.0' => 'required', - ]); +```php +$request->validate([ + 'title' => 'required|unique:posts|max:255', + 'v1\.0' => 'required', +]); +``` ### Displaying the Validation Errors @@ -245,7 +261,9 @@ When Laravel generates a redirect response due to a validation error, the framew To retrieve flashed input from the previous request, invoke the `old` method on an instance of `Illuminate\Http\Request`. The `old` method will pull the previously flashed input data from the [session](/docs/{{version}}/session): - $title = $request->old('title'); +```php +$title = $request->old('title'); +``` Laravel also provides a global `old` helper. If you are displaying old input within a [Blade template](/docs/{{version}}/blade), it is more convenient to use the `old` helper to repopulate the form. If no old input exists for the given field, `null` will be returned: @@ -258,11 +276,13 @@ Laravel also provides a global `old` helper. If you are displaying old input wit By default, Laravel includes the `TrimStrings` and `ConvertEmptyStringsToNull` middleware in your application's global middleware stack. Because of this, you will often need to mark your "optional" request fields as `nullable` if you do not want the validator to consider `null` values as invalid. For example: - $request->validate([ - 'title' => 'required|unique:posts|max:255', - 'body' => 'required', - 'publish_at' => 'nullable|date', - ]); +```php +$request->validate([ + 'title' => 'required|unique:posts|max:255', + 'body' => 'required', + 'publish_at' => 'nullable|date', +]); +``` In this example, we are specifying that the `publish_at` field may be either `null` or a valid date representation. If the `nullable` modifier is not added to the rule definition, the validator would consider `null` an invalid date. @@ -310,42 +330,46 @@ The generated form request class will be placed in the `app/Http/Requests` direc As you might have guessed, the `authorize` method is responsible for determining if the currently authenticated user can perform the action represented by the request, while the `rules` method returns the validation rules that should apply to the request's data: - /** - * Get the validation rules that apply to the request. - * - * @return array|string> - */ - public function rules(): array - { - return [ - 'title' => 'required|unique:posts|max:255', - 'body' => 'required', - ]; - } +```php +/** + * Get the validation rules that apply to the request. + * + * @return array|string> + */ +public function rules(): array +{ + return [ + 'title' => 'required|unique:posts|max:255', + 'body' => 'required', + ]; +} +``` > [!NOTE] > You may type-hint any dependencies you require within the `rules` method's signature. They will automatically be resolved via the Laravel [service container](/docs/{{version}}/container). So, how are the validation rules evaluated? All you need to do is type-hint the request on your controller method. The incoming form request is validated before the controller method is called, meaning you do not need to clutter your controller with any validation logic: - /** - * Store a new blog post. - */ - public function store(StorePostRequest $request): RedirectResponse - { - // The incoming request is valid... +```php +/** + * Store a new blog post. + */ +public function store(StorePostRequest $request): RedirectResponse +{ + // The incoming request is valid... - // Retrieve the validated input data... - $validated = $request->validated(); + // Retrieve the validated input data... + $validated = $request->validated(); - // Retrieve a portion of the validated input data... - $validated = $request->safe()->only(['name', 'email']); - $validated = $request->safe()->except(['name', 'email']); + // Retrieve a portion of the validated input data... + $validated = $request->safe()->only(['name', 'email']); + $validated = $request->safe()->except(['name', 'email']); - // Store the blog post... + // Store the blog post... - return redirect('/posts'); - } + return redirect('/posts'); +} +``` If validation fails, a redirect response will be generated to send the user back to their previous location. The errors will also be flashed to the session so they are available for display. If the request was an XHR request, an HTTP response with a 422 status code will be returned to the user including a [JSON representation of the validation errors](#validation-error-response-format). @@ -359,24 +383,26 @@ Sometimes you need to perform additional validation after your initial validatio The `after` method should return an array of callables or closures which will be invoked after validation is complete. The given callables will receive an `Illuminate\Validation\Validator` instance, allowing you to raise additional error messages if necessary: - use Illuminate\Validation\Validator; +```php +use Illuminate\Validation\Validator; - /** - * Get the "after" validation callables for the request. - */ - public function after(): array - { - return [ - function (Validator $validator) { - if ($this->somethingElseIsInvalid()) { - $validator->errors()->add( - 'field', - 'Something is wrong with this field!' - ); - } +/** + * Get the "after" validation callables for the request. + */ +public function after(): array +{ + return [ + function (Validator $validator) { + if ($this->somethingElseIsInvalid()) { + $validator->errors()->add( + 'field', + 'Something is wrong with this field!' + ); } - ]; - } + } + ]; +} +``` As noted, the array returned by the `after` method may also contain invokable classes. The `__invoke` method of these classes will receive an `Illuminate\Validation\Validator` instance: @@ -405,70 +431,84 @@ public function after(): array By adding a `stopOnFirstFailure` property to your request class, you may inform the validator that it should stop validating all attributes once a single validation failure has occurred: - /** - * Indicates if the validator should stop on the first rule failure. - * - * @var bool - */ - protected $stopOnFirstFailure = true; +```php +/** + * Indicates if the validator should stop on the first rule failure. + * + * @var bool + */ +protected $stopOnFirstFailure = true; +``` #### Customizing the Redirect Location When form request validation fails, a redirect response will be generated to send the user back to their previous location. However, you are free to customize this behavior. To do so, define a `$redirect` property on your form request: - /** - * The URI that users should be redirected to if validation fails. - * - * @var string - */ - protected $redirect = '/dashboard'; +```php +/** + * The URI that users should be redirected to if validation fails. + * + * @var string + */ +protected $redirect = '/dashboard'; +``` Or, if you would like to redirect users to a named route, you may define a `$redirectRoute` property instead: - /** - * The route that users should be redirected to if validation fails. - * - * @var string - */ - protected $redirectRoute = 'dashboard'; +```php +/** + * The route that users should be redirected to if validation fails. + * + * @var string + */ +protected $redirectRoute = 'dashboard'; +``` ### Authorizing Form Requests The form request class also contains an `authorize` method. Within this method, you may determine if the authenticated user actually has the authority to update a given resource. For example, you may determine if a user actually owns a blog comment they are attempting to update. Most likely, you will interact with your [authorization gates and policies](/docs/{{version}}/authorization) within this method: - use App\Models\Comment; +```php +use App\Models\Comment; - /** - * Determine if the user is authorized to make this request. - */ - public function authorize(): bool - { - $comment = Comment::find($this->route('comment')); +/** + * Determine if the user is authorized to make this request. + */ +public function authorize(): bool +{ + $comment = Comment::find($this->route('comment')); - return $comment && $this->user()->can('update', $comment); - } + return $comment && $this->user()->can('update', $comment); +} +``` Since all form requests extend the base Laravel request class, we may use the `user` method to access the currently authenticated user. Also, note the call to the `route` method in the example above. This method grants you access to the URI parameters defined on the route being called, such as the `{comment}` parameter in the example below: - Route::post('/comment/{comment}'); +```php +Route::post('/comment/{comment}'); +``` Therefore, if your application is taking advantage of [route model binding](/docs/{{version}}/routing#route-model-binding), your code may be made even more succinct by accessing the resolved model as a property of the request: - return $this->user()->can('update', $this->comment); +```php +return $this->user()->can('update', $this->comment); +``` If the `authorize` method returns `false`, an HTTP response with a 403 status code will automatically be returned and your controller method will not execute. If you plan to handle authorization logic for the request in another part of your application, you may remove the `authorize` method completely, or simply return `true`: - /** - * Determine if the user is authorized to make this request. - */ - public function authorize(): bool - { - return true; - } +```php +/** + * Determine if the user is authorized to make this request. + */ +public function authorize(): bool +{ + return true; +} +``` > [!NOTE] > You may type-hint any dependencies you need within the `authorize` method's signature. They will automatically be resolved via the Laravel [service container](/docs/{{version}}/container). @@ -478,106 +518,116 @@ If you plan to handle authorization logic for the request in another part of you You may customize the error messages used by the form request by overriding the `messages` method. This method should return an array of attribute / rule pairs and their corresponding error messages: - /** - * Get the error messages for the defined validation rules. - * - * @return array - */ - public function messages(): array - { - return [ - 'title.required' => 'A title is required', - 'body.required' => 'A message is required', - ]; - } +```php +/** + * Get the error messages for the defined validation rules. + * + * @return array + */ +public function messages(): array +{ + return [ + 'title.required' => 'A title is required', + 'body.required' => 'A message is required', + ]; +} +``` #### Customizing the Validation Attributes Many of Laravel's built-in validation rule error messages contain an `:attribute` placeholder. If you would like the `:attribute` placeholder of your validation message to be replaced with a custom attribute name, you may specify the custom names by overriding the `attributes` method. This method should return an array of attribute / name pairs: - /** - * Get custom attributes for validator errors. - * - * @return array - */ - public function attributes(): array - { - return [ - 'email' => 'email address', - ]; - } +```php +/** + * Get custom attributes for validator errors. + * + * @return array + */ +public function attributes(): array +{ + return [ + 'email' => 'email address', + ]; +} +``` ### Preparing Input for Validation If you need to prepare or sanitize any data from the request before you apply your validation rules, you may use the `prepareForValidation` method: - use Illuminate\Support\Str; +```php +use Illuminate\Support\Str; - /** - * Prepare the data for validation. - */ - protected function prepareForValidation(): void - { - $this->merge([ - 'slug' => Str::slug($this->slug), - ]); - } +/** + * Prepare the data for validation. + */ +protected function prepareForValidation(): void +{ + $this->merge([ + 'slug' => Str::slug($this->slug), + ]); +} +``` Likewise, if you need to normalize any request data after validation is complete, you may use the `passedValidation` method: - /** - * Handle a passed validation attempt. - */ - protected function passedValidation(): void - { - $this->replace(['name' => 'Taylor']); - } +```php +/** + * Handle a passed validation attempt. + */ +protected function passedValidation(): void +{ + $this->replace(['name' => 'Taylor']); +} +``` ## Manually Creating Validators If you do not want to use the `validate` method on the request, you may create a validator instance manually using the `Validator` [facade](/docs/{{version}}/facades). The `make` method on the facade generates a new validator instance: - all(), [ - 'title' => 'required|unique:posts|max:255', - 'body' => 'required', - ]); - - if ($validator->fails()) { - return redirect('/post/create') - ->withErrors($validator) - ->withInput(); - } + $validator = Validator::make($request->all(), [ + 'title' => 'required|unique:posts|max:255', + 'body' => 'required', + ]); - // Retrieve the validated input... - $validated = $validator->validated(); + if ($validator->fails()) { + return redirect('/post/create') + ->withErrors($validator) + ->withInput(); + } - // Retrieve a portion of the validated input... - $validated = $validator->safe()->only(['name', 'email']); - $validated = $validator->safe()->except(['name', 'email']); + // Retrieve the validated input... + $validated = $validator->validated(); - // Store the blog post... + // Retrieve a portion of the validated input... + $validated = $validator->safe()->only(['name', 'email']); + $validated = $validator->safe()->except(['name', 'email']); - return redirect('/posts'); - } + // Store the blog post... + + return redirect('/posts'); } +} +``` The first argument passed to the `make` method is the data under validation. The second argument is an array of the validation rules that should be applied to the data. @@ -587,33 +637,41 @@ After determining whether the request validation failed, you may use the `withEr The `stopOnFirstFailure` method will inform the validator that it should stop validating all attributes once a single validation failure has occurred: - if ($validator->stopOnFirstFailure()->fails()) { - // ... - } +```php +if ($validator->stopOnFirstFailure()->fails()) { + // ... +} +``` ### Automatic Redirection If you would like to create a validator instance manually but still take advantage of the automatic redirection offered by the HTTP request's `validate` method, you may call the `validate` method on an existing validator instance. If validation fails, the user will automatically be redirected or, in the case of an XHR request, a [JSON response will be returned](#validation-error-response-format): - Validator::make($request->all(), [ - 'title' => 'required|unique:posts|max:255', - 'body' => 'required', - ])->validate(); +```php +Validator::make($request->all(), [ + 'title' => 'required|unique:posts|max:255', + 'body' => 'required', +])->validate(); +``` You may use the `validateWithBag` method to store the error messages in a [named error bag](#named-error-bags) if validation fails: - Validator::make($request->all(), [ - 'title' => 'required|unique:posts|max:255', - 'body' => 'required', - ])->validateWithBag('post'); +```php +Validator::make($request->all(), [ + 'title' => 'required|unique:posts|max:255', + 'body' => 'required', +])->validateWithBag('post'); +``` ### Named Error Bags If you have multiple forms on a single page, you may wish to name the `MessageBag` containing the validation errors, allowing you to retrieve the error messages for a specific form. To achieve this, pass a name as the second argument to `withErrors`: - return redirect('/register')->withErrors($validator, 'login'); +```php +return redirect('/register')->withErrors($validator, 'login'); +``` You may then access the named `MessageBag` instance from the `$errors` variable: @@ -626,57 +684,67 @@ You may then access the named `MessageBag` instance from the `$errors` variable: If needed, you may provide custom error messages that a validator instance should use instead of the default error messages provided by Laravel. There are several ways to specify custom messages. First, you may pass the custom messages as the third argument to the `Validator::make` method: - $validator = Validator::make($input, $rules, $messages = [ - 'required' => 'The :attribute field is required.', - ]); +```php +$validator = Validator::make($input, $rules, $messages = [ + 'required' => 'The :attribute field is required.', +]); +``` In this example, the `:attribute` placeholder will be replaced by the actual name of the field under validation. You may also utilize other placeholders in validation messages. For example: - $messages = [ - 'same' => 'The :attribute and :other must match.', - 'size' => 'The :attribute must be exactly :size.', - 'between' => 'The :attribute value :input is not between :min - :max.', - 'in' => 'The :attribute must be one of the following types: :values', - ]; +```php +$messages = [ + 'same' => 'The :attribute and :other must match.', + 'size' => 'The :attribute must be exactly :size.', + 'between' => 'The :attribute value :input is not between :min - :max.', + 'in' => 'The :attribute must be one of the following types: :values', +]; +``` #### Specifying a Custom Message for a Given Attribute Sometimes you may wish to specify a custom error message only for a specific attribute. You may do so using "dot" notation. Specify the attribute's name first, followed by the rule: - $messages = [ - 'email.required' => 'We need to know your email address!', - ]; +```php +$messages = [ + 'email.required' => 'We need to know your email address!', +]; +``` #### Specifying Custom Attribute Values Many of Laravel's built-in error messages include an `:attribute` placeholder that is replaced with the name of the field or attribute under validation. To customize the values used to replace these placeholders for specific fields, you may pass an array of custom attributes as the fourth argument to the `Validator::make` method: - $validator = Validator::make($input, $rules, $messages, [ - 'email' => 'email address', - ]); +```php +$validator = Validator::make($input, $rules, $messages, [ + 'email' => 'email address', +]); +``` ### Performing Additional Validation Sometimes you need to perform additional validation after your initial validation is complete. You can accomplish this using the validator's `after` method. The `after` method accepts a closure or an array of callables which will be invoked after validation is complete. The given callables will receive an `Illuminate\Validation\Validator` instance, allowing you to raise additional error messages if necessary: - use Illuminate\Support\Facades\Validator; +```php +use Illuminate\Support\Facades\Validator; - $validator = Validator::make(/* ... */); +$validator = Validator::make(/* ... */); - $validator->after(function ($validator) { - if ($this->somethingElseIsInvalid()) { - $validator->errors()->add( - 'field', 'Something is wrong with this field!' - ); - } - }); - - if ($validator->fails()) { - // ... +$validator->after(function ($validator) { + if ($this->somethingElseIsInvalid()) { + $validator->errors()->add( + 'field', 'Something is wrong with this field!' + ); } +}); + +if ($validator->fails()) { + // ... +} +``` As noted, the `after` method also accepts an array of callables, which is particularly convenient if your "after validation" logic is encapsulated in invokable classes, which will receive an `Illuminate\Validation\Validator` instance via their `__invoke` method: @@ -698,37 +766,47 @@ $validator->after([ After validating incoming request data using a form request or a manually created validator instance, you may wish to retrieve the incoming request data that actually underwent validation. This can be accomplished in several ways. First, you may call the `validated` method on a form request or validator instance. This method returns an array of the data that was validated: - $validated = $request->validated(); +```php +$validated = $request->validated(); - $validated = $validator->validated(); +$validated = $validator->validated(); +``` Alternatively, you may call the `safe` method on a form request or validator instance. This method returns an instance of `Illuminate\Support\ValidatedInput`. This object exposes `only`, `except`, and `all` methods to retrieve a subset of the validated data or the entire array of validated data: - $validated = $request->safe()->only(['name', 'email']); +```php +$validated = $request->safe()->only(['name', 'email']); - $validated = $request->safe()->except(['name', 'email']); +$validated = $request->safe()->except(['name', 'email']); - $validated = $request->safe()->all(); +$validated = $request->safe()->all(); +``` In addition, the `Illuminate\Support\ValidatedInput` instance may be iterated over and accessed like an array: - // Validated data may be iterated... - foreach ($request->safe() as $key => $value) { - // ... - } +```php +// Validated data may be iterated... +foreach ($request->safe() as $key => $value) { + // ... +} - // Validated data may be accessed as an array... - $validated = $request->safe(); +// Validated data may be accessed as an array... +$validated = $request->safe(); - $email = $validated['email']; +$email = $validated['email']; +``` If you would like to add additional fields to the validated data, you may call the `merge` method: - $validated = $request->safe()->merge(['name' => 'Taylor Otwell']); +```php +$validated = $request->safe()->merge(['name' => 'Taylor Otwell']); +``` If you would like to retrieve the validated data as a [collection](/docs/{{version}}/collections) instance, you may call the `collect` method: - $collection = $request->safe()->collect(); +```php +$collection = $request->safe()->collect(); +``` ## Working With Error Messages @@ -740,42 +818,52 @@ After calling the `errors` method on a `Validator` instance, you will receive an To retrieve the first error message for a given field, use the `first` method: - $errors = $validator->errors(); +```php +$errors = $validator->errors(); - echo $errors->first('email'); +echo $errors->first('email'); +``` #### Retrieving All Error Messages for a Field If you need to retrieve an array of all the messages for a given field, use the `get` method: - foreach ($errors->get('email') as $message) { - // ... - } +```php +foreach ($errors->get('email') as $message) { + // ... +} +``` If you are validating an array form field, you may retrieve all of the messages for each of the array elements using the `*` character: - foreach ($errors->get('attachments.*') as $message) { - // ... - } +```php +foreach ($errors->get('attachments.*') as $message) { + // ... +} +``` #### Retrieving All Error Messages for All Fields To retrieve an array of all messages for all fields, use the `all` method: - foreach ($errors->all() as $message) { - // ... - } +```php +foreach ($errors->all() as $message) { + // ... +} +``` #### Determining if Messages Exist for a Field The `has` method may be used to determine if any error messages exist for a given field: - if ($errors->has('email')) { - // ... - } +```php +if ($errors->has('email')) { + // ... +} +``` ### Specifying Custom Messages in Language Files @@ -794,21 +882,25 @@ In addition, you may copy this file to another language directory to translate t You may customize the error messages used for specified attribute and rule combinations within your application's validation language files. To do so, add your message customizations to the `custom` array of your application's `lang/xx/validation.php` language file: - 'custom' => [ - 'email' => [ - 'required' => 'We need to know your email address!', - 'max' => 'Your email address is too long!' - ], +```php +'custom' => [ + 'email' => [ + 'required' => 'We need to know your email address!', + 'max' => 'Your email address is too long!' ], +], +``` ### Specifying Attributes in Language Files Many of Laravel's built-in error messages include an `:attribute` placeholder that is replaced with the name of the field or attribute under validation. If you would like the `:attribute` portion of your validation message to be replaced with a custom value, you may specify the custom attribute name in the `attributes` array of your `lang/xx/validation.php` language file: - 'attributes' => [ - 'email' => 'email address', - ], +```php +'attributes' => [ + 'email' => 'email address', +], +``` > [!WARNING] > By default, the Laravel application skeleton does not include the `lang` directory. If you would like to customize Laravel's language files, you may publish them via the `lang:publish` Artisan command. @@ -818,9 +910,11 @@ Many of Laravel's built-in error messages include an `:attribute` placeholder th Some of Laravel's built-in validation rule error messages contain a `:value` placeholder that is replaced with the current value of the request attribute. However, you may occasionally need the `:value` portion of your validation message to be replaced with a custom representation of the value. For example, consider the following rule that specifies that a credit card number is required if the `payment_type` has a value of `cc`: - Validator::make($request->all(), [ - 'credit_card_number' => 'required_if:payment_type,cc' - ]); +```php +Validator::make($request->all(), [ + 'credit_card_number' => 'required_if:payment_type,cc' +]); +``` If this validation rule fails, it will produce the following error message: @@ -830,11 +924,13 @@ The credit card number field is required when payment type is cc. Instead of displaying `cc` as the payment type value, you may specify a more user-friendly value representation in your `lang/xx/validation.php` language file by defining a `values` array: - 'values' => [ - 'payment_type' => [ - 'cc' => 'credit card' - ], +```php +'values' => [ + 'payment_type' => [ + 'cc' => 'credit card' ], +], +``` > [!WARNING] > By default, the Laravel application skeleton does not include the `lang` directory. If you would like to customize Laravel's language files, you may publish them via the `lang:publish` Artisan command. @@ -1056,27 +1152,35 @@ The field under validation must have a valid A or AAAA record according to the ` The field under validation must be a value after a given date. The dates will be passed into the `strtotime` PHP function in order to be converted to a valid `DateTime` instance: - 'start_date' => 'required|date|after:tomorrow' +```php +'start_date' => 'required|date|after:tomorrow' +``` Instead of passing a date string to be evaluated by `strtotime`, you may specify another field to compare against the date: - 'finish_date' => 'required|date|after:start_date' +```php +'finish_date' => 'required|date|after:start_date' +``` For convenience, date based rules may be constructed using the fluent `date` rule builder: - use Illuminate\Validation\Rule; +```php +use Illuminate\Validation\Rule; - 'start_date' => [ - 'required', - Rule::date()->after(today()->addDays(7)), - ], +'start_date' => [ + 'required', + Rule::date()->after(today()->addDays(7)), +], +``` The `afterToday` and `todayOrAfter` methods may be used to fluently express the date must be after today or or today or after, respectively: - 'start_date' => [ - 'required', - Rule::date()->afterToday(), - ], +```php +'start_date' => [ + 'required', + Rule::date()->afterToday(), +], +``` #### after\_or\_equal:_date_ @@ -1085,12 +1189,14 @@ The field under validation must be a value after or equal to the given date. For For convenience, date based rules may be constructed using the fluent `date` rule builder: - use Illuminate\Validation\Rule; +```php +use Illuminate\Validation\Rule; - 'start_date' => [ - 'required', - Rule::date()->afterOrEqual(today()->addDays(7)), - ], +'start_date' => [ + 'required', + Rule::date()->afterOrEqual(today()->addDays(7)), +], +``` #### alpha @@ -1132,19 +1238,21 @@ The field under validation must be a PHP `array`. When additional values are provided to the `array` rule, each key in the input array must be present within the list of values provided to the rule. In the following example, the `admin` key in the input array is invalid since it is not contained in the list of values provided to the `array` rule: - use Illuminate\Support\Facades\Validator; +```php +use Illuminate\Support\Facades\Validator; - $input = [ - 'user' => [ - 'name' => 'Taylor Otwell', - 'username' => 'taylorotwell', - 'admin' => true, - ], - ]; +$input = [ + 'user' => [ + 'name' => 'Taylor Otwell', + 'username' => 'taylorotwell', + 'admin' => true, + ], +]; - Validator::make($input, [ - 'user' => 'array:name,username', - ]); +Validator::make($input, [ + 'user' => 'array:name,username', +]); +``` In general, you should always specify the array keys that are allowed to be present within your array. @@ -1160,9 +1268,11 @@ Stop running validation rules for the field after the first validation failure. While the `bail` rule will only stop validating a specific field when it encounters a validation failure, the `stopOnFirstFailure` method will inform the validator that it should stop validating all attributes once a single validation failure has occurred: - if ($validator->stopOnFirstFailure()->fails()) { - // ... - } +```php +if ($validator->stopOnFirstFailure()->fails()) { + // ... +} +``` #### before:_date_ @@ -1171,19 +1281,23 @@ The field under validation must be a value preceding the given date. The dates w For convenience, date based rules may also be constructed using the fluent `date` rule builder: - use Illuminate\Validation\Rule; +```php +use Illuminate\Validation\Rule; - 'start_date' => [ - 'required', - Rule::date()->before(today()->subDays(7)), - ], +'start_date' => [ + 'required', + Rule::date()->before(today()->subDays(7)), +], +``` The `beforeToday` and `todayOrBefore` methods may be used to fluently express the date must be before today or or today or before, respectively: - 'start_date' => [ - 'required', - Rule::date()->beforeToday(), - ], +```php +'start_date' => [ + 'required', + Rule::date()->beforeToday(), +], +``` #### before\_or\_equal:_date_ @@ -1192,12 +1306,14 @@ The field under validation must be a value preceding or equal to the given date. For convenience, date based rules may also be constructed using the fluent `date` rule builder: - use Illuminate\Validation\Rule; +```php +use Illuminate\Validation\Rule; - 'start_date' => [ - 'required', - Rule::date()->beforeOrEqual(today()->subDays(7)), - ], +'start_date' => [ + 'required', + Rule::date()->beforeOrEqual(today()->subDays(7)), +], +``` #### between:_min_,_max_ @@ -1226,7 +1342,9 @@ The field under validation must be an array that contains all of the given param The field under validation must match the authenticated user's password. You may specify an [authentication guard](/docs/{{version}}/authentication) using the rule's first parameter: - 'password' => 'current_password:api' +```php +'password' => 'current_password:api' +``` #### date @@ -1245,23 +1363,27 @@ The field under validation must match one of the given _formats_. You should use For convenience, date based rules may be constructed using the fluent `date` rule builder: - use Illuminate\Validation\Rule; +```php +use Illuminate\Validation\Rule; - 'start_date' => [ - 'required', - Rule::date()->format('Y-m-d'), - ], +'start_date' => [ + 'required', + Rule::date()->format('Y-m-d'), +], +``` #### decimal:_min_,_max_ The field under validation must be numeric and must contain the specified number of decimal places: - // Must have exactly two decimal places (9.99)... - 'price' => 'decimal:2' +```php +// Must have exactly two decimal places (9.99)... +'price' => 'decimal:2' - // Must have between 2 and 4 decimal places... - 'price' => 'decimal:2,4' +// Must have between 2 and 4 decimal places... +'price' => 'decimal:2,4' +``` #### declined @@ -1293,43 +1415,55 @@ The integer validation must have a length between the given _min_ and _max_. The file under validation must be an image meeting the dimension constraints as specified by the rule's parameters: - 'avatar' => 'dimensions:min_width=100,min_height=200' +```php +'avatar' => 'dimensions:min_width=100,min_height=200' +``` Available constraints are: _min\_width_, _max\_width_, _min\_height_, _max\_height_, _width_, _height_, _ratio_. A _ratio_ constraint should be represented as width divided by height. This can be specified either by a fraction like `3/2` or a float like `1.5`: - 'avatar' => 'dimensions:ratio=3/2' +```php +'avatar' => 'dimensions:ratio=3/2' +``` Since this rule requires several arguments, it is often more convenient to use use the `Rule::dimensions` method to fluently construct the rule: - use Illuminate\Support\Facades\Validator; - use Illuminate\Validation\Rule; +```php +use Illuminate\Support\Facades\Validator; +use Illuminate\Validation\Rule; - Validator::make($data, [ - 'avatar' => [ - 'required', - Rule::dimensions() - ->maxWidth(1000) - ->maxHeight(500) - ->ratio(3 / 2), - ], - ]); +Validator::make($data, [ + 'avatar' => [ + 'required', + Rule::dimensions() + ->maxWidth(1000) + ->maxHeight(500) + ->ratio(3 / 2), + ], +]); +``` #### distinct When validating arrays, the field under validation must not have any duplicate values: - 'foo.*.id' => 'distinct' +```php +'foo.*.id' => 'distinct' +``` Distinct uses loose variable comparisons by default. To use strict comparisons, you may add the `strict` parameter to your validation rule definition: - 'foo.*.id' => 'distinct:strict' +```php +'foo.*.id' => 'distinct:strict' +``` You may add `ignore_case` to the validation rule's arguments to make the rule ignore capitalization differences: - 'foo.*.id' => 'distinct:ignore_case' +```php +'foo.*.id' => 'distinct:ignore_case' +``` #### doesnt_start_with:_foo_,_bar_,... @@ -1346,7 +1480,9 @@ The field under validation must not end with one of the given values. The field under validation must be formatted as an email address. This validation rule utilizes the [`egulias/email-validator`](https://github.com/egulias/EmailValidator) package for validating the email address. By default, the `RFCValidation` validator is applied, but you can apply other validation styles as well: - 'email' => 'email:rfc,dns' +```php +'email' => 'email:rfc,dns' +``` The example above will apply the `RFCValidation` and `DNSCheckValidation` validations. Here's a full list of validation styles you can apply: @@ -1390,20 +1526,24 @@ The field under validation must end with one of the given values. The `Enum` rule is a class based rule that validates whether the field under validation contains a valid enum value. The `Enum` rule accepts the name of the enum as its only constructor argument. When validating primitive values, a backed Enum should be provided to the `Enum` rule: - use App\Enums\ServerStatus; - use Illuminate\Validation\Rule; +```php +use App\Enums\ServerStatus; +use Illuminate\Validation\Rule; - $request->validate([ - 'status' => [Rule::enum(ServerStatus::class)], - ]); +$request->validate([ + 'status' => [Rule::enum(ServerStatus::class)], +]); +``` The `Enum` rule's `only` and `except` methods may be used to limit which enum cases should be considered valid: - Rule::enum(ServerStatus::class) - ->only([ServerStatus::Pending, ServerStatus::Active]); +```php +Rule::enum(ServerStatus::class) + ->only([ServerStatus::Pending, ServerStatus::Active]); - Rule::enum(ServerStatus::class) - ->except([ServerStatus::Pending, ServerStatus::Active]); +Rule::enum(ServerStatus::class) + ->except([ServerStatus::Pending, ServerStatus::Active]); +``` The `when` method may be used to conditionally modify the `Enum` rule: @@ -1431,16 +1571,18 @@ The field under validation will be excluded from the request data returned by th If complex conditional exclusion logic is required, you may utilize the `Rule::excludeIf` method. This method accepts a boolean or a closure. When given a closure, the closure should return `true` or `false` to indicate if the field under validation should be excluded: - use Illuminate\Support\Facades\Validator; - use Illuminate\Validation\Rule; +```php +use Illuminate\Support\Facades\Validator; +use Illuminate\Validation\Rule; - Validator::make($request->all(), [ - 'role_id' => Rule::excludeIf($request->user()->is_admin), - ]); +Validator::make($request->all(), [ + 'role_id' => Rule::excludeIf($request->user()->is_admin), +]); - Validator::make($request->all(), [ - 'role_id' => Rule::excludeIf(fn () => $request->user()->is_admin), - ]); +Validator::make($request->all(), [ + 'role_id' => Rule::excludeIf(fn () => $request->user()->is_admin), +]); +``` #### exclude_unless:_anotherfield_,_value_ @@ -1465,7 +1607,9 @@ The field under validation must exist in a given database table. #### Basic Usage of Exists Rule - 'state' => 'exists:states' +```php +'state' => 'exists:states' +``` If the `column` option is not specified, the field name will be used. So, in this case, the rule will validate that the `states` database table contains a record with a `state` column value matching the request's `state` attribute value. @@ -1474,41 +1618,53 @@ If the `column` option is not specified, the field name will be used. So, in thi You may explicitly specify the database column name that should be used by the validation rule by placing it after the database table name: - 'state' => 'exists:states,abbreviation' +```php +'state' => 'exists:states,abbreviation' +``` Occasionally, you may need to specify a specific database connection to be used for the `exists` query. You can accomplish this by prepending the connection name to the table name: - 'email' => 'exists:connection.staff,email' +```php +'email' => 'exists:connection.staff,email' +``` Instead of specifying the table name directly, you may specify the Eloquent model which should be used to determine the table name: - 'user_id' => 'exists:App\Models\User,id' +```php +'user_id' => 'exists:App\Models\User,id' +``` If you would like to customize the query executed by the validation rule, you may use the `Rule` class to fluently define the rule. In this example, we'll also specify the validation rules as an array instead of using the `|` character to delimit them: - use Illuminate\Database\Query\Builder; - use Illuminate\Support\Facades\Validator; - use Illuminate\Validation\Rule; +```php +use Illuminate\Database\Query\Builder; +use Illuminate\Support\Facades\Validator; +use Illuminate\Validation\Rule; - Validator::make($data, [ - 'email' => [ - 'required', - Rule::exists('staff')->where(function (Builder $query) { - $query->where('account_id', 1); - }), - ], - ]); +Validator::make($data, [ + 'email' => [ + 'required', + Rule::exists('staff')->where(function (Builder $query) { + $query->where('account_id', 1); + }), + ], +]); +``` You may explicitly specify the database column name that should be used by the `exists` rule generated by the `Rule::exists` method by providing the column name as the second argument to the `exists` method: - 'state' => Rule::exists('states', 'abbreviation'), +```php +'state' => Rule::exists('states', 'abbreviation'), +``` #### extensions:_foo_,_bar_,... The file under validation must have a user-assigned extension corresponding to one of the listed extensions: - 'photo' => ['required', 'extensions:jpg,png'], +```php +'photo' => ['required', 'extensions:jpg,png'], +``` > [!WARNING] > You should never rely on validating a file by its user-assigned extension alone. This rule should typically always be used in combination with the [`mimes`](#rule-mimes) or [`mimetypes`](#rule-mimetypes) rules. @@ -1551,32 +1707,36 @@ The file under validation must be an image (jpg, jpeg, png, bmp, gif, or webp). The field under validation must be included in the given list of values. Since this rule often requires you to `implode` an array, the `Rule::in` method may be used to fluently construct the rule: - use Illuminate\Support\Facades\Validator; - use Illuminate\Validation\Rule; +```php +use Illuminate\Support\Facades\Validator; +use Illuminate\Validation\Rule; - Validator::make($data, [ - 'zones' => [ - 'required', - Rule::in(['first-zone', 'second-zone']), - ], - ]); +Validator::make($data, [ + 'zones' => [ + 'required', + Rule::in(['first-zone', 'second-zone']), + ], +]); +``` When the `in` rule is combined with the `array` rule, each value in the input array must be present within the list of values provided to the `in` rule. In the following example, the `LAS` airport code in the input array is invalid since it is not contained in the list of airports provided to the `in` rule: - use Illuminate\Support\Facades\Validator; - use Illuminate\Validation\Rule; +```php +use Illuminate\Support\Facades\Validator; +use Illuminate\Validation\Rule; - $input = [ - 'airports' => ['NYC', 'LAS'], - ]; +$input = [ + 'airports' => ['NYC', 'LAS'], +]; - Validator::make($input, [ - 'airports' => [ - 'required', - 'array', - ], - 'airports.*' => Rule::in(['NYC', 'LIT']), - ]); +Validator::make($input, [ + 'airports' => [ + 'required', + 'array', + ], + 'airports.*' => Rule::in(['NYC', 'LIT']), +]); +``` #### in_array:_anotherfield_.* @@ -1651,7 +1811,9 @@ The integer under validation must have a maximum length of _value_. The file under validation must match one of the given MIME types: - 'video' => 'mimetypes:video/avi,video/mpeg,video/quicktime' +```php +'video' => 'mimetypes:video/avi,video/mpeg,video/quicktime' +``` To determine the MIME type of the uploaded file, the file's contents will be read and the framework will attempt to guess the MIME type, which may be different from the client's provided MIME type. @@ -1660,7 +1822,9 @@ To determine the MIME type of the uploaded file, the file's contents will be rea The file under validation must have a MIME type corresponding to one of the listed extensions: - 'photo' => 'mimes:jpg,bmp,png' +```php +'photo' => 'mimes:jpg,bmp,png' +``` Even though you only need to specify the extensions, this rule actually validates the MIME type of the file by reading the file's contents and guessing its MIME type. A full listing of MIME types and their corresponding extensions may be found at the following location: @@ -1716,14 +1880,16 @@ The field under validation must not be present _only if_ all of the other specif The field under validation must not be included in the given list of values. The `Rule::notIn` method may be used to fluently construct the rule: - use Illuminate\Validation\Rule; +```php +use Illuminate\Validation\Rule; - Validator::make($data, [ - 'toppings' => [ - 'required', - Rule::notIn(['sprinkles', 'cherries']), - ], - ]); +Validator::make($data, [ + 'toppings' => [ + 'required', + Rule::notIn(['sprinkles', 'cherries']), + ], +]); +``` #### not_regex:_pattern_ @@ -1800,16 +1966,18 @@ The field under validation must be missing or empty if the _anotherfield_ field If complex conditional prohibition logic is required, you may utilize the `Rule::prohibitedIf` method. This method accepts a boolean or a closure. When given a closure, the closure should return `true` or `false` to indicate if the field under validation should be prohibited: - use Illuminate\Support\Facades\Validator; - use Illuminate\Validation\Rule; +```php +use Illuminate\Support\Facades\Validator; +use Illuminate\Validation\Rule; - Validator::make($request->all(), [ - 'role_id' => Rule::prohibitedIf($request->user()->is_admin), - ]); +Validator::make($request->all(), [ + 'role_id' => Rule::prohibitedIf($request->user()->is_admin), +]); - Validator::make($request->all(), [ - 'role_id' => Rule::prohibitedIf(fn () => $request->user()->is_admin), - ]); +Validator::make($request->all(), [ + 'role_id' => Rule::prohibitedIf(fn () => $request->user()->is_admin), +]); +``` #### prohibited_unless:_anotherfield_,_value_,... @@ -1870,16 +2038,18 @@ The field under validation must be present and not empty if the _anotherfield_ f If you would like to construct a more complex condition for the `required_if` rule, you may use the `Rule::requiredIf` method. This method accepts a boolean or a closure. When passed a closure, the closure should return `true` or `false` to indicate if the field under validation is required: - use Illuminate\Support\Facades\Validator; - use Illuminate\Validation\Rule; +```php +use Illuminate\Support\Facades\Validator; +use Illuminate\Validation\Rule; - Validator::make($request->all(), [ - 'role_id' => Rule::requiredIf($request->user()->is_admin), - ]); +Validator::make($request->all(), [ + 'role_id' => Rule::requiredIf($request->user()->is_admin), +]); - Validator::make($request->all(), [ - 'role_id' => Rule::requiredIf(fn () => $request->user()->is_admin), - ]); +Validator::make($request->all(), [ + 'role_id' => Rule::requiredIf(fn () => $request->user()->is_admin), +]); +``` #### required_if_accepted:_anotherfield_,... @@ -1931,17 +2101,19 @@ The given _field_ must match the field under validation. The field under validation must have a size matching the given _value_. For string data, _value_ corresponds to the number of characters. For numeric data, _value_ corresponds to a given integer value (the attribute must also have the `numeric` or `integer` rule). For an array, _size_ corresponds to the `count` of the array. For files, _size_ corresponds to the file size in kilobytes. Let's look at some examples: - // Validate that a string is exactly 12 characters long... - 'title' => 'size:12'; +```php +// Validate that a string is exactly 12 characters long... +'title' => 'size:12'; - // Validate that a provided integer equals 10... - 'seats' => 'integer|size:10'; +// Validate that a provided integer equals 10... +'seats' => 'integer|size:10'; - // Validate that an array has exactly 5 elements... - 'tags' => 'array|size:5'; +// Validate that an array has exactly 5 elements... +'tags' => 'array|size:5'; - // Validate that an uploaded file is exactly 512 kilobytes... - 'image' => 'file|size:512'; +// Validate that an uploaded file is exactly 512 kilobytes... +'image' => 'file|size:512'; +``` #### starts_with:_foo_,_bar_,... @@ -1960,11 +2132,13 @@ The field under validation must be a valid timezone identifier according to the The arguments [accepted by the `DateTimeZone::listIdentifiers` method](https://www.php.net/manual/en/datetimezone.listidentifiers.php) may also be provided to this validation rule: - 'timezone' => 'required|timezone:all'; +```php +'timezone' => 'required|timezone:all'; - 'timezone' => 'required|timezone:Africa'; +'timezone' => 'required|timezone:Africa'; - 'timezone' => 'required|timezone:per_country,US'; +'timezone' => 'required|timezone:per_country,US'; +``` #### unique:_table_,_column_ @@ -1975,17 +2149,23 @@ The field under validation must not exist within the given database table. Instead of specifying the table name directly, you may specify the Eloquent model which should be used to determine the table name: - 'email' => 'unique:App\Models\User,email_address' +```php +'email' => 'unique:App\Models\User,email_address' +``` The `column` option may be used to specify the field's corresponding database column. If the `column` option is not specified, the name of the field under validation will be used. - 'email' => 'unique:users,email_address' +```php +'email' => 'unique:users,email_address' +``` **Specifying a Custom Database Connection** Occasionally, you may need to set a custom connection for database queries made by the Validator. To accomplish this, you may prepend the connection name to the table name: - 'email' => 'unique:connection.users,email_address' +```php +'email' => 'unique:connection.users,email_address' +``` **Forcing a Unique Rule to Ignore a Given ID:** @@ -1993,46 +2173,60 @@ Sometimes, you may wish to ignore a given ID during unique validation. For examp To instruct the validator to ignore the user's ID, we'll use the `Rule` class to fluently define the rule. In this example, we'll also specify the validation rules as an array instead of using the `|` character to delimit the rules: - use Illuminate\Support\Facades\Validator; - use Illuminate\Validation\Rule; +```php +use Illuminate\Support\Facades\Validator; +use Illuminate\Validation\Rule; - Validator::make($data, [ - 'email' => [ - 'required', - Rule::unique('users')->ignore($user->id), - ], - ]); +Validator::make($data, [ + 'email' => [ + 'required', + Rule::unique('users')->ignore($user->id), + ], +]); +``` > [!WARNING] > You should never pass any user controlled request input into the `ignore` method. Instead, you should only pass a system generated unique ID such as an auto-incrementing ID or UUID from an Eloquent model instance. Otherwise, your application will be vulnerable to an SQL injection attack. Instead of passing the model key's value to the `ignore` method, you may also pass the entire model instance. Laravel will automatically extract the key from the model: - Rule::unique('users')->ignore($user) +```php +Rule::unique('users')->ignore($user) +``` If your table uses a primary key column name other than `id`, you may specify the name of the column when calling the `ignore` method: - Rule::unique('users')->ignore($user->id, 'user_id') +```php +Rule::unique('users')->ignore($user->id, 'user_id') +``` By default, the `unique` rule will check the uniqueness of the column matching the name of the attribute being validated. However, you may pass a different column name as the second argument to the `unique` method: - Rule::unique('users', 'email_address')->ignore($user->id) +```php +Rule::unique('users', 'email_address')->ignore($user->id) +``` **Adding Additional Where Clauses:** You may specify additional query conditions by customizing the query using the `where` method. For example, let's add a query condition that scopes the query to only search records that have an `account_id` column value of `1`: - 'email' => Rule::unique('users')->where(fn (Builder $query) => $query->where('account_id', 1)) +```php +'email' => Rule::unique('users')->where(fn (Builder $query) => $query->where('account_id', 1)) +``` **Ignoring Soft Deleteded Records in Unique Checks:** By default, the unique rule includes soft deleted records when determining uniqueness. To exclude soft deleted records from the uniqueness check, you may invoke the `withoutTrashed` method: - Rule::unique('users')->withoutTrashed(); +```php +Rule::unique('users')->withoutTrashed(); +``` If your model uses a column name other than `deleted_at` for soft deleted records, you may provide the column name when invoking the `withoutTrashed` method: - Rule::unique('users')->withoutTrashed('was_deleted_at'); +```php +Rule::unique('users')->withoutTrashed('was_deleted_at'); +``` #### uppercase @@ -2076,30 +2270,36 @@ You may also validate that the given UUID matches a UUID specification by versio You may occasionally wish to not validate a given field if another field has a given value. You may accomplish this using the `exclude_if` validation rule. In this example, the `appointment_date` and `doctor_name` fields will not be validated if the `has_appointment` field has a value of `false`: - use Illuminate\Support\Facades\Validator; +```php +use Illuminate\Support\Facades\Validator; - $validator = Validator::make($data, [ - 'has_appointment' => 'required|boolean', - 'appointment_date' => 'exclude_if:has_appointment,false|required|date', - 'doctor_name' => 'exclude_if:has_appointment,false|required|string', - ]); +$validator = Validator::make($data, [ + 'has_appointment' => 'required|boolean', + 'appointment_date' => 'exclude_if:has_appointment,false|required|date', + 'doctor_name' => 'exclude_if:has_appointment,false|required|string', +]); +``` Alternatively, you may use the `exclude_unless` rule to not validate a given field unless another field has a given value: - $validator = Validator::make($data, [ - 'has_appointment' => 'required|boolean', - 'appointment_date' => 'exclude_unless:has_appointment,true|required|date', - 'doctor_name' => 'exclude_unless:has_appointment,true|required|string', - ]); +```php +$validator = Validator::make($data, [ + 'has_appointment' => 'required|boolean', + 'appointment_date' => 'exclude_unless:has_appointment,true|required|date', + 'doctor_name' => 'exclude_unless:has_appointment,true|required|string', +]); +``` #### Validating When Present In some situations, you may wish to run validation checks against a field **only** if that field is present in the data being validated. To quickly accomplish this, add the `sometimes` rule to your rule list: - $validator = Validator::make($data, [ - 'email' => 'sometimes|required|email', - ]); +```php +$validator = Validator::make($data, [ + 'email' => 'sometimes|required|email', +]); +``` In the example above, the `email` field will only be validated if it is present in the `$data` array. @@ -2111,26 +2311,32 @@ In the example above, the `email` field will only be validated if it is present Sometimes you may wish to add validation rules based on more complex conditional logic. For example, you may wish to require a given field only if another field has a greater value than 100. Or, you may need two fields to have a given value only when another field is present. Adding these validation rules doesn't have to be a pain. First, create a `Validator` instance with your _static rules_ that never change: - use Illuminate\Support\Facades\Validator; +```php +use Illuminate\Support\Facades\Validator; - $validator = Validator::make($request->all(), [ - 'email' => 'required|email', - 'games' => 'required|numeric', - ]); +$validator = Validator::make($request->all(), [ + 'email' => 'required|email', + 'games' => 'required|numeric', +]); +``` Let's assume our web application is for game collectors. If a game collector registers with our application and they own more than 100 games, we want them to explain why they own so many games. For example, perhaps they run a game resale shop, or maybe they just enjoy collecting games. To conditionally add this requirement, we can use the `sometimes` method on the `Validator` instance. - use Illuminate\Support\Fluent; +```php +use Illuminate\Support\Fluent; - $validator->sometimes('reason', 'required|max:500', function (Fluent $input) { - return $input->games >= 100; - }); +$validator->sometimes('reason', 'required|max:500', function (Fluent $input) { + return $input->games >= 100; +}); +``` The first argument passed to the `sometimes` method is the name of the field we are conditionally validating. The second argument is a list of the rules we want to add. If the closure passed as the third argument returns `true`, the rules will be added. This method makes it a breeze to build complex conditional validations. You may even add conditional validations for several fields at once: - $validator->sometimes(['reason', 'cost'], 'required', function (Fluent $input) { - return $input->games >= 100; - }); +```php +$validator->sometimes(['reason', 'cost'], 'required', function (Fluent $input) { + return $input->games >= 100; +}); +``` > [!NOTE] > The `$input` parameter passed to your closure will be an instance of `Illuminate\Support\Fluent` and may be used to access your input and files under validation. @@ -2140,26 +2346,28 @@ The first argument passed to the `sometimes` method is the name of the field we Sometimes you may want to validate a field based on another field in the same nested array whose index you do not know. In these situations, you may allow your closure to receive a second argument which will be the current individual item in the array being validated: - $input = [ - 'channels' => [ - [ - 'type' => 'email', - 'address' => 'abigail@example.com', - ], - [ - 'type' => 'url', - 'address' => 'https://example.com', - ], +```php +$input = [ + 'channels' => [ + [ + 'type' => 'email', + 'address' => 'abigail@example.com', ], - ]; + [ + 'type' => 'url', + 'address' => 'https://example.com', + ], + ], +]; - $validator->sometimes('channels.*.address', 'email', function (Fluent $input, Fluent $item) { - return $item->type === 'email'; - }); +$validator->sometimes('channels.*.address', 'email', function (Fluent $input, Fluent $item) { + return $item->type === 'email'; +}); - $validator->sometimes('channels.*.address', 'url', function (Fluent $input, Fluent $item) { - return $item->type !== 'email'; - }); +$validator->sometimes('channels.*.address', 'url', function (Fluent $input, Fluent $item) { + return $item->type !== 'email'; +}); +``` Like the `$input` parameter passed to the closure, the `$item` parameter is an instance of `Illuminate\Support\Fluent` when the attribute data is an array; otherwise, it is a string. @@ -2168,19 +2376,21 @@ Like the `$input` parameter passed to the closure, the `$item` parameter is an i As discussed in the [`array` validation rule documentation](#rule-array), the `array` rule accepts a list of allowed array keys. If any additional keys are present within the array, validation will fail: - use Illuminate\Support\Facades\Validator; +```php +use Illuminate\Support\Facades\Validator; - $input = [ - 'user' => [ - 'name' => 'Taylor Otwell', - 'username' => 'taylorotwell', - 'admin' => true, - ], - ]; +$input = [ + 'user' => [ + 'name' => 'Taylor Otwell', + 'username' => 'taylorotwell', + 'admin' => true, + ], +]; - Validator::make($input, [ - 'user' => 'array:name,username', - ]); +Validator::make($input, [ + 'user' => 'array:name,username', +]); +``` In general, you should always specify the array keys that are allowed to be present within your array. Otherwise, the validator's `validate` and `validated` methods will return all of the validated data, including the array and all of its keys, even if those keys were not validated by other nested array validation rules. @@ -2189,93 +2399,107 @@ In general, you should always specify the array keys that are allowed to be pres Validating nested array based form input fields doesn't have to be a pain. You may use "dot notation" to validate attributes within an array. For example, if the incoming HTTP request contains a `photos[profile]` field, you may validate it like so: - use Illuminate\Support\Facades\Validator; +```php +use Illuminate\Support\Facades\Validator; - $validator = Validator::make($request->all(), [ - 'photos.profile' => 'required|image', - ]); +$validator = Validator::make($request->all(), [ + 'photos.profile' => 'required|image', +]); +``` You may also validate each element of an array. For example, to validate that each email in a given array input field is unique, you may do the following: - $validator = Validator::make($request->all(), [ - 'person.*.email' => 'email|unique:users', - 'person.*.first_name' => 'required_with:person.*.last_name', - ]); +```php +$validator = Validator::make($request->all(), [ + 'person.*.email' => 'email|unique:users', + 'person.*.first_name' => 'required_with:person.*.last_name', +]); +``` Likewise, you may use the `*` character when specifying [custom validation messages in your language files](#custom-messages-for-specific-attributes), making it a breeze to use a single validation message for array based fields: - 'custom' => [ - 'person.*.email' => [ - 'unique' => 'Each person must have a unique email address', - ] - ], +```php +'custom' => [ + 'person.*.email' => [ + 'unique' => 'Each person must have a unique email address', + ] +], +``` #### Accessing Nested Array Data Sometimes you may need to access the value for a given nested array element when assigning validation rules to the attribute. You may accomplish this using the `Rule::forEach` method. The `forEach` method accepts a closure that will be invoked for each iteration of the array attribute under validation and will receive the attribute's value and explicit, fully-expanded attribute name. The closure should return an array of rules to assign to the array element: - use App\Rules\HasPermission; - use Illuminate\Support\Facades\Validator; - use Illuminate\Validation\Rule; +```php +use App\Rules\HasPermission; +use Illuminate\Support\Facades\Validator; +use Illuminate\Validation\Rule; - $validator = Validator::make($request->all(), [ - 'companies.*.id' => Rule::forEach(function (string|null $value, string $attribute) { - return [ - Rule::exists(Company::class, 'id'), - new HasPermission('manage-company', $value), - ]; - }), - ]); +$validator = Validator::make($request->all(), [ + 'companies.*.id' => Rule::forEach(function (string|null $value, string $attribute) { + return [ + Rule::exists(Company::class, 'id'), + new HasPermission('manage-company', $value), + ]; + }), +]); +``` ### Error Message Indexes and Positions When validating arrays, you may want to reference the index or position of a particular item that failed validation within the error message displayed by your application. To accomplish this, you may include the `:index` (starts from `0`) and `:position` (starts from `1`) placeholders within your [custom validation message](#manual-customizing-the-error-messages): - use Illuminate\Support\Facades\Validator; - - $input = [ - 'photos' => [ - [ - 'name' => 'BeachVacation.jpg', - 'description' => 'A photo of my beach vacation!', - ], - [ - 'name' => 'GrandCanyon.jpg', - 'description' => '', - ], +```php +use Illuminate\Support\Facades\Validator; + +$input = [ + 'photos' => [ + [ + 'name' => 'BeachVacation.jpg', + 'description' => 'A photo of my beach vacation!', ], - ]; + [ + 'name' => 'GrandCanyon.jpg', + 'description' => '', + ], + ], +]; - Validator::validate($input, [ - 'photos.*.description' => 'required', - ], [ - 'photos.*.description.required' => 'Please describe photo #:position.', - ]); +Validator::validate($input, [ + 'photos.*.description' => 'required', +], [ + 'photos.*.description.required' => 'Please describe photo #:position.', +]); +``` Given the example above, validation will fail and the user will be presented with the following error of _"Please describe photo #2."_ If necessary, you may reference more deeply nested indexes and positions via `second-index`, `second-position`, `third-index`, `third-position`, etc. - 'photos.*.attributes.*.string' => 'Invalid attribute for photo #:second-position.', +```php +'photos.*.attributes.*.string' => 'Invalid attribute for photo #:second-position.', +``` ## Validating Files Laravel provides a variety of validation rules that may be used to validate uploaded files, such as `mimes`, `image`, `min`, and `max`. While you are free to specify these rules individually when validating files, Laravel also offers a fluent file validation rule builder that you may find convenient: - use Illuminate\Support\Facades\Validator; - use Illuminate\Validation\Rules\File; +```php +use Illuminate\Support\Facades\Validator; +use Illuminate\Validation\Rules\File; - Validator::validate($input, [ - 'attachment' => [ - 'required', - File::types(['mp3', 'wav']) - ->min(1024) - ->max(12 * 1024), - ], - ]); +Validator::validate($input, [ + 'attachment' => [ + 'required', + File::types(['mp3', 'wav']) + ->min(1024) + ->max(12 * 1024), + ], +]); +``` #### Validating File Types @@ -2302,19 +2526,21 @@ If your application accepts images uploaded by your users, you may use the `File In addition, the `dimensions` rule may be used to limit the dimensions of the image: - use Illuminate\Support\Facades\Validator; - use Illuminate\Validation\Rule; - use Illuminate\Validation\Rules\File; - - Validator::validate($input, [ - 'photo' => [ - 'required', - File::image() - ->min(1024) - ->max(12 * 1024) - ->dimensions(Rule::dimensions()->maxWidth(1000)->maxHeight(500)), - ], - ]); +```php +use Illuminate\Support\Facades\Validator; +use Illuminate\Validation\Rule; +use Illuminate\Validation\Rules\File; + +Validator::validate($input, [ + 'photo' => [ + 'required', + File::image() + ->min(1024) + ->max(12 * 1024) + ->dimensions(Rule::dimensions()->maxWidth(1000)->maxHeight(500)), + ], +]); +``` > [!NOTE] > More information regarding validating image dimensions may be found in the [dimension rule documentation](#rule-dimensions). @@ -2346,49 +2572,59 @@ File::image()->dimensions( To ensure that passwords have an adequate level of complexity, you may use Laravel's `Password` rule object: - use Illuminate\Support\Facades\Validator; - use Illuminate\Validation\Rules\Password; +```php +use Illuminate\Support\Facades\Validator; +use Illuminate\Validation\Rules\Password; - $validator = Validator::make($request->all(), [ - 'password' => ['required', 'confirmed', Password::min(8)], - ]); +$validator = Validator::make($request->all(), [ + 'password' => ['required', 'confirmed', Password::min(8)], +]); +``` The `Password` rule object allows you to easily customize the password complexity requirements for your application, such as specifying that passwords require at least one letter, number, symbol, or characters with mixed casing: - // Require at least 8 characters... - Password::min(8) +```php +// Require at least 8 characters... +Password::min(8) - // Require at least one letter... - Password::min(8)->letters() +// Require at least one letter... +Password::min(8)->letters() - // Require at least one uppercase and one lowercase letter... - Password::min(8)->mixedCase() +// Require at least one uppercase and one lowercase letter... +Password::min(8)->mixedCase() - // Require at least one number... - Password::min(8)->numbers() +// Require at least one number... +Password::min(8)->numbers() - // Require at least one symbol... - Password::min(8)->symbols() +// Require at least one symbol... +Password::min(8)->symbols() +``` In addition, you may ensure that a password has not been compromised in a public password data breach leak using the `uncompromised` method: - Password::min(8)->uncompromised() +```php +Password::min(8)->uncompromised() +``` Internally, the `Password` rule object uses the [k-Anonymity](https://en.wikipedia.org/wiki/K-anonymity) model to determine if a password has been leaked via the [haveibeenpwned.com](https://haveibeenpwned.com) service without sacrificing the user's privacy or security. By default, if a password appears at least once in a data leak, it will be considered compromised. You can customize this threshold using the first argument of the `uncompromised` method: - // Ensure the password appears less than 3 times in the same data leak... - Password::min(8)->uncompromised(3); +```php +// Ensure the password appears less than 3 times in the same data leak... +Password::min(8)->uncompromised(3); +``` Of course, you may chain all the methods in the examples above: - Password::min(8) - ->letters() - ->mixedCase() - ->numbers() - ->symbols() - ->uncompromised() +```php +Password::min(8) + ->letters() + ->mixedCase() + ->numbers() + ->symbols() + ->uncompromised() +``` #### Defining Default Password Rules @@ -2415,17 +2651,21 @@ public function boot(): void Then, when you would like to apply the default rules to a particular password undergoing validation, you may invoke the `defaults` method with no arguments: - 'password' => ['required', Password::defaults()], +```php +'password' => ['required', Password::defaults()], +``` Occasionally, you may want to attach additional validation rules to your default password validation rules. You may use the `rules` method to accomplish this: - use App\Rules\ZxcvbnRule; +```php +use App\Rules\ZxcvbnRule; - Password::defaults(function () { - $rule = Password::min(8)->rules([new ZxcvbnRule]); +Password::defaults(function () { + $rule = Password::min(8)->rules([new ZxcvbnRule]); - // ... - }); + // ... +}); +``` ## Custom Validation Rules @@ -2441,147 +2681,163 @@ php artisan make:rule Uppercase Once the rule has been created, we are ready to define its behavior. A rule object contains a single method: `validate`. This method receives the attribute name, its value, and a callback that should be invoked on failure with the validation error message: - validate([ - 'name' => ['required', 'string', new Uppercase], - ]); +$request->validate([ + 'name' => ['required', 'string', new Uppercase], +]); +``` #### Translating Validation Messages Instead of providing a literal error message to the `$fail` closure, you may also provide a [translation string key](/docs/{{version}}/localization) and instruct Laravel to translate the error message: - if (strtoupper($value) !== $value) { - $fail('validation.uppercase')->translate(); - } +```php +if (strtoupper($value) !== $value) { + $fail('validation.uppercase')->translate(); +} +``` If necessary, you may provide placeholder replacements and the preferred language as the first and second arguments to the `translate` method: - $fail('validation.location')->translate([ - 'value' => $this->value, - ], 'fr') +```php +$fail('validation.location')->translate([ + 'value' => $this->value, +], 'fr') +``` #### Accessing Additional Data If your custom validation rule class needs to access all of the other data undergoing validation, your rule class may implement the `Illuminate\Contracts\Validation\DataAwareRule` interface. This interface requires your class to define a `setData` method. This method will automatically be invoked by Laravel (before validation proceeds) with all of the data under validation: - - */ - protected $data = []; +class Uppercase implements DataAwareRule, ValidationRule +{ + /** + * All of the data under validation. + * + * @var array + */ + protected $data = []; - // ... + // ... - /** - * Set the data under validation. - * - * @param array $data - */ - public function setData(array $data): static - { - $this->data = $data; + /** + * Set the data under validation. + * + * @param array $data + */ + public function setData(array $data): static + { + $this->data = $data; - return $this; - } + return $this; } +} +``` Or, if your validation rule requires access to the validator instance performing the validation, you may implement the `ValidatorAwareRule` interface: - validator = $validator; + /** + * Set the current validator. + */ + public function setValidator(Validator $validator): static + { + $this->validator = $validator; - return $this; - } + return $this; } +} +``` ### Using Closures If you only need the functionality of a custom rule once throughout your application, you may use a closure instead of a rule object. The closure receives the attribute's name, the attribute's value, and a `$fail` callback that should be called if validation fails: - use Illuminate\Support\Facades\Validator; - use Closure; - - $validator = Validator::make($request->all(), [ - 'title' => [ - 'required', - 'max:255', - function (string $attribute, mixed $value, Closure $fail) { - if ($value === 'foo') { - $fail("The {$attribute} is invalid."); - } - }, - ], - ]); +```php +use Illuminate\Support\Facades\Validator; +use Closure; + +$validator = Validator::make($request->all(), [ + 'title' => [ + 'required', + 'max:255', + function (string $attribute, mixed $value, Closure $fail) { + if ($value === 'foo') { + $fail("The {$attribute} is invalid."); + } + }, + ], +]); +``` ### Implicit Rules By default, when an attribute being validated is not present or contains an empty string, normal validation rules, including custom rules, are not run. For example, the [`unique`](#rule-unique) rule will not be run against an empty string: - use Illuminate\Support\Facades\Validator; +```php +use Illuminate\Support\Facades\Validator; - $rules = ['name' => 'unique:users,name']; +$rules = ['name' => 'unique:users,name']; - $input = ['name' => '']; +$input = ['name' => '']; - Validator::make($input, $rules)->passes(); // true +Validator::make($input, $rules)->passes(); // true +``` For a custom rule to run even when an attribute is empty, the rule must imply that the attribute is required. To quickly generate a new implicit rule object, you may use the `make:rule` Artisan command with the `--implicit` option: diff --git a/verification.md b/verification.md index d6c0a752499..c959d72835e 100644 --- a/verification.md +++ b/verification.md @@ -24,28 +24,32 @@ Many web applications require users to verify their email addresses before using Before getting started, verify that your `App\Models\User` model implements the `Illuminate\Contracts\Auth\MustVerifyEmail` contract: - ### Database Preparation @@ -66,9 +70,11 @@ Third, a route will be needed to resend a verification link if the user accident As mentioned previously, a route should be defined that will return a view instructing the user to click the email verification link that was emailed to them by Laravel after registration. This view will be displayed to users when they try to access other parts of the application without verifying their email address first. Remember, the link is automatically emailed to the user as long as your `App\Models\User` model implements the `MustVerifyEmail` interface: - Route::get('/email/verify', function () { - return view('auth.verify-email'); - })->middleware('auth')->name('verification.notice'); +```php +Route::get('/email/verify', function () { + return view('auth.verify-email'); +})->middleware('auth')->name('verification.notice'); +``` The route that returns the email verification notice should be named `verification.notice`. It is important that the route is assigned this exact name since the `verified` middleware [included with Laravel](#protecting-routes) will automatically redirect to this route name if a user has not verified their email address. @@ -80,13 +86,15 @@ The route that returns the email verification notice should be named `verificati Next, we need to define a route that will handle requests generated when the user clicks the email verification link that was emailed to them. This route should be named `verification.verify` and be assigned the `auth` and `signed` middlewares: - use Illuminate\Foundation\Auth\EmailVerificationRequest; +```php +use Illuminate\Foundation\Auth\EmailVerificationRequest; - Route::get('/email/verify/{id}/{hash}', function (EmailVerificationRequest $request) { - $request->fulfill(); +Route::get('/email/verify/{id}/{hash}', function (EmailVerificationRequest $request) { + $request->fulfill(); - return redirect('/home'); - })->middleware(['auth', 'signed'])->name('verification.verify'); + return redirect('/home'); +})->middleware(['auth', 'signed'])->name('verification.verify'); +``` Before moving on, let's take a closer look at this route. First, you'll notice we are using an `EmailVerificationRequest` request type instead of the typical `Illuminate\Http\Request` instance. The `EmailVerificationRequest` is a [form request](/docs/{{version}}/validation#form-request-validation) that is included with Laravel. This request will automatically take care of validating the request's `id` and `hash` parameters. @@ -97,22 +105,26 @@ Next, we can proceed directly to calling the `fulfill` method on the request. Th Sometimes a user may misplace or accidentally delete the email address verification email. To accommodate this, you may wish to define a route to allow the user to request that the verification email be resent. You may then make a request to this route by placing a simple form submission button within your [verification notice view](#the-email-verification-notice): - use Illuminate\Http\Request; +```php +use Illuminate\Http\Request; - Route::post('/email/verification-notification', function (Request $request) { - $request->user()->sendEmailVerificationNotification(); +Route::post('/email/verification-notification', function (Request $request) { + $request->user()->sendEmailVerificationNotification(); - return back()->with('message', 'Verification link sent!'); - })->middleware(['auth', 'throttle:6,1'])->name('verification.send'); + return back()->with('message', 'Verification link sent!'); +})->middleware(['auth', 'throttle:6,1'])->name('verification.send'); +``` ### Protecting Routes [Route middleware](/docs/{{version}}/middleware) may be used to only allow verified users to access a given route. Laravel includes a `verified` [middleware alias](/docs/{{version}}/middleware#middleware-aliases), which is an alias for the `Illuminate\Auth\Middleware\EnsureEmailIsVerified` middleware class. Since this alias is already automatically registered by Laravel, all you need to do is attach the `verified` middleware to a route definition. Typically, this middleware is paired with the `auth` middleware: - Route::get('/profile', function () { - // Only verified users may access this route... - })->middleware(['auth', 'verified']); +```php +Route::get('/profile', function () { + // Only verified users may access this route... +})->middleware(['auth', 'verified']); +``` If an unverified user attempts to access a route that has been assigned this middleware, they will automatically be redirected to the `verification.notice` [named route](/docs/{{version}}/routing#named-routes). @@ -126,23 +138,25 @@ Although the default email verification notification should satisfy the requirem To get started, pass a closure to the `toMailUsing` method provided by the `Illuminate\Auth\Notifications\VerifyEmail` notification. The closure will receive the notifiable model instance that is receiving the notification as well as the signed email verification URL that the user must visit to verify their email address. The closure should return an instance of `Illuminate\Notifications\Messages\MailMessage`. Typically, you should call the `toMailUsing` method from the `boot` method of your application's `AppServiceProvider` class: - use Illuminate\Auth\Notifications\VerifyEmail; - use Illuminate\Notifications\Messages\MailMessage; - - /** - * Bootstrap any application services. - */ - public function boot(): void - { - // ... - - VerifyEmail::toMailUsing(function (object $notifiable, string $url) { - return (new MailMessage) - ->subject('Verify Email Address') - ->line('Click the button below to verify your email address.') - ->action('Verify Email Address', $url); - }); - } +```php +use Illuminate\Auth\Notifications\VerifyEmail; +use Illuminate\Notifications\Messages\MailMessage; + +/** + * Bootstrap any application services. + */ +public function boot(): void +{ + // ... + + VerifyEmail::toMailUsing(function (object $notifiable, string $url) { + return (new MailMessage) + ->subject('Verify Email Address') + ->line('Click the button below to verify your email address.') + ->action('Verify Email Address', $url); + }); +} +``` > [!NOTE] > To learn more about mail notifications, please consult the [mail notification documentation](/docs/{{version}}/notifications#mail-notifications). diff --git a/views.md b/views.md index d57a46f1cce..cc55a972431 100644 --- a/views.md +++ b/views.md @@ -31,9 +31,11 @@ Views separate your controller / application logic from your presentation logic Since this view is stored at `resources/views/greeting.blade.php`, we may return it using the global `view` helper like so: - Route::get('/', function () { - return view('greeting', ['name' => 'James']); - }); +```php +Route::get('/', function () { + return view('greeting', ['name' => 'James']); +}); +``` > [!NOTE] > Looking for more information on how to write Blade templates? Check out the full [Blade documentation](/docs/{{version}}/blade) to get started. @@ -58,15 +60,19 @@ The `.blade.php` extension informs the framework that the file contains a [Blade Once you have created a view, you may return it from one of your application's routes or controllers using the global `view` helper: - Route::get('/', function () { - return view('greeting', ['name' => 'James']); - }); +```php +Route::get('/', function () { + return view('greeting', ['name' => 'James']); +}); +``` Views may also be returned using the `View` facade: - use Illuminate\Support\Facades\View; +```php +use Illuminate\Support\Facades\View; - return View::make('greeting', ['name' => 'James']); +return View::make('greeting', ['name' => 'James']); +``` As you can see, the first argument passed to the `view` helper corresponds to the name of the view file in the `resources/views` directory. The second argument is an array of data that should be made available to the view. In this case, we are passing the `name` variable, which is displayed in the view using [Blade syntax](/docs/{{version}}/blade). @@ -75,7 +81,9 @@ As you can see, the first argument passed to the `view` helper corresponds to th Views may also be nested within subdirectories of the `resources/views` directory. "Dot" notation may be used to reference nested views. For example, if your view is stored at `resources/views/admin/profile.blade.php`, you may return it from one of your application's routes / controllers like so: - return view('admin.profile', $data); +```php +return view('admin.profile', $data); +``` > [!WARNING] > View directory names should not contain the `.` character. @@ -85,65 +93,75 @@ Views may also be nested within subdirectories of the `resources/views` director Using the `View` facade's `first` method, you may create the first view that exists in a given array of views. This may be useful if your application or package allows views to be customized or overwritten: - use Illuminate\Support\Facades\View; +```php +use Illuminate\Support\Facades\View; - return View::first(['custom.admin', 'admin'], $data); +return View::first(['custom.admin', 'admin'], $data); +``` ### Determining if a View Exists If you need to determine if a view exists, you may use the `View` facade. The `exists` method will return `true` if the view exists: - use Illuminate\Support\Facades\View; +```php +use Illuminate\Support\Facades\View; - if (View::exists('admin.profile')) { - // ... - } +if (View::exists('admin.profile')) { + // ... +} +``` ## Passing Data to Views As you saw in the previous examples, you may pass an array of data to views to make that data available to the view: - return view('greetings', ['name' => 'Victoria']); +```php +return view('greetings', ['name' => 'Victoria']); +``` When passing information in this manner, the data should be an array with key / value pairs. After providing data to a view, you can then access each value within your view using the data's keys, such as ``. As an alternative to passing a complete array of data to the `view` helper function, you may use the `with` method to add individual pieces of data to the view. The `with` method returns an instance of the view object so that you can continue chaining methods before returning the view: - return view('greeting') - ->with('name', 'Victoria') - ->with('occupation', 'Astronaut'); +```php +return view('greeting') + ->with('name', 'Victoria') + ->with('occupation', 'Astronaut'); +``` ### Sharing Data With All Views Occasionally, you may need to share data with all views that are rendered by your application. You may do so using the `View` facade's `share` method. Typically, you should place calls to the `share` method within a service provider's `boot` method. You are free to add them to the `App\Providers\AppServiceProvider` class or generate a separate service provider to house them: - ## View Composers @@ -154,70 +172,74 @@ Typically, view composers will be registered within one of your application's [s We'll use the `View` facade's `composer` method to register the view composer. Laravel does not include a default directory for class based view composers, so you are free to organize them however you wish. For example, you could create an `app/View/Composers` directory to house all of your application's view composers: - with('count', $this->users->count()); - } + $view->with('count', $this->users->count()); } +} +``` As you can see, all view composers are resolved via the [service container](/docs/{{version}}/container), so you may type-hint any dependencies you need within a composer's constructor. @@ -226,32 +248,38 @@ As you can see, all view composers are resolved via the [service container](/doc You may attach a view composer to multiple views at once by passing an array of views as the first argument to the `composer` method: - use App\Views\Composers\MultiComposer; - use Illuminate\Support\Facades\View; +```php +use App\Views\Composers\MultiComposer; +use Illuminate\Support\Facades\View; - View::composer( - ['profile', 'dashboard'], - MultiComposer::class - ); +View::composer( + ['profile', 'dashboard'], + MultiComposer::class +); +``` The `composer` method also accepts the `*` character as a wildcard, allowing you to attach a composer to all views: - use Illuminate\Support\Facades; - use Illuminate\View\View; +```php +use Illuminate\Support\Facades; +use Illuminate\View\View; - Facades\View::composer('*', function (View $view) { - // ... - }); +Facades\View::composer('*', function (View $view) { + // ... +}); +``` ### View Creators View "creators" are very similar to view composers; however, they are executed immediately after the view is instantiated instead of waiting until the view is about to render. To register a view creator, use the `creator` method: - use App\View\Creators\ProfileCreator; - use Illuminate\Support\Facades\View; +```php +use App\View\Creators\ProfileCreator; +use Illuminate\Support\Facades\View; - View::creator('profile', ProfileCreator::class); +View::creator('profile', ProfileCreator::class); +``` ## Optimizing Views diff --git a/vite.md b/vite.md index 4464c5d1cc3..7bdd9b9e10f 100644 --- a/vite.md +++ b/vite.md @@ -552,13 +552,15 @@ export default defineConfig({ It is common in JavaScript applications to [create aliases](#aliases) to regularly referenced directories. But, you may also create aliases to use in Blade by using the `macro` method on the `Illuminate\Support\Facades\Vite` class. Typically, "macros" should be defined within the `boot` method of a [service provider](/docs/{{version}}/providers): - /** - * Bootstrap any application services. - */ - public function boot(): void - { - Vite::macro('image', fn (string $asset) => $this->asset("resources/images/{$asset}")); - } +```php +/** + * Bootstrap any application services. + */ +public function boot(): void +{ + Vite::macro('image', fn (string $asset) => $this->asset("resources/images/{$asset}")); +} +``` Once a macro has been defined, it can be invoked within your templates. For example, we can use the `image` macro defined above to reference an asset located at `resources/images/logo.png`: