commit 9b2b03b2efb655b24e8815d7cf2385a00528097e Author: Matthias Guillitte Date: Tue Sep 2 15:19:23 2025 +0200 Init diff --git a/laravelDocScrappy/__init__.py b/laravelDocScrappy/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/laravelDocScrappy/__pycache__/__init__.cpython-312.pyc b/laravelDocScrappy/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000..8487060 Binary files /dev/null and b/laravelDocScrappy/__pycache__/__init__.cpython-312.pyc differ diff --git a/laravelDocScrappy/__pycache__/settings.cpython-312.pyc b/laravelDocScrappy/__pycache__/settings.cpython-312.pyc new file mode 100644 index 0000000..17609e2 Binary files /dev/null and b/laravelDocScrappy/__pycache__/settings.cpython-312.pyc differ diff --git a/laravelDocScrappy/items.py b/laravelDocScrappy/items.py new file mode 100644 index 0000000..002e7dc --- /dev/null +++ b/laravelDocScrappy/items.py @@ -0,0 +1,12 @@ +# Define here the models for your scraped items +# +# See documentation in: +# https://docs.scrapy.org/en/latest/topics/items.html + +import scrapy + + +class LaraveldocscrappyItem(scrapy.Item): + # define the fields for your item here like: + # name = scrapy.Field() + pass diff --git a/laravelDocScrappy/middlewares.py b/laravelDocScrappy/middlewares.py new file mode 100644 index 0000000..64a3037 --- /dev/null +++ b/laravelDocScrappy/middlewares.py @@ -0,0 +1,103 @@ +# Define here the models for your spider middleware +# +# See documentation in: +# https://docs.scrapy.org/en/latest/topics/spider-middleware.html + +from scrapy import signals + +# useful for handling different item types with a single interface +from itemadapter import is_item, ItemAdapter + + +class LaraveldocscrappySpiderMiddleware: + # Not all methods need to be defined. If a method is not defined, + # scrapy acts as if the spider middleware does not modify the + # passed objects. + + @classmethod + def from_crawler(cls, crawler): + # This method is used by Scrapy to create your spiders. + s = cls() + crawler.signals.connect(s.spider_opened, signal=signals.spider_opened) + return s + + def process_spider_input(self, response, spider): + # Called for each response that goes through the spider + # middleware and into the spider. + + # Should return None or raise an exception. + return None + + def process_spider_output(self, response, result, spider): + # Called with the results returned from the Spider, after + # it has processed the response. + + # Must return an iterable of Request, or item objects. + for i in result: + yield i + + def process_spider_exception(self, response, exception, spider): + # Called when a spider or process_spider_input() method + # (from other spider middleware) raises an exception. + + # Should return either None or an iterable of Request or item objects. + pass + + def process_start_requests(self, start_requests, spider): + # Called with the start requests of the spider, and works + # similarly to the process_spider_output() method, except + # that it doesn’t have a response associated. + + # Must return only requests (not items). + for r in start_requests: + yield r + + def spider_opened(self, spider): + spider.logger.info("Spider opened: %s" % spider.name) + + +class LaraveldocscrappyDownloaderMiddleware: + # Not all methods need to be defined. If a method is not defined, + # scrapy acts as if the downloader middleware does not modify the + # passed objects. + + @classmethod + def from_crawler(cls, crawler): + # This method is used by Scrapy to create your spiders. + s = cls() + crawler.signals.connect(s.spider_opened, signal=signals.spider_opened) + return s + + def process_request(self, request, spider): + # Called for each request that goes through the downloader + # middleware. + + # Must either: + # - return None: continue processing this request + # - or return a Response object + # - or return a Request object + # - or raise IgnoreRequest: process_exception() methods of + # installed downloader middleware will be called + return None + + def process_response(self, request, response, spider): + # Called with the response returned from the downloader. + + # Must either; + # - return a Response object + # - return a Request object + # - or raise IgnoreRequest + return response + + def process_exception(self, request, exception, spider): + # Called when a download handler or a process_request() + # (from other downloader middleware) raises an exception. + + # Must either: + # - return None: continue processing this exception + # - return a Response object: stops process_exception() chain + # - return a Request object: stops process_exception() chain + pass + + def spider_opened(self, spider): + spider.logger.info("Spider opened: %s" % spider.name) diff --git a/laravelDocScrappy/pipelines.py b/laravelDocScrappy/pipelines.py new file mode 100644 index 0000000..f33a465 --- /dev/null +++ b/laravelDocScrappy/pipelines.py @@ -0,0 +1,13 @@ +# Define your item pipelines here +# +# Don't forget to add your pipeline to the ITEM_PIPELINES setting +# See: https://docs.scrapy.org/en/latest/topics/item-pipeline.html + + +# useful for handling different item types with a single interface +from itemadapter import ItemAdapter + + +class LaraveldocscrappyPipeline: + def process_item(self, item, spider): + return item diff --git a/laravelDocScrappy/settings.py b/laravelDocScrappy/settings.py new file mode 100644 index 0000000..5b4fc8a --- /dev/null +++ b/laravelDocScrappy/settings.py @@ -0,0 +1,93 @@ +# Scrapy settings for laravelDocScrappy project +# +# For simplicity, this file contains only settings considered important or +# commonly used. You can find more settings consulting the documentation: +# +# https://docs.scrapy.org/en/latest/topics/settings.html +# https://docs.scrapy.org/en/latest/topics/downloader-middleware.html +# https://docs.scrapy.org/en/latest/topics/spider-middleware.html + +BOT_NAME = "laravelDocScrappy" + +SPIDER_MODULES = ["laravelDocScrappy.spiders"] +NEWSPIDER_MODULE = "laravelDocScrappy.spiders" + + +# Crawl responsibly by identifying yourself (and your website) on the user-agent +#USER_AGENT = "laravelDocScrappy (+http://www.yourdomain.com)" + +# Obey robots.txt rules +ROBOTSTXT_OBEY = True + +# Configure maximum concurrent requests performed by Scrapy (default: 16) +#CONCURRENT_REQUESTS = 32 + +# Configure a delay for requests for the same website (default: 0) +# See https://docs.scrapy.org/en/latest/topics/settings.html#download-delay +# See also autothrottle settings and docs +#DOWNLOAD_DELAY = 3 +# The download delay setting will honor only one of: +#CONCURRENT_REQUESTS_PER_DOMAIN = 16 +#CONCURRENT_REQUESTS_PER_IP = 16 + +# Disable cookies (enabled by default) +#COOKIES_ENABLED = False + +# Disable Telnet Console (enabled by default) +#TELNETCONSOLE_ENABLED = False + +# Override the default request headers: +#DEFAULT_REQUEST_HEADERS = { +# "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", +# "Accept-Language": "en", +#} + +# Enable or disable spider middlewares +# See https://docs.scrapy.org/en/latest/topics/spider-middleware.html +#SPIDER_MIDDLEWARES = { +# "laravelDocScrappy.middlewares.LaraveldocscrappySpiderMiddleware": 543, +#} + +# Enable or disable downloader middlewares +# See https://docs.scrapy.org/en/latest/topics/downloader-middleware.html +#DOWNLOADER_MIDDLEWARES = { +# "laravelDocScrappy.middlewares.LaraveldocscrappyDownloaderMiddleware": 543, +#} + +# Enable or disable extensions +# See https://docs.scrapy.org/en/latest/topics/extensions.html +#EXTENSIONS = { +# "scrapy.extensions.telnet.TelnetConsole": None, +#} + +# Configure item pipelines +# See https://docs.scrapy.org/en/latest/topics/item-pipeline.html +#ITEM_PIPELINES = { +# "laravelDocScrappy.pipelines.LaraveldocscrappyPipeline": 300, +#} + +# Enable and configure the AutoThrottle extension (disabled by default) +# See https://docs.scrapy.org/en/latest/topics/autothrottle.html +#AUTOTHROTTLE_ENABLED = True +# The initial download delay +#AUTOTHROTTLE_START_DELAY = 5 +# The maximum download delay to be set in case of high latencies +#AUTOTHROTTLE_MAX_DELAY = 60 +# The average number of requests Scrapy should be sending in parallel to +# each remote server +#AUTOTHROTTLE_TARGET_CONCURRENCY = 1.0 +# Enable showing throttling stats for every response received: +#AUTOTHROTTLE_DEBUG = False + +# Enable and configure HTTP caching (disabled by default) +# See https://docs.scrapy.org/en/latest/topics/downloader-middleware.html#httpcache-middleware-settings +#HTTPCACHE_ENABLED = True +#HTTPCACHE_EXPIRATION_SECS = 0 +#HTTPCACHE_DIR = "httpcache" +#HTTPCACHE_IGNORE_HTTP_CODES = [] +#HTTPCACHE_STORAGE = "scrapy.extensions.httpcache.FilesystemCacheStorage" + +# Set settings whose default value is deprecated to a future-proof value +REQUEST_FINGERPRINTER_IMPLEMENTATION = "2.7" +TWISTED_REACTOR = "twisted.internet.asyncioreactor.AsyncioSelectorReactor" +FEED_EXPORT_ENCODING = "utf-8" diff --git a/laravelDocScrappy/spiders/__init__.py b/laravelDocScrappy/spiders/__init__.py new file mode 100644 index 0000000..ebd689a --- /dev/null +++ b/laravelDocScrappy/spiders/__init__.py @@ -0,0 +1,4 @@ +# This package will contain the spiders of your Scrapy project +# +# Please refer to the documentation for information on how to create and manage +# your spiders. diff --git a/laravelDocScrappy/spiders/__pycache__/__init__.cpython-312.pyc b/laravelDocScrappy/spiders/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000..fedcbf3 Binary files /dev/null and b/laravelDocScrappy/spiders/__pycache__/__init__.cpython-312.pyc differ diff --git a/laravelDocScrappy/spiders/__pycache__/laravel_docs.cpython-312.pyc b/laravelDocScrappy/spiders/__pycache__/laravel_docs.cpython-312.pyc new file mode 100644 index 0000000..a52efc4 Binary files /dev/null and b/laravelDocScrappy/spiders/__pycache__/laravel_docs.cpython-312.pyc differ diff --git a/laravelDocScrappy/spiders/laravel_docs.py b/laravelDocScrappy/spiders/laravel_docs.py new file mode 100644 index 0000000..955adb6 --- /dev/null +++ b/laravelDocScrappy/spiders/laravel_docs.py @@ -0,0 +1,37 @@ +import scrapy +import html2text + + +class LaravelDocsSpider(scrapy.Spider): + name = "laravel_docs" + allowed_domains = ["laravel.com"] + start_urls = ["https://laravel.com/docs/12.x/installation"] + + def parse(self, response): + # Extract the Markdown content using the "copy as markdown" button + html_content = response.xpath("//div[@id='main-content']").get() + markdown_content = html2text.html2text(html_content) if html_content else None + + if markdown_content: + # Determine the current path and create the file structure accordingly + url_path = response.url.split('https://laravel.com/docs/')[1] + version, *path_parts = url_path.split('/') + + output_folder = f'output/{version}/' + self.create_output_folder(output_folder) + + file_name = '/'.join(path_parts) + '.md' + file_path = f'{output_folder}{file_name}' + + with open(file_path, 'w') as file: + file.write(markdown_content) + + # Follow the navigation links to go through other pages + next_page = response.css('div.docs_sidebar a::attr(href)').getall() + for href in next_page: + yield scrapy.Request(response.urljoin(href), callback=self.parse) + + def create_output_folder(self, folder_path): + import os + if not os.path.exists(folder_path): + os.makedirs(folder_path) \ No newline at end of file diff --git a/output/12.x/artisan.md b/output/12.x/artisan.md new file mode 100644 index 0000000..79c2c73 --- /dev/null +++ b/output/12.x/artisan.md @@ -0,0 +1,1867 @@ +# Artisan Console + + * Introduction + * Tinker (REPL) + * Writing Commands + * Generating Commands + * Command Structure + * Closure Commands + * Isolatable Commands + * Defining Input Expectations + * Arguments + * Options + * Input Arrays + * Input Descriptions + * Prompting for Missing Input + * Command I/O + * Retrieving Input + * Prompting for Input + * Writing Output + * Registering Commands + * Programmatically Executing Commands + * Calling Commands From Other Commands + * Signal Handling + * Stub Customization + * Events + +## Introduction + +Artisan is the command line interface included with Laravel. Artisan exists at +the root of your application as the `artisan` script and provides a number of +helpful commands that can assist you while you build your application. To view +a list of all available Artisan commands, you may use the `list` command: + + + + 1php artisan list + + + php artisan list + +Every command also includes a "help" screen which displays and describes the +command's available arguments and options. To view a help screen, precede the +name of the command with `help`: + + + + 1php artisan help migrate + + + php artisan help migrate + +#### Laravel Sail + +If you are using [Laravel Sail](/docs/12.x/sail) as your local development +environment, remember to use the `sail` command line to invoke Artisan +commands. Sail will execute your Artisan commands within your application's +Docker containers: + + + + 1./vendor/bin/sail artisan list + + + ./vendor/bin/sail artisan list + +### Tinker (REPL) + +[Laravel Tinker](https://github.com/laravel/tinker) is a powerful REPL for the +Laravel framework, powered by the [PsySH](https://github.com/bobthecow/psysh) +package. + +#### Installation + +All Laravel applications include Tinker by default. However, you may install +Tinker using Composer if you have previously removed it from your application: + + + + 1composer require laravel/tinker + + + composer require laravel/tinker + +Looking for hot reloading, multiline code editing, and autocompletion when +interacting with your Laravel application? Check out +[Tinkerwell](https://tinkerwell.app)! + +#### Usage + +Tinker allows you to interact with your entire Laravel application on the +command line, including your Eloquent models, jobs, events, and more. To enter +the Tinker environment, run the `tinker` Artisan command: + + + + 1php artisan tinker + + + php artisan tinker + +You can publish Tinker's configuration file using the `vendor:publish` +command: + + + + 1php artisan vendor:publish --provider="Laravel\Tinker\TinkerServiceProvider" + + + php artisan vendor:publish --provider="Laravel\Tinker\TinkerServiceProvider" + +The `dispatch` helper function and `dispatch` method on the `Dispatchable` +class depends on garbage collection to place the job on the queue. Therefore, +when using tinker, you should use `Bus::dispatch` or `Queue::push` to dispatch +jobs. + +#### Command Allow List + +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: + + + + 1'commands' => [ + + 2 // App\Console\Commands\ExampleCommand::class, + + 3], + + + '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: + + + + 1'dont_alias' => [ + + 2 App\Models\User::class, + + 3], + + + 'dont_alias' => [ + App\Models\User::class, + ], + +## Writing Commands + +In addition to the commands provided with Artisan, you may build your own +custom commands. Commands are typically stored in the `app/Console/Commands` +directory; however, you are free to choose your own storage location as long +as you instruct Laravel to scan other directories for Artisan commands. + +### Generating Commands + +To create a new command, you may use the `make:command` Artisan command. This +command will create a new command class in the `app/Console/Commands` +directory. Don't worry if this directory does not exist in your application - +it will be created the first time you run the `make:command` Artisan command: + + + + 1php artisan make:command SendEmails + + + php artisan make:command SendEmails + +### Command Structure + +After generating your command, you should define appropriate values for the +`signature` and `description` properties of the class. These properties will +be used when displaying your command on the `list` screen. The `signature` +property also allows you to define your command's input expectations. The +`handle` method will be called when your command is executed. You may place +your command logic in this method. + +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/12.x/container) will automatically inject all dependencies +that are type-hinted in this method's signature: + + + + 1send(User::find($this->argument('user'))); + + 31 } + + 32} + + + send(User::find($this->argument('user'))); + } + } + +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. + +#### Exit Codes + +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: + + + + 1$this->error('Something went wrong.'); + + 2  + + 3return 1; + + + $this->error('Something went wrong.'); + + 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`: + + + + 1$this->fail('Something went wrong.'); + + + $this->fail('Something went wrong.'); + +### Closure Commands + +Closure-based commands provide an alternative to defining console commands as +classes. In the same way that route closures are an alternative to +controllers, think of command closures as an alternative to command classes. + +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 and a closure which receives the command's arguments and +options: + + + + 1Artisan::command('mail:send {user}', function (string $user) { + + 2 $this->info("Sending email to: {$user}!"); + + 3}); + + + 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. + +#### Type-Hinting Dependencies + +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/12.x/container): + + + + 1use App\Models\User; + + 2use App\Support\DripEmailer; + + 3use Illuminate\Support\Facades\Artisan; + + 4  + + 5Artisan::command('mail:send {user}', function (DripEmailer $drip, string $user) { + + 6 $drip->send(User::find($user)); + + 7}); + + + use App\Models\User; + use App\Support\DripEmailer; + use Illuminate\Support\Facades\Artisan; + + 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: + + + + 1Artisan::command('mail:send {user}', function (string $user) { + + 2 // ... + + 3})->purpose('Send a marketing email to a user'); + + + Artisan::command('mail:send {user}', function (string $user) { + // ... + })->purpose('Send a marketing email to a user'); + +### Isolatable Commands + +To utilize this feature, your application must be using the `memcached`, +`redis`, `dynamodb`, `database`, `file`, or `array` cache driver as your +application's default cache driver. In addition, all servers must be +communicating with the same central cache server. + +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: + + + + 1argument('user'); + + 7} + + + /** + * Get the isolatable ID for the command. + */ + public function isolatableId(): string + { + return $this->argument('user'); + } + +#### Lock Expiration Time + +By default, isolation locks expire after the command is finished. Or, if the +command is interrupted and unable to finish, the lock will expire after one +hour. However, you may adjust the lock expiration time by defining a +`isolationLockExpiresAt` method on your command: + + + + 1use DateTimeInterface; + + 2use DateInterval; + + 3  + + 4/** + + 5 * Determine when an isolation lock expires for the command. + + 6 */ + + 7public function isolationLockExpiresAt(): DateTimeInterface|DateInterval + + 8{ + + 9 return now()->addMinutes(5); + + 10} + + + use DateTimeInterface; + use DateInterval; + + /** + * Determine when an isolation lock expires for the command. + */ + public function isolationLockExpiresAt(): DateTimeInterface|DateInterval + { + return now()->addMinutes(5); + } + +## Defining Input Expectations + +When writing console commands, it is common to gather input from the user +through arguments or options. Laravel makes it very convenient to define the +input you expect from the user using the `signature` property on your +commands. The `signature` property allows you to define the name, arguments, +and options for the command in a single, expressive, route-like syntax. + +### Arguments + +All user supplied arguments and options are wrapped in curly braces. In the +following example, the command defines one required argument: `user`: + + + + 1/** + + 2 * The name and signature of the console command. + + 3 * + + 4 * @var string + + 5 */ + + 6protected $signature = 'mail:send {user}'; + + + /** + * The name and signature of the console command. + * + * @var string + */ + protected $signature = 'mail:send {user}'; + +You may also make arguments optional or define default values for arguments: + + + + 1// Optional argument... + + 2'mail:send {user?}' + + 3  + + 4// Optional argument with default value... + + 5'mail:send {user=foo}' + + + // Optional argument... + 'mail:send {user?}' + + // Optional argument with default value... + 'mail:send {user=foo}' + +### 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: + + + + 1/** + + 2 * The name and signature of the console command. + + 3 * + + 4 * @var string + + 5 */ + + 6protected $signature = 'mail:send {user} {--queue}'; + + + /** + * 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`: + + + + 1php artisan mail:send 1 --queue + + + php artisan mail:send 1 --queue + +#### Options With Values + +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: + + + + 1/** + + 2 * The name and signature of the console command. + + 3 * + + 4 * @var string + + 5 */ + + 6protected $signature = 'mail:send {user} {--queue=}'; + + + /** + * 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`: + + + + 1php artisan mail:send 1 --queue=default + + + 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: + + + + 1'mail:send {user} {--queue=default}' + + + '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: + + + + 1'mail:send {user} {--Q|queue}' + + + '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: + + + + 1php artisan mail:send 1 -Qdefault + + + php artisan mail:send 1 -Qdefault + +### Input Arrays + +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: + + + + 1'mail:send {user*}' + + + 'mail:send {user*}' + +When running this command, 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: + + + + 1php artisan mail:send 1 2 + + + 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: + + + + 1'mail:send {user?*}' + + + '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: + + + + 1'mail:send {--id=*}' + + + 'mail:send {--id=*}' + +Such a command may be invoked by passing multiple `--id` arguments: + + + + 1php artisan mail:send --id=1 --id=2 + + + php artisan mail:send --id=1 --id=2 + +### Input Descriptions + +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: + + + + 1/** + + 2 * The name and signature of the console command. + + 3 * + + 4 * @var string + + 5 */ + + 6protected $signature = 'mail:send + + 7 {user : The ID of the user} + + 8 {--queue : Whether the job should be queued}'; + + + /** + * 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: + + + + 1 + + 5 */ + + 6protected function promptForMissingArgumentsUsing(): array + + 7{ + + 8 return [ + + 9 'user' => 'Which user ID should receive the mail?', + + 10 ]; + + 11} + + + /** + * Prompt for missing input arguments using the returned questions. + * + * @return array + */ + protected function promptForMissingArgumentsUsing(): array + { + return [ + 'user' => 'Which user ID should receive the mail?', + ]; + } + +You may also provide placeholder text by using a tuple containing the question +and placeholder: + + + + 1return [ + + 2 'user' => ['Which user ID should receive the mail?', 'E.g. 123'], + + 3]; + + + 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: + + + + 1use App\Models\User; + + 2use function Laravel\Prompts\search; + + 3  + + 4// ... + + 5  + + 6return [ + + 7 'user' => fn () => search( + + 8 label: 'Search for a user:', + + 9 placeholder: 'E.g. Taylor Otwell', + + 10 options: fn ($value) => strlen($value) > 0 + + 11 ? User::whereLike('name', "%{$value}%")->pluck('name', 'id')->all() + + 12 : [] + + 13 ), + + 14]; + + + 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::whereLike('name', "%{$value}%")->pluck('name', 'id')->all() + : [] + ), + ]; + +The comprehensive [Laravel Prompts](/docs/12.x/prompts) documentation includes +additional information on the available prompts and their usage. + +If you wish to prompt the user to select or enter 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: + + + + 1use Symfony\Component\Console\Input\InputInterface; + + 2use Symfony\Component\Console\Output\OutputInterface; + + 3use function Laravel\Prompts\confirm; + + 4  + + 5// ... + + 6  + + 7/** + + 8 * Perform actions after the user was prompted for missing arguments. + + 9 */ + + 10protected function afterPromptingForMissingArguments(InputInterface $input, OutputInterface $output): void + + 11{ + + 12 $input->setOption('queue', confirm( + + 13 label: 'Would you like to queue the mail?', + + 14 default: $this->option('queue') + + 15 )); + + 16} + + + 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') + )); + } + +## Command I/O + +### Retrieving Input + +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: + + + + 1/** + + 2 * Execute the console command. + + 3 */ + + 4public function handle(): void + + 5{ + + 6 $userId = $this->argument('user'); + + 7} + + + /** + * 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: + + + + 1$arguments = $this->arguments(); + + + $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: + + + + 1// Retrieve a specific option... + + 2$queueName = $this->option('queue'); + + 3  + + 4// Retrieve all options as an array... + + 5$options = $this->options(); + + + // Retrieve a specific option... + $queueName = $this->option('queue'); + + // Retrieve all options as an array... + $options = $this->options(); + +### Prompting for Input + +[Laravel Prompts](/docs/12.x/prompts) is a PHP package for adding beautiful +and user-friendly forms to your command-line applications, with browser-like +features including placeholder text and validation. + +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: + + + + 1/** + + 2 * Execute the console command. + + 3 */ + + 4public function handle(): void + + 5{ + + 6 $name = $this->ask('What is your name?'); + + 7  + + 8 // ... + + 9} + + + /** + * 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: + + + + 1$name = $this->ask('What is your name?', 'Taylor'); + + + $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: + + + + 1$password = $this->secret('What is the password?'); + + + $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`. + + + + 1if ($this->confirm('Do you wish to continue?')) { + + 2 // ... + + 3} + + + 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: + + + + 1if ($this->confirm('Do you wish to continue?', true)) { + + 2 // ... + + 3} + + + 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: + + + + 1$name = $this->anticipate('What is your name?', ['Taylor', 'Dayle']); + + + $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: + + + + 1use App\Models\Address; + + 2  + + 3$name = $this->anticipate('What is your address?', function (string $input) { + + 4 return Address::whereLike('name', "{$input}%") + + 5 ->limit(5) + + 6 ->pluck('name') + + 7 ->all(); + + 8}); + + + use App\Models\Address; + + $name = $this->anticipate('What is your address?', function (string $input) { + return Address::whereLike('name', "{$input}%") + ->limit(5) + ->pluck('name') + ->all(); + }); + +#### 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: + + + + 1$name = $this->choice( + + 2 'What is your name?', + + 3 ['Taylor', 'Dayle'], + + 4 $defaultIndex + + 5); + + + $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: + + + + 1$name = $this->choice( + + 2 'What is your name?', + + 3 ['Taylor', 'Dayle'], + + 4 $defaultIndex, + + 5 $maxAttempts = null, + + 6 $allowMultipleSelections = false + + 7); + + + $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`, `newLine`, `info`, +`comment`, `question`, `warn`, `alert`, 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: + + + + 1/** + + 2 * Execute the console command. + + 3 */ + + 4public function handle(): void + + 5{ + + 6 // ... + + 7  + + 8 $this->info('The command was successful!'); + + 9} + + + /** + * Execute the console command. + */ + public function handle(): void + { + // ... + + $this->info('The command was successful!'); + } + +To display an error message, use the `error` method. Error message text is +typically displayed in red: + + + + 1$this->error('Something went wrong!'); + + + $this->error('Something went wrong!'); + +You may use the `line` method to display plain, uncolored text: + + + + 1$this->line('Display this on the screen'); + + + $this->line('Display this on the screen'); + +You may use the `newLine` method to display a blank line: + + + + 1// Write a single blank line... + + 2$this->newLine(); + + 3  + + 4// Write three blank lines... + + 5$this->newLine(3); + + + // Write a single blank line... + $this->newLine(); + + // Write three blank lines... + $this->newLine(3); + +#### Tables + +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: + + + + 1use App\Models\User; + + 2  + + 3$this->table( + + 4 ['Name', 'Email'], + + 5 User::all(['name', 'email'])->toArray() + + 6); + + + use App\Models\User; + + $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: + + + + 1use App\Models\User; + + 2  + + 3$users = $this->withProgressBar(User::all(), function (User $user) { + + 4 $this->performTask($user); + + 5}); + + + use App\Models\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: + + + + 1$users = App\Models\User::all(); + + 2  + + 3$bar = $this->output->createProgressBar(count($users)); + + 4  + + 5$bar->start(); + + 6  + + 7foreach ($users as $user) { + + 8 $this->performTask($user); + + 9  + + 10 $bar->advance(); + + 11} + + 12  + + 13$bar->finish(); + + + $users = App\Models\User::all(); + + $bar = $this->output->createProgressBar(count($users)); + + $bar->start(); + + foreach ($users as $user) { + $this->performTask($user); + + $bar->advance(); + } + + $bar->finish(); + +For more advanced options, check out the [Symfony Progress Bar component +documentation](https://symfony.com/doc/current/components/console/helpers/progressbar.html). + +## Registering Commands + +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: + + + + 1->withCommands([ + + 2 __DIR__.'/../app/Domain/Orders/Commands', + + 3]) + + + ->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: + + + + 1use App\Domain\Orders\Commands\SendEmails; + + 2  + + 3->withCommands([ + + 4 SendEmails::class, + + 5]) + + + use App\Domain\Orders\Commands\SendEmails; + + ->withCommands([ + SendEmails::class, + ]) + +When Artisan boots, all the commands in your application will be resolved by +the [service container](/docs/12.x/container) and registered with Artisan. + +## Programmatically Executing Commands + +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: + + + + 1use Illuminate\Support\Facades\Artisan; + + 2use Illuminate\Support\Facades\Route; + + 3  + + 4Route::post('/user/{user}/mail', function (string $user) { + + 5 $exitCode = Artisan::call('mail:send', [ + + 6 'user' => $user, '--queue' => 'default' + + 7 ]); + + 8  + + 9 // ... + + 10}); + + + use Illuminate\Support\Facades\Artisan; + use Illuminate\Support\Facades\Route; + + 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: + + + + 1Artisan::call('mail:send 1 --queue=default'); + + + 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: + + + + 1use Illuminate\Support\Facades\Artisan; + + 2use Illuminate\Support\Facades\Route; + + 3  + + 4Route::post('/mail', function () { + + 5 $exitCode = Artisan::call('mail:send', [ + + 6 '--id' => [5, 13] + + 7 ]); + + 8}); + + + use Illuminate\Support\Facades\Artisan; + use Illuminate\Support\Facades\Route; + + 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: + + + + 1$exitCode = Artisan::call('migrate:refresh', [ + + 2 '--force' => true, + + 3]); + + + $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/12.x/queues). Before using this method, make sure you have +configured your queue and are running a queue listener: + + + + 1use Illuminate\Support\Facades\Artisan; + + 2use Illuminate\Support\Facades\Route; + + 3  + + 4Route::post('/user/{user}/mail', function (string $user) { + + 5 Artisan::queue('mail:send', [ + + 6 'user' => $user, '--queue' => 'default' + + 7 ]); + + 8  + + 9 // ... + + 10}); + + + use Illuminate\Support\Facades\Artisan; + use Illuminate\Support\Facades\Route; + + 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: + + + + 1Artisan::queue('mail:send', [ + + 2 'user' => 1, '--queue' => 'default' + + 3])->onConnection('redis')->onQueue('commands'); + + + 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: + + + + 1/** + + 2 * Execute the console command. + + 3 */ + + 4public function handle(): void + + 5{ + + 6 $this->call('mail:send', [ + + 7 'user' => 1, '--queue' => 'default' + + 8 ]); + + 9  + + 10 // ... + + 11} + + + /** + * 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: + + + + 1$this->callSilently('mail:send', [ + + 2 'user' => 1, '--queue' => 'default' + + 3]); + + + $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: + + + + 1/** + + 2 * Execute the console command. + + 3 */ + + 4public function handle(): void + + 5{ + + 6 $this->trap(SIGTERM, fn () => $this->shouldKeepRunning = false); + + 7  + + 8 while ($this->shouldKeepRunning) { + + 9 // ... + + 10 } + + 11} + + + /** + * Execute the console command. + */ + public function handle(): void + { + $this->trap(SIGTERM, fn () => $this->shouldKeepRunning = false); + + while ($this->shouldKeepRunning) { + // ... + } + } + +To listen for multiple signals at once, you may provide an array of signals to +the `trap` method: + + + + 1$this->trap([SIGTERM, SIGQUIT], function (int $signal) { + + 2 $this->shouldKeepRunning = false; + + 3  + + 4 dump($signal); // SIGTERM / SIGQUIT + + 5}); + + + $this->trap([SIGTERM, SIGQUIT], function (int $signal) { + $this->shouldKeepRunning = false; + + dump($signal); // SIGTERM / SIGQUIT + }); + +## Stub Customization + +The Artisan console's `make` commands are used to create a variety of classes, +such as controllers, jobs, migrations, and tests. These classes are generated +using "stub" files that are populated with values based on your input. +However, you may want to make small changes to files generated by Artisan. To +accomplish this, you may use the `stub:publish` command to publish the most +common stubs to your application so that you can customize them: + + + + 1php artisan stub:publish + + + php artisan stub:publish + +The published stubs will be located within a `stubs` directory in the root of +your application. Any changes you make to these stubs will be reflected when +you generate their corresponding classes using Artisan's `make` commands. + +## Events + +Artisan dispatches three events when running commands: +`Illuminate\Console\Events\ArtisanStarting`, +`Illuminate\Console\Events\CommandStarting`, and +`Illuminate\Console\Events\CommandFinished`. The `ArtisanStarting` event is +dispatched immediately when Artisan starts running. Next, the +`CommandStarting` event is dispatched immediately before a command runs. +Finally, the `CommandFinished` event is dispatched once a command finishes +executing. + diff --git a/output/12.x/authentication.md b/output/12.x/authentication.md new file mode 100644 index 0000000..3d4cc03 --- /dev/null +++ b/output/12.x/authentication.md @@ -0,0 +1,1850 @@ +# Authentication + + * Introduction + * Starter Kits + * Database Considerations + * Ecosystem Overview + * Authentication Quickstart + * Install a Starter Kit + * Retrieving the Authenticated User + * Protecting Routes + * Login Throttling + * Manually Authenticating Users + * Remembering Users + * Other Authentication Methods + * HTTP Basic Authentication + * Stateless HTTP Basic Authentication + * Logging Out + * Invalidating Sessions on Other Devices + * Password Confirmation + * Configuration + * Routing + * Protecting Routes + * Adding Custom Guards + * Closure Request Guards + * Adding Custom User Providers + * The User Provider Contract + * The Authenticatable Contract + * Automatic Password Rehashing + * [Social Authentication](/docs/12.x/socialite) + * Events + +## Introduction + +Many web applications provide a way for their users to authenticate with the +application and "login". Implementing this feature in web applications can be +a complex and potentially risky endeavor. For this reason, Laravel strives to +give you the tools you need to implement authentication quickly, securely, and +easily. + +At its core, Laravel's authentication facilities are made up of "guards" and +"providers". Guards define how users are authenticated for each request. For +example, Laravel ships with a `session` guard which maintains state using +session storage and cookies. + +Providers define how users are retrieved from your persistent storage. Laravel +ships with support for retrieving users using [Eloquent](/docs/12.x/eloquent) +and the database query builder. However, you are free to define additional +providers as needed for your application. + +Your application's authentication configuration file is located at +`config/auth.php`. This file contains several well-documented options for +tweaking the behavior of Laravel's authentication services. + +Guards and providers should not be confused with "roles" and "permissions". To +learn more about authorizing user actions via permissions, please refer to the +[authorization](/docs/12.x/authorization) documentation. + +### Starter Kits + +Want to get started fast? Install a [Laravel application starter +kit](/docs/12.x/starter-kits) in a fresh Laravel application. After migrating +your database, navigate your browser to `/register` or any other URL that is +assigned to your application. The starter kits will take care of scaffolding +your entire authentication system! + +**Even if you choose not to use a starter kit in your final Laravel +application, installing a[starter kit](/docs/12.x/starter-kits) can be a +wonderful opportunity to learn how to implement all of Laravel's +authentication functionality in an actual Laravel project.** Since the Laravel +starter kits contain authentication controllers, routes, and views for you, +you can examine the code within these files to learn how Laravel's +authentication features may be implemented. + +### Database Considerations + +By default, Laravel includes an `App\Models\User` [Eloquent +model](/docs/12.x/eloquent) in your `app/Models` directory. This model may be +used with the default Eloquent authentication driver. + +If your application is not using Eloquent, you may use the `database` +authentication provider which uses the Laravel query builder. If your +application is using MongoDB, check out MongoDB's official [Laravel user +authentication +documentation](https://www.mongodb.com/docs/drivers/php/laravel- +mongodb/current/user-authentication/). + +When building the database schema for the `App\Models\User` model, make sure +the password column is at least 60 characters in length. Of course, the +`users` table migration that is included in new Laravel applications already +creates a column that exceeds this length. + +Also, you should verify that your `users` (or equivalent) table contains a +nullable, string `remember_token` column of 100 characters. This column will +be used to store a token for users that select the "remember me" option when +logging into your application. Again, the default `users` table migration that +is included in new Laravel applications already contains this column. + +### Ecosystem Overview + +Laravel offers several packages related to authentication. Before continuing, +we'll review the general authentication ecosystem in Laravel and discuss each +package's intended purpose. + +First, consider how authentication works. When using a web browser, a user +will provide their username and password via a login form. If these +credentials are correct, the application will store information about the +authenticated user in the user's [session](/docs/12.x/session). A cookie +issued to the browser contains the session ID so that subsequent requests to +the application can associate the user with the correct session. After the +session cookie is received, the application will retrieve the session data +based on the session ID, note that the authentication information has been +stored in the session, and will consider the user as "authenticated". + +When a remote service needs to authenticate to access an API, cookies are not +typically used for authentication because there is no web browser. Instead, +the remote service sends an API token to the API on each request. The +application may validate the incoming token against a table of valid API +tokens and "authenticate" the request as being performed by the user +associated with that API token. + +#### Laravel's Built-in Browser Authentication Services + +Laravel includes built-in authentication and session services which are +typically accessed via the `Auth` and `Session` facades. These features +provide cookie-based authentication for requests that are initiated from web +browsers. They provide methods that allow you to verify a user's credentials +and authenticate the user. In addition, these services will automatically +store the proper authentication data in the user's session and issue the +user's session cookie. A discussion of how to use these services is contained +within this documentation. + +**Application Starter Kits** + +As discussed in this documentation, you can interact with these authentication +services manually to build your application's own authentication layer. +However, to help you get started more quickly, we have released [free starter +kits](/docs/12.x/starter-kits) that provide robust, modern scaffolding of the +entire authentication layer. + +#### Laravel's API Authentication Services + +Laravel provides two optional packages to assist you in managing API tokens +and authenticating requests made with API tokens: +[Passport](/docs/12.x/passport) and [Sanctum](/docs/12.x/sanctum). Please note +that these libraries and Laravel's built-in cookie based authentication +libraries are not mutually exclusive. These libraries primarily focus on API +token authentication while the built-in authentication services focus on +cookie based browser authentication. Many applications will use both Laravel's +built-in cookie based authentication services and one of Laravel's API +authentication packages. + +**Passport** + +Passport is an OAuth2 authentication provider, offering a variety of OAuth2 +"grant types" which allow you to issue various types of tokens. In general, +this is a robust and complex package for API authentication. However, most +applications do not require the complex features offered by the OAuth2 spec, +which can be confusing for both users and developers. In addition, developers +have been historically confused about how to authenticate SPA applications or +mobile applications using OAuth2 authentication providers like Passport. + +**Sanctum** + +In response to the complexity of OAuth2 and developer confusion, we set out to +build a simpler, more streamlined authentication package that could handle +both first-party web requests from a web browser and API requests via tokens. +This goal was realized with the release of [Laravel +Sanctum](/docs/12.x/sanctum), which should be considered the preferred and +recommended authentication package for applications that will be offering a +first-party web UI in addition to an API, or will be powered by a single-page +application (SPA) that exists separately from the backend Laravel application, +or applications that offer a mobile client. + +Laravel Sanctum is a hybrid web / API authentication package that can manage +your application's entire authentication process. This is possible because +when Sanctum based applications receive a request, Sanctum will first +determine if the request includes a session cookie that references an +authenticated session. Sanctum accomplishes this by calling Laravel's built-in +authentication services which we discussed earlier. If the request is not +being authenticated via a session cookie, Sanctum will inspect the request for +an API token. If an API token is present, Sanctum will authenticate the +request using that token. To learn more about this process, please consult +Sanctum's ["how it works"](/docs/12.x/sanctum#how-it-works) documentation. + +#### Summary and Choosing Your Stack + +In summary, if your application will be accessed using a browser and you are +building a monolithic Laravel application, your application will use Laravel's +built-in authentication services. + +Next, if your application offers an API that will be consumed by third +parties, you will choose between [Passport](/docs/12.x/passport) or +[Sanctum](/docs/12.x/sanctum) to provide API token authentication for your +application. In general, Sanctum should be preferred when possible since it is +a simple, complete solution for API authentication, SPA authentication, and +mobile authentication, including support for "scopes" or "abilities". + +If you are building a single-page application (SPA) that will be powered by a +Laravel backend, you should use [Laravel Sanctum](/docs/12.x/sanctum). When +using Sanctum, you will either need to manually implement your own backend +authentication routes or utilize [Laravel Fortify](/docs/12.x/fortify) as a +headless authentication backend service that provides routes and controllers +for features such as registration, password reset, email verification, and +more. + +Passport may be chosen when your application absolutely needs all of the +features provided by the OAuth2 specification. + +And, if you would like to get started quickly, we are pleased to recommend +[our application starter kits](/docs/12.x/starter-kits) as a quick way to +start a new Laravel application that already uses our preferred authentication +stack of Laravel's built-in authentication services. + +## Authentication Quickstart + +This portion of the documentation discusses authenticating users via the +[Laravel application starter kits](/docs/12.x/starter-kits), which includes UI +scaffolding to help you get started quickly. If you would like to integrate +with Laravel's authentication systems directly, check out the documentation on +manually authenticating users. + +### Install a Starter Kit + +First, you should [install a Laravel application starter +kit](/docs/12.x/starter-kits). Our starter kits offer beautifully designed +starting points for incorporating authentication into your fresh Laravel +application. + +### Retrieving the Authenticated User + +After creating an application from a 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: + + + + 1use Illuminate\Support\Facades\Auth; + + 2  + + 3// Retrieve the currently authenticated user... + + 4$user = Auth::user(); + + 5  + + 6// Retrieve the currently authenticated user's ID... + + 7$id = Auth::id(); + + + use Illuminate\Support\Facades\Auth; + + // Retrieve the currently authenticated user... + $user = Auth::user(); + + // 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: + + + + 1user(); + + 16  + + 17 // ... + + 18  + + 19 return redirect('/flights'); + + 20 } + + 21} + + + user(); + + // ... + + 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: + + + + 1use Illuminate\Support\Facades\Auth; + + 2  + + 3if (Auth::check()) { + + 4 // The user is logged in... + + 5} + + + use Illuminate\Support\Facades\Auth; + + if (Auth::check()) { + // The user is logged in... + } + +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/12.x/authentication#protecting-routes). + +### Protecting Routes + +[Route middleware](/docs/12.x/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/12.x/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: + + + + 1Route::get('/flights', function () { + + 2 // Only authenticated users may access this route... + + 3})->middleware('auth'); + + + 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/12.x/routing#named-routes). You +may modify this behavior using the `redirectGuestsTo` method within your +application's `bootstrap/app.php` file: + + + + 1use Illuminate\Http\Request; + + 2  + + 3->withMiddleware(function (Middleware $middleware) { + + 4 $middleware->redirectGuestsTo('/login'); + + 5  + + 6 // Using a closure... + + 7 $middleware->redirectGuestsTo(fn (Request $request) => route('login')); + + 8}) + + + use Illuminate\Http\Request; + + ->withMiddleware(function (Middleware $middleware) { + $middleware->redirectGuestsTo('/login'); + + // Using a closure... + $middleware->redirectGuestsTo(fn (Request $request) => route('login')); + }) + +#### Redirecting Authenticated Users + +When the `guest` middleware detects an authenticated user, it will redirect +the user to the `dashboard` or `home` named route. You may modify this +behavior using the `redirectUsersTo` method within your application's +`bootstrap/app.php` file: + + + + 1use Illuminate\Http\Request; + + 2  + + 3->withMiddleware(function (Middleware $middleware) { + + 4 $middleware->redirectUsersTo('/panel'); + + 5  + + 6 // Using a closure... + + 7 $middleware->redirectUsersTo(fn (Request $request) => route('panel')); + + 8}) + + + use Illuminate\Http\Request; + + ->withMiddleware(function (Middleware $middleware) { + $middleware->redirectUsersTo('/panel'); + + // Using a closure... + $middleware->redirectUsersTo(fn (Request $request) => route('panel')); + }) + +#### 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: + + + + 1Route::get('/flights', function () { + + 2 // Only authenticated users may access this route... + + 3})->middleware('auth:admin'); + + + Route::get('/flights', function () { + // Only authenticated users may access this route... + })->middleware('auth:admin'); + +### Login Throttling + +If you are using one of our [application starter kits](/docs/12.x/starter- +kits), rate limiting will automatically be applied to login attempts. By +default, the user will not be able to login for one minute if they fail to +provide the correct credentials after several attempts. The throttling is +unique to the user's username / email address and their IP address. + +If you would like to rate limit other routes in your application, check out +the [rate limiting documentation](/docs/12.x/routing#rate-limiting). + +## Manually Authenticating Users + +You are not required to use the authentication scaffolding included with +Laravel's [application starter kits](/docs/12.x/starter-kits). If you choose +not to use this scaffolding, you will need to manage user authentication using +the Laravel authentication classes directly. Don't worry, it's a cinch! + +We will access Laravel's authentication services via the `Auth` +[facade](/docs/12.x/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/12.x/session) to prevent [session +fixation](https://en.wikipedia.org/wiki/Session_fixation): + + + + 1validate([ + + 17 'email' => ['required', 'email'], + + 18 'password' => ['required'], + + 19 ]); + + 20  + + 21 if (Auth::attempt($credentials)) { + + 22 $request->session()->regenerate(); + + 23  + + 24 return redirect()->intended('dashboard'); + + 25 } + + 26  + + 27 return back()->withErrors([ + + 28 'email' => 'The provided credentials do not match our records.', + + 29 ])->onlyInput('email'); + + 30 } + + 31} + + + 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. + +Remember, Laravel's authentication services will retrieve users from your +database based on your authentication guard's "provider" configuration. In the +default `config/auth.php` configuration file, the Eloquent user provider is +specified and it is instructed to use the `App\Models\User` model when +retrieving users. You may change these values within your configuration file +based on the needs of your application. + +The `attempt` method will return `true` if authentication was successful. +Otherwise, `false` will be returned. + +The `intended` method provided by Laravel's redirector will redirect the user +to the URL they were attempting to access before being intercepted by the +authentication middleware. A fallback URI may be given to this method in case +the intended destination is not available. + +#### Specifying Additional Conditions + +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": + + + + 1if (Auth::attempt(['email' => $email, 'password' => $password, 'active' => 1])) { + + 2 // Authentication was successful... + + 3} + + + 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: + + + + 1use Illuminate\Database\Eloquent\Builder; + + 2  + + 3if (Auth::attempt([ + + 4 'email' => $email, + + 5 'password' => $password, + + 6 fn (Builder $query) => $query->has('activeSubscription'), + + 7])) { + + 8 // Authentication was successful... + + 9} + + + use Illuminate\Database\Eloquent\Builder; + + if (Auth::attempt([ + 'email' => $email, + 'password' => $password, + fn (Builder $query) => $query->has('activeSubscription'), + ])) { + // Authentication was successful... + } + +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: + + + + 1if (Auth::attemptWhen([ + + 2 'email' => $email, + + 3 'password' => $password, + + 4], function (User $user) { + + 5 return $user->isNotBanned(); + + 6})) { + + 7 // Authentication was successful... + + 8} + + + if (Auth::attemptWhen([ + 'email' => $email, + 'password' => $password, + ], function (User $user) { + return $user->isNotBanned(); + })) { + // Authentication was successful... + } + +#### Accessing Specific Guard Instances + +Via the `Auth` facade's `guard` method, you may specify which guard instance +you would like to utilize when authenticating the user. This allows you to +manage authentication for separate parts of your application using entirely +separate authenticatable models or user tables. + +The guard name passed to the `guard` method should correspond to one of the +guards configured in your `auth.php` configuration file: + + + + 1if (Auth::guard('admin')->attempt($credentials)) { + + 2 // ... + + 3} + + + if (Auth::guard('admin')->attempt($credentials)) { + // ... + } + +### Remembering Users + +Many web applications provide a "remember me" checkbox on their login form. If +you would like to provide "remember me" functionality in your application, you +may pass a boolean value as the second argument to the `attempt` method. + +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: + + + + 1use Illuminate\Support\Facades\Auth; + + 2  + + 3if (Auth::attempt(['email' => $email, 'password' => $password], $remember)) { + + 4 // The user is being remembered... + + 5} + + + use Illuminate\Support\Facades\Auth; + + 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: + + + + 1use Illuminate\Support\Facades\Auth; + + 2  + + 3if (Auth::viaRemember()) { + + 4 // ... + + 5} + + + use Illuminate\Support\Facades\Auth; + + if (Auth::viaRemember()) { + // ... + } + +### Other Authentication Methods + +#### Authenticate a User Instance + +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/12.x/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: + + + + 1use Illuminate\Support\Facades\Auth; + + 2  + + 3Auth::login($user); + + + use Illuminate\Support\Facades\Auth; + + 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: + + + + 1Auth::login($user, $remember = true); + + + Auth::login($user, $remember = true); + +If needed, you may specify an authentication guard before calling the `login` +method: + + + + 1Auth::guard('admin')->login($user); + + + 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: + + + + 1Auth::loginUsingId(1); + + + 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: + + + + 1Auth::loginUsingId(1, remember: true); + + + 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, and the `Login` event will not be dispatched: + + + + 1if (Auth::once($credentials)) { + + 2 // ... + + 3} + + + 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/12.x/middleware) to a route. The `auth.basic` middleware is +included with the Laravel framework, so you do not need to define it: + + + + 1Route::get('/profile', function () { + + 2 // Only authenticated users may access this route... + + 3})->middleware('auth.basic'); + + + 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". + +#### A Note on FastCGI + +If you are using [PHP FastCGI](https://www.php.net/manual/en/install.fpm.php) +and Apache to serve your Laravel application, HTTP Basic authentication may +not work correctly. To correct these problems, the following lines may be +added to your application's `.htaccess` file: + + + + 1RewriteCond %{HTTP:Authorization} ^(.+)$ + + 2RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}] + + + RewriteCond %{HTTP:Authorization} ^(.+)$ + RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}] + +### Stateless HTTP Basic Authentication + +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/12.x/middleware) that calls the +`onceBasic` method. If no response is returned by the `onceBasic` method, the +request may be passed further into the application: + + + + 1middleware(AuthenticateOnceWithBasicAuth::class); + + + Route::get('/api/user', function () { + // Only authenticated users may access this route... + })->middleware(AuthenticateOnceWithBasicAuth::class); + +## Logging Out + +To manually log users out of your application, you may use the `logout` method +provided by the `Auth` facade. This will remove the authentication information +from the user's session so that subsequent requests are not authenticated. + +In addition to calling the `logout` method, it is recommended that you +invalidate the user's session and regenerate their [CSRF +token](/docs/12.x/csrf). After logging the user out, you would typically +redirect the user to the root of your application: + + + + 1use Illuminate\Http\Request; + + 2use Illuminate\Http\RedirectResponse; + + 3use Illuminate\Support\Facades\Auth; + + 4  + + 5/** + + 6 * Log the user out of the application. + + 7 */ + + 8public function logout(Request $request): RedirectResponse + + 9{ + + 10 Auth::logout(); + + 11  + + 12 $request->session()->invalidate(); + + 13  + + 14 $request->session()->regenerateToken(); + + 15  + + 16 return redirect('/'); + + 17} + + + 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(); + + $request->session()->invalidate(); + + $request->session()->regenerateToken(); + + return redirect('/'); + } + +### Invalidating Sessions on Other Devices + +Laravel also provides a mechanism for invalidating and "logging out" a user's +sessions that are active on other devices without invalidating the session on +their current device. This feature is typically utilized when a user is +changing or updating their password and you would like to invalidate sessions +on other devices while keeping the current device authenticated. + +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/12.x/middleware#middleware-aliases): + + + + 1Route::middleware(['auth', 'auth.session'])->group(function () { + + 2 Route::get('/', function () { + + 3 // ... + + 4 }); + + 5}); + + + 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: + + + + 1use Illuminate\Support\Facades\Auth; + + 2  + + 3Auth::logoutOtherDevices($currentPassword); + + + use Illuminate\Support\Facades\Auth; + + 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. + +## Password Confirmation + +While building your application, you may occasionally have actions that should +require the user to confirm their password before the action is performed or +before the user is redirected to a sensitive area of the application. Laravel +includes built-in middleware to make this process a breeze. Implementing this +feature will require you to define two routes: one route to display a view +asking the user to confirm their password and another route to confirm that +the password is valid and redirect the user to their intended destination. + +The following documentation discusses how to integrate with Laravel's password +confirmation features directly; however, if you would like to get started more +quickly, the [Laravel application starter kits](/docs/12.x/starter-kits) +include support for this feature! + +### Configuration + +After confirming their password, a user will not be asked to confirm their +password again for three hours. However, you may configure the length of time +before the user is re-prompted for their password by changing the value of the +`password_timeout` configuration value within your application's +`config/auth.php` configuration file. + +### Routing + +#### The Password Confirmation Form + +First, we will define a route to display a view that requests the user to +confirm their password: + + + + 1Route::get('/confirm-password', function () { + + 2 return view('auth.confirm-password'); + + 3})->middleware('auth')->name('password.confirm'); + + + 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. + +#### Confirming the Password + +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: + + + + 1use Illuminate\Http\Request; + + 2use Illuminate\Support\Facades\Hash; + + 3use Illuminate\Support\Facades\Redirect; + + 4  + + 5Route::post('/confirm-password', function (Request $request) { + + 6 if (! Hash::check($request->password, $request->user()->password)) { + + 7 return back()->withErrors([ + + 8 'password' => ['The provided password does not match our records.'] + + 9 ]); + + 10 } + + 11  + + 12 $request->session()->passwordConfirmed(); + + 13  + + 14 return redirect()->intended(); + + 15})->middleware(['auth', 'throttle:6,1']); + + + 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(); + + 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. + +### Protecting Routes + +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/12.x/routing#named-routes): + + + + 1Route::get('/settings', function () { + + 2 // ... + + 3})->middleware(['password.confirm']); + + 4  + + 5Route::post('/settings', function () { + + 6 // ... + + 7})->middleware(['password.confirm']); + + + Route::get('/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/12.x/providers). Since Laravel already ships with an +`AppServiceProvider`, we can place the code in that provider: + + + + 1 [ + + 2 'api' => [ + + 3 'driver' => 'jwt', + + 4 'provider' => 'users', + + 5 ], + + 6], + + + 'guards' => [ + 'api' => [ + 'driver' => 'jwt', + 'provider' => 'users', + ], + ], + +### Closure Request Guards + +The simplest way to implement a custom, HTTP request based authentication +system is by using the `Auth::viaRequest` method. This method allows you to +quickly define your authentication process using a single closure. + +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`: + + + + 1use App\Models\User; + + 2use Illuminate\Http\Request; + + 3use Illuminate\Support\Facades\Auth; + + 4  + + 5/** + + 6 * Bootstrap any application services. + + 7 */ + + 8public function boot(): void + + 9{ + + 10 Auth::viaRequest('custom-token', function (Request $request) { + + 11 return User::where('token', (string) $request->token)->first(); + + 12 }); + + 13} + + + 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: + + + + 1'guards' => [ + + 2 'api' => [ + + 3 'driver' => 'custom-token', + + 4 ], + + 5], + + + 'guards' => [ + 'api' => [ + 'driver' => 'custom-token', + ], + ], + +Finally, you may reference the guard when assigning the authentication +middleware to a route: + + + + 1Route::middleware('auth:api')->group(function () { + + 2 // ... + + 3}); + + + 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`: + + + + 1make('mongo.connection')); + + 23 }); + + 24 } + + 25} + + + 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: + + + + 1'providers' => [ + + 2 'users' => [ + + 3 'driver' => 'mongo', + + 4 ], + + 5], + + + 'providers' => [ + 'users' => [ + 'driver' => 'mongo', + ], + ], + +Finally, you may reference this provider in your `guards` configuration: + + + + 1'guards' => [ + + 2 'web' => [ + + 3 'driver' => 'session', + + 4 'provider' => 'users', + + 5 ], + + 6], + + + 'guards' => [ + 'web' => [ + 'driver' => 'session', + 'provider' => 'users', + ], + ], + +### The User Provider Contract + +`Illuminate\Contracts\Auth\UserProvider` implementations are responsible for +fetching an `Illuminate\Contracts\Auth\Authenticatable` implementation out of +a persistent storage system, such as MySQL, MongoDB, etc. These two interfaces +allow the Laravel authentication mechanisms to continue functioning regardless +of how the user data is stored or what type of class is used to represent the +authenticated user: + +Let's take a look at the `Illuminate\Contracts\Auth\UserProvider` contract: + + + + 1getAuthPassword()` to the value of `$credentials['password']`. This +method should return `true` or `false` indicating whether the password is +valid. + +The `rehashPasswordIfRequired` method should rehash the given `$user`'s +password if required and supported. For example, this method will typically +use the `Hash::needsRehash` method to determine if the +`$credentials['password']` value needs to be rehashed. If the password needs +to be rehashed, the method should use the `Hash::make` method to rehash the +password and update the user's record in the underlying persistent storage. + +### The Authenticatable Contract + +Now that we have explored each of the methods on the `UserProvider`, let's +take a look at the `Authenticatable` contract. Remember, user providers should +return implementations of this interface from the `retrieveById`, +`retrieveByToken`, and `retrieveByCredentials` methods: + + + + 1 false, + + + 'rehash_on_login' => false, + +## Events + +Laravel dispatches a variety of [events](/docs/12.x/events) during the +authentication process. You may [define listeners](/docs/12.x/events) for any +of the following events: + +Event Name +--- +`Illuminate\Auth\Events\Registered` +`Illuminate\Auth\Events\Attempting` +`Illuminate\Auth\Events\Authenticated` +`Illuminate\Auth\Events\Login` +`Illuminate\Auth\Events\Failed` +`Illuminate\Auth\Events\Validated` +`Illuminate\Auth\Events\Verified` +`Illuminate\Auth\Events\Logout` +`Illuminate\Auth\Events\CurrentDeviceLogout` +`Illuminate\Auth\Events\OtherDeviceLogout` +`Illuminate\Auth\Events\Lockout` +`Illuminate\Auth\Events\PasswordReset` + diff --git a/output/12.x/authorization.md b/output/12.x/authorization.md new file mode 100644 index 0000000..add61d3 --- /dev/null +++ b/output/12.x/authorization.md @@ -0,0 +1,1998 @@ +# Authorization + + * Introduction + * Gates + * Writing Gates + * Authorizing Actions + * Gate Responses + * Intercepting Gate Checks + * Inline Authorization + * Creating Policies + * Generating Policies + * Registering Policies + * Writing Policies + * Policy Methods + * Policy Responses + * Methods Without Models + * Guest Users + * Policy Filters + * Authorizing Actions Using Policies + * Via the User Model + * Via the Gate Facade + * Via Middleware + * Via Blade Templates + * Supplying Additional Context + * Authorization & Inertia + +## Introduction + +In addition to providing built-in [authentication](/docs/12.x/authentication) +services, Laravel also provides a simple way to authorize user actions against +a given resource. For example, even though a user is authenticated, they may +not be authorized to update or delete certain Eloquent models or database +records managed by your application. Laravel's authorization features provide +an easy, organized way of managing these types of authorization checks. + +Laravel provides two primary ways of authorizing actions: gates and policies. +Think of gates and policies like routes and controllers. Gates provide a +simple, closure-based approach to authorization while policies, like +controllers, group logic around a particular model or resource. In this +documentation, we'll explore gates first and then examine policies. + +You do not need to choose between exclusively using gates or exclusively using +policies when building an application. Most applications will most likely +contain some mixture of gates and policies, and that is perfectly fine! Gates +are most applicable to actions that are not related to any model or resource, +such as viewing an administrator dashboard. In contrast, policies should be +used when you wish to authorize an action for a particular model or resource. + +## Gates + +### Writing Gates + +Gates are a great way to learn the basics of Laravel's authorization features; +however, when building robust Laravel applications you should consider using +policies to organize your authorization rules. + +Gates are simply closures that determine if a user is authorized to perform a +given action. Typically, gates are defined within the `boot` method of the +`App\Providers\AppServiceProvider` class using the `Gate` facade. Gates always +receive a user instance as their first argument and may optionally receive +additional arguments such as a relevant Eloquent model. + +In this example, we'll define a gate to determine if a user can update a given +`App\Models\Post` model. The gate will accomplish this by comparing the user's +`id` against the `user_id` of the user that created the post: + + + + 1use App\Models\Post; + + 2use App\Models\User; + + 3use Illuminate\Support\Facades\Gate; + + 4  + + 5/** + + 6 * Bootstrap any application services. + + 7 */ + + 8public function boot(): void + + 9{ + + 10 Gate::define('update-post', function (User $user, Post $post) { + + 11 return $user->id === $post->user_id; + + 12 }); + + 13} + + + use App\Models\Post; + use App\Models\User; + use Illuminate\Support\Facades\Gate; + + /** + * 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: + + + + 1use App\Policies\PostPolicy; + + 2use Illuminate\Support\Facades\Gate; + + 3  + + 4/** + + 5 * Bootstrap any application services. + + 6 */ + + 7public function boot(): void + + 8{ + + 9 Gate::define('update-post', [PostPolicy::class, 'update']); + + 10} + + + use App\Policies\PostPolicy; + use Illuminate\Support\Facades\Gate; + + /** + * 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: + + + + 1allows('update-post', $post)) { + + 2 // The user can update the post... + + 3} + + 4  + + 5if (Gate::forUser($user)->denies('update-post', $post)) { + + 6 // The user can't update the post... + + 7} + + + 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... + } + +You may authorize multiple actions at a time using the `any` or `none` +methods: + + + + 1if (Gate::any(['update-post', 'delete-post'], $post)) { + + 2 // The user can update or delete the post... + + 3} + + 4  + + 5if (Gate::none(['update-post', 'delete-post'], $post)) { + + 6 // The user can't update or delete the post... + + 7} + + + 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... + } + +#### 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: + + + + 1Gate::authorize('update-post', $post); + + 2  + + 3// The action is authorized... + + + Gate::authorize('update-post', $post); + + // 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 (`@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: + + + + 1use App\Models\Category; + + 2use App\Models\User; + + 3use Illuminate\Support\Facades\Gate; + + 4  + + 5Gate::define('create-post', function (User $user, Category $category, bool $pinned) { + + 6 if (! $user->canPublishToGroup($category->group)) { + + 7 return false; + + 8 } elseif ($pinned && ! $user->canPinPosts()) { + + 9 return false; + + 10 } + + 11  + + 12 return true; + + 13}); + + 14  + + 15if (Gate::check('create-post', [$category, $pinned])) { + + 16 // The user can create the post... + + 17} + + + 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; + } + + return true; + }); + + 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: + + + + 1use App\Models\User; + + 2use Illuminate\Auth\Access\Response; + + 3use Illuminate\Support\Facades\Gate; + + 4  + + 5Gate::define('edit-settings', function (User $user) { + + 6 return $user->isAdmin + + 7 ? Response::allow() + + 8 : Response::deny('You must be an administrator.'); + + 9}); + + + 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.'); + }); + +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: + + + + 1$response = Gate::inspect('edit-settings'); + + 2  + + 3if ($response->allowed()) { + + 4 // The action is authorized... + + 5} else { + + 6 echo $response->message(); + + 7} + + + $response = Gate::inspect('edit-settings'); + + 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: + + + + 1Gate::authorize('edit-settings'); + + 2  + + 3// The action is authorized... + + + Gate::authorize('edit-settings'); + + // 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: + + + + 1use App\Models\User; + + 2use Illuminate\Auth\Access\Response; + + 3use Illuminate\Support\Facades\Gate; + + 4  + + 5Gate::define('edit-settings', function (User $user) { + + 6 return $user->isAdmin + + 7 ? Response::allow() + + 8 : Response::denyWithStatus(404); + + 9}); + + + 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); + }); + +Because hiding resources via a `404` response is such a common pattern for web +applications, the `denyAsNotFound` method is offered for convenience: + + + + 1use App\Models\User; + + 2use Illuminate\Auth\Access\Response; + + 3use Illuminate\Support\Facades\Gate; + + 4  + + 5Gate::define('edit-settings', function (User $user) { + + 6 return $user->isAdmin + + 7 ? Response::allow() + + 8 : Response::denyAsNotFound(); + + 9}); + + + 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(); + }); + +### 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: + + + + 1use App\Models\User; + + 2use Illuminate\Support\Facades\Gate; + + 3  + + 4Gate::before(function (User $user, string $ability) { + + 5 if ($user->isAdministrator()) { + + 6 return true; + + 7 } + + 8}); + + + use App\Models\User; + use Illuminate\Support\Facades\Gate; + + 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: + + + + 1use App\Models\User; + + 2  + + 3Gate::after(function (User $user, string $ability, bool|null $result, mixed $arguments) { + + 4 if ($user->isAdministrator()) { + + 5 return true; + + 6 } + + 7}); + + + use App\Models\User; + + 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`. + +### Inline Authorization + +Occasionally, you may wish to determine if the currently authenticated user is +authorized to perform a given action without writing a dedicated gate that +corresponds to the action. Laravel allows you to perform these types of +"inline" authorization checks via the `Gate::allowIf` and `Gate::denyIf` +methods. Inline authorization does not execute any defined "before" or "after" +authorization hooks: + + + + 1use App\Models\User; + + 2use Illuminate\Support\Facades\Gate; + + 3  + + 4Gate::allowIf(fn (User $user) => $user->isAdministrator()); + + 5  + + 6Gate::denyIf(fn (User $user) => $user->banned()); + + + use App\Models\User; + use Illuminate\Support\Facades\Gate; + + Gate::allowIf(fn (User $user) => $user->isAdministrator()); + + Gate::denyIf(fn (User $user) => $user->banned()); + +If the action is not authorized or if no user is currently authenticated, +Laravel will automatically throw an +`Illuminate\Auth\Access\AuthorizationException` exception. Instances of +`AuthorizationException` are automatically converted to a 403 HTTP response by +Laravel's exception handler. + +## Creating Policies + +### Generating Policies + +Policies are classes that organize authorization logic around a particular +model or resource. For example, if your application is a blog, you may have an +`App\Models\Post` model and a corresponding `App\Policies\PostPolicy` to +authorize user actions such as creating or updating posts. + +You may generate a policy using the `make:policy` Artisan command. The +generated policy will be placed in the `app/Policies` directory. If this +directory does not exist in your application, Laravel will create it for you: + + + + 1php artisan make:policy PostPolicy + + + php artisan make:policy PostPolicy + +The `make:policy` command will generate an empty policy class. If you would +like to generate a class with example policy methods related to viewing, +creating, updating, and deleting the resource, you may provide a `--model` +option when executing the command: + + + + 1php artisan make:policy PostPolicy --model=Post + + + php artisan make:policy PostPolicy --model=Post + +### Registering Policies + +#### Policy Discovery + +By default, Laravel automatically discover policies as long as the model and +policy follow standard Laravel naming conventions. Specifically, the policies +must be in a `Policies` directory at or above the directory that contains your +models. So, for example, the models may be placed in the `app/Models` +directory while the policies may be placed in the `app/Policies` directory. In +this situation, Laravel will check for policies in `app/Models/Policies` then +`app/Policies`. In addition, the policy name must match the model name and +have a `Policy` suffix. So, a `User` model would correspond to a `UserPolicy` +policy class. + +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`: + + + + 1use Illuminate\Support\Facades\Gate; + + 2  + + 3Gate::guessPolicyNamesUsing(function (string $modelClass) { + + 4 // Return the name of the policy class for the given model... + + 5}); + + + use Illuminate\Support\Facades\Gate; + + 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`: + + + + 1use App\Models\Order; + + 2use App\Policies\OrderPolicy; + + 3use Illuminate\Support\Facades\Gate; + + 4  + + 5/** + + 6 * Bootstrap any application services. + + 7 */ + + 8public function boot(): void + + 9{ + + 10 Gate::policy(Order::class, OrderPolicy::class); + + 11} + + + 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); + } + +Alternatively, you may place the `UsePolicy` attribute on a model class to +inform Laravel of the model's corresponding policy: + + + + 1id === $post->user_id; + + 16 } + + 17} + + + 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. + +If you used the `--model` option when generating your policy via the Artisan +console, it will already contain methods for the `viewAny`, `view`, `create`, +`update`, `delete`, `restore`, and `forceDelete` actions. + +All policies are resolved via the Laravel [service +container](/docs/12.x/container), allowing you to type-hint any needed +dependencies in the policy's constructor to have them automatically injected. + +### Policy Responses + +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: + + + + 1use App\Models\Post; + + 2use App\Models\User; + + 3use Illuminate\Auth\Access\Response; + + 4  + + 5/** + + 6 * Determine if the given post can be updated by the user. + + 7 */ + + 8public function update(User $user, Post $post): Response + + 9{ + + 10 return $user->id === $post->user_id + + 11 ? Response::allow() + + 12 : Response::deny('You do not own this post.'); + + 13} + + + 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.'); + } + +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: + + + + 1use Illuminate\Support\Facades\Gate; + + 2  + + 3$response = Gate::inspect('update', $post); + + 4  + + 5if ($response->allowed()) { + + 6 // The action is authorized... + + 7} else { + + 8 echo $response->message(); + + 9} + + + use Illuminate\Support\Facades\Gate; + + $response = Gate::inspect('update', $post); + + 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: + + + + 1Gate::authorize('update', $post); + + 2  + + 3// The action is authorized... + + + Gate::authorize('update', $post); + + // 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: + + + + 1use App\Models\Post; + + 2use App\Models\User; + + 3use Illuminate\Auth\Access\Response; + + 4  + + 5/** + + 6 * Determine if the given post can be updated by the user. + + 7 */ + + 8public function update(User $user, Post $post): Response + + 9{ + + 10 return $user->id === $post->user_id + + 11 ? Response::allow() + + 12 : Response::denyWithStatus(404); + + 13} + + + 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); + } + +Because hiding resources via a `404` response is such a common pattern for web +applications, the `denyAsNotFound` method is offered for convenience: + + + + 1use App\Models\Post; + + 2use App\Models\User; + + 3use Illuminate\Auth\Access\Response; + + 4  + + 5/** + + 6 * Determine if the given post can be updated by the user. + + 7 */ + + 8public function update(User $user, Post $post): Response + + 9{ + + 10 return $user->id === $post->user_id + + 11 ? Response::allow() + + 12 : Response::denyAsNotFound(); + + 13} + + + 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(); + } + +### 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: + + + + 1/** + + 2 * Determine if the given user can create posts. + + 3 */ + + 4public function create(User $user): bool + + 5{ + + 6 return $user->role == 'writer'; + + 7} + + + /** + * 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: + + + + 1id === $post->user_id; + + 16 } + + 17} + + + 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: + + + + 1use App\Models\User; + + 2  + + 3/** + + 4 * Perform pre-authorization checks. + + 5 */ + + 6public function before(User $user, string $ability): bool|null + + 7{ + + 8 if ($user->isAdministrator()) { + + 9 return true; + + 10 } + + 11  + + 12 return null; + + 13} + + + use App\Models\User; + + /** + * 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. + +The `before` method of a policy class will not be called if the class doesn't +contain a method with a name matching the name of the ability being checked. + +## Authorizing Actions Using Policies + +### Via the User Model + +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: + + + + 1user()->cannot('update', $post)) { + + 17 abort(403); + + 18 } + + 19  + + 20 // Update the post... + + 21  + + 22 return redirect('/posts'); + + 23 } + + 24} + + + user()->cannot('update', $post)) { + abort(403); + } + + // Update the post... + + return redirect('/posts'); + } + } + +If a policy is registered 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. + +#### Actions That Don't Require Models + +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: + + + + 1user()->cannot('create', Post::class)) { + + 17 abort(403); + + 18 } + + 19  + + 20 // Create the post... + + 21  + + 22 return redirect('/posts'); + + 23 } + + 24} + + + user()->cannot('create', Post::class)) { + abort(403); + } + + // Create the post... + + return redirect('/posts'); + } + } + +### Via the `Gate` Facade + +In addition to helpful methods provided to the `App\Models\User` model, you +can always authorize actions via the `Gate` facade's `authorize` method. + +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: + + + + 1middleware('can:update,post'); + + + use App\Models\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/12.x/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: + + + + 1use App\Models\Post; + + 2  + + 3Route::put('/post/{post}', function (Post $post) { + + 4 // The current user may update the post... + + 5})->can('update', 'post'); + + + use App\Models\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: + + + + 1Route::post('/post', function () { + + 2 // The current user may create posts... + + 3})->middleware('can:create,App\Models\Post'); + + + 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: + + + + 1use App\Models\Post; + + 2  + + 3Route::post('/post', function () { + + 4 // The current user may create posts... + + 5})->can('create', Post::class); + + + use App\Models\Post; + + Route::post('/post', function () { + // The current user may create posts... + })->can('create', Post::class); + +### Via Blade Templates + +When writing Blade templates, you may wish to display a portion of the page +only if the user is authorized to perform a given action. For example, you may +wish to show an update form for a blog post only if the user can actually +update the post. In this situation, you may use the `@can` and `@cannot` +directives: + + + + 1@can('update', $post) + + 2 + + 3@elsecan('create', App\Models\Post::class) + + 4 + + 5@else + + 6 + + 7@endcan + + 8  + + 9@cannot('update', $post) + + 10 + + 11@elsecannot('create', App\Models\Post::class) + + 12 + + 13@endcannot + + + @can('update', $post) + + @elsecan('create', App\Models\Post::class) + + @else + + @endcan + + @cannot('update', $post) + + @elsecannot('create', App\Models\Post::class) + + @endcannot + +These directives are convenient shortcuts for writing `@if` and `@unless` +statements. The `@can` and `@cannot` statements above are equivalent to the +following statements: + + + + 1@if (Auth::user()->can('update', $post)) + + 2 + + 3@endif + + 4  + + 5@unless (Auth::user()->can('update', $post)) + + 6 + + 7@endunless + + + @if (Auth::user()->can('update', $post)) + + @endif + + @unless (Auth::user()->can('update', $post)) + + @endunless + +You may also determine if a user is authorized to perform any action from a +given array of actions. To accomplish this, use the `@canany` directive: + + + + 1@canany(['update', 'view', 'delete'], $post) + + 2 + + 3@elsecanany(['create'], \App\Models\Post::class) + + 4 + + 5@endcanany + + + @canany(['update', 'view', 'delete'], $post) + + @elsecanany(['create'], \App\Models\Post::class) + + @endcanany + +#### Actions That Don't Require Models + +Like most of the other authorization methods, you may pass a class name to the +`@can` and `@cannot` directives if the action does not require a model +instance: + + + + 1@can('create', App\Models\Post::class) + + 2 + + 3@endcan + + 4  + + 5@cannot('create', App\Models\Post::class) + + 6 + + 7@endcannot + + + @can('create', App\Models\Post::class) + + @endcan + + @cannot('create', App\Models\Post::class) + + @endcannot + +### Supplying Additional Context + +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: + + + + 1/** + + 2 * Determine if the given post can be updated by the user. + + 3 */ + + 4public function update(User $user, Post $post, int $category): bool + + 5{ + + 6 return $user->id === $post->user_id && + + 7 $user->canUpdateCategory($category); + + 8} + + + /** + * 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: + + + + 1/** + + 2 * Update the given blog post. + + 3 * + + 4 * @throws \Illuminate\Auth\Access\AuthorizationException + + 5 */ + + 6public function update(Request $request, Post $post): RedirectResponse + + 7{ + + 8 Gate::authorize('update', [$post, $request->category]); + + 9  + + 10 // The current user can update the blog post... + + 11  + + 12 return redirect('/posts'); + + 13} + + + /** + * 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... + + return redirect('/posts'); + } + +## Authorization & Inertia + +Although authorization must always be handled on the server, it can often be +convenient to provide your frontend application with authorization data in +order to properly render your application's UI. Laravel does not define a +required convention for exposing authorization information to an Inertia +powered frontend. + +However, if you are using one of Laravel's Inertia-based [starter +kits](/docs/12.x/starter-kits), your application already contains a +`HandleInertiaRequests` middleware. Within this middleware's `share` method, +you may return shared data that will be provided to all Inertia pages in your +application. This shared data can serve as a convenient location to define +authorization information for the user: + + + + 1 + + 17 */ + + 18 public function share(Request $request) + + 19 { + + 20 return [ + + 21 ...parent::share($request), + + 22 'auth' => [ + + 23 'user' => $request->user(), + + 24 'permissions' => [ + + 25 'post' => [ + + 26 'create' => $request->user()->can('create', Post::class), + + 27 ], + + 28 ], + + 29 ], + + 30 ]; + + 31 } + + 32} + + + + */ + public function share(Request $request) + { + return [ + ...parent::share($request), + 'auth' => [ + 'user' => $request->user(), + 'permissions' => [ + 'post' => [ + 'create' => $request->user()->can('create', Post::class), + ], + ], + ], + ]; + } + } + diff --git a/output/12.x/billing.md b/output/12.x/billing.md new file mode 100644 index 0000000..fe316d6 --- /dev/null +++ b/output/12.x/billing.md @@ -0,0 +1,5396 @@ +# Laravel Cashier (Stripe) + + * Introduction + * Upgrading Cashier + * Installation + * Configuration + * Billable Model + * API Keys + * Currency Configuration + * Tax Configuration + * Logging + * Using Custom Models + * Quickstart + * Selling Products + * Selling Subscriptions + * Customers + * Retrieving Customers + * Creating Customers + * Updating Customers + * Balances + * Tax IDs + * Syncing Customer Data With Stripe + * Billing Portal + * Payment Methods + * Storing Payment Methods + * Retrieving Payment Methods + * Payment Method Presence + * Updating the Default Payment Method + * Adding Payment Methods + * Deleting Payment Methods + * Subscriptions + * Creating Subscriptions + * Checking Subscription Status + * Changing Prices + * Subscription Quantity + * Subscriptions With Multiple Products + * Multiple Subscriptions + * Usage Based Billing + * Subscription Taxes + * Subscription Anchor Date + * Canceling Subscriptions + * Resuming Subscriptions + * Subscription Trials + * With Payment Method Up Front + * Without Payment Method Up Front + * Extending Trials + * Handling Stripe Webhooks + * Defining Webhook Event Handlers + * Verifying Webhook Signatures + * Single Charges + * Simple Charge + * Charge With Invoice + * Creating Payment Intents + * Refunding Charges + * Checkout + * Product Checkouts + * Single Charge Checkouts + * Subscription Checkouts + * Collecting Tax IDs + * Guest Checkouts + * Invoices + * Retrieving Invoices + * Upcoming Invoices + * Previewing Subscription Invoices + * Generating Invoice PDFs + * Handling Failed Payments + * Confirming Payments + * Strong Customer Authentication (SCA) + * Payments Requiring Additional Confirmation + * Off-session Payment Notifications + * Stripe SDK + * Testing + +## Introduction + +[Laravel Cashier Stripe](https://github.com/laravel/cashier-stripe) provides +an expressive, fluent interface to [Stripe's](https://stripe.com) subscription +billing services. It handles almost all of the boilerplate subscription +billing code you are dreading writing. In addition to basic subscription +management, Cashier can handle coupons, swapping subscription, subscription +"quantities", cancellation grace periods, and even generate invoice PDFs. + +## Upgrading Cashier + +When upgrading to a new version of Cashier, it's important that you carefully +review [the upgrade guide](https://github.com/laravel/cashier- +stripe/blob/master/UPGRADE.md). + +To prevent breaking changes, Cashier uses a fixed Stripe API version. Cashier +15 utilizes Stripe API version `2023-10-16`. The Stripe API version will be +updated on minor releases in order to make use of new Stripe features and +improvements. + +## Installation + +First, install the Cashier package for Stripe using the Composer package +manager: + + + + 1composer require laravel/cashier + + + composer require laravel/cashier + +After installing the package, publish Cashier's migrations using the +`vendor:publish` Artisan command: + + + + 1php artisan vendor:publish --tag="cashier-migrations" + + + php artisan vendor:publish --tag="cashier-migrations" + +Then, migrate your database: + + + + 1php artisan migrate + + + php artisan migrate + +Cashier's migrations will add several columns to your `users` table. They will +also create a new `subscriptions` table to hold all of your customer's +subscriptions and a `subscription_items` table for subscriptions with multiple +prices. + +If you wish, you can also publish Cashier's configuration file using the +`vendor:publish` Artisan command: + + + + 1php artisan vendor:publish --tag="cashier-config" + + + php artisan vendor:publish --tag="cashier-config" + +Lastly, to ensure Cashier properly handles all Stripe events, remember to +configure Cashier's webhook handling. + +Stripe recommends that any column used for storing Stripe identifiers should +be case-sensitive. Therefore, you should ensure the column collation for the +`stripe_id` column is set to `utf8_bin` when using MySQL. More information +regarding this can be found in the [Stripe +documentation](https://stripe.com/docs/upgrades#what-changes-does-stripe- +consider-to-be-backwards-compatible). + +## Configuration + +### Billable Model + +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: + + + + 1use Laravel\Cashier\Billable; + + 2  + + 3class User extends Authenticatable + + 4{ + + 5 use Billable; + + 6} + + + use Laravel\Cashier\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: + + + + 1use App\Models\Cashier\User; + + 2use Laravel\Cashier\Cashier; + + 3  + + 4/** + + 5 * Bootstrap any application services. + + 6 */ + + 7public function boot(): void + + 8{ + + 9 Cashier::useCustomerModel(User::class); + + 10} + + + use App\Models\Cashier\User; + use Laravel\Cashier\Cashier; + + /** + * Bootstrap any application services. + */ + public function boot(): void + { + Cashier::useCustomerModel(User::class); + } + +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 provided to match your +alternative model's table name. + +### API Keys + +Next, you should configure your Stripe API keys in your application's `.env` +file. You can retrieve your Stripe API keys from the Stripe control panel: + + + + 1STRIPE_KEY=your-stripe-key + + 2STRIPE_SECRET=your-stripe-secret + + 3STRIPE_WEBHOOK_SECRET=your-stripe-webhook-secret + + + STRIPE_KEY=your-stripe-key + STRIPE_SECRET=your-stripe-secret + STRIPE_WEBHOOK_SECRET=your-stripe-webhook-secret + +You should ensure that the `STRIPE_WEBHOOK_SECRET` environment variable is +defined in your application's `.env` file, as this variable is used to ensure +that incoming webhooks are actually from Stripe. + +### Currency Configuration + +The default Cashier currency is United States Dollars (USD). You can change +the default currency by setting the `CASHIER_CURRENCY` environment variable +within your application's `.env` file: + + + + 1CASHIER_CURRENCY=eur + + + CASHIER_CURRENCY=eur + +In addition to configuring Cashier's currency, you may also specify a locale +to be used when formatting money values for display on invoices. Internally, +Cashier utilizes [PHP's `NumberFormatter` +class](https://www.php.net/manual/en/class.numberformatter.php) to set the +currency locale: + + + + 1CASHIER_CURRENCY_LOCALE=nl_BE + + + CASHIER_CURRENCY_LOCALE=nl_BE + +In order to use locales other than `en`, ensure the `ext-intl` PHP extension +is installed and configured on your server. + +### Tax Configuration + +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: + + + + 1use Laravel\Cashier\Cashier; + + 2  + + 3/** + + 4 * Bootstrap any application services. + + 5 */ + + 6public function boot(): void + + 7{ + + 8 Cashier::calculateTaxes(); + + 9} + + + use Laravel\Cashier\Cashier; + + /** + * 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. + +For this feature to work properly, your customer's billing details, such as +the customer's name, address, and tax ID, need to be synced to Stripe. You may +use the customer data synchronization and Tax ID methods offered by Cashier to +accomplish this. + +### Logging + +Cashier allows you to specify the log channel to be used when logging fatal +Stripe errors. You may specify the log channel by defining the +`CASHIER_LOGGER` environment variable within your application's `.env` file: + + + + 1CASHIER_LOGGER=stack + + + CASHIER_LOGGER=stack + +Exceptions that are generated by API calls to Stripe will be logged through +your application's default log channel. + +### Using Custom Models + +You are free to extend the models used internally by Cashier by defining your +own model and extending the corresponding Cashier model: + + + + 1use Laravel\Cashier\Subscription as CashierSubscription; + + 2  + + 3class Subscription extends CashierSubscription + + 4{ + + 5 // ... + + 6} + + + use Laravel\Cashier\Subscription as 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: + + + + 1use App\Models\Cashier\Subscription; + + 2use App\Models\Cashier\SubscriptionItem; + + 3  + + 4/** + + 5 * Bootstrap any application services. + + 6 */ + + 7public function boot(): void + + 8{ + + 9 Cashier::useSubscriptionModel(Subscription::class); + + 10 Cashier::useSubscriptionItemModel(SubscriptionItem::class); + + 11} + + + 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 + +### Selling Products + +Before utilizing Stripe Checkout, you should define Products with fixed prices +in your Stripe dashboard. In addition, you should configure Cashier's webhook +handling. + +Offering product and subscription billing via your application can be +intimidating. However, thanks to Cashier and [Stripe +Checkout](https://stripe.com/payments/checkout), you can easily build modern, +robust payment integrations. + +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: + + + + 1use Illuminate\Http\Request; + + 2  + + 3Route::get('/checkout', function (Request $request) { + + 4 $stripePriceId = 'price_deluxe_album'; + + 5  + + 6 $quantity = 1; + + 7  + + 8 return $request->user()->checkout([$stripePriceId => $quantity], [ + + 9 'success_url' => route('checkout-success'), + + 10 'cancel_url' => route('checkout-cancel'), + + 11 ]); + + 12})->name('checkout'); + + 13  + + 14Route::view('/checkout/success', 'checkout.success')->name('checkout-success'); + + 15Route::view('/checkout/cancel', 'checkout.cancel')->name('checkout-cancel'); + + + use Illuminate\Http\Request; + + Route::get('/checkout', function (Request $request) { + $stripePriceId = 'price_deluxe_album'; + + $quantity = 1; + + 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'); + +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). + +If necessary, the `checkout` method will automatically create a customer in +Stripe and connect that Stripe customer record to the corresponding user in +your application's database. After completing the checkout session, the +customer will be redirected to a dedicated success or cancellation page where +you can display an informational message to the customer. + +#### Providing Meta Data to Stripe Checkout + +When selling products, it's common to keep track of completed orders and +purchased products via `Cart` and `Order` models defined by your own +application. When redirecting customers to Stripe Checkout to complete a +purchase, you may need to provide an existing order identifier so that you can +associate the completed purchase with the corresponding order when the +customer is redirected back to your application. + +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: + + + + 1use App\Models\Cart; + + 2use App\Models\Order; + + 3use Illuminate\Http\Request; + + 4  + + 5Route::get('/cart/{cart}/checkout', function (Request $request, Cart $cart) { + + 6 $order = Order::create([ + + 7 'cart_id' => $cart->id, + + 8 'price_ids' => $cart->price_ids, + + 9 'status' => 'incomplete', + + 10 ]); + + 11  + + 12 return $request->user()->checkout($order->price_ids, [ + + 13 'success_url' => route('checkout-success').'?session_id={CHECKOUT_SESSION_ID}', + + 14 'cancel_url' => route('checkout-cancel'), + + 15 'metadata' => ['order_id' => $order->id], + + 16 ]); + + 17})->name('checkout'); + + + 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', + ]); + + 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: + + + + 1use App\Models\Order; + + 2use Illuminate\Http\Request; + + 3use Laravel\Cashier\Cashier; + + 4  + + 5Route::get('/checkout/success', function (Request $request) { + + 6 $sessionId = $request->get('session_id'); + + 7  + + 8 if ($sessionId === null) { + + 9 return; + + 10 } + + 11  + + 12 $session = Cashier::stripe()->checkout->sessions->retrieve($sessionId); + + 13  + + 14 if ($session->payment_status !== 'paid') { + + 15 return; + + 16 } + + 17  + + 18 $orderId = $session['metadata']['order_id'] ?? null; + + 19  + + 20 $order = Order::findOrFail($orderId); + + 21  + + 22 $order->update(['status' => 'completed']); + + 23  + + 24 return view('checkout-success', ['order' => $order]); + + 25})->name('checkout-success'); + + + use App\Models\Order; + use Illuminate\Http\Request; + use Laravel\Cashier\Cashier; + + Route::get('/checkout/success', function (Request $request) { + $sessionId = $request->get('session_id'); + + if ($sessionId === null) { + return; + } + + $session = Cashier::stripe()->checkout->sessions->retrieve($sessionId); + + if ($session->payment_status !== 'paid') { + return; + } + + $orderId = $session['metadata']['order_id'] ?? null; + + $order = Order::findOrFail($orderId); + + $order->update(['status' => 'completed']); + + 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). + +### Selling Subscriptions + +Before utilizing Stripe Checkout, you should define Products with fixed prices +in your Stripe dashboard. In addition, you should configure Cashier's webhook +handling. + +Offering product and subscription billing via your application can be +intimidating. However, thanks to Cashier and [Stripe +Checkout](https://stripe.com/payments/checkout), you can easily build modern, +robust payment integrations. + +To learn how to sell subscriptions using Cashier and Stripe Checkout, let's +consider the simple scenario of a subscription service with a basic monthly +(`price_basic_monthly`) and yearly (`price_basic_yearly`) plan. These two +prices could be grouped under a "Basic" product (`pro_basic`) in our Stripe +dashboard. In addition, our subscription service might offer an Expert plan as +`pro_expert`. + +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: + + + + 1use Illuminate\Http\Request; + + 2  + + 3Route::get('/subscription-checkout', function (Request $request) { + + 4 return $request->user() + + 5 ->newSubscription('default', 'price_basic_monthly') + + 6 ->trialDays(5) + + 7 ->allowPromotionCodes() + + 8 ->checkout([ + + 9 'success_url' => route('your-success-route'), + + 10 'cancel_url' => route('your-cancel-route'), + + 11 ]); + + 12}); + + + 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'), + ]); + }); + +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. + +Now that customers can start subscriptions, we need to restrict certain +portions of our application so that only subscribed users can access them. Of +course, we can always determine a user's current subscription status via the +`subscribed` method provided by Cashier's `Billable` trait: + + + + 1@if ($user->subscribed()) + + 2

You are subscribed.

+ + 3@endif + + + @if ($user->subscribed()) +

You are subscribed.

+ @endif + +We can even easily determine if a user is subscribed to specific product or +price: + + + + 1@if ($user->subscribedToProduct('pro_basic')) + + 2

You are subscribed to our Basic product.

+ + 3@endif + + 4  + + 5@if ($user->subscribedToPrice('price_basic_monthly')) + + 6

You are subscribed to our monthly Basic plan.

+ + 7@endif + + + @if ($user->subscribedToProduct('pro_basic')) +

You are subscribed to our Basic product.

+ @endif + + @if ($user->subscribedToPrice('price_basic_monthly')) +

You are subscribed to our monthly Basic plan.

+ @endif + +#### Building a Subscribed Middleware + +For convenience, you may wish to create a [middleware](/docs/12.x/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: + + + + 1user()?->subscribed()) { + + 17 // Redirect user to billing page and ask them to subscribe... + + 18 return redirect('/billing'); + + 19 } + + 20  + + 21 return $next($request); + + 22 } + + 23} + + + 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: + + + + 1use App\Http\Middleware\Subscribed; + + 2  + + 3Route::get('/dashboard', function () { + + 4 // ... + + 5})->middleware([Subscribed::class]); + + + use App\Http\Middleware\Subscribed; + + 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". The easiest way to allow this is by directing customers to +Stripe's [Customer Billing Portal](https://stripe.com/docs/no-code/customer- +portal), which provides a hosted user interface that allows customers to +download invoices, update their payment method, and change subscription plans. + +First, define a link or button within your application that directs users to a +Laravel route which we will utilize to initiate a Billing Portal session: + + + + 1 + + 2 Billing + + 3 + + + + Billing + + +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: + + + + 1use Illuminate\Http\Request; + + 2  + + 3Route::get('/billing', function (Request $request) { + + 4 return $request->user()->redirectToBillingPortal(route('dashboard')); + + 5})->middleware(['auth'])->name('billing'); + + + use Illuminate\Http\Request; + + Route::get('/billing', function (Request $request) { + return $request->user()->redirectToBillingPortal(route('dashboard')); + })->middleware(['auth'])->name('billing'); + +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. + +## Customers + +### Retrieving Customers + +You can retrieve a customer by their Stripe ID using the +`Cashier::findBillable` method. This method will return an instance of the +billable model: + + + + 1use Laravel\Cashier\Cashier; + + 2  + + 3$user = Cashier::findBillable($stripeId); + + + use Laravel\Cashier\Cashier; + + $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: + + + + 1$stripeCustomer = $user->createAsStripeCustomer(); + + + $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): + + + + 1$stripeCustomer = $user->createAsStripeCustomer($options); + + + $stripeCustomer = $user->createAsStripeCustomer($options); + +You may use the `asStripeCustomer` method if you want to return the Stripe +customer object for a billable model: + + + + 1$stripeCustomer = $user->asStripeCustomer(); + + + $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: + + + + 1$stripeCustomer = $user->createOrGetStripeCustomer(); + + + $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): + + + + 1$stripeCustomer = $user->updateStripeCustomer($options); + + + $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: + + + + 1$balance = $user->balance(); + + + $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: + + + + 1$user->creditBalance(500, 'Premium customer top-up.'); + + + $user->creditBalance(500, 'Premium customer top-up.'); + +Providing a value to the `debitBalance` method will debit the customer's +balance: + + + + 1$user->debitBalance(300, 'Bad usage penalty.'); + + + $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: + + + + 1// Retrieve all transactions... + + 2$transactions = $user->balanceTransactions(); + + 3  + + 4foreach ($transactions as $transaction) { + + 5 // Transaction amount... + + 6 $amount = $transaction->amount(); // $2.31 + + 7  + + 8 // Retrieve the related invoice when available... + + 9 $invoice = $transaction->invoice(); + + 10} + + + // Retrieve all transactions... + $transactions = $user->balanceTransactions(); + + foreach ($transactions as $transaction) { + // Transaction amount... + $amount = $transaction->amount(); // $2.31 + + // 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: + + + + 1$taxIds = $user->taxIds(); + + + $taxIds = $user->taxIds(); + +You can also retrieve a specific tax ID for a customer by its identifier: + + + + 1$taxId = $user->findTaxId('txi_belgium'); + + + $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: + + + + 1$taxId = $user->createTaxId('eu_vat', 'BE0123456789'); + + + $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. + +You may delete a tax ID using the `deleteTaxId` method: + + + + 1$user->deleteTaxId('txi_belgium'); + + + $user->deleteTaxId('txi_belgium'); + +### Syncing Customer Data With Stripe + +Typically, when your application's users update their name, email address, or +other information that is also stored by Stripe, you should inform Stripe of +the updates. By doing so, Stripe's copy of the information will be in sync +with your application's. + +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: + + + + 1use App\Models\User; + + 2use function Illuminate\Events\queueable; + + 3  + + 4/** + + 5 * The "booted" method of the model. + + 6 */ + + 7protected static function booted(): void + + 8{ + + 9 static::updated(queueable(function (User $customer) { + + 10 if ($customer->hasStripeId()) { + + 11 $customer->syncStripeCustomerDetails(); + + 12 } + + 13 })); + + 14} + + + 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: + + + + 1/** + + 2 * Get the customer name that should be synced to Stripe. + + 3 */ + + 4public function stripeName(): string|null + + 5{ + + 6 return $this->company_name; + + 7} + + + /** + * 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. + +### Billing Portal + +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: + + + + 1use Illuminate\Http\Request; + + 2  + + 3Route::get('/billing-portal', function (Request $request) { + + 4 return $request->user()->redirectToBillingPortal(); + + 5}); + + + use Illuminate\Http\Request; + + 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: + + + + 1use Illuminate\Http\Request; + + 2  + + 3Route::get('/billing-portal', function (Request $request) { + + 4 return $request->user()->redirectToBillingPortal(route('billing')); + + 5}); + + + use Illuminate\Http\Request; + + 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: + + + + 1$url = $request->user()->billingPortalUrl(route('billing')); + + + $url = $request->user()->billingPortalUrl(route('billing')); + +## Payment Methods + +### Storing Payment Methods + +In order to create subscriptions or perform "one-off" charges with Stripe, you +will need to store a payment method and retrieve its identifier from Stripe. +The approach used to accomplish this differs based on whether you plan to use +the payment method for subscriptions or single charges, so we will examine +both below. + +#### Payment Methods for Subscriptions + +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: + + + + 1return view('update-payment-method', [ + + 2 'intent' => $user->createSetupIntent() + + 3]); + + + 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: + + + + 1 + + 2  + + 3 + + 4
+ + 5  + + 6 + + + + + +
+ + + +Next, the Stripe.js library may be used to attach a [Stripe +Element](https://stripe.com/docs/stripe-js) to the form and securely gather +the customer's payment details: + + + + 1 + + 2  + + 3 + + + + + + +Next, the card can be verified and a secure "payment method identifier" can be +retrieved from Stripe using [Stripe's `confirmCardSetup` +method](https://stripe.com/docs/js/setup_intents/confirm_card_setup): + + + + 1const cardHolderName = document.getElementById('card-holder-name'); + + 2const cardButton = document.getElementById('card-button'); + + 3const clientSecret = cardButton.dataset.secret; + + 4  + + 5cardButton.addEventListener('click', async (e) => { + + 6 const { setupIntent, error } = await stripe.confirmCardSetup( + + 7 clientSecret, { + + 8 payment_method: { + + 9 card: cardElement, + + 10 billing_details: { name: cardHolderName.value } + + 11 } + + 12 } + + 13 ); + + 14  + + 15 if (error) { + + 16 // Display "error.message" to the user... + + 17 } else { + + 18 // The card has been verified successfully... + + 19 } + + 20}); + + + const cardHolderName = document.getElementById('card-holder-name'); + const cardButton = document.getElementById('card-button'); + const clientSecret = cardButton.dataset.secret; + + cardButton.addEventListener('click', async (e) => { + const { setupIntent, error } = await stripe.confirmCardSetup( + clientSecret, { + payment_method: { + card: cardElement, + billing_details: { name: cardHolderName.value } + } + } + ); + + if (error) { + // Display "error.message" to the user... + } else { + // The card has been verified successfully... + } + }); + +After the card has been verified by Stripe, you may pass the resulting +`setupIntent.payment_method` identifier to your Laravel application, where it +can be attached to the customer. The payment method can either be added as a +new payment method or used to update the default payment method. You can also +immediately use the payment method identifier to create a new subscription. + +If you would like more information about Setup Intents and gathering customer +payment details please [review this overview provided by +Stripe](https://stripe.com/docs/payments/save-and-reuse#php). + +#### Payment Methods for Single Charges + +Of course, when making a single charge against a customer's payment method, we +will only need to use a payment method identifier once. Due to Stripe +limitations, you may not use the stored default payment method of a customer +for single charges. You must allow the customer to enter their payment method +details using the Stripe.js library. For example, consider the following form: + + + + 1 + + 2  + + 3 + + 4
+ + 5  + + 6 + + + + + +
+ + + +After defining such a form, the Stripe.js library may be used to attach a +[Stripe Element](https://stripe.com/docs/stripe-js) to the form and securely +gather the customer's payment details: + + + + 1 + + 2  + + 3 + + + + + + +Next, the card can be verified and a secure "payment method identifier" can be +retrieved from Stripe using [Stripe's `createPaymentMethod` +method](https://stripe.com/docs/stripe-js/reference#stripe-create-payment- +method): + + + + 1const cardHolderName = document.getElementById('card-holder-name'); + + 2const cardButton = document.getElementById('card-button'); + + 3  + + 4cardButton.addEventListener('click', async (e) => { + + 5 const { paymentMethod, error } = await stripe.createPaymentMethod( + + 6 'card', cardElement, { + + 7 billing_details: { name: cardHolderName.value } + + 8 } + + 9 ); + + 10  + + 11 if (error) { + + 12 // Display "error.message" to the user... + + 13 } else { + + 14 // The card has been verified successfully... + + 15 } + + 16}); + + + const cardHolderName = document.getElementById('card-holder-name'); + const cardButton = document.getElementById('card-button'); + + cardButton.addEventListener('click', async (e) => { + const { paymentMethod, error } = await stripe.createPaymentMethod( + 'card', cardElement, { + billing_details: { name: cardHolderName.value } + } + ); + + if (error) { + // Display "error.message" to the user... + } else { + // The card has been verified successfully... + } + }); + +If the card is verified successfully, you may pass the `paymentMethod.id` to +your Laravel application and process a single charge. + +### Retrieving Payment Methods + +The `paymentMethods` method on the billable model instance returns a +collection of `Laravel\Cashier\PaymentMethod` instances: + + + + 1$paymentMethods = $user->paymentMethods(); + + + $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: + + + + 1$paymentMethods = $user->paymentMethods('sepa_debit'); + + + $paymentMethods = $user->paymentMethods('sepa_debit'); + +To retrieve the customer's default payment method, the `defaultPaymentMethod` +method may be used: + + + + 1$paymentMethod = $user->defaultPaymentMethod(); + + + $paymentMethod = $user->defaultPaymentMethod(); + +You can retrieve a specific payment method that is attached to the billable +model using the `findPaymentMethod` method: + + + + 1$paymentMethod = $user->findPaymentMethod($paymentMethodId); + + + $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: + + + + 1if ($user->hasDefaultPaymentMethod()) { + + 2 // ... + + 3} + + + 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: + + + + 1if ($user->hasPaymentMethod()) { + + 2 // ... + + 3} + + + 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: + + + + 1if ($user->hasPaymentMethod('sepa_debit')) { + + 2 // ... + + 3} + + + 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: + + + + 1$user->updateDefaultPaymentMethod($paymentMethod); + + + $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: + + + + 1$user->updateDefaultPaymentMethodFromStripe(); + + + $user->updateDefaultPaymentMethodFromStripe(); + +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. + +### Adding Payment Methods + +To add a new payment method, you may call the `addPaymentMethod` method on the +billable model, passing the payment method identifier: + + + + 1$user->addPaymentMethod($paymentMethod); + + + $user->addPaymentMethod($paymentMethod); + +To learn how to retrieve payment method identifiers please review the payment +method storage documentation. + +### Deleting Payment Methods + +To delete a payment method, you may call the `delete` method on the +`Laravel\Cashier\PaymentMethod` instance you wish to delete: + + + + 1$paymentMethod->delete(); + + + $paymentMethod->delete(); + +The `deletePaymentMethod` method will delete a specific payment method from +the billable model: + + + + 1$user->deletePaymentMethod('pm_visa'); + + + $user->deletePaymentMethod('pm_visa'); + +The `deletePaymentMethods` method will delete all of the payment method +information for the billable model: + + + + 1$user->deletePaymentMethods(); + + + $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: + + + + 1$user->deletePaymentMethods('sepa_debit'); + + + $user->deletePaymentMethods('sepa_debit'); + +If a user has an active subscription, your application should not allow them +to delete their default payment method. + +## Subscriptions + +Subscriptions provide a way to set up recurring payments for your customers. +Stripe subscriptions managed by Cashier provide support for multiple +subscription prices, subscription quantities, trials, and more. + +### Creating Subscriptions + +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: + + + + 1use Illuminate\Http\Request; + + 2  + + 3Route::post('/user/subscribe', function (Request $request) { + + 4 $request->user()->newSubscription( + + 5 'default', 'price_monthly' + + 6 )->create($request->paymentMethodId); + + 7  + + 8 // ... + + 9}); + + + use Illuminate\Http\Request; + + 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. + +The `create` method, which accepts a Stripe payment method identifier or +Stripe `PaymentMethod` object, will begin the subscription as well as update +your database with the billable model's Stripe customer ID and other relevant +billing information. + +Passing a payment method identifier directly to the `create` subscription +method will also automatically add it to the user's stored payment methods. + +#### Collecting Recurring Payments via Invoice Emails + +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: + + + + 1$user->newSubscription('default', 'price_monthly')->createAndSendInvoice(); + + + $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: + + + + 1$user->newSubscription('default', 'price_monthly')->createAndSendInvoice([], [ + + 2 'days_until_due' => 30 + + 3]); + + + $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: + + + + 1$user->newSubscription('default', 'price_monthly') + + 2 ->quantity(5) + + 3 ->create($paymentMethod); + + + $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: + + + + 1$user->newSubscription('default', 'price_monthly')->create($paymentMethod, [ + + 2 'email' => $email, + + 3], [ + + 4 'metadata' => ['note' => 'Some extra information.'], + + 5]); + + + $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: + + + + 1$user->newSubscription('default', 'price_monthly') + + 2 ->withCoupon('code') + + 3 ->create($paymentMethod); + + + $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: + + + + 1$user->newSubscription('default', 'price_monthly') + + 2 ->withPromotionCode('promo_code_id') + + 3 ->create($paymentMethod); + + + $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: + + + + 1// Find a promotion code ID by its customer facing code... + + 2$promotionCode = $user->findPromotionCode('SUMMERSALE'); + + 3  + + 4// Find an active promotion code ID by its customer facing code... + + 5$promotionCode = $user->findActivePromotionCode('SUMMERSALE'); + + + // 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'); + +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: + + + + 1$coupon = $user->findPromotionCode('SUMMERSALE')->coupon(); + + + $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: + + + + 1if ($coupon->isPercentage()) { + + 2 return $coupon->percentOff().'%'; // 21.5% + + 3} else { + + 4 return $coupon->amountOff(); // $5.99 + + 5} + + + 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: + + + + 1$discount = $billable->discount(); + + 2  + + 3$discount = $subscription->discount(); + + + $discount = $billable->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: + + + + 1$coupon = $subscription->discount()->coupon(); + + + $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: + + + + 1$billable->applyCoupon('coupon_id'); + + 2$billable->applyPromotionCode('promotion_code_id'); + + 3  + + 4$subscription->applyCoupon('coupon_id'); + + 5$subscription->applyPromotionCode('promotion_code_id'); + + + $billable->applyCoupon('coupon_id'); + $billable->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. + +For more info on this subject, please consult the Stripe documentation +regarding [coupons](https://stripe.com/docs/billing/subscriptions/coupons) and +[promotion +codes](https://stripe.com/docs/billing/subscriptions/coupons/codes). + +#### Adding Subscriptions + +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: + + + + 1use App\Models\User; + + 2  + + 3$user = User::find(1); + + 4  + + 5$user->newSubscription('default', 'price_monthly')->add(); + + + use App\Models\User; + + $user = User::find(1); + + $user->newSubscription('default', 'price_monthly')->add(); + +#### Creating Subscriptions From the Stripe Dashboard + +You may also create subscriptions from the Stripe dashboard itself. When doing +so, Cashier will sync newly added subscriptions and assign them a type of +`default`. To customize the subscription type that is assigned to dashboard +created subscriptions, define webhook event handlers. + +In addition, you may only create one type of subscription via the Stripe +dashboard. If your application offers multiple subscriptions that use +different types, only one type of subscription may be added through the Stripe +dashboard. + +Finally, you should always make sure to only add one active subscription per +type of subscription offered by your application. If a customer has two +`default` subscriptions, only the most recently added subscription will be +used by Cashier even though both would be synced with your application's +database. + +### Checking Subscription Status + +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: + + + + 1if ($user->subscribed('default')) { + + 2 // ... + + 3} + + + if ($user->subscribed('default')) { + // ... + } + +The `subscribed` method also makes a great candidate for a [route +middleware](/docs/12.x/middleware), allowing you to filter access to routes +and controllers based on the user's subscription status: + + + + 1user() && ! $request->user()->subscribed('default')) { + + 19 // This user is not a paying customer... + + 20 return redirect('/billing'); + + 21 } + + 22  + + 23 return $next($request); + + 24 } + + 25} + + + 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: + + + + 1if ($user->subscription('default')->onTrial()) { + + 2 // ... + + 3} + + + 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: + + + + 1if ($user->subscribedToProduct('prod_premium', 'default')) { + + 2 // ... + + 3} + + + 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: + + + + 1if ($user->subscribedToProduct(['prod_basic', 'prod_premium'], 'default')) { + + 2 // ... + + 3} + + + 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: + + + + 1if ($user->subscribedToPrice('price_basic_monthly', 'default')) { + + 2 // ... + + 3} + + + 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: + + + + 1if ($user->subscription('default')->recurring()) { + + 2 // ... + + 3} + + + if ($user->subscription('default')->recurring()) { + // ... + } + +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. + +#### Canceled Subscription Status + +To determine if the user was once an active subscriber but has canceled their +subscription, you may use the `canceled` method: + + + + 1if ($user->subscription('default')->canceled()) { + + 2 // ... + + 3} + + + 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: + + + + 1if ($user->subscription('default')->onGracePeriod()) { + + 2 // ... + + 3} + + + 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: + + + + 1if ($user->subscription('default')->ended()) { + + 2 // ... + + 3} + + + if ($user->subscription('default')->ended()) { + // ... + } + +#### Incomplete and Past Due Status + +If a subscription requires a secondary payment action after creation the +subscription will be marked as `incomplete`. Subscription statuses are stored +in the `stripe_status` column of Cashier's `subscriptions` database table. + +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: + + + + 1if ($user->hasIncompletePayment('default')) { + + 2 // ... + + 3} + + 4  + + 5if ($user->subscription('default')->hasIncompletePayment()) { + + 6 // ... + + 7} + + + if ($user->hasIncompletePayment('default')) { + // ... + } + + 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: + + + + 1 + + 2 Please confirm your payment. + + 3 + + + + Please confirm your payment. + + +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`: + + + + 1use Laravel\Cashier\Cashier; + + 2  + + 3/** + + 4 * Register any application services. + + 5 */ + + 6public function register(): void + + 7{ + + 8 Cashier::keepPastDueSubscriptionsActive(); + + 9 Cashier::keepIncompleteSubscriptionsActive(); + + 10} + + + use Laravel\Cashier\Cashier; + + /** + * Register any application services. + */ + public function register(): void + { + Cashier::keepPastDueSubscriptionsActive(); + Cashier::keepIncompleteSubscriptionsActive(); + } + +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. + +#### Subscription Scopes + +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: + + + + 1// Get all active subscriptions... + + 2$subscriptions = Subscription::query()->active()->get(); + + 3  + + 4// Get all of the canceled subscriptions for a user... + + 5$subscriptions = $user->subscriptions()->canceled()->get(); + + + // Get all active subscriptions... + $subscriptions = Subscription::query()->active()->get(); + + // Get all of the canceled subscriptions for a user... + $subscriptions = $user->subscriptions()->canceled()->get(); + +A complete list of available scopes is available below: + + + + 1Subscription::query()->active(); + + 2Subscription::query()->canceled(); + + 3Subscription::query()->ended(); + + 4Subscription::query()->incomplete(); + + 5Subscription::query()->notCanceled(); + + 6Subscription::query()->notOnGracePeriod(); + + 7Subscription::query()->notOnTrial(); + + 8Subscription::query()->onGracePeriod(); + + 9Subscription::query()->onTrial(); + + 10Subscription::query()->pastDue(); + + 11Subscription::query()->recurring(); + + + 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: + + + + 1use App\Models\User; + + 2  + + 3$user = App\Models\User::find(1); + + 4  + + 5$user->subscription('default')->swap('price_yearly'); + + + use App\Models\User; + + $user = App\Models\User::find(1); + + $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: + + + + 1$user->subscription('default') + + 2 ->skipTrial() + + 3 ->swap('price_yearly'); + + + $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: + + + + 1$user = User::find(1); + + 2  + + 3$user->subscription('default')->swapAndInvoice('price_yearly'); + + + $user = User::find(1); + + $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: + + + + 1$user->subscription('default')->noProrate()->swap('price_yearly'); + + + $user->subscription('default')->noProrate()->swap('price_yearly'); + +For more information on subscription proration, consult the [Stripe +documentation](https://stripe.com/docs/billing/subscriptions/prorations). + +Executing the `noProrate` method before the `swapAndInvoice` method will have +no effect on proration. An invoice will always be issued. + +### Subscription Quantity + +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: + + + + 1use App\Models\User; + + 2  + + 3$user = User::find(1); + + 4  + + 5$user->subscription('default')->incrementQuantity(); + + 6  + + 7// Add five to the subscription's current quantity... + + 8$user->subscription('default')->incrementQuantity(5); + + 9  + + 10$user->subscription('default')->decrementQuantity(); + + 11  + + 12// Subtract five from the subscription's current quantity... + + 13$user->subscription('default')->decrementQuantity(5); + + + use App\Models\User; + + $user = User::find(1); + + $user->subscription('default')->incrementQuantity(); + + // Add five to the subscription's current quantity... + $user->subscription('default')->incrementQuantity(5); + + $user->subscription('default')->decrementQuantity(); + + // Subtract five from the subscription's current quantity... + $user->subscription('default')->decrementQuantity(5); + +Alternatively, you may set a specific quantity using the `updateQuantity` +method: + + + + 1$user->subscription('default')->updateQuantity(10); + + + $user->subscription('default')->updateQuantity(10); + +The `noProrate` method may be used to update the subscription's quantity +without prorating the charges: + + + + 1$user->subscription('default')->noProrate()->updateQuantity(10); + + + $user->subscription('default')->noProrate()->updateQuantity(10); + +For more information on subscription quantities, consult the [Stripe +documentation](https://stripe.com/docs/subscriptions/quantities). + +#### Quantities for Subscriptions With Multiple Products + +If your subscription is a subscription 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: + + + + 1$user->subscription('default')->incrementQuantity(1, 'price_chat'); + + + $user->subscription('default')->incrementQuantity(1, 'price_chat'); + +### Subscriptions With Multiple Products + +[Subscription with multiple +products](https://stripe.com/docs/billing/subscriptions/multiple-products) +allow you to assign multiple billing products to a single subscription. For +example, imagine you are building a customer service "helpdesk" application +that has a base subscription price of $10 per month but offers a live chat +add-on product for an additional $15 per month. Information for subscriptions +with multiple products is stored in Cashier's `subscription_items` database +table. + +You may specify multiple products for a given subscription by passing an array +of prices as the second argument to the `newSubscription` method: + + + + 1use Illuminate\Http\Request; + + 2  + + 3Route::post('/user/subscribe', function (Request $request) { + + 4 $request->user()->newSubscription('default', [ + + 5 'price_monthly', + + 6 'price_chat', + + 7 ])->create($request->paymentMethodId); + + 8  + + 9 // ... + + 10}); + + + use Illuminate\Http\Request; + + 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: + + + + 1$user = User::find(1); + + 2  + + 3$user->newSubscription('default', ['price_monthly', 'price_chat']) + + 4 ->quantity(5, 'price_chat') + + 5 ->create($paymentMethod); + + + $user = User::find(1); + + $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: + + + + 1$user = User::find(1); + + 2  + + 3$user->subscription('default')->addPrice('price_chat'); + + + $user = User::find(1); + + $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: + + + + 1$user->subscription('default')->addPriceAndInvoice('price_chat'); + + + $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: + + + + 1$user = User::find(1); + + 2  + + 3$user->subscription('default')->addPrice('price_chat', 5); + + + $user = User::find(1); + + $user->subscription('default')->addPrice('price_chat', 5); + +You may remove prices from subscriptions using the `removePrice` method: + + + + 1$user->subscription('default')->removePrice('price_chat'); + + + $user->subscription('default')->removePrice('price_chat'); + +You may not remove the last price on a subscription. Instead, you should +simply cancel the subscription. + +#### Swapping Prices + +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: + + + + 1use App\Models\User; + + 2  + + 3$user = User::find(1); + + 4  + + 5$user->subscription('default')->swap(['price_pro', 'price_chat']); + + + use App\Models\User; + + $user = User::find(1); + + $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: + + + + 1$user = User::find(1); + + 2  + + 3$user->subscription('default')->swap([ + + 4 'price_pro' => ['quantity' => 5], + + 5 'price_chat' + + 6]); + + + $user = User::find(1); + + $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: + + + + 1$user = User::find(1); + + 2  + + 3$user->subscription('default') + + 4 ->findItemOrFail('price_basic') + + 5 ->swap('price_pro'); + + + $user = User::find(1); + + $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: + + + + 1$user->subscription('default')->noProrate()->removePrice('price_chat'); + + + $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 by passing the ID of the price +as an additional argument to the method: + + + + 1$user = User::find(1); + + 2  + + 3$user->subscription('default')->incrementQuantity(5, 'price_chat'); + + 4  + + 5$user->subscription('default')->decrementQuantity(3, 'price_chat'); + + 6  + + 7$user->subscription('default')->updateQuantity(10, 'price_chat'); + + + $user = User::find(1); + + $user->subscription('default')->incrementQuantity(5, 'price_chat'); + + $user->subscription('default')->decrementQuantity(3, 'price_chat'); + + $user->subscription('default')->updateQuantity(10, 'price_chat'); + +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. + +#### Subscription Items + +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: + + + + 1use App\Models\User; + + 2  + + 3$user = User::find(1); + + 4  + + 5$subscriptionItem = $user->subscription('default')->items->first(); + + 6  + + 7// Retrieve the Stripe price and quantity for a specific item... + + 8$stripePrice = $subscriptionItem->stripe_price; + + 9$quantity = $subscriptionItem->quantity; + + + use App\Models\User; + + $user = User::find(1); + + $subscriptionItem = $user->subscription('default')->items->first(); + + // 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: + + + + 1$user = User::find(1); + + 2  + + 3$subscriptionItem = $user->subscription('default')->findItemOrFail('price_chat'); + + + $user = User::find(1); + + $subscriptionItem = $user->subscription('default')->findItemOrFail('price_chat'); + +### Multiple Subscriptions + +Stripe allows your customers to have multiple subscriptions simultaneously. +For example, you may run a gym that offers a swimming subscription and a +weight-lifting subscription, and each subscription may have different pricing. +Of course, customers should be able to subscribe to either or both plans. + +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: + + + + 1use Illuminate\Http\Request; + + 2  + + 3Route::post('/swimming/subscribe', function (Request $request) { + + 4 $request->user()->newSubscription('swimming') + + 5 ->price('price_swimming_monthly') + + 6 ->create($request->paymentMethodId); + + 7  + + 8 // ... + + 9}); + + + use Illuminate\Http\Request; + + 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: + + + + 1$user->subscription('swimming')->swap('price_swimming_yearly'); + + + $user->subscription('swimming')->swap('price_swimming_yearly'); + +Of course, you may also cancel the subscription entirely: + + + + 1$user->subscription('swimming')->cancel(); + + + $user->subscription('swimming')->cancel(); + +### Usage Based Billing + +[Usage based billing](https://stripe.com/docs/billing/subscriptions/metered- +billing) allows you to charge customers based on their product usage during a +billing cycle. For example, you may charge customers based on the number of +text messages or emails they send per month. + +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: + + + + 1use Illuminate\Http\Request; + + 2  + + 3Route::post('/user/subscribe', function (Request $request) { + + 4 $request->user()->newSubscription('default') + + 5 ->meteredPrice('price_metered') + + 6 ->create($request->paymentMethodId); + + 7  + + 8 // ... + + 9}); + + + use Illuminate\Http\Request; + + 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: + + + + 1$checkout = Auth::user() + + 2 ->newSubscription('default', []) + + 3 ->meteredPrice('price_metered') + + 4 ->checkout(); + + 5  + + 6return view('your-checkout-view', [ + + 7 'checkout' => $checkout, + + 8]); + + + $checkout = Auth::user() + ->newSubscription('default', []) + ->meteredPrice('price_metered') + ->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: + + + + 1$user = User::find(1); + + 2  + + 3$user->reportMeterEvent('emails-sent'); + + + $user = User::find(1); + + $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: + + + + 1$user = User::find(1); + + 2  + + 3$user->reportMeterEvent('emails-sent', quantity: 15); + + + $user = User::find(1); + + $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: + + + + 1$user = User::find(1); + + 2  + + 3$meterUsage = $user->meterEventSummaries($meterId); + + 4  + + 5$meterUsage->first()->aggregated_value // 10 + + + $user = User::find(1); + + $meterUsage = $user->meterEventSummaries($meterId); + + $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: + + + + 1$user = User::find(1); + + 2  + + 3$user->meters(); + + + $user = User::find(1); + + $user->meters(); + +### Subscription Taxes + +Instead of calculating Tax Rates manually, you can automatically calculate +taxes using Stripe Tax + +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): + + + + 1/** + + 2 * The tax rates that should apply to the customer's subscriptions. + + 3 * + + 4 * @return array + + 5 */ + + 6public function taxRates(): array + + 7{ + + 8 return ['txr_id']; + + 9} + + + /** + * 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: + + + + 1/** + + 2 * The tax rates that should apply to the customer's subscriptions. + + 3 * + + 4 * @return array> + + 5 */ + + 6public function priceTaxRates(): array + + 7{ + + 8 return [ + + 9 'price_monthly' => ['txr_id'], + + 10 ]; + + 11} + + + /** + * The tax rates that should apply to the customer's subscriptions. + * + * @return array> + */ + public function priceTaxRates(): array + { + return [ + 'price_monthly' => ['txr_id'], + ]; + } + +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. + +#### Syncing Tax Rates + +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: + + + + 1$user->subscription('default')->syncTaxRates(); + + + $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. + +#### Tax Exemption + +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: + + + + 1use App\Models\User; + + 2  + + 3$user = User::find(1); + + 4  + + 5$user->isTaxExempt(); + + 6$user->isNotTaxExempt(); + + 7$user->reverseChargeApplies(); + + + use App\Models\User; + + $user = User::find(1); + + $user->isTaxExempt(); + $user->isNotTaxExempt(); + $user->reverseChargeApplies(); + +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. + +### Subscription Anchor Date + +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: + + + + 1use Illuminate\Http\Request; + + 2  + + 3Route::post('/user/subscribe', function (Request $request) { + + 4 $anchor = Carbon::parse('first day of next month'); + + 5  + + 6 $request->user()->newSubscription('default', 'price_monthly') + + 7 ->anchorBillingCycleOn($anchor->startOfDay()) + + 8 ->create($request->paymentMethodId); + + 9  + + 10 // ... + + 11}); + + + use Illuminate\Http\Request; + + 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); + + // ... + }); + +For more information on managing subscription billing cycles, consult the +[Stripe billing cycle +documentation](https://stripe.com/docs/billing/subscriptions/billing-cycle) + +### Cancelling Subscriptions + +To cancel a subscription, call the `cancel` method on the user's subscription: + + + + 1$user->subscription('default')->cancel(); + + + $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`. + +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: + + + + 1if ($user->subscription('default')->onGracePeriod()) { + + 2 // ... + + 3} + + + if ($user->subscription('default')->onGracePeriod()) { + // ... + } + +If you wish to cancel a subscription immediately, call the `cancelNow` method +on the user's subscription: + + + + 1$user->subscription('default')->cancelNow(); + + + $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: + + + + 1$user->subscription('default')->cancelNowAndInvoice(); + + + $user->subscription('default')->cancelNowAndInvoice(); + +You may also choose to cancel the subscription at a specific moment in time: + + + + 1$user->subscription('default')->cancelAt( + + 2 now()->addDays(10) + + 3); + + + $user->subscription('default')->cancelAt( + now()->addDays(10) + ); + +Finally, you should always cancel user subscriptions before deleting the +associated user model: + + + + 1$user->subscription('default')->cancelNow(); + + 2  + + 3$user->delete(); + + + $user->subscription('default')->cancelNow(); + + $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: + + + + 1$user->subscription('default')->resume(); + + + $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. + +## Subscription Trials + +### With Payment Method Up Front + +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: + + + + 1use Illuminate\Http\Request; + + 2  + + 3Route::post('/user/subscribe', function (Request $request) { + + 4 $request->user()->newSubscription('default', 'price_monthly') + + 5 ->trialDays(10) + + 6 ->create($request->paymentMethodId); + + 7  + + 8 // ... + + 9}); + + + use Illuminate\Http\Request; + + 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. + +If the customer's subscription is not canceled before the trial ending date +they will be charged as soon as the trial expires, so you should be sure to +notify your users of their trial ending date. + +The `trialUntil` method allows you to provide a `DateTime` instance that +specifies when the trial period should end: + + + + 1use Illuminate\Support\Carbon; + + 2  + + 3$user->newSubscription('default', 'price_monthly') + + 4 ->trialUntil(Carbon::now()->addDays(10)) + + 5 ->create($paymentMethod); + + + use Illuminate\Support\Carbon; + + $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: + + + + 1if ($user->onTrial('default')) { + + 2 // ... + + 3} + + 4  + + 5if ($user->subscription('default')->onTrial()) { + + 6 // ... + + 7} + + + if ($user->onTrial('default')) { + // ... + } + + if ($user->subscription('default')->onTrial()) { + // ... + } + +You may use the `endTrial` method to immediately end a subscription trial: + + + + 1$user->subscription('default')->endTrial(); + + + $user->subscription('default')->endTrial(); + +To determine if an existing trial has expired, you may use the +`hasExpiredTrial` methods: + + + + 1if ($user->hasExpiredTrial('default')) { + + 2 // ... + + 3} + + 4  + + 5if ($user->subscription('default')->hasExpiredTrial()) { + + 6 // ... + + 7} + + + if ($user->hasExpiredTrial('default')) { + // ... + } + + if ($user->subscription('default')->hasExpiredTrial()) { + // ... + } + +#### Defining Trial Days in Stripe / Cashier + +You may choose to define how many trial days your price's receive in the +Stripe dashboard or always pass them explicitly using Cashier. If you choose +to define your price's trial days in Stripe you should be aware that new +subscriptions, including new subscriptions for a customer that had a +subscription in the past, will always receive a trial period unless you +explicitly call the `skipTrial()` method. + +### 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 +user record to your desired trial ending date. This is typically done during +user registration: + + + + 1use App\Models\User; + + 2  + + 3$user = User::create([ + + 4 // ... + + 5 'trial_ends_at' => now()->addDays(10), + + 6]); + + + use App\Models\User; + + $user = User::create([ + // ... + 'trial_ends_at' => now()->addDays(10), + ]); + +Be sure to add a [date cast](/docs/12.x/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`: + + + + 1if ($user->onTrial()) { + + 2 // User is within their trial period... + + 3} + + + 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: + + + + 1$user = User::find(1); + + 2  + + 3$user->newSubscription('default', 'price_monthly')->create($paymentMethod); + + + $user = User::find(1); + + $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: + + + + 1if ($user->onTrial()) { + + 2 $trialEndsAt = $user->trialEndsAt('main'); + + 3} + + + 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: + + + + 1if ($user->onGenericTrial()) { + + 2 // User is within their "generic" trial period... + + 3} + + + 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: + + + + 1use App\Models\User; + + 2  + + 3$subscription = User::find(1)->subscription('default'); + + 4  + + 5// End the trial 7 days from now... + + 6$subscription->extendTrial( + + 7 now()->addDays(7) + + 8); + + 9  + + 10// Add an additional 5 days to the trial... + + 11$subscription->extendTrial( + + 12 $subscription->trial_ends_at->addDays(5) + + 13); + + + use App\Models\User; + + $subscription = User::find(1)->subscription('default'); + + // 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) + ); + +## Handling Stripe Webhooks + +You may use [the Stripe CLI](https://stripe.com/docs/stripe-cli) to help test +webhooks during local development. + +Stripe can notify your application of a variety of events via webhooks. By +default, a route that points to Cashier's webhook controller is automatically +registered by the Cashier service provider. This controller will handle all +incoming webhook requests. + +By default, the Cashier webhook controller will automatically handle +cancelling subscriptions that have too many failed charges (as defined by your +Stripe settings), customer updates, customer deletions, subscription updates, +and payment method changes; however, as we'll soon discover, you can extend +this controller to handle any Stripe webhook event you like. + +To ensure your application can handle Stripe webhooks, be sure to configure +the webhook URL in the Stripe control panel. By default, Cashier's webhook +controller responds to the `/stripe/webhook` URL path. The full list of all +webhooks you should enable in the Stripe control panel are: + + * `customer.subscription.created` + * `customer.subscription.updated` + * `customer.subscription.deleted` + * `customer.updated` + * `customer.deleted` + * `payment_method.automatically_updated` + * `invoice.payment_action_required` + * `invoice.payment_succeeded` + +For convenience, Cashier includes a `cashier:webhook` Artisan command. This +command will create a webhook in Stripe that listens to all of the events +required by Cashier: + + + + 1php artisan cashier:webhook + + + php artisan cashier:webhook + +By default, the created webhook will point to the URL defined by the `APP_URL` +environment variable and the `cashier.webhook` route that is included with +Cashier. You may provide the `--url` option when invoking the command if you +would like to use a different URL: + + + + 1php artisan cashier:webhook --url "https://example.com/stripe/webhook" + + + php artisan cashier:webhook --url "https://example.com/stripe/webhook" + +The webhook that is created will use the Stripe API version that your version +of Cashier is compatible with. If you would like to use a different Stripe +version, you may provide the `--api-version` option: + + + + 1php artisan cashier:webhook --api-version="2019-12-03" + + + php artisan cashier:webhook --api-version="2019-12-03" + +After creation, the webhook will be immediately active. If you wish to create +the webhook but have it disabled until you're ready, you may provide the +`--disabled` option when invoking the command: + + + + 1php artisan cashier:webhook --disabled + + + php artisan cashier:webhook --disabled + +Make sure you protect incoming Stripe webhook requests with Cashier's included +webhook signature verification middleware. + +#### Webhooks and CSRF Protection + +Since Stripe webhooks need to bypass Laravel's [CSRF +protection](/docs/12.x/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: + + + + 1->withMiddleware(function (Middleware $middleware) { + + 2 $middleware->validateCsrfTokens(except: [ + + 3 'stripe/*', + + 4 ]); + + 5}) + + + ->withMiddleware(function (Middleware $middleware) { + $middleware->validateCsrfTokens(except: [ + 'stripe/*', + ]); + }) + +### Defining Webhook Event Handlers + +Cashier automatically handles subscription cancellations for failed charges +and other common Stripe webhook events. However, if you have additional +webhook events you would like to handle, you may do so by listening to the +following events that are dispatched by Cashier: + + * `Laravel\Cashier\Events\WebhookReceived` + * `Laravel\Cashier\Events\WebhookHandled` + +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/12.x/events#defining-listeners) that will handle the event: + + + + 1payload['type'] === 'invoice.payment_succeeded') { + + 15 // Handle the incoming event... + + 16 } + + 17 } + + 18} + + + payload['type'] === 'invoice.payment_succeeded') { + // Handle the incoming event... + } + } + } + +### Verifying Webhook Signatures + +To secure your webhooks, you may use [Stripe's webhook +signatures](https://stripe.com/docs/webhooks/signatures). For convenience, +Cashier automatically includes a middleware which validates that the incoming +Stripe webhook request is valid. + +To enable webhook verification, ensure that the `STRIPE_WEBHOOK_SECRET` +environment variable is set in your application's `.env` file. The webhook +`secret` may be retrieved from your Stripe account dashboard. + +## Single Charges + +### Simple Charge + +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 as the second argument to the `charge` method: + + + + 1use Illuminate\Http\Request; + + 2  + + 3Route::post('/purchase', function (Request $request) { + + 4 $stripeCharge = $request->user()->charge( + + 5 100, $request->paymentMethodId + + 6 ); + + 7  + + 8 // ... + + 9}); + + + use Illuminate\Http\Request; + + 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): + + + + 1$user->charge(100, $paymentMethod, [ + + 2 'custom_option' => $value, + + 3]); + + + $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: + + + + 1use App\Models\User; + + 2  + + 3$stripeCharge = (new User)->charge(100, $paymentMethod); + + + use App\Models\User; + + $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: + + + + 1try { + + 2 $payment = $user->charge(100, $paymentMethod); + + 3} catch (Exception $e) { + + 4 // ... + + 5} + + + try { + $payment = $user->charge(100, $paymentMethod); + } catch (Exception $e) { + // ... + } + +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. + +### Charge With Invoice + +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: + + + + 1$user->invoicePrice('price_tshirt', 5); + + + $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: + + + + 1$user->invoicePrice('price_tshirt', 5, [ + + 2 'discounts' => [ + + 3 ['coupon' => 'SUMMER21SALE'] + + 4 ], + + 5], [ + + 6 'default_tax_rates' => ['txr_id'], + + 7]); + + + $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: + + + + 1$user->tabPrice('price_tshirt', 5); + + 2$user->tabPrice('price_mug', 2); + + 3$user->invoice(); + + + $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: + + + + 1$user->invoiceFor('One Time Fee', 500); + + + $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. + +The `invoice`, `invoicePrice`, and `invoiceFor` methods will create a Stripe +invoice which will retry failed billing attempts. If you do not want invoices +to retry failed charges, you will need to close them using the Stripe API +after the first failed charge. + +### Creating Payment Intents + +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: + + + + 1use Illuminate\Http\Request; + + 2  + + 3Route::post('/pay', function (Request $request) { + + 4 $payment = $request->user()->pay( + + 5 $request->get('amount') + + 6 ); + + 7  + + 8 return $payment->client_secret; + + 9}); + + + use Illuminate\Http\Request; + + Route::post('/pay', function (Request $request) { + $payment = $request->user()->pay( + $request->get('amount') + ); + + 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: + + + + 1use Illuminate\Http\Request; + + 2  + + 3Route::post('/pay', function (Request $request) { + + 4 $payment = $request->user()->payWith( + + 5 $request->get('amount'), ['card', 'bancontact'] + + 6 ); + + 7  + + 8 return $payment->client_secret; + + 9}); + + + use Illuminate\Http\Request; + + Route::post('/pay', function (Request $request) { + $payment = $request->user()->payWith( + $request->get('amount'), ['card', 'bancontact'] + ); + + return $payment->client_secret; + }); + +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. + +### Refunding Charges + +If you need to refund a Stripe charge, you may use the `refund` method. This +method accepts the Stripe payment intent ID as its first argument: + + + + 1$payment = $user->charge(100, $paymentMethodId); + + 2  + + 3$user->refund($payment->id); + + + $payment = $user->charge(100, $paymentMethodId); + + $user->refund($payment->id); + +## Invoices + +### Retrieving Invoices + +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: + + + + 1$invoices = $user->invoices(); + + + $invoices = $user->invoices(); + +If you would like to include pending invoices in the results, you may use the +`invoicesIncludingPending` method: + + + + 1$invoices = $user->invoicesIncludingPending(); + + + $invoices = $user->invoicesIncludingPending(); + +You may use the `findInvoice` method to retrieve a specific invoice by its ID: + + + + 1$invoice = $user->findInvoice($invoiceId); + + + $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: + + + + 1 + + 2 @foreach ($invoices as $invoice) + + 3 + + 4 + + 5 + + 6 + + 7 + + 8 @endforeach + + 9
{{ $invoice->date()->toFormattedDateString() }}{{ $invoice->total() }}Download
+ + + + @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: + + + + 1$invoice = $user->upcomingInvoice(); + + + $invoice = $user->upcomingInvoice(); + +Similarly, if the customer has multiple subscriptions, you can also retrieve +the upcoming invoice for a specific subscription: + + + + 1$invoice = $user->subscription('default')->upcomingInvoice(); + + + $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: + + + + 1$invoice = $user->subscription('default')->previewInvoice('price_yearly'); + + + $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: + + + + 1$invoice = $user->subscription('default')->previewInvoice(['price_yearly', 'price_metered']); + + + $invoice = $user->subscription('default')->previewInvoice(['price_yearly', 'price_metered']); + +### Generating Invoice PDFs + +Before generating invoice PDFs, you should use Composer to install the Dompdf +library, which is the default invoice renderer for Cashier: + + + + 1composer require dompdf/dompdf + + + 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: + + + + 1use Illuminate\Http\Request; + + 2  + + 3Route::get('/user/invoice/{invoice}', function (Request $request, string $invoiceId) { + + 4 return $request->user()->downloadInvoice($invoiceId); + + 5}); + + + use Illuminate\Http\Request; + + 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: + + + + 1return $request->user()->downloadInvoice($invoiceId, [ + + 2 'vendor' => 'Your Company', + + 3 'product' => 'Your Product', + + 4 'street' => 'Main Str. 1', + + 5 'location' => '2000 Antwerp, Belgium', + + 6 'phone' => '+32 499 00 00 00', + + 7 'email' => '[[email protected]](/cdn-cgi/l/email-protection)', + + 8 'url' => 'https://example.com', + + 9 'vendorVat' => 'BE123456789', + + 10]); + + + 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' => '[[email protected]](/cdn-cgi/l/email-protection)', + '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`: + + + + 1return $request->user()->downloadInvoice($invoiceId, [], 'my-invoice'); + + + 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: + + + + 1use Illuminate\Support\Facades\Http; + + 2use Laravel\Cashier\Contracts\InvoiceRenderer; + + 3use Laravel\Cashier\Invoice; + + 4  + + 5class ApiInvoiceRenderer implements InvoiceRenderer + + 6{ + + 7 /** + + 8 * Render the given invoice and return the raw PDF bytes. + + 9 */ + + 10 public function render(Invoice $invoice, array $data = [], array $options = []): string + + 11 { + + 12 $html = $invoice->view($data)->render(); + + 13  + + 14 return Http::get('https://example.com/html-to-pdf', ['html' => $html])->get()->body(); + + 15 } + + 16} + + + use Illuminate\Support\Facades\Http; + use Laravel\Cashier\Contracts\InvoiceRenderer; + use Laravel\Cashier\Invoice; + + class ApiInvoiceRenderer implements InvoiceRenderer + { + /** + * 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(); + } + } + +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. + +## Checkout + +Cashier Stripe also provides support for [Stripe +Checkout](https://stripe.com/payments/checkout). Stripe Checkout takes the +pain out of implementing custom pages to accept payments by providing a pre- +built, hosted payment page. + +The following documentation contains information on how to get started using +Stripe Checkout with Cashier. To learn more about Stripe Checkout, you should +also consider reviewing [Stripe's own documentation on +Checkout](https://stripe.com/docs/payments/checkout). + +### Product Checkouts + +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: + + + + 1use Illuminate\Http\Request; + + 2  + + 3Route::get('/product-checkout', function (Request $request) { + + 4 return $request->user()->checkout('price_tshirt'); + + 5}); + + + use Illuminate\Http\Request; + + Route::get('/product-checkout', function (Request $request) { + return $request->user()->checkout('price_tshirt'); + }); + +If needed, you may also specify a product quantity: + + + + 1use Illuminate\Http\Request; + + 2  + + 3Route::get('/product-checkout', function (Request $request) { + + 4 return $request->user()->checkout(['price_tshirt' => 15]); + + 5}); + + + use Illuminate\Http\Request; + + 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: + + + + 1use Illuminate\Http\Request; + + 2  + + 3Route::get('/product-checkout', function (Request $request) { + + 4 return $request->user()->checkout(['price_tshirt' => 1], [ + + 5 'success_url' => route('your-success-route'), + + 6 'cancel_url' => route('your-cancel-route'), + + 7 ]); + + 8}); + + + 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'), + ]); + }); + +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: + + + + 1use Illuminate\Http\Request; + + 2use Stripe\Checkout\Session; + + 3use Stripe\Customer; + + 4  + + 5Route::get('/product-checkout', function (Request $request) { + + 6 return $request->user()->checkout(['price_tshirt' => 1], [ + + 7 'success_url' => route('checkout-success').'?session_id={CHECKOUT_SESSION_ID}', + + 8 'cancel_url' => route('checkout-cancel'), + + 9 ]); + + 10}); + + 11  + + 12Route::get('/checkout-success', function (Request $request) { + + 13 $checkoutSession = $request->user()->stripe()->checkout->sessions->retrieve($request->get('session_id')); + + 14  + + 15 return view('checkout.success', ['checkoutSession' => $checkoutSession]); + + 16})->name('checkout-success'); + + + 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('/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'); + +#### 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: + + + + 1use Illuminate\Http\Request; + + 2  + + 3Route::get('/product-checkout', function (Request $request) { + + 4 return $request->user() + + 5 ->allowPromotionCodes() + + 6 ->checkout('price_tshirt'); + + 7}); + + + use Illuminate\Http\Request; + + 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: + + + + 1use Illuminate\Http\Request; + + 2  + + 3Route::get('/charge-checkout', function (Request $request) { + + 4 return $request->user()->checkoutCharge(1200, 'T-Shirt', 5); + + 5}); + + + use Illuminate\Http\Request; + + Route::get('/charge-checkout', function (Request $request) { + return $request->user()->checkoutCharge(1200, 'T-Shirt', 5); + }); + +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. + +### Subscription Checkouts + +Using Stripe Checkout for subscriptions requires you to enable the +`customer.subscription.created` webhook in your Stripe dashboard. This webhook +will create the subscription record in your database and store all of the +relevant subscription items. + +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: + + + + 1use Illuminate\Http\Request; + + 2  + + 3Route::get('/subscription-checkout', function (Request $request) { + + 4 return $request->user() + + 5 ->newSubscription('default', 'price_monthly') + + 6 ->checkout(); + + 7}); + + + use Illuminate\Http\Request; + + 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: + + + + 1use Illuminate\Http\Request; + + 2  + + 3Route::get('/subscription-checkout', function (Request $request) { + + 4 return $request->user() + + 5 ->newSubscription('default', 'price_monthly') + + 6 ->checkout([ + + 7 'success_url' => route('your-success-route'), + + 8 'cancel_url' => route('your-cancel-route'), + + 9 ]); + + 10}); + + + 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'), + ]); + }); + +Of course, you can also enable promotion codes for subscription checkouts: + + + + 1use Illuminate\Http\Request; + + 2  + + 3Route::get('/subscription-checkout', function (Request $request) { + + 4 return $request->user() + + 5 ->newSubscription('default', 'price_monthly') + + 6 ->allowPromotionCodes() + + 7 ->checkout(); + + 8}); + + + use Illuminate\Http\Request; + + Route::get('/subscription-checkout', function (Request $request) { + return $request->user() + ->newSubscription('default', 'price_monthly') + ->allowPromotionCodes() + ->checkout(); + }); + +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. + +#### Stripe Checkout and Trial Periods + +Of course, you can define a trial period when building a subscription that +will be completed using Stripe Checkout: + + + + 1$checkout = Auth::user()->newSubscription('default', 'price_monthly') + + 2 ->trialDays(3) + + 3 ->checkout(); + + + $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. + +#### Subscriptions and Webhooks + +Remember, Stripe and Cashier update subscription statuses via webhooks, so +there's a possibility a subscription might not yet be active when the customer +returns to the application after entering their payment information. To handle +this scenario, you may wish to display a message informing the user that their +payment or subscription is pending. + +### Collecting Tax IDs + +Checkout also supports collecting a customer's Tax ID. To enable this on a +checkout session, invoke the `collectTaxIds` method when creating the session: + + + + 1$checkout = $user->collectTaxIds()->checkout('price_tshirt'); + + + $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. + +If you have already configured automatic tax collection in your application's +service provider then this feature will be enabled automatically and there is +no need to invoke the `collectTaxIds` method. + +### Guest Checkouts + +Using the `Checkout::guest` method, you may initiate checkout sessions for +guests of your application that do not have an "account": + + + + 1use Illuminate\Http\Request; + + 2use Laravel\Cashier\Checkout; + + 3  + + 4Route::get('/product-checkout', function (Request $request) { + + 5 return Checkout::guest()->create('price_tshirt', [ + + 6 'success_url' => route('your-success-route'), + + 7 'cancel_url' => route('your-cancel-route'), + + 8 ]); + + 9}); + + + 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'), + ]); + }); + +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: + + + + 1use Illuminate\Http\Request; + + 2use Laravel\Cashier\Checkout; + + 3  + + 4Route::get('/product-checkout', function (Request $request) { + + 5 return Checkout::guest() + + 6 ->withPromotionCode('promo-code') + + 7 ->create('price_tshirt', [ + + 8 'success_url' => route('your-success-route'), + + 9 'cancel_url' => route('your-cancel-route'), + + 10 ]); + + 11}); + + + 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'), + ]); + }); + +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. 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. + +## Handling Failed Payments + +Sometimes, payments for subscriptions or single charges can fail. When this +happens, Cashier will throw an `Laravel\Cashier\Exceptions\IncompletePayment` +exception that informs you that this happened. After catching this exception, +you have two options on how to proceed. + +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: + + + + 1use Laravel\Cashier\Exceptions\IncompletePayment; + + 2  + + 3try { + + 4 $subscription = $user->newSubscription('default', 'price_monthly') + + 5 ->create($paymentMethod); + + 6} catch (IncompletePayment $exception) { + + 7 return redirect()->route( + + 8 'cashier.payment', + + 9 [$exception->payment->id, 'redirect' => route('home')] + + 10 ); + + 11} + + + 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')] + ); + } + +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: + + * Credit Cards + * Alipay + * Bancontact + * BECS Direct Debit + * EPS + * Giropay + * iDEAL + * SEPA Direct Debit + +Alternatively, you could allow Stripe to handle the payment confirmation for +you. In this case, instead of redirecting to the payment confirmation page, +you may [setup Stripe's automatic billing +emails](https://dashboard.stripe.com/account/billing/automatic) in your Stripe +dashboard. However, if an `IncompletePayment` exception is caught, you should +still inform the user they will receive an email with further payment +confirmation instructions. + +Payment exceptions may be thrown for the following methods: `charge`, +`invoiceFor`, and `invoice` on models using the `Billable` trait. When +interacting with subscriptions, the `create` method on the +`SubscriptionBuilder`, and the `incrementAndInvoice` and `swapAndInvoice` +methods on the `Subscription` and `SubscriptionItem` models may throw +incomplete payment exceptions. + +Determining if an existing subscription has an incomplete payment may be +accomplished using the `hasIncompletePayment` method on the billable model or +a subscription instance: + + + + 1if ($user->hasIncompletePayment('default')) { + + 2 // ... + + 3} + + 4  + + 5if ($user->subscription('default')->hasIncompletePayment()) { + + 6 // ... + + 7} + + + if ($user->hasIncompletePayment('default')) { + // ... + } + + if ($user->subscription('default')->hasIncompletePayment()) { + // ... + } + +You can derive the specific status of an incomplete payment by inspecting the +`payment` property on the exception instance: + + + + 1use Laravel\Cashier\Exceptions\IncompletePayment; + + 2  + + 3try { + + 4 $user->charge(1000, 'pm_card_threeDSecure2Required'); + + 5} catch (IncompletePayment $exception) { + + 6 // Get the payment intent status... + + 7 $exception->payment->status; + + 8  + + 9 // Check specific conditions... + + 10 if ($exception->payment->requiresPaymentMethod()) { + + 11 // ... + + 12 } elseif ($exception->payment->requiresConfirmation()) { + + 13 // ... + + 14 } + + 15} + + + use Laravel\Cashier\Exceptions\IncompletePayment; + + 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()) { + // ... + } + } + +### 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: + + + + 1$subscription->withPaymentConfirmationOptions([ + + 2 'mandate_data' => '...', + + 3])->swap('price_xxx'); + + + $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. + +## Strong Customer Authentication + +If your business or one of your customers is based in Europe you will need to +abide by the EU's Strong Customer Authentication (SCA) regulations. These +regulations were imposed in September 2019 by the European Union to prevent +payment fraud. Luckily, Stripe and Cashier are prepared for building SCA +compliant applications. + +Before getting started, review [Stripe's guide on PSD2 and +SCA](https://stripe.com/guides/strong-customer-authentication) as well as +their [documentation on the new SCA APIs](https://stripe.com/docs/strong- +customer-authentication). + +### Payments Requiring Additional Confirmation + +SCA regulations often require extra verification in order to confirm and +process a payment. When this happens, Cashier will throw a +`Laravel\Cashier\Exceptions\IncompletePayment` exception that informs you that +extra verification is needed. More information on how to handle these +exceptions be found can be found in the documentation on handling failed +payments. + +Payment confirmation screens presented by Stripe or Cashier may be tailored to +a specific bank or card issuer's payment flow and can include additional card +confirmation, a temporary small charge, separate device authentication, or +other forms of verification. + +#### Incomplete and Past Due State + +When a payment needs additional confirmation, the subscription will remain in +an `incomplete` or `past_due` state as indicated by its `stripe_status` +database column. Cashier will automatically activate the customer's +subscription as soon as payment confirmation is complete and your application +is notified by Stripe via webhook of its completion. + +For more information on `incomplete` and `past_due` states, please refer to +our additional documentation on these states. + +### Off-Session Payment Notifications + +Since SCA regulations require customers to occasionally verify their payment +details even while their subscription is active, Cashier can send a +notification to the customer when off-session payment confirmation is +required. For example, this may occur when a subscription is renewing. +Cashier's payment notification can be enabled by setting the +`CASHIER_PAYMENT_NOTIFICATION` environment variable to a notification class. +By default, this notification is disabled. Of course, Cashier includes a +notification class you may use for this purpose, but you are free to provide +your own notification class if desired: + + + + 1CASHIER_PAYMENT_NOTIFICATION=Laravel\Cashier\Notifications\ConfirmPayment + + + CASHIER_PAYMENT_NOTIFICATION=Laravel\Cashier\Notifications\ConfirmPayment + +To ensure that off-session payment confirmation notifications are delivered, +verify that Stripe webhooks are configured for your application and the +`invoice.payment_action_required` webhook is enabled in your Stripe dashboard. +In addition, your `Billable` model should also use Laravel's +`Illuminate\Notifications\Notifiable` trait. + +Notifications will be sent even when customers are manually making a payment +that requires additional confirmation. Unfortunately, there is no way for +Stripe to know that the payment was done manually or "off-session". But, a +customer will simply see a "Payment Successful" message if they visit the +payment page after already confirming their payment. The customer will not be +allowed to accidentally confirm the same payment twice and incur an accidental +second charge. + +## Stripe SDK + +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: + + + + 1$stripeSubscription = $subscription->asStripeSubscription(); + + 2  + + 3$stripeSubscription->application_fee_percent = 5; + + 4  + + 5$stripeSubscription->save(); + + + $stripeSubscription = $subscription->asStripeSubscription(); + + $stripeSubscription->application_fee_percent = 5; + + $stripeSubscription->save(); + +You may also use the `updateStripeSubscription` method to update a Stripe +subscription directly: + + + + 1$subscription->updateStripeSubscription(['application_fee_percent' => 5]); + + + $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: + + + + 1use Laravel\Cashier\Cashier; + + 2  + + 3$prices = Cashier::stripe()->prices->all(); + + + use Laravel\Cashier\Cashier; + + $prices = Cashier::stripe()->prices->all(); + +## Testing + +When testing an application that uses Cashier, you may mock the actual HTTP +requests to the Stripe API; however, this requires you to partially re- +implement Cashier's own behavior. Therefore, we recommend allowing your tests +to hit the actual Stripe API. While this is slower, it provides more +confidence that your application is working as expected and any slow tests may +be placed within their own Pest / PHPUnit testing group. + +When testing, remember that Cashier itself already has a great test suite, so +you should only focus on testing the subscription and payment flow of your own +application and not every underlying Cashier behavior. + +To get started, add the **testing** version of your Stripe secret to your +`phpunit.xml` file: + + + + 1 + + + + +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. + +In order to test a variety of billing scenarios, such as credit card denials +and failures, you may use the vast range of [testing card numbers and +tokens](https://stripe.com/docs/testing) provided by Stripe. + diff --git a/output/12.x/blade.md b/output/12.x/blade.md new file mode 100644 index 0000000..a9668e2 --- /dev/null +++ b/output/12.x/blade.md @@ -0,0 +1,4189 @@ +# Blade Templates + + * Introduction + * Supercharging Blade With Livewire + * Displaying Data + * HTML Entity Encoding + * Blade and JavaScript Frameworks + * Blade Directives + * If Statements + * Switch Statements + * Loops + * The Loop Variable + * Conditional Classes + * Additional Attributes + * Including Subviews + * The `@once` Directive + * Raw PHP + * Comments + * Components + * Rendering Components + * Index Components + * Passing Data to Components + * Component Attributes + * Reserved Keywords + * Slots + * Inline Component Views + * Dynamic Components + * Manually Registering Components + * Anonymous Components + * Anonymous Index Components + * Data Properties / Attributes + * Accessing Parent Data + * Anonymous Components Paths + * Building Layouts + * Layouts Using Components + * Layouts Using Template Inheritance + * Forms + * CSRF Field + * Method Field + * Validation Errors + * Stacks + * Service Injection + * Rendering Inline Blade Templates + * Rendering Blade Fragments + * Extending Blade + * Custom Echo Handlers + * Custom If Statements + +## Introduction + +Blade is the simple, yet powerful templating engine that is included with +Laravel. Unlike some PHP templating engines, Blade does not restrict you from +using plain PHP code in your templates. In fact, all Blade templates are +compiled into plain PHP code and cached until they are modified, meaning Blade +adds essentially zero overhead to your application. Blade template files use +the `.blade.php` file extension and are typically stored in the +`resources/views` directory. + +Blade views may be returned from routes or controllers using the global `view` +helper. Of course, as mentioned in the documentation on +[views](/docs/12.x/views), data may be passed to the Blade view using the +`view` helper's second argument: + + + + 1Route::get('/', function () { + + 2 return view('greeting', ['name' => 'Finn']); + + 3}); + + + Route::get('/', function () { + return view('greeting', ['name' => 'Finn']); + }); + +### Supercharging Blade With Livewire + +Want to take your Blade templates to the next level and build dynamic +interfaces with ease? Check out [Laravel +Livewire](https://livewire.laravel.com). Livewire allows you to write Blade +components that are augmented with dynamic functionality that would typically +only be possible via frontend frameworks like React or Vue, providing a great +approach to building modern, reactive frontends without the complexities, +client-side rendering, or build steps of many JavaScript frameworks. + +## Displaying Data + +You may display data that is passed to your Blade views by wrapping the +variable in curly braces. For example, given the following route: + + + + 1Route::get('/', function () { + + 2 return view('welcome', ['name' => 'Samantha']); + + 3}); + + + Route::get('/', function () { + return view('welcome', ['name' => 'Samantha']); + }); + +You may display the contents of the `name` variable like so: + + + + 1Hello, {{ $name }}. + + + Hello, {{ $name }}. + +Blade's `{{ }}` echo statements are automatically sent through PHP's +`htmlspecialchars` function to prevent XSS attacks. + +You are not limited to displaying the contents of the variables passed to the +view. You may also echo the results of any PHP function. In fact, you can put +any PHP code you wish inside of a Blade echo statement: + + + + 1The current UNIX timestamp is {{ time() }}. + + + The current UNIX timestamp is {{ time() }}. + +### HTML Entity Encoding + +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`: + + + + 1Laravel + + 2  + + 3Hello, @{{ name }}. + + +

Laravel

+ + Hello, @{{ name }}. + +In this example, the `@` symbol will be removed by Blade; however, `{{ name +}}` expression will remain untouched by the Blade engine, allowing it to be +rendered by your JavaScript framework. + +The `@` symbol may also be used to escape Blade directives: + + + + 1{{-- Blade template --}} + + 2@@if() + + 3  + + 4 + + 5@if() + + + {{-- Blade template --}} + @@if() + + + @if() + +#### Rendering JSON + +Sometimes you may pass an array to your view with the intention of rendering +it as JSON in order to initialize a JavaScript variable. For example: + + + + 1 + + + + +However, instead of manually calling `json_encode`, you may use the +`Illuminate\Support\Js::from` method directive. The `from` method accepts the +same arguments as PHP's `json_encode` function; however, it will ensure that +the resulting JSON has been properly escaped for inclusion within HTML quotes. +The `from` method will return a string `JSON.parse` JavaScript statement that +will convert the given object or array into a valid JavaScript object: + + + + 1 + + + + +The latest versions of the Laravel application skeleton include a `Js` facade, +which provides convenient access to this functionality within your Blade +templates: + + + + 1 + + + + +You should only use the `Js::from` method to render existing variables as +JSON. The Blade templating is based on regular expressions and attempts to +pass a complex expression to the directive may cause unexpected failures. + +#### The `@verbatim` Directive + +If you are displaying JavaScript variables in a large portion of your +template, you may wrap the HTML in the `@verbatim` directive so that you do +not have to prefix each Blade echo statement with an `@` symbol: + + + + 1@verbatim + + 2
+ + 3 Hello, {{ name }}. + + 4
+ + 5@endverbatim + + + @verbatim +
+ Hello, {{ name }}. +
+ @endverbatim + +## Blade Directives + +In addition to template inheritance and displaying data, Blade also provides +convenient shortcuts for common PHP control structures, such as conditional +statements and loops. These shortcuts provide a very clean, terse way of +working with PHP control structures while also remaining familiar to their PHP +counterparts. + +### If Statements + +You may construct `if` statements using the `@if`, `@elseif`, `@else`, and +`@endif` directives. These directives function identically to their PHP +counterparts: + + + + 1@if (count($records) === 1) + + 2 I have one record! + + 3@elseif (count($records) > 1) + + 4 I have multiple records! + + 5@else + + 6 I don't have any records! + + 7@endif + + + @if (count($records) === 1) + I have one record! + @elseif (count($records) > 1) + I have multiple records! + @else + I don't have any records! + @endif + +For convenience, Blade also provides an `@unless` directive: + + + + 1@unless (Auth::check()) + + 2 You are not signed in. + + 3@endunless + + + @unless (Auth::check()) + You are not signed in. + @endunless + +In addition to the conditional directives already discussed, the `@isset` and +`@empty` directives may be used as convenient shortcuts for their respective +PHP functions: + + + + 1@isset($records) + + 2 // $records is defined and is not null... + + 3@endisset + + 4  + + 5@empty($records) + + 6 // $records is "empty"... + + 7@endempty + + + @isset($records) + // $records is defined and is not null... + @endisset + + @empty($records) + // $records is "empty"... + @endempty + +#### Authentication Directives + +The `@auth` and `@guest` directives may be used to quickly determine if the +current user is [authenticated](/docs/12.x/authentication) or is a guest: + + + + 1@auth + + 2 // The user is authenticated... + + 3@endauth + + 4  + + 5@guest + + 6 // The user is not authenticated... + + 7@endguest + + + @auth + // The user is authenticated... + @endauth + + @guest + // The user is not authenticated... + @endguest + +If needed, you may specify the authentication guard that should be checked +when using the `@auth` and `@guest` directives: + + + + 1@auth('admin') + + 2 // The user is authenticated... + + 3@endauth + + 4  + + 5@guest('admin') + + 6 // The user is not authenticated... + + 7@endguest + + + @auth('admin') + // The user is authenticated... + @endauth + + @guest('admin') + // The user is not authenticated... + @endguest + +#### Environment Directives + +You may check if the application is running in the production environment +using the `@production` directive: + + + + 1@production + + 2 // Production specific content... + + 3@endproduction + + + @production + // Production specific content... + @endproduction + +Or, you may determine if the application is running in a specific environment +using the `@env` directive: + + + + 1@env('staging') + + 2 // The application is running in "staging"... + + 3@endenv + + 4  + + 5@env(['staging', 'production']) + + 6 // The application is running in "staging" or "production"... + + 7@endenv + + + @env('staging') + // The application is running in "staging"... + @endenv + + @env(['staging', 'production']) + // The application is running in "staging" or "production"... + @endenv + +#### Section Directives + +You may determine if a template inheritance section has content using the +`@hasSection` directive: + + + + 1@hasSection('navigation') + + 2
+ + 3 @yield('navigation') + + 4
+ + 5  + + 6
+ + 7@endif + + + @hasSection('navigation') +
+ @yield('navigation') +
+ +
+ @endif + +You may use the `sectionMissing` directive to determine if a section does not +have content: + + + + 1@sectionMissing('navigation') + + 2
+ + 3 @include('default-navigation') + + 4
+ + 5@endif + + + @sectionMissing('navigation') +
+ @include('default-navigation') +
+ @endif + +#### Session Directives + +The `@session` directive may be used to determine if a +[session](/docs/12.x/session) value exists. If the session value exists, the +template contents within the `@session` and `@endsession` directives will be +evaluated. Within the `@session` directive's contents, you may echo the +`$value` variable to display the session value: + + + + 1@session('status') + + 2
+ + 3 {{ $value }} + + 4
+ + 5@endsession + + + @session('status') +
+ {{ $value }} +
+ @endsession + +#### Context Directives + +The `@context` directive may be used to determine if a +[context](/docs/12.x/context) value exists. If the context value exists, the +template contents within the `@context` and `@endcontext` directives will be +evaluated. Within the `@context` directive's contents, you may echo the +`$value` variable to display the context value: + + + + 1@context('canonical') + + 2 + + 3@endcontext + + + @context('canonical') + + @endcontext + +### Switch Statements + +Switch statements can be constructed using the `@switch`, `@case`, `@break`, +`@default` and `@endswitch` directives: + + + + 1@switch($i) + + 2 @case(1) + + 3 First case... + + 4 @break + + 5  + + 6 @case(2) + + 7 Second case... + + 8 @break + + 9  + + 10 @default + + 11 Default case... + + 12@endswitch + + + @switch($i) + @case(1) + First case... + @break + + @case(2) + Second case... + @break + + @default + Default case... + @endswitch + +### Loops + +In addition to conditional statements, Blade provides simple directives for +working with PHP's loop structures. Again, each of these directives functions +identically to their PHP counterparts: + + + + 1@for ($i = 0; $i < 10; $i++) + + 2 The current value is {{ $i }} + + 3@endfor + + 4  + + 5@foreach ($users as $user) + + 6

This is user {{ $user->id }}

+ + 7@endforeach + + 8  + + 9@forelse ($users as $user) + + 10
  • {{ $user->name }}
  • + + 11@empty + + 12

    No users

    + + 13@endforelse + + 14  + + 15@while (true) + + 16

    I'm looping forever.

    + + 17@endwhile + + + @for ($i = 0; $i < 10; $i++) + The current value is {{ $i }} + @endfor + + @foreach ($users as $user) +

    This is user {{ $user->id }}

    + @endforeach + + @forelse ($users as $user) +
  • {{ $user->name }}
  • + @empty +

    No users

    + @endforelse + + @while (true) +

    I'm looping forever.

    + @endwhile + +While iterating through a `foreach` loop, you may use the loop variable to +gain valuable information about the loop, such as whether you are in the first +or last iteration through the loop. + +When using loops you may also skip the current iteration or end the loop using +the `@continue` and `@break` directives: + + + + 1@foreach ($users as $user) + + 2 @if ($user->type == 1) + + 3 @continue + + 4 @endif + + 5  + + 6
  • {{ $user->name }}
  • + + 7  + + 8 @if ($user->number == 5) + + 9 @break + + 10 @endif + + 11@endforeach + + + @foreach ($users as $user) + @if ($user->type == 1) + @continue + @endif + +
  • {{ $user->name }}
  • + + @if ($user->number == 5) + @break + @endif + @endforeach + +You may also include the continuation or break condition within the directive +declaration: + + + + 1@foreach ($users as $user) + + 2 @continue($user->type == 1) + + 3  + + 4
  • {{ $user->name }}
  • + + 5  + + 6 @break($user->number == 5) + + 7@endforeach + + + @foreach ($users as $user) + @continue($user->type == 1) + +
  • {{ $user->name }}
  • + + @break($user->number == 5) + @endforeach + +### The Loop Variable + +While iterating through a `foreach` loop, a `$loop` variable will be available +inside of your loop. This variable provides access to some useful bits of +information such as the current loop index and whether this is the first or +last iteration through the loop: + + + + 1@foreach ($users as $user) + + 2 @if ($loop->first) + + 3 This is the first iteration. + + 4 @endif + + 5  + + 6 @if ($loop->last) + + 7 This is the last iteration. + + 8 @endif + + 9  + + 10

    This is user {{ $user->id }}

    + + 11@endforeach + + + @foreach ($users as $user) + @if ($loop->first) + This is the first iteration. + @endif + + @if ($loop->last) + This is the last iteration. + @endif + +

    This is user {{ $user->id }}

    + @endforeach + +If you are in a nested loop, you may access the parent loop's `$loop` variable +via the `parent` property: + + + + 1@foreach ($users as $user) + + 2 @foreach ($user->posts as $post) + + 3 @if ($loop->parent->first) + + 4 This is the first iteration of the parent loop. + + 5 @endif + + 6 @endforeach + + 7@endforeach + + + @foreach ($users as $user) + @foreach ($user->posts as $post) + @if ($loop->parent->first) + This is the first iteration of the parent loop. + @endif + @endforeach + @endforeach + +The `$loop` variable also contains a variety of other useful properties: + +Property | Description +---|--- +`$loop->index` | The index of the current loop iteration (starts at 0). +`$loop->iteration` | The current loop iteration (starts at 1). +`$loop->remaining` | The iterations remaining in the loop. +`$loop->count` | The total number of items in the array being iterated. +`$loop->first` | Whether this is the first iteration through the loop. +`$loop->last` | Whether this is the last iteration through the loop. +`$loop->even` | Whether this is an even iteration through the loop. +`$loop->odd` | Whether this is an odd iteration through the loop. +`$loop->depth` | The nesting level of the current loop. +`$loop->parent` | When in a nested loop, the parent's loop variable. + +### Conditional Classes & Styles + +The `@class` directive conditionally compiles a CSS class string. The +directive 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: + + + + 1@php + + 2 $isActive = false; + + 3 $hasError = true; + + 4@endphp + + 5  + + 6 $isActive, + + 9 'text-gray-500' => ! $isActive, + + 10 'bg-red' => $hasError, + + 11])> + + 12  + + 13 + + + @php + $isActive = false; + $hasError = true; + @endphp + + $isActive, + 'text-gray-500' => ! $isActive, + 'bg-red' => $hasError, + ])> + + + +Likewise, the `@style` directive may be used to conditionally add inline CSS +styles to an HTML element: + + + + 1@php + + 2 $isActive = true; + + 3@endphp + + 4  + + 5 $isActive, + + 8])> + + 9  + + 10 + + + @php + $isActive = true; + @endphp + + $isActive, + ])> + + + +### Additional Attributes + +For convenience, you may use the `@checked` directive to easily indicate if a +given HTML checkbox input is "checked". This directive will echo `checked` if +the provided condition evaluates to `true`: + + + + 1active)) + + 6/> + + + active)) + /> + +Likewise, the `@selected` directive may be used to indicate if a given select +option should be "selected": + + + + 1 + + + + +Additionally, the `@disabled` directive may be used to indicate if a given +element should be "disabled": + + + + 1 + + + + +Moreover, the `@readonly` directive may be used to indicate if a given element +should be "readonly": + + + + 1isNotAdmin()) + + 6/> + + + isNotAdmin()) + /> + +In addition, the `@required` directive may be used to indicate if a given +element should be "required": + + + + 1isAdmin()) + + 6/> + + + isAdmin()) + /> + +### Including Subviews + +While you're free to use the `@include` directive, Blade components provide +similar functionality and offer several benefits over the `@include` directive +such as data and attribute binding. + +Blade's `@include` directive allows you to include a Blade view from within +another view. All variables that are available to the parent view will be made +available to the included view: + + + + 1
    + + 2 @include('shared.errors') + + 3  + + 4
    + + 5 + + 6
    + + 7
    + + +
    + @include('shared.errors') + +
    + +
    +
    + +Even though the included view will inherit all data available in the parent +view, you may also pass an array of additional data that should be made +available to the included view: + + + + 1@include('view.name', ['status' => 'complete']) + + + @include('view.name', ['status' => 'complete']) + +If you attempt to `@include` a view which does not exist, Laravel will throw +an error. If you would like to include a view that may or may not be present, +you should use the `@includeIf` directive: + + + + 1@includeIf('view.name', ['status' => 'complete']) + + + @includeIf('view.name', ['status' => 'complete']) + +If you would like to `@include` a view if a given boolean expression evaluates +to `true` or `false`, you may use the `@includeWhen` and `@includeUnless` +directives: + + + + 1@includeWhen($boolean, 'view.name', ['status' => 'complete']) + + 2  + + 3@includeUnless($boolean, 'view.name', ['status' => 'complete']) + + + @includeWhen($boolean, 'view.name', ['status' => 'complete']) + + @includeUnless($boolean, 'view.name', ['status' => 'complete']) + +To include the first view that exists from a given array of views, you may use +the `includeFirst` directive: + + + + 1@includeFirst(['custom.admin', 'admin'], ['status' => 'complete']) + + + @includeFirst(['custom.admin', 'admin'], ['status' => 'complete']) + +You should avoid using the `__DIR__` and `__FILE__` constants in your Blade +views, since they will refer to the location of the cached, compiled view. + +#### Rendering Views for Collections + +You may combine loops and includes into one line with Blade's `@each` +directive: + + + + 1@each('view.name', $jobs, 'job') + + + @each('view.name', $jobs, 'job') + +The `@each` directive's first argument is the view to render for each element +in the array or collection. The second argument is the array or collection you +wish to iterate over, while the third argument is the variable name that will +be assigned to the current iteration within the view. So, for example, if you +are iterating over an array of `jobs`, typically you will want to access each +job as a `job` variable within the view. The array key for the current +iteration will be available as the `key` variable within the view. + +You may also pass a fourth argument to the `@each` directive. This argument +determines the view that will be rendered if the given array is empty. + + + + 1@each('view.name', $jobs, 'job', 'view.empty') + + + @each('view.name', $jobs, 'job', 'view.empty') + +Views rendered via `@each` do not inherit the variables from the parent view. +If the child view requires these variables, you should use the `@foreach` and +`@include` directives instead. + +### The `@once` Directive + +The `@once` directive allows you to define a portion of the template that will +only be evaluated once per rendering cycle. This may be useful for pushing a +given piece of JavaScript into the page's header using stacks. For example, if +you are rendering a given component within a loop, you may wish to only push +the JavaScript to the header the first time the component is rendered: + + + + 1@once + + 2 @push('scripts') + + 3 + + 6 @endpush + + 7@endonce + + + @once + @push('scripts') + + @endpush + @endonce + +Since the `@once` directive is often used in conjunction with the `@push` or +`@prepend` directives, the `@pushOnce` and `@prependOnce` directives are +available for your convenience: + + + + 1@pushOnce('scripts') + + 2 + + 5@endPushOnce + + + @pushOnce('scripts') + + @endPushOnce + +### Raw PHP + +In some situations, it's useful to embed PHP code into your views. You can use +the Blade `@php` directive to execute a block of plain PHP within your +template: + + + + 1@php + + 2 $counter = 1; + + 3@endphp + + + @php + $counter = 1; + @endphp + +Or, if you only need to use PHP to import a class, you may use the `@use` +directive: + + + + 1@use('App\Models\Flight') + + + @use('App\Models\Flight') + +A second argument may be provided to the `@use` directive to alias the +imported class: + + + + 1@use('App\Models\Flight', 'FlightModel') + + + @use('App\Models\Flight', 'FlightModel') + +If you have multiple classes within the same namespace, you may group the +imports of those classes: + + + + 1@use('App\Models\{Flight, Airport}') + + + @use('App\Models\{Flight, Airport}') + +The `@use` directive also supports importing PHP functions and constants by +prefixing the import path with the `function` or `const` modifiers: + + + + 1@use(function App\Helpers\format_currency) + + 2@use(const App\Constants\MAX_ATTEMPTS) + + + @use(function App\Helpers\format_currency) + @use(const App\Constants\MAX_ATTEMPTS) + +Just like class imports, aliases are supported for functions and constants as +well: + + + + 1@use(function App\Helpers\format_currency, 'formatMoney') + + 2@use(const App\Constants\MAX_ATTEMPTS, 'MAX_TRIES') + + + @use(function App\Helpers\format_currency, 'formatMoney') + @use(const App\Constants\MAX_ATTEMPTS, 'MAX_TRIES') + +Grouped imports are also supported with both function and const modifiers, +allowing you to import multiple symbols from the same namespace in a single +directive: + + + + 1@use(function App\Helpers\{format_currency, format_date}) + + 2@use(const App\Constants\{MAX_ATTEMPTS, DEFAULT_TIMEOUT}) + + + @use(function App\Helpers\{format_currency, format_date}) + @use(const App\Constants\{MAX_ATTEMPTS, DEFAULT_TIMEOUT}) + +### Comments + +Blade also allows you to define comments in your views. However, unlike HTML +comments, Blade comments are not included in the HTML returned by your +application: + + + + 1{{-- This comment will not be present in the rendered HTML --}} + + + {{-- This comment will not be present in the rendered HTML --}} + +## Components + +Components and slots provide similar benefits to sections, layouts, and +includes; however, some may find the mental model of components and slots +easier to understand. There are two approaches to writing components: class- +based components and anonymous components. + +To create a class-based component, you may use the `make:component` Artisan +command. To illustrate how to use components, we will create a simple `Alert` +component. The `make:component` command will place the component in the +`app/View/Components` directory: + + + + 1php artisan make:component Alert + + + php artisan make:component Alert + +The `make:component` command will also create a view template for the +component. The view will be placed in the `resources/views/components` +directory. When writing components for your own application, components are +automatically discovered within the `app/View/Components` directory and +`resources/views/components` directory, so no further component registration +is typically required. + +You may also create components within subdirectories: + + + + 1php artisan make:component Forms/Input + + + php artisan make:component Forms/Input + +The command above will create an `Input` component in the +`app/View/Components/Forms` directory and the view will be placed in the +`resources/views/components/forms` directory. + +If you would like to create an anonymous component (a component with only a +Blade template and no class), you may use the `--view` flag when invoking the +`make:component` command: + + + + 1php artisan make:component forms.input --view + + + php artisan make:component forms.input --view + +The command above will create a Blade file at +`resources/views/components/forms/input.blade.php` which can be rendered as a +component via ``. + +#### Manually Registering Package Components + +When writing components for your own application, components are automatically +discovered within the `app/View/Components` directory and +`resources/views/components` directory. + +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: + + + + 1use Illuminate\Support\Facades\Blade; + + 2  + + 3/** + + 4 * Bootstrap your package's services. + + 5 */ + + 6public function boot(): void + + 7{ + + 8 Blade::component('package-alert', Alert::class); + + 9} + + + use Illuminate\Support\Facades\Blade; + + /** + * 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: + + + + 1 + + + + +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: + + + + 1use Illuminate\Support\Facades\Blade; + + 2  + + 3/** + + 4 * Bootstrap your package's services. + + 5 */ + + 6public function boot(): void + + 7{ + + 8 Blade::componentNamespace('Nightshade\\Views\\Components', 'nightshade'); + + 9} + + + use Illuminate\Support\Facades\Blade; + + /** + * 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: + + + + 1 + + 2 + + + + + +Blade will automatically detect the class that's linked to this component by +pascal-casing the component name. Subdirectories are also supported using +"dot" notation. + +### Rendering Components + +To display a component, you may use a Blade component tag within one of your +Blade templates. Blade component tags start with the string `x-` followed by +the kebab case name of the component class: + + + + 1 + + 2  + + 3 + + + + + + +If the component class is nested deeper within the `app/View/Components` +directory, you may use the `.` character to indicate directory nesting. For +example, if we assume a component is located at +`app/View/Components/Inputs/Button.php`, we may render it like so: + + + + 1 + + + + +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: + + + + 1use Illuminate\Support\Str; + + 2  + + 3/** + + 4 * Whether the component should be rendered + + 5 */ + + 6public function shouldRender(): bool + + 7{ + + 8 return Str::length($this->message) > 0; + + 9} + + + use Illuminate\Support\Str; + + /** + * Whether the component should be rendered + */ + public function shouldRender(): bool + { + return Str::length($this->message) > 0; + } + +### Index Components + +Sometimes components are part of a component group and you may wish to group +the related components within a single directory. For example, imagine a +"card" component with the following class structure: + + + + 1App\Views\Components\Card\Card + + 2App\Views\Components\Card\Header + + 3App\Views\Components\Card\Body + + + App\Views\Components\Card\Card + App\Views\Components\Card\Header + App\Views\Components\Card\Body + +Since the root `Card` component is nested within a `Card` directory, you might +expect that you would need to render the component via ``. +However, when a component's file name matches the name of the component's +directory, Laravel automatically assumes that component is the "root" +component and allows you to render the component without repeating the +directory name: + + + + 1 + + 2 ... + + 3 ... + + 4 + + + + ... + ... + + +### Passing Data to Components + +You may pass data to Blade components using HTML attributes. Hard-coded, +primitive values may be passed to the component using simple HTML attribute +strings. PHP expressions and variables should be passed to the component via +attributes that use the `:` character as a prefix: + + + + 1 + + + + +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: + + + + 1 + + 2 {{ $message }} + + 3 + + +
    + {{ $message }} +
    + +#### Casing + +Component constructor arguments should be specified using `camelCase`, while +`kebab-case` should be used when referencing the argument names in your HTML +attributes. For example, given the following component constructor: + + + + 1/** + + 2 * Create the component instance. + + 3 */ + + 4public function __construct( + + 5 public string $alertType, + + 6) {} + + + /** + * Create the component instance. + */ + public function __construct( + public string $alertType, + ) {} + +The `$alertType` argument may be provided to the component like so: + + + + 1 + + + + +#### Short Attribute Syntax + +When passing attributes to components, you may also use a "short attribute" +syntax. This is often convenient since attribute names frequently match the +variable names they correspond to: + + + + 1{{-- Short attribute syntax... --}} + + 2 + + 3  + + 4{{-- Is equivalent to... --}} + + 5 + + + {{-- Short attribute syntax... --}} + + + {{-- Is equivalent to... --}} + + +#### Escaping Attribute Rendering + +Since some JavaScript frameworks such as Alpine.js also use colon-prefixed +attributes, you may use a double colon (`::`) prefix to inform Blade that the +attribute is not a PHP expression. For example, given the following component: + + + + 1 + + 2 Submit + + 3 + + + + Submit + + +The following HTML will be rendered by Blade: + + + + 1 + + + + +#### Component Methods + +In addition to public variables being available to your component template, +any public methods on the component may be invoked. For example, imagine a +component that has an `isSelected` method: + + + + 1/** + + 2 * Determine if the given option is the currently selected option. + + 3 */ + + 4public function isSelected(string $option): bool + + 5{ + + 6 return $option === $this->selected; + + 7} + + + /** + * 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: + + + + 1 + + + + +#### Accessing Attributes and Slots Within Component Classes + +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: + + + + 1use Closure; + + 2  + + 3/** + + 4 * Get the view / contents that represent the component. + + 5 */ + + 6public function render(): Closure + + 7{ + + 8 return function () { + + 9 return '
    Components content
    '; + + 10 }; + + 11} + + + use Closure; + + /** + * 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: + + + + 1return function (array $data) { + + 2 // $data['componentName']; + + 3 // $data['attributes']; + + 4 // $data['slot']; + + 5  + + 6 return '
    Components content
    '; + + 7} + + + return function (array $data) { + // $data['componentName']; + // $data['attributes']; + // $data['slot']; + + return '
    Components content
    '; + } + +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. + +The `componentName` is equal to the name used in the HTML tag after the `x-` +prefix. So ``'s `componentName` will be `alert`. The `attributes` +element will contain all of the attributes that were present on the HTML tag. +The `slot` element is an `Illuminate\Support\HtmlString` instance with the +contents of the component's slot. + +The closure should return a string. If the returned string corresponds to an +existing view, that view will be rendered; otherwise, the returned string will +be evaluated as an inline Blade view. + +#### Additional Dependencies + +If your component requires dependencies from Laravel's [service +container](/docs/12.x/container), you may list them before any of the +component's data attributes and they will automatically be injected by the +container: + + + + 1use App\Services\AlertCreator; + + 2  + + 3/** + + 4 * Create the component instance. + + 5 */ + + 6public function __construct( + + 7 public AlertCreator $creator, + + 8 public string $type, + + 9 public string $message, + + 10) {} + + + use App\Services\AlertCreator; + + /** + * Create the component instance. + */ + public function __construct( + public AlertCreator $creator, + public string $type, + public string $message, + ) {} + +#### Hiding Attributes / Methods + +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: + + + + 1 + + + + +All of the attributes that are not part of the component's constructor will +automatically be added to the component's "attribute bag". This attribute bag +is automatically made available to the component via the `$attributes` +variable. All of the attributes may be rendered within the component by +echoing this variable: + + + + 1
    + + 2 + + 3
    + + +
    + +
    + +Using directives such as `@env` within component tags is not supported at this +time. For example, `` will not be +compiled. + +#### Default / Merged Attributes + +Sometimes you may need to specify default values for attributes or merge +additional values into some of the component's attributes. To accomplish this, +you may use the attribute bag's `merge` method. This method is particularly +useful for defining a set of default CSS classes that should always be applied +to a component: + + + + 1
    merge(['class' => 'alert alert-'.$type]) }}> + + 2 {{ $message }} + + 3
    + + +
    merge(['class' => 'alert alert-'.$type]) }}> + {{ $message }} +
    + +If we assume this component is utilized like so: + + + + 1 + + + + +The final, rendered HTML of the component will appear like the following: + + + + 1
    + + 2 + + 3
    + + +
    + +
    + +#### Conditionally Merge Classes + +Sometimes you may wish to merge classes if a given condition is `true`. You +can accomplish this via the `class` method, which 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: + + + + 1
    class(['p-4', 'bg-red' => $hasError]) }}> + + 2 {{ $message }} + + 3
    + + +
    class(['p-4', 'bg-red' => $hasError]) }}> + {{ $message }} +
    + +If you need to merge other attributes onto your component, you can chain the +`merge` method onto the `class` method: + + + + 1 + + + + +If you need to conditionally compile classes on other HTML elements that +shouldn't receive merged attributes, you can use the @class directive. + +#### Non-Class Attribute Merging + +When merging attributes that are not `class` attributes, the values provided +to the `merge` method will be considered the "default" values of the +attribute. However, unlike the `class` attribute, these attributes will not be +merged with injected attribute values. Instead, they will be overwritten. For +example, a `button` component's implementation may look like the following: + + + + 1 + + + + +To render the button component with a custom `type`, it may be specified when +consuming the component. If no type is specified, the `button` type will be +used: + + + + 1 + + 2 Submit + + 3 + + + + Submit + + +The rendered HTML of the `button` component in this example would be: + + + + 1 + + + + +If you would like an attribute other than `class` to have its default value +and injected values joined together, you may use the `prepends` method. In +this example, the `data-controller` attribute will always begin with `profile- +controller` and any additional injected `data-controller` values will be +placed after this default value: + + + + 1
    merge(['data-controller' => $attributes->prepends('profile-controller')]) }}> + + 2 {{ $slot }} + + 3
    + + +
    merge(['data-controller' => $attributes->prepends('profile-controller')]) }}> + {{ $slot }} +
    + +#### Retrieving and Filtering Attributes + +You may filter attributes using the `filter` method. This method accepts a +closure which should return `true` if you wish to retain the attribute in the +attribute bag: + + + + 1{{ $attributes->filter(fn (string $value, string $key) => $key == 'foo') }} + + + {{ $attributes->filter(fn (string $value, string $key) => $key == 'foo') }} + +For convenience, you may use the `whereStartsWith` method to retrieve all +attributes whose keys begin with a given string: + + + + 1{{ $attributes->whereStartsWith('wire:model') }} + + + {{ $attributes->whereStartsWith('wire:model') }} + +Conversely, the `whereDoesntStartWith` method may be used to exclude all +attributes whose keys begin with a given string: + + + + 1{{ $attributes->whereDoesntStartWith('wire:model') }} + + + {{ $attributes->whereDoesntStartWith('wire:model') }} + +Using the `first` method, you may render the first attribute in a given +attribute bag: + + + + 1{{ $attributes->whereStartsWith('wire:model')->first() }} + + + {{ $attributes->whereStartsWith('wire:model')->first() }} + +If you would like to check if an attribute is present on the component, you +may use the `has` method. This method accepts the attribute name as its only +argument and returns a boolean indicating whether or not the attribute is +present: + + + + 1@if ($attributes->has('class')) + + 2
    Class attribute is present
    + + 3@endif + + + @if ($attributes->has('class')) +
    Class attribute is present
    + @endif + +If an array is passed to the `has` method, the method will determine if all of +the given attributes are present on the component: + + + + 1@if ($attributes->has(['name', 'class'])) + + 2
    All of the attributes are present
    + + 3@endif + + + @if ($attributes->has(['name', 'class'])) +
    All of the attributes are present
    + @endif + +The `hasAny` method may be used to determine if any of the given attributes +are present on the component: + + + + 1@if ($attributes->hasAny(['href', ':href', 'v-bind:href'])) + + 2
    One of the attributes is present
    + + 3@endif + + + @if ($attributes->hasAny(['href', ':href', 'v-bind:href'])) +
    One of the attributes is present
    + @endif + +You may retrieve a specific attribute's value using the `get` method: + + + + 1{{ $attributes->get('class') }} + + + {{ $attributes->get('class') }} + +The `only` method may be used to retrieve only the attributes with the given +keys: + + + + 1{{ $attributes->only(['class']) }} + + + {{ $attributes->only(['class']) }} + +The `except` method may be used to retrieve all attributes except those with +the given keys: + + + + 1{{ $attributes->except(['class']) }} + + + {{ $attributes->except(['class']) }} + +### Reserved Keywords + +By default, some keywords are reserved for Blade's internal use in order to +render components. The following keywords cannot be defined as public +properties or method names within your components: + + * `data` + * `render` + * `resolveView` + * `shouldRender` + * `view` + * `withAttributes` + * `withName` + +### Slots + +You will often need to pass additional content to your component via "slots". +Component slots are rendered by echoing the `$slot` variable. To explore this +concept, let's imagine that an `alert` component has the following markup: + + + + 1 + + 2  + + 3
    + + 4 {{ $slot }} + + 5
    + + + + +
    + {{ $slot }} +
    + +We may pass content to the `slot` by injecting content into the component: + + + + 1 + + 2 Whoops! Something went wrong! + + 3 + + + + Whoops! Something went wrong! + + +Sometimes a component may need to render multiple different slots in different +locations within the component. Let's modify our alert component to allow for +the injection of a "title" slot: + + + + 1 + + 2  + + 3{{ $title }} + + 4  + + 5
    + + 6 {{ $slot }} + + 7
    + + + + + {{ $title }} + +
    + {{ $slot }} +
    + +You may define the content of the named slot using the `x-slot` tag. Any +content not within an explicit `x-slot` tag will be passed to the component in +the `$slot` variable: + + + + 1 + + 2 + + 3 Server Error + + 4 + + 5  + + 6 Whoops! Something went wrong! + + 7 + + + + + Server Error + + + Whoops! Something went wrong! + + +You may invoke a slot's `isEmpty` method to determine if the slot contains +content: + + + + 1{{ $title }} + + 2  + + 3
    + + 4 @if ($slot->isEmpty()) + + 5 This is default content if the slot is empty. + + 6 @else + + 7 {{ $slot }} + + 8 @endif + + 9
    + + + {{ $title }} + +
    + @if ($slot->isEmpty()) + This is default content if the slot is empty. + @else + {{ $slot }} + @endif +
    + +Additionally, the `hasActualContent` method may be used to determine if the +slot contains any "actual" content that is not an HTML comment: + + + + 1@if ($slot->hasActualContent()) + + 2 The scope has non-comment content. + + 3@endif + + + @if ($slot->hasActualContent()) + The scope has non-comment content. + @endif + +#### Scoped Slots + +If you have used a JavaScript framework such as Vue, you may be familiar with +"scoped slots", which allow you to access data or methods from the component +within your slot. You may achieve similar behavior in Laravel by defining +public methods or properties on your component and accessing the component +within your slot via the `$component` variable. In this example, we will +assume that the `x-alert` component has a public `formatAlert` method defined +on its component class: + + + + 1 + + 2 + + 3 {{ $component->formatAlert('Server Error') }} + + 4 + + 5  + + 6 Whoops! Something went wrong! + + 7 + + + + + {{ $component->formatAlert('Server Error') }} + + + Whoops! Something went wrong! + + +#### Slot Attributes + +Like Blade components, you may assign additional attributes to slots such as +CSS class names: + + + + 1 + + 2 + + 3 Heading + + 4 + + 5  + + 6 Content + + 7  + + 8 + + 9 Footer + + 10 + + 11 + + + + + Heading + + + Content + + + Footer + + + +To interact with slot attributes, you may access the `attributes` property of +the slot's variable. For more information on how to interact with attributes, +please consult the documentation on component attributes: + + + + 1@props([ + + 2 'heading', + + 3 'footer', + + 4]) + + 5  + + 6
    class(['border']) }}> + + 7

    attributes->class(['text-lg']) }}> + + 8 {{ $heading }} + + 9

    + + 10  + + 11 {{ $slot }} + + 12  + + 13
    attributes->class(['text-gray-700']) }}> + + 14 {{ $footer }} + + 15
    + + 16
    + + + @props([ + 'heading', + 'footer', + ]) + +
    class(['border']) }}> +

    attributes->class(['text-lg']) }}> + {{ $heading }} +

    + + {{ $slot }} + +
    attributes->class(['text-gray-700']) }}> + {{ $footer }} +
    +
    + +### Inline Component Views + +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: + + + + 1/** + + 2 * Get the view / contents that represent the component. + + 3 */ + + 4public function render(): string + + 5{ + + 6 return <<<'blade' + + 7
    + + 8 {{ $slot }} + + 9
    + + 10 blade; + + 11} + + + /** + * Get the view / contents that represent the component. + */ + public function render(): string + { + return <<<'blade' +
    + {{ $slot }} +
    + blade; + } + +#### Generating Inline View Components + +To create a component that renders an inline view, you may use the `inline` +option when executing the `make:component` command: + + + + 1php artisan make:component Alert --inline + + + php artisan make:component Alert --inline + +### Dynamic Components + +Sometimes you may need to render a component but not know which component +should be rendered until runtime. In this situation, you may use Laravel's +built-in `dynamic-component` component to render the component based on a +runtime value or variable: + + + + 1// $componentName = "secondary-button"; + + 2  + + 3 + + + // $componentName = "secondary-button"; + + + +### Manually Registering Components + +The following documentation on manually registering components is primarily +applicable to those who are writing Laravel packages that include view +components. If you are not writing a package, this portion of the component +documentation may not be relevant to you. + +When writing components for your own application, components are automatically +discovered within the `app/View/Components` directory and +`resources/views/components` directory. + +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: + + + + 1use Illuminate\Support\Facades\Blade; + + 2use VendorPackage\View\Components\AlertComponent; + + 3  + + 4/** + + 5 * Bootstrap your package's services. + + 6 */ + + 7public function boot(): void + + 8{ + + 9 Blade::component('package-alert', AlertComponent::class); + + 10} + + + 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); + } + +Once your component has been registered, it may be rendered using its tag +alias: + + + + 1 + + + + +#### Autoloading Package Components + +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: + + + + 1use Illuminate\Support\Facades\Blade; + + 2  + + 3/** + + 4 * Bootstrap your package's services. + + 5 */ + + 6public function boot(): void + + 7{ + + 8 Blade::componentNamespace('Nightshade\\Views\\Components', 'nightshade'); + + 9} + + + use Illuminate\Support\Facades\Blade; + + /** + * 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: + + + + 1 + + 2 + + + + + +Blade will automatically detect the class that's linked to this component by +pascal-casing the component name. Subdirectories are also supported using +"dot" notation. + +## Anonymous Components + +Similar to inline components, anonymous components provide a mechanism for +managing a component via a single file. However, anonymous components utilize +a single view file and have no associated class. To define an anonymous +component, you only need to place a Blade template within your +`resources/views/components` directory. For example, assuming you have defined +a component at `resources/views/components/alert.blade.php`, you may simply +render it like so: + + + + 1 + + + + +You may use the `.` character to indicate if a component is nested deeper +inside the `components` directory. For example, assuming the component is +defined at `resources/views/components/inputs/button.blade.php`, you may +render it like so: + + + + 1 + + + + +### Anonymous Index Components + +Sometimes, when a component is made up of many Blade templates, you may wish +to group the given component's templates within a single directory. For +example, imagine an "accordion" component with the following directory +structure: + + + + 1/resources/views/components/accordion.blade.php + + 2/resources/views/components/accordion/item.blade.php + + + /resources/views/components/accordion.blade.php + /resources/views/components/accordion/item.blade.php + +This directory structure allows you to render the accordion component and its +item like so: + + + + 1 + + 2 + + 3 ... + + 4 + + 5 + + + + + ... + + + +However, in order to render the accordion component via `x-accordion`, we were +forced to place the "index" accordion component template in the +`resources/views/components` directory instead of nesting it within the +`accordion` directory with the other accordion related templates. + +Thankfully, Blade allows you to place a file matching the component's +directory name within the component's directory itself. When this template +exists, it can be rendered as the "root" element of the component even though +it is nested within a directory. So, we can continue to use the same Blade +syntax given in the example above; however, we will adjust our directory +structure like so: + + + + 1/resources/views/components/accordion/accordion.blade.php + + 2/resources/views/components/accordion/item.blade.php + + + /resources/views/components/accordion/accordion.blade.php + /resources/views/components/accordion/item.blade.php + +### Data Properties / Attributes + +Since anonymous components do not have any associated class, you may wonder +how you may differentiate which data should be passed to the component as +variables and which attributes should be placed in the component's attribute +bag. + +You may specify which attributes should be considered data variables using the +`@props` directive at the top of your component's Blade template. All other +attributes on the component will be available via the component's attribute +bag. If you wish to give a data variable a default value, you may specify the +variable's name as the array key and the default value as the array value: + + + + 1 + + 2  + + 3@props(['type' => 'info', 'message']) + + 4  + + 5
    merge(['class' => 'alert alert-'.$type]) }}> + + 6 {{ $message }} + + 7
    + + + + + @props(['type' => 'info', 'message']) + +
    merge(['class' => 'alert alert-'.$type]) }}> + {{ $message }} +
    + +Given the component definition above, we may render the component like so: + + + + 1 + + + + +### Accessing Parent Data + +Sometimes you may want to access data from a parent component inside a child +component. In these cases, you may use the `@aware` directive. For example, +imagine we are building a complex menu component consisting of a parent +`` and child ``: + + + + 1 + + 2 ... + + 3 ... + + 4 + + + + ... + ... + + +The `` component may have an implementation like the following: + + + + 1 + + 2  + + 3@props(['color' => 'gray']) + + 4  + + 5
      merge(['class' => 'bg-'.$color.'-200']) }}> + + 6 {{ $slot }} + + 7
    + + + + + @props(['color' => 'gray']) + +
      merge(['class' => 'bg-'.$color.'-200']) }}> + {{ $slot }} +
    + +Because the `color` prop was only passed into the parent (``), it +won't be available inside ``. However, if we use the `@aware` +directive, we can make it available inside `` as well: + + + + 1 + + 2  + + 3@aware(['color' => 'gray']) + + 4  + + 5
  • merge(['class' => 'text-'.$color.'-800']) }}> + + 6 {{ $slot }} + + 7
  • + + + + + @aware(['color' => 'gray']) + +
  • merge(['class' => 'text-'.$color.'-800']) }}> + {{ $slot }} +
  • + +The `@aware` directive cannot access parent data that is not explicitly passed +to the parent component via HTML attributes. Default `@props` values that are +not explicitly passed to the parent component cannot be accessed by the +`@aware` directive. + +### Anonymous Component Paths + +As previously discussed, anonymous components are typically defined by placing +a Blade template within your `resources/views/components` directory. However, +you may occasionally want to register other anonymous component paths with +Laravel in addition to the default path. + +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/12.x/providers): + + + + 1/** + + 2 * Bootstrap any application services. + + 3 */ + + 4public function boot(): void + + 5{ + + 6 Blade::anonymousComponentPath(__DIR__.'/../components'); + + 7} + + + /** + * 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: + + + + 1 + + + + +Prefix "namespaces" may be provided as the second argument to the +`anonymousComponentPath` method: + + + + 1Blade::anonymousComponentPath(__DIR__.'/../components', 'dashboard'); + + + 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: + + + + 1 + + + + +## Building Layouts + +### Layouts Using Components + +Most web applications maintain the same general layout across various pages. +It would be incredibly cumbersome and hard to maintain our application if we +had to repeat the entire layout HTML in every view we create. Thankfully, it's +convenient to define this layout as a single Blade component and then use it +throughout our application. + +#### Defining the Layout Component + +For example, imagine we are building a "todo" list application. We might +define a `layout` component that looks like the following: + + + + 1 + + 2  + + 3 + + 4 + + 5 {{ $title ?? 'Todo Manager' }} + + 6 + + 7 + + 8

    Todos

    + + 9
    + + 10 {{ $slot }} + + 11 + + 12 + + + + + + + {{ $title ?? 'Todo Manager' }} + + +

    Todos

    +
    + {{ $slot }} + + + +#### Applying the Layout Component + +Once the `layout` component has been defined, we may create a Blade view that +utilizes the component. In this example, we will define a simple view that +displays our task list: + + + + 1 + + 2  + + 3 + + 4 @foreach ($tasks as $task) + + 5
    {{ $task }}
    + + 6 @endforeach + + 7
    + + + + + + @foreach ($tasks as $task) +
    {{ $task }}
    + @endforeach +
    + +Remember, content that is injected into a component will be supplied to the +default `$slot` variable within our `layout` component. As you may have +noticed, our `layout` also respects a `$title` slot if one is provided; +otherwise, a default title is shown. We may inject a custom title from our +task list view using the standard slot syntax discussed in the component +documentation: + + + + 1 + + 2  + + 3 + + 4 + + 5 Custom Title + + 6 + + 7  + + 8 @foreach ($tasks as $task) + + 9
    {{ $task }}
    + + 10 @endforeach + + 11
    + + + + + + + Custom Title + + + @foreach ($tasks as $task) +
    {{ $task }}
    + @endforeach +
    + +Now that we have defined our layout and task list views, we just need to +return the `task` view from a route: + + + + 1use App\Models\Task; + + 2  + + 3Route::get('/tasks', function () { + + 4 return view('tasks', ['tasks' => Task::all()]); + + 5}); + + + use App\Models\Task; + + Route::get('/tasks', function () { + return view('tasks', ['tasks' => Task::all()]); + }); + +### Layouts Using Template Inheritance + +#### Defining a Layout + +Layouts may also be created via "template inheritance". This was the primary +way of building applications prior to the introduction of components. + +To get started, let's take a look at a simple example. First, we will examine +a page layout. Since most web applications maintain the same general layout +across various pages, it's convenient to define this layout as a single Blade +view: + + + + 1 + + 2  + + 3 + + 4 + + 5 App Name - @yield('title') + + 6 + + 7 + + 8 @section('sidebar') + + 9 This is the master sidebar. + + 10 @show + + 11  + + 12
    + + 13 @yield('content') + + 14
    + + 15 + + 16 + + + + + + + App Name - @yield('title') + + + @section('sidebar') + This is the master sidebar. + @show + +
    + @yield('content') +
    + + + +As you can see, this file contains typical HTML mark-up. However, take note of +the `@section` and `@yield` directives. The `@section` directive, as the name +implies, defines a section of content, while the `@yield` directive is used to +display the contents of a given section. + +Now that we have defined a layout for our application, let's define a child +page that inherits the layout. + +#### Extending a Layout + +When defining a child view, use the `@extends` Blade directive to specify +which layout the child view should "inherit". Views which extend a Blade +layout may inject content into the layout's sections using `@section` +directives. Remember, as seen in the example above, the contents of these +sections will be displayed in the layout using `@yield`: + + + + 1 + + 2  + + 3@extends('layouts.app') + + 4  + + 5@section('title', 'Page Title') + + 6  + + 7@section('sidebar') + + 8 @@parent + + 9  + + 10

    This is appended to the master sidebar.

    + + 11@endsection + + 12  + + 13@section('content') + + 14

    This is my body content.

    + + 15@endsection + + + + + @extends('layouts.app') + + @section('title', 'Page Title') + + @section('sidebar') + @@parent + +

    This is appended to the master sidebar.

    + @endsection + + @section('content') +

    This is my body content.

    + @endsection + +In this example, the `sidebar` section is utilizing the `@@parent` directive +to append (rather than overwriting) content to the layout's sidebar. The +`@@parent` directive will be replaced by the content of the layout when the +view is rendered. + +Contrary to the previous example, this `sidebar` section ends with +`@endsection` instead of `@show`. The `@endsection` directive will only define +a section while `@show` will define and **immediately yield** the section. + +The `@yield` directive also accepts a default value as its second parameter. +This value will be rendered if the section being yielded is undefined: + + + + 1@yield('content', 'Default content') + + + @yield('content', 'Default content') + +## Forms + +### CSRF Field + +Anytime you define an HTML form in your application, you should include a +hidden CSRF token field in the form so that [the CSRF +protection](/docs/12.x/csrf) middleware can validate the request. You may use +the `@csrf` Blade directive to generate the token field: + + + + 1
    + + 2 @csrf + + 3  + + 4 ... + + 5
    + + +
    + @csrf + + ... +
    + +### Method Field + +Since HTML forms can't make `PUT`, `PATCH`, or `DELETE` requests, you will +need to add a hidden `_method` field to spoof these HTTP verbs. The `@method` +Blade directive can create this field for you: + + + + 1
    + + 2 @method('PUT') + + 3  + + 4 ... + + 5
    + + +
    + @method('PUT') + + ... +
    + +### Validation Errors + +The `@error` directive may be used to quickly check if [validation error +messages](/docs/12.x/validation#quick-displaying-the-validation-errors) exist +for a given attribute. Within an `@error` directive, you may echo the +`$message` variable to display the error message: + + + + 1 + + 2  + + 3 + + 4  + + 5 + + 10  + + 11@error('title') + + 12
    {{ $message }}
    + + 13@enderror + + + + + + + + + @error('title') +
    {{ $message }}
    + @enderror + +Since the `@error` directive compiles to an "if" statement, you may use the +`@else` directive to render content when there is not an error for an +attribute: + + + + 1 + + 2  + + 3 + + 4  + + 5 + + + + + + + + +You may pass [the name of a specific error bag](/docs/12.x/validation#named- +error-bags) as the second parameter to the `@error` directive to retrieve +validation error messages on pages containing multiple forms: + + + + 1 + + 2  + + 3 + + 4  + + 5 + + 10  + + 11@error('email', 'login') + + 12
    {{ $message }}
    + + 13@enderror + + + + + + + + + @error('email', 'login') +
    {{ $message }}
    + @enderror + +## Stacks + +Blade allows you to push to named stacks which can be rendered somewhere else +in another view or layout. This can be particularly useful for specifying any +JavaScript libraries required by your child views: + + + + 1@push('scripts') + + 2 + + 3@endpush + + + @push('scripts') + + @endpush + +If you would like to `@push` content if a given boolean expression evaluates +to `true`, you may use the `@pushIf` directive: + + + + 1@pushIf($shouldPush, 'scripts') + + 2 + + 3@endPushIf + + + @pushIf($shouldPush, 'scripts') + + @endPushIf + +You may push to a stack as many times as needed. To render the complete stack +contents, pass the name of the stack to the `@stack` directive: + + + + 1 + + 2 + + 3  + + 4 @stack('scripts') + + 5 + + + + + + @stack('scripts') + + +If you would like to prepend content onto the beginning of a stack, you should +use the `@prepend` directive: + + + + 1@push('scripts') + + 2 This will be second... + + 3@endpush + + 4  + + 5// Later... + + 6  + + 7@prepend('scripts') + + 8 This will be first... + + 9@endprepend + + + @push('scripts') + This will be second... + @endpush + + // Later... + + @prepend('scripts') + This will be first... + @endprepend + +## Service Injection + +The `@inject` directive may be used to retrieve a service from the Laravel +[service container](/docs/12.x/container). The first argument passed to +`@inject` is the name of the variable the service will be placed into, while +the second argument is the class or interface name of the service you wish to +resolve: + + + + 1@inject('metrics', 'App\Services\MetricsService') + + 2  + + 3
    + + 4 Monthly Revenue: {{ $metrics->monthlyRevenue() }}. + + 5
    + + + @inject('metrics', 'App\Services\MetricsService') + +
    + Monthly Revenue: {{ $metrics->monthlyRevenue() }}. +
    + +## Rendering Inline Blade Templates + +Sometimes you may need to transform a raw Blade template string into valid +HTML. You may accomplish this using the `render` method provided by the +`Blade` facade. The `render` method accepts the Blade template string and an +optional array of data to provide to the template: + + + + 1use Illuminate\Support\Facades\Blade; + + 2  + + 3return Blade::render('Hello, {{ $name }}', ['name' => 'Julian Bashir']); + + + use Illuminate\Support\Facades\Blade; + + return Blade::render('Hello, {{ $name }}', ['name' => 'Julian Bashir']); + +Laravel renders inline Blade templates by writing them to the +`storage/framework/views` directory. If you would like Laravel to remove these +temporary files after rendering the Blade template, you may provide the +`deleteCachedView` argument to the method: + + + + 1return Blade::render( + + 2 'Hello, {{ $name }}', + + 3 ['name' => 'Julian Bashir'], + + 4 deleteCachedView: true + + 5); + + + return Blade::render( + 'Hello, {{ $name }}', + ['name' => 'Julian Bashir'], + deleteCachedView: true + ); + +## Rendering Blade Fragments + +When using frontend frameworks such as [Turbo](https://turbo.hotwired.dev/) +and [htmx](https://htmx.org/), you may occasionally need to only return a +portion of a Blade template within your HTTP response. Blade "fragments" allow +you to do just that. To get started, place a portion of your Blade template +within `@fragment` and `@endfragment` directives: + + + + 1@fragment('user-list') + + 2
      + + 3 @foreach ($users as $user) + + 4
    • {{ $user->name }}
    • + + 5 @endforeach + + 6
    + + 7@endfragment + + + @fragment('user-list') +
      + @foreach ($users as $user) +
    • {{ $user->name }}
    • + @endforeach +
    + @endfragment + +Then, when rendering the view that utilizes this template, you may invoke the +`fragment` method to specify that only the specified fragment should be +included in the outgoing HTTP response: + + + + 1return view('dashboard', ['users' => $users])->fragment('user-list'); + + + return view('dashboard', ['users' => $users])->fragment('user-list'); + +The `fragmentIf` method allows you to conditionally return a fragment of a +view based on a given condition. Otherwise, the entire view will be returned: + + + + 1return view('dashboard', ['users' => $users]) + + 2 ->fragmentIf($request->hasHeader('HX-Request'), 'user-list'); + + + return view('dashboard', ['users' => $users]) + ->fragmentIf($request->hasHeader('HX-Request'), 'user-list'); + +The `fragments` and `fragmentsIf` methods allow you to return multiple view +fragments in the response. The fragments will be concatenated together: + + + + 1view('dashboard', ['users' => $users]) + + 2 ->fragments(['user-list', 'comment-list']); + + 3  + + 4view('dashboard', ['users' => $users]) + + 5 ->fragmentsIf( + + 6 $request->hasHeader('HX-Request'), + + 7 ['user-list', 'comment-list'] + + 8 ); + + + view('dashboard', ['users' => $users]) + ->fragments(['user-list', 'comment-list']); + + view('dashboard', ['users' => $users]) + ->fragmentsIf( + $request->hasHeader('HX-Request'), + ['user-list', 'comment-list'] + ); + +## Extending Blade + +Blade allows you to define your own custom directives using the `directive` +method. When the Blade compiler encounters the custom directive, it will call +the provided callback with the expression that the directive contains. + +The following example creates a `@datetime($var)` directive which formats a +given `$var`, which should be an instance of `DateTime`: + + + + 1format('m/d/Y H:i'); ?>"; + + 25 }); + + 26 } + + 27} + + + 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: + + + + 1format('m/d/Y H:i'); ?> + + + format('m/d/Y H:i'); ?> + +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. + +### Custom Echo Handlers + +If you attempt to "echo" an object using Blade, the object's `__toString` +method will be invoked. The +[__toString](https://www.php.net/manual/en/language.oop5.magic.php#object.tostring) +method is one of PHP's built-in "magic methods". However, sometimes you may +not have control over the `__toString` method of a given class, such as when +the class that you are interacting with belongs to a third-party library. + +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: + + + + 1use Illuminate\Support\Facades\Blade; + + 2use Money\Money; + + 3  + + 4/** + + 5 * Bootstrap any application services. + + 6 */ + + 7public function boot(): void + + 8{ + + 9 Blade::stringable(function (Money $money) { + + 10 return $money->formatTo('en_GB'); + + 11 }); + + 12} + + + 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'); + }); + } + +Once your custom echo handler has been defined, you may simply echo the object +in your Blade template: + + + + 1Cost: {{ $money }} + + + Cost: {{ $money }} + +### Custom If Statements + +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`: + + + + 1use Illuminate\Support\Facades\Blade; + + 2  + + 3/** + + 4 * Bootstrap any application services. + + 5 */ + + 6public function boot(): void + + 7{ + + 8 Blade::if('disk', function (string $value) { + + 9 return config('filesystems.default') === $value; + + 10 }); + + 11} + + + use Illuminate\Support\Facades\Blade; + + /** + * 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: + + + + 1@disk('local') + + 2 + + 3@elsedisk('s3') + + 4 + + 5@else + + 6 + + 7@enddisk + + 8  + + 9@unlessdisk('local') + + 10 + + 11@enddisk + + + @disk('local') + + @elsedisk('s3') + + @else + + @enddisk + + @unlessdisk('local') + + @enddisk + diff --git a/output/12.x/broadcasting.md b/output/12.x/broadcasting.md new file mode 100644 index 0000000..295511f --- /dev/null +++ b/output/12.x/broadcasting.md @@ -0,0 +1,3790 @@ +# Broadcasting + + * Introduction + * Quickstart + * Server Side Installation + * Reverb + * Pusher Channels + * Ably + * Client Side Installation + * Reverb + * Pusher Channels + * Ably + * Concept Overview + * Using an Example Application + * Defining Broadcast Events + * Broadcast Name + * Broadcast Data + * Broadcast Queue + * Broadcast Conditions + * Broadcasting and Database Transactions + * Authorizing Channels + * Defining Authorization Callbacks + * Defining Channel Classes + * Broadcasting Events + * Only to Others + * Customizing the Connection + * Anonymous Events + * Rescuing Broadcasts + * Receiving Broadcasts + * Listening for Events + * Leaving a Channel + * Namespaces + * Using React or Vue + * Presence Channels + * Authorizing Presence Channels + * Joining Presence Channels + * Broadcasting to Presence Channels + * Model Broadcasting + * Model Broadcasting Conventions + * Listening for Model Broadcasts + * Client Events + * Notifications + +## Introduction + +In many modern web applications, WebSockets are used to implement realtime, +live-updating user interfaces. When some data is updated on the server, a +message is typically sent over a WebSocket connection to be handled by the +client. WebSockets provide a more efficient alternative to continually polling +your application's server for data changes that should be reflected in your +UI. + +For example, imagine your application is able to export a user's data to a CSV +file and email it to them. However, creating this CSV file takes several +minutes so you choose to create and mail the CSV within a [queued +job](/docs/12.x/queues). When the CSV has been created and mailed to the user, +we can use event broadcasting to dispatch an `App\Events\UserDataExported` +event that is received by our application's JavaScript. Once the event is +received, we can display a message to the user that their CSV has been emailed +to them without them ever needing to refresh the page. + +To assist you in building these types of features, Laravel makes it easy to +"broadcast" your server-side Laravel [events](/docs/12.x/events) over a +WebSocket connection. Broadcasting your Laravel events allows you to share the +same event names and data between your server-side Laravel application and +your client-side JavaScript application. + +The core concepts behind broadcasting are simple: clients connect to named +channels on the frontend, while your Laravel application broadcasts events to +these channels on the backend. These events can contain any additional data +you wish to make available to the frontend. + +#### Supported Drivers + +By default, Laravel includes three server-side broadcasting drivers for you to +choose from: [Laravel Reverb](https://reverb.laravel.com), [Pusher +Channels](https://pusher.com/channels), and [Ably](https://ably.com). + +Before diving into event broadcasting, make sure you have read Laravel's +documentation on [events and listeners](/docs/12.x/events). + +## Quickstart + +By default, broadcasting is not enabled in new Laravel applications. You may +enable broadcasting using the `install:broadcasting` Artisan command: + + + + 1php artisan install:broadcasting + + + php artisan install:broadcasting + +The `install:broadcasting` command will prompt you for which event +broadcasting service you would like to use. In addition, it will create the +`config/broadcasting.php` configuration file and the `routes/channels.php` +file where you may register your application's broadcast authorization routes +and callbacks. + +Laravel supports several broadcast drivers out of the box: [Laravel +Reverb](/docs/12.x/reverb), [Pusher Channels](https://pusher.com/channels), +[Ably](https://ably.com), and a `log` driver for local development and +debugging. Additionally, a `null` driver is included which allows you to +disable broadcasting during testing. A configuration example is included for +each of these drivers in the `config/broadcasting.php` configuration file. + +All of your application's event broadcasting configuration is stored in the +`config/broadcasting.php` configuration file. Don't worry if this file does +not exist in your application; it will be created when you run the +`install:broadcasting` Artisan command. + +#### Next Steps + +Once you have enabled event broadcasting, you're ready to learn more about +defining broadcast events and listening for events. If you're using Laravel's +React or Vue [starter kits](/docs/12.x/starter-kits), you may listen for +events using Echo's useEcho hook. + +Before broadcasting any events, you should first configure and run a [queue +worker](/docs/12.x/queues). All event broadcasting is done via queued jobs so +that the response time of your application is not seriously affected by events +being broadcast. + +## Server Side Installation + +To get started using Laravel's event broadcasting, we need to do some +configuration within the Laravel application as well as install a few +packages. + +Event broadcasting is accomplished by a server-side broadcasting driver that +broadcasts your Laravel events so that Laravel Echo (a JavaScript library) can +receive them within the browser client. Don't worry - we'll walk through each +part of the installation process step-by-step. + +### Reverb + +To quickly enable support for Laravel's broadcasting features while using +Reverb as your event broadcaster, invoke the `install:broadcasting` Artisan +command with the `--reverb` option. This Artisan command will install Reverb's +required Composer and NPM packages and update your application's `.env` file +with the appropriate variables: + + + + 1php artisan install:broadcasting --reverb + + + php artisan install:broadcasting --reverb + +#### Manual Installation + +When running the `install:broadcasting` command, you will be prompted to +install [Laravel Reverb](/docs/12.x/reverb). Of course, you may also install +Reverb manually using the Composer package manager: + + + + 1composer require laravel/reverb + + + composer require laravel/reverb + +Once the package is installed, you may run Reverb's installation command to +publish the configuration, add Reverb's required environment variables, and +enable event broadcasting in your application: + + + + 1php artisan reverb:install + + + php artisan reverb:install + +You can find detailed Reverb installation and usage instructions in the +[Reverb documentation](/docs/12.x/reverb). + +### Pusher Channels + +To quickly enable support for Laravel's broadcasting features while using +Pusher as your event broadcaster, invoke the `install:broadcasting` Artisan +command with the `--pusher` option. This Artisan command will prompt you for +your Pusher credentials, install the Pusher PHP and JavaScript SDKs, and +update your application's `.env` file with the appropriate variables: + + + + 1php artisan install:broadcasting --pusher + + + php artisan install:broadcasting --pusher + +#### Manual Installation + +To install Pusher support manually, you should install the Pusher Channels PHP +SDK using the Composer package manager: + + + + 1composer require pusher/pusher-php-server + + + composer require pusher/pusher-php-server + +Next, you should configure your Pusher Channels credentials in the +`config/broadcasting.php` configuration file. An example Pusher Channels +configuration is already included in this file, allowing you to quickly +specify your key, secret, and application ID. Typically, you should configure +your Pusher Channels credentials in your application's `.env` file: + + + + 1PUSHER_APP_ID="your-pusher-app-id" + + 2PUSHER_APP_KEY="your-pusher-key" + + 3PUSHER_APP_SECRET="your-pusher-secret" + + 4PUSHER_HOST= + + 5PUSHER_PORT=443 + + 6PUSHER_SCHEME="https" + + 7PUSHER_APP_CLUSTER="mt1" + + + PUSHER_APP_ID="your-pusher-app-id" + PUSHER_APP_KEY="your-pusher-key" + PUSHER_APP_SECRET="your-pusher-secret" + PUSHER_HOST= + PUSHER_PORT=443 + PUSHER_SCHEME="https" + PUSHER_APP_CLUSTER="mt1" + +The `config/broadcasting.php` file's `pusher` configuration also allows you to +specify additional `options` that are supported by Channels, such as the +cluster. + +Then, set the `BROADCAST_CONNECTION` environment variable to `pusher` in your +application's `.env` file: + + + + 1BROADCAST_CONNECTION=pusher + + + BROADCAST_CONNECTION=pusher + +Finally, you are ready to install and configure Laravel Echo, which will +receive the broadcast events on the client-side. + +### Ably + +The documentation below discusses how to use Ably in "Pusher compatibility" +mode. However, the Ably team recommends and maintains a broadcaster and Echo +client that is able to take advantage of the unique capabilities offered by +Ably. For more information on using the Ably maintained drivers, please +[consult Ably's Laravel broadcaster +documentation](https://github.com/ably/laravel-broadcaster). + +To quickly enable support for Laravel's broadcasting features while using +[Ably](https://ably.com) as your event broadcaster, invoke the +`install:broadcasting` Artisan command with the `--ably` option. This Artisan +command will prompt you for your Ably credentials, install the Ably PHP and +JavaScript SDKs, and update your application's `.env` file with the +appropriate variables: + + + + 1php artisan install:broadcasting --ably + + + php artisan install:broadcasting --ably + +**Before continuing, you should enable Pusher protocol support in your Ably +application settings. You may enable this feature within the "Protocol Adapter +Settings" portion of your Ably application's settings dashboard.** + +#### Manual Installation + +To install Ably support manually, you should install the Ably PHP SDK using +the Composer package manager: + + + + 1composer require ably/ably-php + + + composer require ably/ably-php + +Next, you should configure your Ably credentials in the +`config/broadcasting.php` configuration file. An example Ably configuration is +already included in this file, allowing you to quickly specify your key. +Typically, this value should be set via the `ABLY_KEY` [environment +variable](/docs/12.x/configuration#environment-configuration): + + + + 1ABLY_KEY=your-ably-key + + + ABLY_KEY=your-ably-key + +Then, set the `BROADCAST_CONNECTION` environment variable to `ably` in your +application's `.env` file: + + + + 1BROADCAST_CONNECTION=ably + + + BROADCAST_CONNECTION=ably + +Finally, you are ready to install and configure Laravel Echo, which will +receive the broadcast events on the client-side. + +## Client Side Installation + +### Reverb + +[Laravel Echo](https://github.com/laravel/echo) is a JavaScript library that +makes it painless to subscribe to channels and listen for events broadcast by +your server-side broadcasting driver. + +When installing Laravel Reverb via the `install:broadcasting` Artisan command, +Reverb and Echo's scaffolding and configuration will be injected into your +application automatically. However, if you wish to manually configure Laravel +Echo, you may do so by following the instructions below. + +#### Manual Installation + +To manually configure Laravel Echo for your application's frontend, first +install the `pusher-js` package since Reverb utilizes the Pusher protocol for +WebSocket subscriptions, channels, and messages: + + + + 1npm install --save-dev laravel-echo pusher-js + + + npm install --save-dev laravel-echo pusher-js + +Once Echo is installed, you are ready to create a fresh Echo instance in your +application's JavaScript. A great place to do this is at the bottom of the +`resources/js/bootstrap.js` file that is included with the Laravel framework: + +JavaScript React Vue + + + + 1import Echo from 'laravel-echo'; + + 2  + + 3import Pusher from 'pusher-js'; + + 4window.Pusher = Pusher; + + 5  + + 6window.Echo = new Echo({ + + 7 broadcaster: 'reverb', + + 8 key: import.meta.env.VITE_REVERB_APP_KEY, + + 9 wsHost: import.meta.env.VITE_REVERB_HOST, + + 10 wsPort: import.meta.env.VITE_REVERB_PORT ?? 80, + + 11 wssPort: import.meta.env.VITE_REVERB_PORT ?? 443, + + 12 forceTLS: (import.meta.env.VITE_REVERB_SCHEME ?? 'https') === 'https', + + 13 enabledTransports: ['ws', 'wss'], + + 14}); + + + import Echo from 'laravel-echo'; + + import Pusher from 'pusher-js'; + window.Pusher = Pusher; + + window.Echo = new Echo({ + broadcaster: 'reverb', + key: import.meta.env.VITE_REVERB_APP_KEY, + wsHost: import.meta.env.VITE_REVERB_HOST, + wsPort: import.meta.env.VITE_REVERB_PORT ?? 80, + wssPort: import.meta.env.VITE_REVERB_PORT ?? 443, + forceTLS: (import.meta.env.VITE_REVERB_SCHEME ?? 'https') === 'https', + enabledTransports: ['ws', 'wss'], + }); + + + 1import { configureEcho } from "@laravel/echo-react"; + + 2  + + 3configureEcho({ + + 4 broadcaster: "reverb", + + 5 // key: import.meta.env.VITE_REVERB_APP_KEY, + + 6 // wsHost: import.meta.env.VITE_REVERB_HOST, + + 7 // wsPort: import.meta.env.VITE_REVERB_PORT, + + 8 // wssPort: import.meta.env.VITE_REVERB_PORT, + + 9 // forceTLS: (import.meta.env.VITE_REVERB_SCHEME ?? 'https') === 'https', + + 10 // enabledTransports: ['ws', 'wss'], + + 11}); + + + import { configureEcho } from "@laravel/echo-react"; + + configureEcho({ + broadcaster: "reverb", + // key: import.meta.env.VITE_REVERB_APP_KEY, + // wsHost: import.meta.env.VITE_REVERB_HOST, + // wsPort: import.meta.env.VITE_REVERB_PORT, + // wssPort: import.meta.env.VITE_REVERB_PORT, + // forceTLS: (import.meta.env.VITE_REVERB_SCHEME ?? 'https') === 'https', + // enabledTransports: ['ws', 'wss'], + }); + + + 1import { configureEcho } from "@laravel/echo-vue"; + + 2  + + 3configureEcho({ + + 4 broadcaster: "reverb", + + 5 // key: import.meta.env.VITE_REVERB_APP_KEY, + + 6 // wsHost: import.meta.env.VITE_REVERB_HOST, + + 7 // wsPort: import.meta.env.VITE_REVERB_PORT, + + 8 // wssPort: import.meta.env.VITE_REVERB_PORT, + + 9 // forceTLS: (import.meta.env.VITE_REVERB_SCHEME ?? 'https') === 'https', + + 10 // enabledTransports: ['ws', 'wss'], + + 11}); + + + import { configureEcho } from "@laravel/echo-vue"; + + configureEcho({ + broadcaster: "reverb", + // key: import.meta.env.VITE_REVERB_APP_KEY, + // wsHost: import.meta.env.VITE_REVERB_HOST, + // wsPort: import.meta.env.VITE_REVERB_PORT, + // wssPort: import.meta.env.VITE_REVERB_PORT, + // forceTLS: (import.meta.env.VITE_REVERB_SCHEME ?? 'https') === 'https', + // enabledTransports: ['ws', 'wss'], + }); + +Next, you should compile your application's assets: + + + + 1npm run build + + + npm run build + +The Laravel Echo `reverb` broadcaster requires laravel-echo v1.16.0+. + +### Pusher Channels + +[Laravel Echo](https://github.com/laravel/echo) is a JavaScript library that +makes it painless to subscribe to channels and listen for events broadcast by +your server-side broadcasting driver. + +When installing broadcasting support via the `install:broadcasting --pusher` +Artisan command, Pusher and Echo's scaffolding and configuration will be +injected into your application automatically. However, if you wish to manually +configure Laravel Echo, you may do so by following the instructions below. + +#### Manual Installation + +To manually configure Laravel Echo for your application's frontend, first +install the `laravel-echo` and `pusher-js` packages which utilize the Pusher +protocol for WebSocket subscriptions, channels, and messages: + + + + 1npm install --save-dev laravel-echo pusher-js + + + npm install --save-dev laravel-echo pusher-js + +Once Echo is installed, you are ready to create a fresh Echo instance in your +application's `resources/js/bootstrap.js` file: + +JavaScript React Vue + + + + 1import Echo from 'laravel-echo'; + + 2  + + 3import Pusher from 'pusher-js'; + + 4window.Pusher = Pusher; + + 5  + + 6window.Echo = new Echo({ + + 7 broadcaster: 'pusher', + + 8 key: import.meta.env.VITE_PUSHER_APP_KEY, + + 9 cluster: import.meta.env.VITE_PUSHER_APP_CLUSTER, + + 10 forceTLS: true + + 11}); + + + import Echo from 'laravel-echo'; + + import Pusher from 'pusher-js'; + window.Pusher = Pusher; + + window.Echo = new Echo({ + broadcaster: 'pusher', + key: import.meta.env.VITE_PUSHER_APP_KEY, + cluster: import.meta.env.VITE_PUSHER_APP_CLUSTER, + forceTLS: true + }); + + + 1import { configureEcho } from "@laravel/echo-react"; + + 2  + + 3configureEcho({ + + 4 broadcaster: "pusher", + + 5 // key: import.meta.env.VITE_PUSHER_APP_KEY, + + 6 // cluster: import.meta.env.VITE_PUSHER_APP_CLUSTER, + + 7 // forceTLS: true, + + 8 // wsHost: import.meta.env.VITE_PUSHER_HOST, + + 9 // wsPort: import.meta.env.VITE_PUSHER_PORT, + + 10 // wssPort: import.meta.env.VITE_PUSHER_PORT, + + 11 // enabledTransports: ["ws", "wss"], + + 12}); + + + import { configureEcho } from "@laravel/echo-react"; + + configureEcho({ + broadcaster: "pusher", + // key: import.meta.env.VITE_PUSHER_APP_KEY, + // cluster: import.meta.env.VITE_PUSHER_APP_CLUSTER, + // forceTLS: true, + // wsHost: import.meta.env.VITE_PUSHER_HOST, + // wsPort: import.meta.env.VITE_PUSHER_PORT, + // wssPort: import.meta.env.VITE_PUSHER_PORT, + // enabledTransports: ["ws", "wss"], + }); + + + 1import { configureEcho } from "@laravel/echo-vue"; + + 2  + + 3configureEcho({ + + 4 broadcaster: "pusher", + + 5 // key: import.meta.env.VITE_PUSHER_APP_KEY, + + 6 // cluster: import.meta.env.VITE_PUSHER_APP_CLUSTER, + + 7 // forceTLS: true, + + 8 // wsHost: import.meta.env.VITE_PUSHER_HOST, + + 9 // wsPort: import.meta.env.VITE_PUSHER_PORT, + + 10 // wssPort: import.meta.env.VITE_PUSHER_PORT, + + 11 // enabledTransports: ["ws", "wss"], + + 12}); + + + import { configureEcho } from "@laravel/echo-vue"; + + configureEcho({ + broadcaster: "pusher", + // key: import.meta.env.VITE_PUSHER_APP_KEY, + // cluster: import.meta.env.VITE_PUSHER_APP_CLUSTER, + // forceTLS: true, + // wsHost: import.meta.env.VITE_PUSHER_HOST, + // wsPort: import.meta.env.VITE_PUSHER_PORT, + // wssPort: import.meta.env.VITE_PUSHER_PORT, + // enabledTransports: ["ws", "wss"], + }); + +Next, you should define the appropriate values for the Pusher environment +variables in your application's `.env` file. If these variables do not already +exist in your `.env` file, you should add them: + + + + 1PUSHER_APP_ID="your-pusher-app-id" + + 2PUSHER_APP_KEY="your-pusher-key" + + 3PUSHER_APP_SECRET="your-pusher-secret" + + 4PUSHER_HOST= + + 5PUSHER_PORT=443 + + 6PUSHER_SCHEME="https" + + 7PUSHER_APP_CLUSTER="mt1" + + 8  + + 9VITE_APP_NAME="${APP_NAME}" + + 10VITE_PUSHER_APP_KEY="${PUSHER_APP_KEY}" + + 11VITE_PUSHER_HOST="${PUSHER_HOST}" + + 12VITE_PUSHER_PORT="${PUSHER_PORT}" + + 13VITE_PUSHER_SCHEME="${PUSHER_SCHEME}" + + 14VITE_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}" + + + PUSHER_APP_ID="your-pusher-app-id" + PUSHER_APP_KEY="your-pusher-key" + PUSHER_APP_SECRET="your-pusher-secret" + PUSHER_HOST= + PUSHER_PORT=443 + PUSHER_SCHEME="https" + PUSHER_APP_CLUSTER="mt1" + + VITE_APP_NAME="${APP_NAME}" + VITE_PUSHER_APP_KEY="${PUSHER_APP_KEY}" + VITE_PUSHER_HOST="${PUSHER_HOST}" + VITE_PUSHER_PORT="${PUSHER_PORT}" + VITE_PUSHER_SCHEME="${PUSHER_SCHEME}" + VITE_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}" + +Once you have adjusted the Echo configuration according to your application's +needs, you may compile your application's assets: + + + + 1npm run build + + + npm run build + +To learn more about compiling your application's JavaScript assets, please +consult the documentation on [Vite](/docs/12.x/vite). + +#### Using an Existing Client Instance + +If you already have a pre-configured Pusher Channels client instance that you +would like Echo to utilize, you may pass it to Echo via the `client` +configuration option: + + + + 1import Echo from 'laravel-echo'; + + 2import Pusher from 'pusher-js'; + + 3  + + 4const options = { + + 5 broadcaster: 'pusher', + + 6 key: import.meta.env.VITE_PUSHER_APP_KEY + + 7} + + 8  + + 9window.Echo = new Echo({ + + 10 ...options, + + 11 client: new Pusher(options.key, options) + + 12}); + + + import Echo from 'laravel-echo'; + import Pusher from 'pusher-js'; + + const options = { + broadcaster: 'pusher', + key: import.meta.env.VITE_PUSHER_APP_KEY + } + + window.Echo = new Echo({ + ...options, + client: new Pusher(options.key, options) + }); + +### Ably + +The documentation below discusses how to use Ably in "Pusher compatibility" +mode. However, the Ably team recommends and maintains a broadcaster and Echo +client that is able to take advantage of the unique capabilities offered by +Ably. For more information on using the Ably maintained drivers, please +[consult Ably's Laravel broadcaster +documentation](https://github.com/ably/laravel-broadcaster). + +[Laravel Echo](https://github.com/laravel/echo) is a JavaScript library that +makes it painless to subscribe to channels and listen for events broadcast by +your server-side broadcasting driver. + +When installing broadcasting support via the `install:broadcasting --ably` +Artisan command, Ably and Echo's scaffolding and configuration will be +injected into your application automatically. However, if you wish to manually +configure Laravel Echo, you may do so by following the instructions below. + +#### Manual Installation + +To manually configure Laravel Echo for your application's frontend, first +install the `laravel-echo` and `pusher-js` packages which utilize the Pusher +protocol for WebSocket subscriptions, channels, and messages: + + + + 1npm install --save-dev laravel-echo pusher-js + + + npm install --save-dev laravel-echo pusher-js + +**Before continuing, you should enable Pusher protocol support in your Ably +application settings. You may enable this feature within the "Protocol Adapter +Settings" portion of your Ably application's settings dashboard.** + +Once Echo is installed, you are ready to create a fresh Echo instance in your +application's `resources/js/bootstrap.js` file: + +JavaScript React Vue + + + + 1import Echo from 'laravel-echo'; + + 2  + + 3import Pusher from 'pusher-js'; + + 4window.Pusher = Pusher; + + 5  + + 6window.Echo = new Echo({ + + 7 broadcaster: 'pusher', + + 8 key: import.meta.env.VITE_ABLY_PUBLIC_KEY, + + 9 wsHost: 'realtime-pusher.ably.io', + + 10 wsPort: 443, + + 11 disableStats: true, + + 12 encrypted: true, + + 13}); + + + import Echo from 'laravel-echo'; + + import Pusher from 'pusher-js'; + window.Pusher = Pusher; + + window.Echo = new Echo({ + broadcaster: 'pusher', + key: import.meta.env.VITE_ABLY_PUBLIC_KEY, + wsHost: 'realtime-pusher.ably.io', + wsPort: 443, + disableStats: true, + encrypted: true, + }); + + + 1import { configureEcho } from "@laravel/echo-react"; + + 2  + + 3configureEcho({ + + 4 broadcaster: "ably", + + 5 // key: import.meta.env.VITE_ABLY_PUBLIC_KEY, + + 6 // wsHost: "realtime-pusher.ably.io", + + 7 // wsPort: 443, + + 8 // disableStats: true, + + 9 // encrypted: true, + + 10}); + + + import { configureEcho } from "@laravel/echo-react"; + + configureEcho({ + broadcaster: "ably", + // key: import.meta.env.VITE_ABLY_PUBLIC_KEY, + // wsHost: "realtime-pusher.ably.io", + // wsPort: 443, + // disableStats: true, + // encrypted: true, + }); + + + 1import { configureEcho } from "@laravel/echo-vue"; + + 2  + + 3configureEcho({ + + 4 broadcaster: "ably", + + 5 // key: import.meta.env.VITE_ABLY_PUBLIC_KEY, + + 6 // wsHost: "realtime-pusher.ably.io", + + 7 // wsPort: 443, + + 8 // disableStats: true, + + 9 // encrypted: true, + + 10}); + + + import { configureEcho } from "@laravel/echo-vue"; + + configureEcho({ + broadcaster: "ably", + // key: import.meta.env.VITE_ABLY_PUBLIC_KEY, + // wsHost: "realtime-pusher.ably.io", + // wsPort: 443, + // disableStats: true, + // encrypted: true, + }); + +You may have noticed our Ably Echo configuration references a +`VITE_ABLY_PUBLIC_KEY` environment variable. This variable's value should be +your Ably public key. Your public key is the portion of your Ably key that +occurs before the `:` character. + +Once you have adjusted the Echo configuration according to your needs, you may +compile your application's assets: + + + + 1npm run dev + + + npm run dev + +To learn more about compiling your application's JavaScript assets, please +consult the documentation on [Vite](/docs/12.x/vite). + +## Concept Overview + +Laravel's event broadcasting allows you to broadcast your server-side Laravel +events to your client-side JavaScript application using a driver-based +approach to WebSockets. Currently, Laravel ships with [Laravel +Reverb](https://reverb.laravel.com), [Pusher +Channels](https://pusher.com/channels), and [Ably](https://ably.com) drivers. +The events may be easily consumed on the client-side using the Laravel Echo +JavaScript package. + +Events are broadcast over "channels", which may be specified as public or +private. Any visitor to your application may subscribe to a public channel +without any authentication or authorization; however, in order to subscribe to +a private channel, a user must be authenticated and authorized to listen on +that channel. + +### Using an Example Application + +Before diving into each component of event broadcasting, let's take a high +level overview using an e-commerce store as an example. + +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: + + + + 1use App\Events\OrderShipmentStatusUpdated; + + 2  + + 3OrderShipmentStatusUpdated::dispatch($order); + + + use App\Events\OrderShipmentStatusUpdated; + + 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: + + + + 1order->id); + + 10} + + + use Illuminate\Broadcasting\Channel; + use Illuminate\Broadcasting\PrivateChannel; + + /** + * 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: + + + + 1use Illuminate\Broadcasting\PrivateChannel; + + 2  + + 3/** + + 4 * Get the channels the event should broadcast on. + + 5 * + + 6 * @return array + + 7 */ + + 8public function broadcastOn(): array + + 9{ + + 10 return [ + + 11 new PrivateChannel('orders.'.$this->order->id), + + 12 // ... + + 13 ]; + + 14} + + + 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), + // ... + ]; + } + +#### 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: + + + + 1use App\Models\Order; + + 2use App\Models\User; + + 3  + + 4Broadcast::channel('orders.{orderId}', function (User $user, int $orderId) { + + 5 return $user->id === Order::findOrNew($orderId)->user_id; + + 6}); + + + 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; + }); + +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. + +All authorization callbacks receive the currently authenticated user as their +first argument and any additional wildcard parameters as their subsequent +arguments. In this example, we are using the `{orderId}` placeholder to +indicate that the "ID" portion of the channel name is a wildcard. + +#### Listening for Event Broadcasts + +Next, all that remains is to listen for the event in our JavaScript +application. We can do this using Laravel Echo. Laravel Echo's built-in React +and Vue hooks make it simple to get started, and, by default, all of the +event's public properties will be included on the broadcast event: + +React Vue + + + + 1import { useEcho } from "@laravel/echo-react"; + + 2  + + 3useEcho( + + 4 `orders.${orderId}`, + + 5 "OrderShipmentStatusUpdated", + + 6 (e) => { + + 7 console.log(e.order); + + 8 }, + + 9); + + + import { useEcho } from "@laravel/echo-react"; + + useEcho( + `orders.${orderId}`, + "OrderShipmentStatusUpdated", + (e) => { + console.log(e.order); + }, + ); + + + 1 + + + + +## Defining Broadcast Events + +To inform Laravel that a given event should be broadcast, you must implement +the `Illuminate\Contracts\Broadcasting\ShouldBroadcast` interface on the event +class. This interface is already imported into all event classes generated by +the framework so you may easily add it to any of your events. + +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: + + + + 1 + + 28 */ + + 29 public function broadcastOn(): array + + 30 { + + 31 return [ + + 32 new PrivateChannel('user.'.$this->user->id), + + 33 ]; + + 34 } + + 35} + + + + */ + public function broadcastOn(): array + { + return [ + new PrivateChannel('user.'.$this->user->id), + ]; + } + } + +After implementing the `ShouldBroadcast` interface, you only need to [fire the +event](/docs/12.x/events) as you normally would. Once the event has been +fired, a [queued job](/docs/12.x/queues) will automatically broadcast the +event using your specified broadcast driver. + +### Broadcast Name + +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: + + + + 1/** + + 2 * The event's broadcast name. + + 3 */ + + 4public function broadcastAs(): string + + 5{ + + 6 return 'server.created'; + + 7} + + + /** + * 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: + + + + 1.listen('.server.created', function (e) { + + 2 // ... + + 3}); + + + .listen('.server.created', function (e) { + // ... + }); + +### Broadcast Data + +When an event is broadcast, all of its `public` properties are automatically +serialized and broadcast as the event's payload, allowing you to access any of +its public data from your JavaScript application. So, for example, if your +event has a single public `$user` property that contains an Eloquent model, +the event's broadcast payload would be: + + + + 1{ + + 2 "user": { + + 3 "id": 1, + + 4 "name": "Patrick Stewart" + + 5 ... + + 6 } + + 7} + + + { + "user": { + "id": 1, + "name": "Patrick Stewart" + ... + } + } + +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: + + + + 1/** + + 2 * Get the data to broadcast. + + 3 * + + 4 * @return array + + 5 */ + + 6public function broadcastWith(): array + + 7{ + + 8 return ['id' => $this->user->id]; + + 9} + + + /** + * 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: + + + + 1/** + + 2 * The name of the queue connection to use when broadcasting the event. + + 3 * + + 4 * @var string + + 5 */ + + 6public $connection = 'redis'; + + 7  + + 8/** + + 9 * The name of the queue on which to place the broadcasting job. + + 10 * + + 11 * @var string + + 12 */ + + 13public $queue = 'default'; + + + /** + * 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'; + +Alternatively, you may customize the queue name by defining a `broadcastQueue` +method on your event: + + + + 1/** + + 2 * The name of the queue on which to place the broadcasting job. + + 3 */ + + 4public function broadcastQueue(): string + + 5{ + + 6 return 'default'; + + 7} + + + /** + * 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`: + + + + 1order->value > 100; + + 7} + + + /** + * Determine if this event should broadcast. + */ + public function broadcastWhen(): bool + { + return $this->order->value > 100; + } + +#### Broadcasting and Database Transactions + +When broadcast events are dispatched within database transactions, they may be +processed by the queue before the database transaction has committed. When +this happens, any updates you have made to models or database records during +the database transaction may not yet be reflected in the database. In +addition, any models or database records created within the transaction may +not exist in the database. If your event depends on these models, unexpected +errors can occur when the job that broadcasts the event is processed. + +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: + + + + 1id === Order::findOrNew($orderId)->user_id; + + 5}); + + + use App\Models\User; + + 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. + +All authorization callbacks receive the currently authenticated user as their +first argument and any additional wildcard parameters as their subsequent +arguments. In this example, we are using the `{orderId}` placeholder to +indicate that the "ID" portion of the channel name is a wildcard. + +You may view a list of your application's broadcast authorization callbacks +using the `channel:list` Artisan command: + + + + 1php artisan channel:list + + + php artisan channel:list + +#### Authorization Callback Model Binding + +Just like HTTP routes, channel routes may also take advantage of implicit and +explicit [route model binding](/docs/12.x/routing#route-model-binding). For +example, instead of receiving a string or numeric order ID, you may request an +actual `Order` model instance: + + + + 1use App\Models\Order; + + 2use App\Models\User; + + 3  + + 4Broadcast::channel('orders.{order}', function (User $user, Order $order) { + + 5 return $user->id === $order->user_id; + + 6}); + + + use App\Models\Order; + use App\Models\User; + + Broadcast::channel('orders.{order}', function (User $user, Order $order) { + return $user->id === $order->user_id; + }); + +Unlike HTTP route model binding, channel model binding does not support +automatic [implicit model binding scoping](/docs/12.x/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. + +#### Authorization Callback Authentication + +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: + + + + 1Broadcast::channel('channel', function () { + + 2 // ... + + 3}, ['guards' => ['web', 'admin']]); + + + Broadcast::channel('channel', function () { + // ... + }, ['guards' => ['web', 'admin']]); + +### Defining Channel Classes + +If your application is consuming many different channels, your +`routes/channels.php` file could become bulky. So, instead of using closures +to authorize channels, you may use channel classes. To generate a channel +class, use the `make:channel` Artisan command. This command will place a new +channel class in the `App/Broadcasting` directory. + + + + 1php artisan make:channel OrderChannel + + + php artisan make:channel OrderChannel + +Next, register your channel in your `routes/channels.php` file: + + + + 1use App\Broadcasting\OrderChannel; + + 2  + + 3Broadcast::channel('orders.{order}', OrderChannel::class); + + + use App\Broadcasting\OrderChannel; + + 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: + + + + 1id === $order->user_id; + + 21 } + + 22} + + + id === $order->user_id; + } + } + +Like many other classes in Laravel, channel classes will automatically be +resolved by the [service container](/docs/12.x/container). So, you may type- +hint any dependencies required by your channel in its constructor. + +## Broadcasting Events + +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: + + + + 1use App\Events\OrderShipmentStatusUpdated; + + 2  + + 3OrderShipmentStatusUpdated::dispatch($order); + + + use App\Events\OrderShipmentStatusUpdated; + + 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: + + + + 1use App\Events\OrderShipmentStatusUpdated; + + 2  + + 3broadcast(new OrderShipmentStatusUpdated($update))->toOthers(); + + + use App\Events\OrderShipmentStatusUpdated; + + 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: + + + + 1axios.post('/task', task) + + 2 .then((response) => { + + 3 this.tasks.push(response.data); + + 4 }); + + + axios.post('/task', task) + .then((response) => { + this.tasks.push(response.data); + }); + +However, remember that we also broadcast the task's creation. If your +JavaScript application is also listening for this event in order to add tasks +to the task list, you will have duplicate tasks in your list: one from the +end-point and one from the broadcast. You may solve this by using the +`toOthers` method to instruct the broadcaster to not broadcast the event to +the current user. + +Your event must use the `Illuminate\Broadcasting\InteractsWithSockets` trait +in order to call the `toOthers` method. + +#### Configuration + +When you initialize a Laravel Echo instance, a socket ID is assigned to the +connection. If you are using a global [Axios](https://github.com/axios/axios) +instance to make HTTP requests from your JavaScript application, the socket ID +will automatically be attached to every outgoing request as an `X-Socket-ID` +header. Then, when you call the `toOthers` method, Laravel will extract the +socket ID from the header and instruct the broadcaster to not broadcast to any +connections with that socket ID. + +If you are not using a global Axios instance, you will need to manually +configure your JavaScript application to send the `X-Socket-ID` header with +all outgoing requests. You may retrieve the socket ID using the +`Echo.socketId` method: + + + + 1var socketId = Echo.socketId(); + + + var socketId = Echo.socketId(); + +### Customizing the Connection + +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: + + + + 1use App\Events\OrderShipmentStatusUpdated; + + 2  + + 3broadcast(new OrderShipmentStatusUpdated($update))->via('pusher'); + + + use App\Events\OrderShipmentStatusUpdated; + + 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: + + + + 1broadcastVia('pusher'); + + 23 } + + 24} + + + broadcastVia('pusher'); + } + } + +### Anonymous Events + +Sometimes, you may want to broadcast a simple event to your application's +frontend without creating a dedicated event class. To accommodate this, the +`Broadcast` facade allows you to broadcast "anonymous events": + + + + 1Broadcast::on('orders.'.$order->id)->send(); + + + Broadcast::on('orders.'.$order->id)->send(); + +The example above will broadcast the following event: + + + + 1{ + + 2 "event": "AnonymousEvent", + + 3 "data": "[]", + + 4 "channel": "orders.1" + + 5} + + + { + "event": "AnonymousEvent", + "data": "[]", + "channel": "orders.1" + } + +Using the `as` and `with` methods, you may customize the event's name and +data: + + + + 1Broadcast::on('orders.'.$order->id) + + 2 ->as('OrderPlaced') + + 3 ->with($order) + + 4 ->send(); + + + Broadcast::on('orders.'.$order->id) + ->as('OrderPlaced') + ->with($order) + ->send(); + +The example above will broadcast an event like the following: + + + + 1{ + + 2 "event": "OrderPlaced", + + 3 "data": "{ id: 1, total: 100 }", + + 4 "channel": "orders.1" + + 5} + + + { + "event": "OrderPlaced", + "data": "{ id: 1, total: 100 }", + "channel": "orders.1" + } + +If you would like to broadcast the anonymous event on a private or presence +channel, you may utilize the `private` and `presence` methods: + + + + 1Broadcast::private('orders.'.$order->id)->send(); + + 2Broadcast::presence('channels.'.$channel->id)->send(); + + + Broadcast::private('orders.'.$order->id)->send(); + Broadcast::presence('channels.'.$channel->id)->send(); + +Broadcasting an anonymous event using the `send` method dispatches the event +to your application's [queue](/docs/12.x/queues) for processing. However, if +you would like to broadcast the event immediately, you may use the `sendNow` +method: + + + + 1Broadcast::on('orders.'.$order->id)->sendNow(); + + + Broadcast::on('orders.'.$order->id)->sendNow(); + +To broadcast the event to all channel subscribers except the currently +authenticated user, you can invoke the `toOthers` method: + + + + 1Broadcast::on('orders.'.$order->id) + + 2 ->toOthers() + + 3 ->send(); + + + Broadcast::on('orders.'.$order->id) + ->toOthers() + ->send(); + +### Rescuing Broadcasts + +When your application's queue server is unavailable or Laravel encounters an +error while broadcasting an event, an exception is thrown that typically +causes the end user to see an application error. Since event broadcasting is +often supplementary to your application's core functionality, you can prevent +these exceptions from disrupting the user experience by implementing the +`ShouldRescue` interface on your events. + +Events that implement the `ShouldRescue` interface automatically utilize +Laravel's [rescue helper function](/docs/12.x/helpers#method-rescue) during +broadcast attempts. This helper catches any exceptions, reports them to your +application's exception handler for logging, and allows the application to +continue executing normally without interrupting the user's workflow: + + + + 1 { + + 3 console.log(e.order.name); + + 4 }); + + + Echo.channel(`orders.${this.order.id}`) + .listen('OrderShipmentStatusUpdated', (e) => { + console.log(e.order.name); + }); + +If you would like to listen for events on a private channel, use the `private` +method instead. You may continue to chain calls to the `listen` method to +listen for multiple events on a single channel: + + + + 1Echo.private(`orders.${this.order.id}`) + + 2 .listen(/* ... */) + + 3 .listen(/* ... */) + + 4 .listen(/* ... */); + + + Echo.private(`orders.${this.order.id}`) + .listen(/* ... */) + .listen(/* ... */) + .listen(/* ... */); + +#### Stop Listening for Events + +If you would like to stop listening to a given event without leaving the +channel, you may use the `stopListening` method: + + + + 1Echo.private(`orders.${this.order.id}`) + + 2 .stopListening('OrderShipmentStatusUpdated'); + + + Echo.private(`orders.${this.order.id}`) + .stopListening('OrderShipmentStatusUpdated'); + +### Leaving a Channel + +To leave a channel, you may call the `leaveChannel` method on your Echo +instance: + + + + 1Echo.leaveChannel(`orders.${this.order.id}`); + + + Echo.leaveChannel(`orders.${this.order.id}`); + +If you would like to leave a channel and also its associated private and +presence channels, you may call the `leave` method: + + + + 1Echo.leave(`orders.${this.order.id}`); + + + Echo.leave(`orders.${this.order.id}`); + +### Namespaces + +You may have noticed in the examples above that we did not specify the full +`App\Events` namespace for the event classes. This is because Echo will +automatically assume the events are located in the `App\Events` namespace. +However, you may configure the root namespace when you instantiate Echo by +passing a `namespace` configuration option: + + + + 1window.Echo = new Echo({ + + 2 broadcaster: 'pusher', + + 3 // ... + + 4 namespace: 'App.Other.Namespace' + + 5}); + + + window.Echo = new Echo({ + broadcaster: 'pusher', + // ... + namespace: 'App.Other.Namespace' + }); + +Alternatively, you may prefix event classes with a `.` when subscribing to +them using Echo. This will allow you to always specify the fully-qualified +class name: + + + + 1Echo.channel('orders') + + 2 .listen('.Namespace\\Event\\Class', (e) => { + + 3 // ... + + 4 }); + + + Echo.channel('orders') + .listen('.Namespace\\Event\\Class', (e) => { + // ... + }); + +### Using React or Vue + +Laravel Echo includes React and Vue hooks that make it painless to listen for +events. To get started, invoke the `useEcho` hook, which is used to listen for +private events. The `useEcho` hook will automatically leave channels when the +consuming component is unmounted: + +React Vue + + + + 1import { useEcho } from "@laravel/echo-react"; + + 2  + + 3useEcho( + + 4 `orders.${orderId}`, + + 5 "OrderShipmentStatusUpdated", + + 6 (e) => { + + 7 console.log(e.order); + + 8 }, + + 9); + + + import { useEcho } from "@laravel/echo-react"; + + useEcho( + `orders.${orderId}`, + "OrderShipmentStatusUpdated", + (e) => { + console.log(e.order); + }, + ); + + + 1 + + + + +You may listen to multiple events by providing an array of events to +`useEcho`: + + + + 1useEcho( + + 2 `orders.${orderId}`, + + 3 ["OrderShipmentStatusUpdated", "OrderShipped"], + + 4 (e) => { + + 5 console.log(e.order); + + 6 }, + + 7); + + + useEcho( + `orders.${orderId}`, + ["OrderShipmentStatusUpdated", "OrderShipped"], + (e) => { + console.log(e.order); + }, + ); + +You may also specify the shape of the broadcast event payload data, providing +greater type safety and editing convenience: + + + + 1type OrderData = { + + 2 order: { + + 3 id: number; + + 4 user: { + + 5 id: number; + + 6 name: string; + + 7 }; + + 8 created_at: string; + + 9 }; + + 10}; + + 11  + + 12useEcho(`orders.${orderId}`, "OrderShipmentStatusUpdated", (e) => { + + 13 console.log(e.order.id); + + 14 console.log(e.order.user.id); + + 15}); + + + type OrderData = { + order: { + id: number; + user: { + id: number; + name: string; + }; + created_at: string; + }; + }; + + useEcho(`orders.${orderId}`, "OrderShipmentStatusUpdated", (e) => { + console.log(e.order.id); + console.log(e.order.user.id); + }); + +The `useEcho` hook will automatically leave channels when the consuming +component is unmounted; however, you may utilize the returned functions to +manually stop / start listening to channels programmatically when necessary: + +React Vue + + + + 1import { useEcho } from "@laravel/echo-react"; + + 2  + + 3const { leaveChannel, leave, stopListening, listen } = useEcho( + + 4 `orders.${orderId}`, + + 5 "OrderShipmentStatusUpdated", + + 6 (e) => { + + 7 console.log(e.order); + + 8 }, + + 9); + + 10  + + 11// Stop listening without leaving channel... + + 12stopListening(); + + 13  + + 14// Start listening again... + + 15listen(); + + 16  + + 17// Leave channel... + + 18leaveChannel(); + + 19  + + 20// Leave a channel and also its associated private and presence channels... + + 21leave(); + + + import { useEcho } from "@laravel/echo-react"; + + const { leaveChannel, leave, stopListening, listen } = useEcho( + `orders.${orderId}`, + "OrderShipmentStatusUpdated", + (e) => { + console.log(e.order); + }, + ); + + // Stop listening without leaving channel... + stopListening(); + + // Start listening again... + listen(); + + // Leave channel... + leaveChannel(); + + // Leave a channel and also its associated private and presence channels... + leave(); + + + 1 + + + + +#### Connecting to Public Channels + +To connect to a public channel, you may use the `useEchoPublic` hook: + +React Vue + + + + 1import { useEchoPublic } from "@laravel/echo-react"; + + 2  + + 3useEchoPublic("posts", "PostPublished", (e) => { + + 4 console.log(e.post); + + 5}); + + + import { useEchoPublic } from "@laravel/echo-react"; + + useEchoPublic("posts", "PostPublished", (e) => { + console.log(e.post); + }); + + + 1 + + + + +#### Connecting to Presence Channels + +To connect to a presence channel, you may use the `useEchoPresence` hook: + +React Vue + + + + 1import { useEchoPresence } from "@laravel/echo-react"; + + 2  + + 3useEchoPresence("posts", "PostPublished", (e) => { + + 4 console.log(e.post); + + 5}); + + + import { useEchoPresence } from "@laravel/echo-react"; + + useEchoPresence("posts", "PostPublished", (e) => { + console.log(e.post); + }); + + + 1 + + + + +## Presence Channels + +Presence channels build on the security of private channels while exposing the +additional feature of awareness of who is subscribed to the channel. This +makes it easy to build powerful, collaborative application features such as +notifying users when another user is viewing the same page or listing the +inhabitants of a chat room. + +### Authorizing Presence Channels + +All presence channels are also private channels; therefore, users must be +authorized to access them. However, when defining authorization callbacks for +presence channels, you will not return `true` if the user is authorized to +join the channel. Instead, you should return an array of data about the user. + +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`: + + + + 1use App\Models\User; + + 2  + + 3Broadcast::channel('chat.{roomId}', function (User $user, int $roomId) { + + 4 if ($user->canJoinRoom($roomId)) { + + 5 return ['id' => $user->id, 'name' => $user->name]; + + 6 } + + 7}); + + + use App\Models\User; + + Broadcast::channel('chat.{roomId}', function (User $user, int $roomId) { + if ($user->canJoinRoom($roomId)) { + return ['id' => $user->id, 'name' => $user->name]; + } + }); + +### Joining Presence Channels + +To join a presence channel, you may use Echo's `join` method. The `join` +method will return a `PresenceChannel` implementation which, along with +exposing the `listen` method, allows you to subscribe to the `here`, +`joining`, and `leaving` events. + + + + 1Echo.join(`chat.${roomId}`) + + 2 .here((users) => { + + 3 // ... + + 4 }) + + 5 .joining((user) => { + + 6 console.log(user.name); + + 7 }) + + 8 .leaving((user) => { + + 9 console.log(user.name); + + 10 }) + + 11 .error((error) => { + + 12 console.error(error); + + 13 }); + + + Echo.join(`chat.${roomId}`) + .here((users) => { + // ... + }) + .joining((user) => { + console.log(user.name); + }) + .leaving((user) => { + console.log(user.name); + }) + .error((error) => { + console.error(error); + }); + +The `here` callback will be executed immediately once the channel is joined +successfully, and will receive an array containing the user information for +all of the other users currently subscribed to the channel. The `joining` +method will be executed when a new user joins a channel, while the `leaving` +method will be executed when a user leaves the channel. The `error` method +will be executed when the authentication endpoint returns an HTTP status code +other than 200 or if there is a problem parsing the returned JSON. + +### Broadcasting to Presence Channels + +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: + + + + 1/** + + 2 * Get the channels the event should broadcast on. + + 3 * + + 4 * @return array + + 5 */ + + 6public function broadcastOn(): array + + 7{ + + 8 return [ + + 9 new PresenceChannel('chat.'.$this->message->room_id), + + 10 ]; + + 11} + + + /** + * 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: + + + + 1broadcast(new NewMessage($message)); + + 2  + + 3broadcast(new NewMessage($message))->toOthers(); + + + broadcast(new NewMessage($message)); + + 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: + + + + 1Echo.join(`chat.${roomId}`) + + 2 .here(/* ... */) + + 3 .joining(/* ... */) + + 4 .leaving(/* ... */) + + 5 .listen('NewMessage', (e) => { + + 6 // ... + + 7 }); + + + Echo.join(`chat.${roomId}`) + .here(/* ... */) + .joining(/* ... */) + .leaving(/* ... */) + .listen('NewMessage', (e) => { + // ... + }); + +## Model Broadcasting + +Before reading the following documentation about model broadcasting, we +recommend you become familiar with the general concepts of Laravel's model +broadcasting services as well as how to manually create and listen to +broadcast events. + +It is common to broadcast events when your application's [Eloquent +models](/docs/12.x/eloquent) are created, updated, or deleted. Of course, this +can easily be accomplished by manually [defining custom events for Eloquent +model state changes](/docs/12.x/eloquent#events) and marking those events with +the `ShouldBroadcast` interface. + +However, if you are not using these events for any other purposes in your +application, it can be cumbersome to create event classes for the sole purpose +of broadcasting them. To remedy this, Laravel allows you to indicate that an +Eloquent model should automatically broadcast its state changes. + +To get started, your Eloquent model should use the +`Illuminate\Database\Eloquent\BroadcastsEvents` trait. In addition, the model +should define a `broadcastOn` method, which will return an array of channels +that the model's events should broadcast on: + + + + 1belongsTo(User::class); + + 22 } + + 23  + + 24 /** + + 25 * Get the channels that model events should broadcast on. + + 26 * + + 27 * @return array + + 28 */ + + 29 public function broadcastOn(string $event): array + + 30 { + + 31 return [$this, $this->user]; + + 32 } + + 33} + + + belongsTo(User::class); + } + + /** + * Get the channels that model events should broadcast on. + * + * @return array + */ + public function broadcastOn(string $event): array + { + return [$this, $this->user]; + } + } + +Once your model includes this trait and defines its broadcast channels, it +will begin automatically broadcasting events when a model instance is created, +updated, deleted, trashed, or restored. + +In addition, you may have noticed that the `broadcastOn` method receives a +string `$event` argument. This argument contains the type of event that has +occurred on the model and will have a value of `created`, `updated`, +`deleted`, `trashed`, or `restored`. By inspecting the value of this variable, +you may determine which channels (if any) the model should broadcast to for a +particular event: + + + + 1/** + + 2 * Get the channels that model events should broadcast on. + + 3 * + + 4 * @return array> + + 5 */ + + 6public function broadcastOn(string $event): array + + 7{ + + 8 return match ($event) { + + 9 'deleted' => [], + + 10 default => [$this, $this->user], + + 11 }; + + 12} + + + /** + * Get the channels that model events should broadcast on. + * + * @return array> + */ + public function broadcastOn(string $event): array + { + return match ($event) { + 'deleted' => [], + default => [$this, $this->user], + }; + } + +#### Customizing Model Broadcasting Event Creation + +Occasionally, you may wish to customize how Laravel creates the underlying +model broadcasting event. You may accomplish this by defining a +`newBroadcastableEvent` method on your Eloquent model. This method should +return an `Illuminate\Database\Eloquent\BroadcastableModelEventOccurred` +instance: + + + + 1use Illuminate\Database\Eloquent\BroadcastableModelEventOccurred; + + 2  + + 3/** + + 4 * Create a new broadcastable model event for the model. + + 5 */ + + 6protected function newBroadcastableEvent(string $event): BroadcastableModelEventOccurred + + 7{ + + 8 return (new BroadcastableModelEventOccurred( + + 9 $this, $event + + 10 ))->dontBroadcastToCurrentUser(); + + 11} + + + use Illuminate\Database\Eloquent\BroadcastableModelEventOccurred; + + /** + * Create a new broadcastable model event for the model. + */ + protected function newBroadcastableEvent(string $event): BroadcastableModelEventOccurred + { + return (new BroadcastableModelEventOccurred( + $this, $event + ))->dontBroadcastToCurrentUser(); + } + +### Model Broadcasting Conventions + +#### Channel Conventions + +As you may have noticed, the `broadcastOn` method in the model example above +did not return `Channel` instances. Instead, Eloquent models were returned +directly. If an Eloquent model instance is returned by your model's +`broadcastOn` method (or is contained in an array returned by the method), +Laravel will automatically instantiate a private channel instance for the +model using the model's class name and primary key identifier as the channel +name. + +So, an `App\Models\User` model with an `id` of `1` would be converted into an +`Illuminate\Broadcasting\PrivateChannel` instance with a name of +`App.Models.User.1`. Of course, in addition to returning Eloquent model +instances from your model's `broadcastOn` method, you may return complete +`Channel` instances in order to have full control over the model's channel +names: + + + + 1use Illuminate\Broadcasting\PrivateChannel; + + 2  + + 3/** + + 4 * Get the channels that model events should broadcast on. + + 5 * + + 6 * @return array + + 7 */ + + 8public function broadcastOn(string $event): array + + 9{ + + 10 return [ + + 11 new PrivateChannel('user.'.$this->id) + + 12 ]; + + 13} + + + use Illuminate\Broadcasting\PrivateChannel; + + /** + * Get the channels that model events should broadcast on. + * + * @return array + */ + public function broadcastOn(string $event): array + { + return [ + new PrivateChannel('user.'.$this->id) + ]; + } + +If you plan to explicitly return a channel instance from your model's +`broadcastOn` method, you may pass an Eloquent model instance to the channel's +constructor. When doing so, Laravel will use the model channel conventions +discussed above to convert the Eloquent model into a channel name string: + + + + 1return [new Channel($this->user)]; + + + return [new Channel($this->user)]; + +If you need to determine the channel name of a model, you may call the +`broadcastChannel` method on any model instance. For example, this method +returns the string `App.Models.User.1` for an `App\Models\User` model with an +`id` of `1`: + + + + 1$user->broadcastChannel(); + + + $user->broadcastChannel(); + +#### Event Conventions + +Since model broadcast events are not associated with an "actual" event within +your application's `App\Events` directory, they are assigned a name and a +payload based on conventions. Laravel's convention is to broadcast the event +using the class name of the model (not including the namespace) and the name +of the model event that triggered the broadcast. + +So, for example, an update to the `App\Models\Post` model would broadcast an +event to your client-side application as `PostUpdated` with the following +payload: + + + + 1{ + + 2 "model": { + + 3 "id": 1, + + 4 "title": "My first post" + + 5 ... + + 6 }, + + 7 ... + + 8 "socket": "someSocketId" + + 9} + + + { + "model": { + "id": 1, + "title": "My first post" + ... + }, + ... + "socket": "someSocketId" + } + +The deletion of the `App\Models\User` model would broadcast an event named +`UserDeleted`. + +If you would like, you may define a custom broadcast name and payload by +adding a `broadcastAs` and `broadcastWith` method to your model. These methods +receive the name of the model event / operation that is occurring, allowing +you to customize the event's name and payload for each model operation. If +`null` is returned from the `broadcastAs` method, Laravel will use the model +broadcasting event name conventions discussed above when broadcasting the +event: + + + + 1/** + + 2 * The model event's broadcast name. + + 3 */ + + 4public function broadcastAs(string $event): string|null + + 5{ + + 6 return match ($event) { + + 7 'created' => 'post.created', + + 8 default => null, + + 9 }; + + 10} + + 11  + + 12/** + + 13 * Get the data to broadcast for the model. + + 14 * + + 15 * @return array + + 16 */ + + 17public function broadcastWith(string $event): array + + 18{ + + 19 return match ($event) { + + 20 'created' => ['title' => $this->title], + + 21 default => ['model' => $this], + + 22 }; + + 23} + + + /** + * The model event's broadcast name. + */ + public function broadcastAs(string $event): string|null + { + return match ($event) { + 'created' => 'post.created', + default => null, + }; + } + + /** + * Get the data to broadcast for the model. + * + * @return array + */ + public function broadcastWith(string $event): array + { + return match ($event) { + 'created' => ['title' => $this->title], + default => ['model' => $this], + }; + } + +### Listening for Model Broadcasts + +Once you have added the `BroadcastsEvents` trait to your model and defined +your model's `broadcastOn` method, you are ready to start listening for +broadcasted model events within your client-side application. Before getting +started, you may wish to consult the complete documentation on listening for +events. + +First, use the `private` method to retrieve an instance of a channel, then +call the `listen` method to listen for a specified event. Typically, the +channel name given to the `private` method should correspond to Laravel's +model broadcasting conventions. + +Once you have obtained a channel instance, you may use the `listen` method to +listen for a particular event. Since model broadcast events are not associated +with an "actual" event within your application's `App\Events` directory, the +event name must be prefixed with a `.` to indicate it does not belong to a +particular namespace. Each model broadcast event has a `model` property which +contains all of the broadcastable properties of the model: + + + + 1Echo.private(`App.Models.User.${this.user.id}`) + + 2 .listen('.UserUpdated', (e) => { + + 3 console.log(e.model); + + 4 }); + + + Echo.private(`App.Models.User.${this.user.id}`) + .listen('.UserUpdated', (e) => { + console.log(e.model); + }); + +#### Using React or Vue + +If you are using React or Vue, you may use Laravel Echo's included +`useEchoModel` hook to easily listen for model broadcasts: + +React Vue + + + + 1import { useEchoModel } from "@laravel/echo-react"; + + 2  + + 3useEchoModel("App.Models.User", userId, ["UserUpdated"], (e) => { + + 4 console.log(e.model); + + 5}); + + + import { useEchoModel } from "@laravel/echo-react"; + + useEchoModel("App.Models.User", userId, ["UserUpdated"], (e) => { + console.log(e.model); + }); + + + 1 + + + + +You may also specify the shape of the model event payload data, providing +greater type safety and editing convenience: + + + + 1type User = { + + 2 id: number; + + 3 name: string; + + 4 email: string; + + 5}; + + 6  + + 7useEchoModel("App.Models.User", userId, ["UserUpdated"], (e) => { + + 8 console.log(e.model.id); + + 9 console.log(e.model.name); + + 10}); + + + type User = { + id: number; + name: string; + email: string; + }; + + useEchoModel("App.Models.User", userId, ["UserUpdated"], (e) => { + console.log(e.model.id); + console.log(e.model.name); + }); + +## Client Events + +When using [Pusher Channels](https://pusher.com/channels), you must enable the +"Client Events" option in the "App Settings" section of your [application +dashboard](https://dashboard.pusher.com/) in order to send client events. + +Sometimes you may wish to broadcast an event to other connected clients +without hitting your Laravel application at all. This can be particularly +useful for things like "typing" notifications, where you want to alert users +of your application that another user is typing a message on a given screen. + +To broadcast client events, you may use Echo's `whisper` method: + +JavaScript React Vue + + + + 1Echo.private(`chat.${roomId}`) + + 2 .whisper('typing', { + + 3 name: this.user.name + + 4 }); + + + Echo.private(`chat.${roomId}`) + .whisper('typing', { + name: this.user.name + }); + + + 1import { useEcho } from "@laravel/echo-react"; + + 2  + + 3const { channel } = useEcho(`chat.${roomId}`, ['update'], (e) => { + + 4 console.log('Chat event received:', e); + + 5}); + + 6  + + 7channel().whisper('typing', { name: user.name }); + + + import { useEcho } from "@laravel/echo-react"; + + const { channel } = useEcho(`chat.${roomId}`, ['update'], (e) => { + console.log('Chat event received:', e); + }); + + channel().whisper('typing', { name: user.name }); + + + 1 + + + + +To listen for client events, you may use the `listenForWhisper` method: + +JavaScript React Vue + + + + 1Echo.private(`chat.${roomId}`) + + 2 .listenForWhisper('typing', (e) => { + + 3 console.log(e.name); + + 4 }); + + + Echo.private(`chat.${roomId}`) + .listenForWhisper('typing', (e) => { + console.log(e.name); + }); + + + 1import { useEcho } from "@laravel/echo-react"; + + 2  + + 3const { channel } = useEcho(`chat.${roomId}`, ['update'], (e) => { + + 4 console.log('Chat event received:', e); + + 5}); + + 6  + + 7channel().listenForWhisper('typing', (e) => { + + 8 console.log(e.name); + + 9}); + + + import { useEcho } from "@laravel/echo-react"; + + const { channel } = useEcho(`chat.${roomId}`, ['update'], (e) => { + console.log('Chat event received:', e); + }); + + channel().listenForWhisper('typing', (e) => { + console.log(e.name); + }); + + + 1 + + + + +## Notifications + +By pairing event broadcasting with [notifications](/docs/12.x/notifications), +your JavaScript application may receive new notifications as they occur +without needing to refresh the page. Before getting started, be sure to read +over the documentation on using [the broadcast notification +channel](/docs/12.x/notifications#broadcast-notifications). + +Once you have configured a notification to use the broadcast channel, you may +listen for the broadcast events using Echo's `notification` method. Remember, +the channel name should match the class name of the entity receiving the +notifications: + +JavaScript React Vue + + + + 1Echo.private(`App.Models.User.${userId}`) + + 2 .notification((notification) => { + + 3 console.log(notification.type); + + 4 }); + + + Echo.private(`App.Models.User.${userId}`) + .notification((notification) => { + console.log(notification.type); + }); + + + 1import { useEchoModel } from "@laravel/echo-react"; + + 2  + + 3const { channel } = useEchoModel('App.Models.User', userId); + + 4  + + 5channel().notification((notification) => { + + 6 console.log(notification.type); + + 7}); + + + import { useEchoModel } from "@laravel/echo-react"; + + const { channel } = useEchoModel('App.Models.User', userId); + + channel().notification((notification) => { + console.log(notification.type); + }); + + + 1 + + + + +In this example, all notifications sent to `App\Models\User` instances via the +`broadcast` channel would be received by the callback. A channel authorization +callback for the `App.Models.User.{id}` channel is included in your +application's `routes/channels.php` file. + diff --git a/output/12.x/cache.md b/output/12.x/cache.md new file mode 100644 index 0000000..c98c303 --- /dev/null +++ b/output/12.x/cache.md @@ -0,0 +1,1217 @@ +# Cache + + * Introduction + * Configuration + * Driver Prerequisites + * Cache Usage + * Obtaining a Cache Instance + * Retrieving Items From the Cache + * Storing Items in the Cache + * Removing Items From the Cache + * Cache Memoization + * The Cache Helper + * Atomic Locks + * Managing Locks + * Managing Locks Across Processes + * Adding Custom Cache Drivers + * Writing the Driver + * Registering the Driver + * Events + +## Introduction + +Some of the data retrieval or processing tasks performed by your application +could be CPU intensive or take several seconds to complete. When this is the +case, it is common to cache the retrieved data for a time so it can be +retrieved quickly on subsequent requests for the same data. The cached data is +usually stored in a very fast data store such as +[Memcached](https://memcached.org) or [Redis](https://redis.io). + +Thankfully, Laravel provides an expressive, unified API for various cache +backends, allowing you to take advantage of their blazing fast data retrieval +and speed up your web application. + +## Configuration + +Your application's cache configuration file is located at `config/cache.php`. +In this file, you may specify which cache store you would like to be used by +default throughout your application. Laravel supports popular caching backends +like [Memcached](https://memcached.org), [Redis](https://redis.io), +[DynamoDB](https://aws.amazon.com/dynamodb), and relational databases out of +the box. In addition, a file based cache driver is available, while `array` +and `null` cache drivers provide convenient cache backends for your automated +tests. + +The cache configuration file also contains a variety of other options that you +may review. By default, Laravel is configured to use the `database` cache +driver, which stores the serialized, cached objects in your application's +database. + +### Driver Prerequisites + +#### Database + +When using the `database` cache driver, you will need a database table to +contain the cache data. Typically, this is included in Laravel's default +`0001_01_01_000001_create_cache_table.php` [database +migration](/docs/12.x/migrations); however, if your application does not +contain this migration, you may use the `make:cache-table` Artisan command to +create it: + + + + 1php artisan make:cache-table + + 2  + + 3php artisan migrate + + + php artisan make:cache-table + + php artisan migrate + +#### Memcached + +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: + + + + 1'memcached' => [ + + 2 // ... + + 3  + + 4 'servers' => [ + + 5 [ + + 6 'host' => env('MEMCACHED_HOST', '127.0.0.1'), + + 7 'port' => env('MEMCACHED_PORT', 11211), + + 8 'weight' => 100, + + 9 ], + + 10 ], + + 11], + + + 'memcached' => [ + // ... + + '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`: + + + + 1'memcached' => [ + + 2 // ... + + 3  + + 4 'servers' => [ + + 5 [ + + 6 'host' => '/var/run/memcached/memcached.sock', + + 7 'port' => 0, + + 8 'weight' => 100 + + 9 ], + + 10 ], + + 11], + + + 'memcached' => [ + // ... + + 'servers' => [ + [ + 'host' => '/var/run/memcached/memcached.sock', + 'port' => 0, + 'weight' => 100 + ], + ], + ], + +#### Redis + +Before using a Redis cache with Laravel, you will need to either install the +PhpRedis PHP extension via PECL or install the `predis/predis` package (~2.0) +via Composer. [Laravel Sail](/docs/12.x/sail) already includes this extension. +In addition, official Laravel application platforms such as [Laravel +Cloud](https://cloud.laravel.com) and [Laravel +Forge](https://forge.laravel.com) have the PhpRedis extension installed by +default. + +For more information on configuring Redis, consult its [Laravel documentation +page](/docs/12.x/redis#configuration). + +#### DynamoDB + +Before using the [DynamoDB](https://aws.amazon.com/dynamodb) cache driver, you +must create a DynamoDB table to store all of the cached data. Typically, this +table should be named `cache`. However, you should name the table based on the +value of the `stores.dynamodb.table` configuration value within the `cache` +configuration file. The table name may also be set via the +`DYNAMODB_CACHE_TABLE` environment variable. + +This table should also have a string partition key with a name that +corresponds to the value of the `stores.dynamodb.attributes.key` configuration +item within your application's `cache` configuration file. By default, the +partition key should be named `key`. + +Typically, DynamoDB will not proactively remove expired items from a table. +Therefore, you should [enable Time to Live +(TTL)](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/TTL.html) +on the table. When configuring the table's TTL settings, you should set the +TTL attribute name to `expires_at`. + +Next, install the AWS SDK so that your Laravel application can communicate +with DynamoDB: + + + + 1composer require aws/aws-sdk-php + + + composer require aws/aws-sdk-php + +In addition, you should ensure that values are provided for the DynamoDB cache +store configuration options. Typically these options, such as +`AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY`, should be defined in your +application's `.env` configuration file: + + + + 1'dynamodb' => [ + + 2 'driver' => 'dynamodb', + + 3 'key' => env('AWS_ACCESS_KEY_ID'), + + 4 'secret' => env('AWS_SECRET_ACCESS_KEY'), + + 5 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'), + + 6 'table' => env('DYNAMODB_CACHE_TABLE', 'cache'), + + 7 'endpoint' => env('DYNAMODB_ENDPOINT'), + + 8], + + + 'dynamodb' => [ + 'driver' => 'dynamodb', + 'key' => env('AWS_ACCESS_KEY_ID'), + 'secret' => env('AWS_SECRET_ACCESS_KEY'), + 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'), + 'table' => env('DYNAMODB_CACHE_TABLE', 'cache'), + 'endpoint' => env('DYNAMODB_ENDPOINT'), + ], + +#### MongoDB + +If you are using MongoDB, a `mongodb` cache driver is provided by the official +`mongodb/laravel-mongodb` package and can be configured using a `mongodb` +database connection. MongoDB supports TTL indexes, which can be used to +automatically clear expired cache items. + +For more information on configuring MongoDB, please refer to the MongoDB +[Cache and Locks +documentation](https://www.mongodb.com/docs/drivers/php/laravel- +mongodb/current/cache/). + +## Cache Usage + +### Obtaining a Cache Instance + +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: + + + + 1get('foo'); + + 2  + + 3Cache::store('redis')->put('bar', 'baz', 600); // 10 Minutes + + + $value = Cache::store('file')->get('foo'); + + 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: + + + + 1$value = Cache::get('key'); + + 2  + + 3$value = Cache::get('key', 'default'); + + + $value = Cache::get('key'); + + $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: + + + + 1$value = Cache::get('key', function () { + + 2 return DB::table(/* ... */)->get(); + + 3}); + + + $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`: + + + + 1if (Cache::has('key')) { + + 2 // ... + + 3} + + + 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: + + + + 1// Initialize the value if it does not exist... + + 2Cache::add('key', 0, now()->addHours(4)); + + 3  + + 4// Increment or decrement the value... + + 5Cache::increment('key'); + + 6Cache::increment('key', $amount); + + 7Cache::decrement('key'); + + 8Cache::decrement('key', $amount); + + + // 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: + + + + 1$value = Cache::remember('users', $seconds, function () { + + 2 return DB::table('users')->get(); + + 3}); + + + $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: + + + + 1$value = Cache::rememberForever('users', function () { + + 2 return DB::table('users')->get(); + + 3}); + + + $value = Cache::rememberForever('users', function () { + return DB::table('users')->get(); + }); + +#### Stale While Revalidate + +When using the `Cache::remember` method, some users may experience slow +response times if the cached value has expired. For certain types of data, it +can be useful to allow partially stale data to be served while the cached +value is recalculated in the background, preventing some users from +experiencing slow response times while cached values are calculated. This is +often referred to as the "stale-while-revalidate" pattern, and the +`Cache::flexible` method provides an implementation of this pattern. + +The flexible method accepts an array that specifies how long the cached value +is considered “fresh” and when it becomes “stale.” The first value in the +array represents the number of seconds the cache is considered fresh, while +the second value defines how long it can be served as stale data before +recalculation is necessary. + +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/12.x/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: + + + + 1$value = Cache::flexible('users', [5, 10], function () { + + 2 return DB::table('users')->get(); + + 3}); + + + $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: + + + + 1$value = Cache::pull('key'); + + 2  + + 3$value = Cache::pull('key', 'default'); + + + $value = Cache::pull('key'); + + $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: + + + + 1Cache::put('key', 'value', $seconds = 10); + + + Cache::put('key', 'value', $seconds = 10); + +If the storage time is not passed to the `put` method, the item will be stored +indefinitely: + + + + 1Cache::put('key', 'value'); + + + 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: + + + + 1Cache::put('key', 'value', now()->addMinutes(10)); + + + 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: + + + + 1Cache::add('key', 'value', $seconds); + + + 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: + + + + 1Cache::forever('key', 'value'); + + + Cache::forever('key', 'value'); + +If you are using the Memcached driver, items that are stored "forever" may be +removed when the cache reaches its size limit. + +### Removing Items From the Cache + +You may remove items from the cache using the `forget` method: + + + + 1Cache::forget('key'); + + + Cache::forget('key'); + +You may also remove items by providing a zero or negative number of expiration +seconds: + + + + 1Cache::put('key', 'value', 0); + + 2  + + 3Cache::put('key', 'value', -5); + + + Cache::put('key', 'value', 0); + + Cache::put('key', 'value', -5); + +You may clear the entire cache using the `flush` method: + + + + 1Cache::flush(); + + + Cache::flush(); + +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. + +### Cache Memoization + +Laravel's `memo` cache driver allows you to temporarily store resolved cache +values in memory during a single request or job execution. This prevents +repeated cache hits within the same execution, significantly improving +performance. + +To use the memoized cache, invoke the `memo` method: + + + + 1use Illuminate\Support\Facades\Cache; + + 2  + + 3$value = Cache::memo()->get('key'); + + + use Illuminate\Support\Facades\Cache; + + $value = Cache::memo()->get('key'); + +The `memo` method optionally accepts the name of a cache store, which +specifies the underlying cache store the memoized driver will decorate: + + + + 1// Using the default cache store... + + 2$value = Cache::memo()->get('key'); + + 3  + + 4// Using the Redis cache store... + + 5$value = Cache::memo('redis')->get('key'); + + + // Using the default cache store... + $value = Cache::memo()->get('key'); + + // Using the Redis cache store... + $value = Cache::memo('redis')->get('key'); + +The first `get` call for a given key retrieves the value from your cache +store, but subsequent calls within the same request or job will retrieve the +value from memory: + + + + 1// Hits the cache... + + 2$value = Cache::memo()->get('key'); + + 3  + + 4// Does not hit the cache, returns memoized value... + + 5$value = Cache::memo()->get('key'); + + + // Hits the cache... + $value = Cache::memo()->get('key'); + + // Does not hit the cache, returns memoized value... + $value = Cache::memo()->get('key'); + +When calling methods that modify cache values (such as `put`, `increment`, +`remember`, etc.), the memoized cache automatically forgets the memoized value +and delegates the mutating method call to the underlying cache store: + + + + 1Cache::memo()->put('name', 'Taylor'); // Writes to underlying cache... + + 2Cache::memo()->get('name'); // Hits underlying cache... + + 3Cache::memo()->get('name'); // Memoized, does not hit cache... + + 4  + + 5Cache::memo()->put('name', 'Tim'); // Forgets memoized value, writes new value... + + 6Cache::memo()->get('name'); // Hits underlying cache again... + + + Cache::memo()->put('name', 'Taylor'); // Writes to underlying cache... + Cache::memo()->get('name'); // Hits underlying cache... + Cache::memo()->get('name'); // Memoized, does not hit cache... + + Cache::memo()->put('name', 'Tim'); // Forgets memoized value, writes new value... + Cache::memo()->get('name'); // Hits underlying cache again... + +### The Cache Helper + +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: + + + + 1$value = cache('key'); + + + $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: + + + + 1cache(['key' => 'value'], $seconds); + + 2  + + 3cache(['key' => 'value'], now()->addMinutes(10)); + + + cache(['key' => 'value'], $seconds); + + 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: + + + + 1cache()->remember('users', $seconds, function () { + + 2 return DB::table('users')->get(); + + 3}); + + + cache()->remember('users', $seconds, function () { + return DB::table('users')->get(); + }); + +When testing calls to the global `cache` function, you may use the +`Cache::shouldReceive` method just as if you were [testing the +facade](/docs/12.x/mocking#mocking-facades). + +## Atomic Locks + +To utilize this feature, your application must be using the `memcached`, +`redis`, `dynamodb`, `database`, `file`, or `array` cache driver as your +application's default cache driver. In addition, all servers must be +communicating with the same central cache server. + +### Managing Locks + +Atomic locks allow for the manipulation of distributed locks without worrying +about race conditions. For example, [Laravel Cloud](https://cloud.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: + + + + 1use Illuminate\Support\Facades\Cache; + + 2  + + 3$lock = Cache::lock('foo', 10); + + 4  + + 5if ($lock->get()) { + + 6 // Lock acquired for 10 seconds... + + 7  + + 8 $lock->release(); + + 9} + + + use Illuminate\Support\Facades\Cache; + + $lock = Cache::lock('foo', 10); + + if ($lock->get()) { + // Lock acquired for 10 seconds... + + $lock->release(); + } + +The `get` method also accepts a closure. After the closure is executed, +Laravel will automatically release the lock: + + + + 1Cache::lock('foo', 10)->get(function () { + + 2 // Lock acquired for 10 seconds and automatically released... + + 3}); + + + 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: + + + + 1use Illuminate\Contracts\Cache\LockTimeoutException; + + 2  + + 3$lock = Cache::lock('foo', 10); + + 4  + + 5try { + + 6 $lock->block(5); + + 7  + + 8 // Lock acquired after waiting a maximum of 5 seconds... + + 9} catch (LockTimeoutException $e) { + + 10 // Unable to acquire lock... + + 11} finally { + + 12 $lock->release(); + + 13} + + + use Illuminate\Contracts\Cache\LockTimeoutException; + + $lock = Cache::lock('foo', 10); + + try { + $lock->block(5); + + // 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: + + + + 1Cache::lock('foo', 10)->block(5, function () { + + 2 // Lock acquired for 10 seconds after waiting a maximum of 5 seconds... + + 3}); + + + Cache::lock('foo', 10)->block(5, function () { + // Lock acquired for 10 seconds after waiting a maximum of 5 seconds... + }); + +### Managing Locks Across Processes + +Sometimes, you may wish to acquire a lock in one process and release it in +another process. For example, you may acquire a lock during a web request and +wish to release the lock at the end of a queued job that is triggered by that +request. In this scenario, you should pass the lock's scoped "owner token" to +the queued job so that the job can re-instantiate the lock using the given +token. + +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: + + + + 1$podcast = Podcast::find($id); + + 2  + + 3$lock = Cache::lock('processing', 120); + + 4  + + 5if ($lock->get()) { + + 6 ProcessPodcast::dispatch($podcast, $lock->owner()); + + 7} + + + $podcast = Podcast::find($id); + + $lock = Cache::lock('processing', 120); + + 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: + + + + 1Cache::restoreLock('processing', $this->owner)->release(); + + + 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: + + + + 1Cache::lock('processing')->forceRelease(); + + + Cache::lock('processing')->forceRelease(); + +## Adding Custom Cache Drivers + +### Writing the Driver + +To create our custom cache driver, we first need to implement the +`Illuminate\Contracts\Cache\Store` [contract](/docs/12.x/contracts). So, a +MongoDB cache implementation might look something like this: + + + + 1app->booting(function () { + + 18 Cache::extend('mongo', function (Application $app) { + + 19 return Cache::repository(new MongoStore); + + 20 }); + + 21 }); + + 22 } + + 23  + + 24 /** + + 25 * Bootstrap any application services. + + 26 */ + + 27 public function boot(): void + + 28 { + + 29 // ... + + 30 } + + 31} + + + app->booting(function () { + Cache::extend('mongo', function (Application $app) { + return Cache::repository(new MongoStore); + }); + }); + } + + /** + * 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/12.x/container). + +Once your extension is registered, update the `CACHE_STORE` environment +variable or `default` option within your application's `config/cache.php` +configuration file to the name of your extension. + +## Events + +To execute code on every cache operation, you may listen for various +[events](/docs/12.x/events) dispatched by the cache: + +Event Name +--- +`Illuminate\Cache\Events\CacheFlushed` +`Illuminate\Cache\Events\CacheFlushing` +`Illuminate\Cache\Events\CacheHit` +`Illuminate\Cache\Events\CacheMissed` +`Illuminate\Cache\Events\ForgettingKey` +`Illuminate\Cache\Events\KeyForgetFailed` +`Illuminate\Cache\Events\KeyForgotten` +`Illuminate\Cache\Events\KeyWriteFailed` +`Illuminate\Cache\Events\KeyWritten` +`Illuminate\Cache\Events\RetrievingKey` +`Illuminate\Cache\Events\RetrievingManyKeys` +`Illuminate\Cache\Events\WritingKey` +`Illuminate\Cache\Events\WritingManyKeys` + +To increase performance, you may disable cache events by setting the `events` +configuration option to `false` for a given cache store in your application's +`config/cache.php` configuration file: + + + + 1'database' => [ + + 2 'driver' => 'database', + + 3 // ... + + 4 'events' => false, + + 5], + + + 'database' => [ + 'driver' => 'database', + // ... + 'events' => false, + ], + diff --git a/output/12.x/cashier-paddle.md b/output/12.x/cashier-paddle.md new file mode 100644 index 0000000..0aeb70b --- /dev/null +++ b/output/12.x/cashier-paddle.md @@ -0,0 +1,3244 @@ +# Laravel Cashier (Paddle) + + * Introduction + * Upgrading Cashier + * Installation + * Paddle Sandbox + * Configuration + * Billable Model + * API Keys + * Paddle JS + * Currency Configuration + * Overriding Default Models + * Quickstart + * Selling Products + * Selling Subscriptions + * Checkout Sessions + * Overlay Checkout + * Inline Checkout + * Guest Checkouts + * Price Previews + * Customer Price Previews + * Discounts + * Customers + * Customer Defaults + * Retrieving Customers + * Creating Customers + * Subscriptions + * Creating Subscriptions + * Checking Subscription Status + * Subscription Single Charges + * Updating Payment Information + * Changing Plans + * Subscription Quantity + * Subscriptions With Multiple Products + * Multiple Subscriptions + * Pausing Subscriptions + * Canceling Subscriptions + * Subscription Trials + * With Payment Method Up Front + * Without Payment Method Up Front + * Extend or Activate a Trial + * Handling Paddle Webhooks + * Defining Webhook Event Handlers + * Verifying Webhook Signatures + * Single Charges + * Charging for Products + * Refunding Transactions + * Crediting Transactions + * Transactions + * Past and Upcoming Payments + * Testing + +## Introduction + +This documentation is for Cashier Paddle 2.x's integration with Paddle +Billing. If you're still using Paddle Classic, you should use [Cashier Paddle +1.x](https://github.com/laravel/cashier-paddle/tree/1.x). + +[Laravel Cashier Paddle](https://github.com/laravel/cashier-paddle) provides +an expressive, fluent interface to [Paddle's](https://paddle.com) subscription +billing services. It handles almost all of the boilerplate subscription +billing code you are dreading. In addition to basic subscription management, +Cashier can handle: swapping subscriptions, subscription "quantities", +subscription pausing, cancelation grace periods, and more. + +Before digging into Cashier Paddle, we recommend you also review Paddle's +[concept guides](https://developer.paddle.com/concepts/overview) and [API +documentation](https://developer.paddle.com/api-reference/overview). + +## Upgrading Cashier + +When upgrading to a new version of Cashier, it's important that you carefully +review [the upgrade guide](https://github.com/laravel/cashier- +paddle/blob/master/UPGRADE.md). + +## Installation + +First, install the Cashier package for Paddle using the Composer package +manager: + + + + 1composer require laravel/cashier-paddle + + + composer require laravel/cashier-paddle + +Next, you should publish the Cashier migration files using the +`vendor:publish` Artisan command: + + + + 1php artisan vendor:publish --tag="cashier-migrations" + + + php artisan vendor:publish --tag="cashier-migrations" + +Then, you should run your application's database migrations. The Cashier +migrations will create a new `customers` table. In addition, new +`subscriptions` and `subscription_items` tables will be created to store all +of your customer's subscriptions. Lastly, a new `transactions` table will be +created to store all of the Paddle transactions associated with your +customers: + + + + 1php artisan migrate + + + php artisan migrate + +To ensure Cashier properly handles all Paddle events, remember to set up +Cashier's webhook handling. + +### Paddle Sandbox + +During local and staging development, you should [register a Paddle Sandbox +account](https://sandbox-login.paddle.com/signup). This account will give you +a sandboxed environment to test and develop your applications without making +actual payments. You may use Paddle's [test card +numbers](https://developer.paddle.com/concepts/payment-methods/credit-debit- +card#test-payment-method) to simulate various payment scenarios. + +When using the Paddle Sandbox environment, you should set the `PADDLE_SANDBOX` +environment variable to `true` within your application's `.env` file: + + + + 1PADDLE_SANDBOX=true + + + PADDLE_SANDBOX=true + +After you have finished developing your application you may [apply for a +Paddle vendor account](https://paddle.com). Before your application is placed +into production, Paddle will need to approve your application's domain. + +## Configuration + +### Billable Model + +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: + + + + 1use Laravel\Paddle\Billable; + + 2  + + 3class User extends Authenticatable + + 4{ + + 5 use Billable; + + 6} + + + use Laravel\Paddle\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: + + + + 1use Illuminate\Database\Eloquent\Model; + + 2use Laravel\Paddle\Billable; + + 3  + + 4class Team extends Model + + 5{ + + 6 use Billable; + + 7} + + + use Illuminate\Database\Eloquent\Model; + use Laravel\Paddle\Billable; + + class Team extends Model + { + use Billable; + } + +### API Keys + +Next, you should configure your Paddle keys in your application's `.env` file. +You can retrieve your Paddle API keys from the Paddle control panel: + + + + 1PADDLE_CLIENT_SIDE_TOKEN=your-paddle-client-side-token + + 2PADDLE_API_KEY=your-paddle-api-key + + 3PADDLE_RETAIN_KEY=your-paddle-retain-key + + 4PADDLE_WEBHOOK_SECRET="your-paddle-webhook-secret" + + 5PADDLE_SANDBOX=true + + + PADDLE_CLIENT_SIDE_TOKEN=your-paddle-client-side-token + PADDLE_API_KEY=your-paddle-api-key + PADDLE_RETAIN_KEY=your-paddle-retain-key + PADDLE_WEBHOOK_SECRET="your-paddle-webhook-secret" + PADDLE_SANDBOX=true + +The `PADDLE_SANDBOX` environment variable should be set to `true` when you are +using Paddle's Sandbox environment. The `PADDLE_SANDBOX` variable should be +set to `false` if you are deploying your application to production and are +using Paddle's live vendor environment. + +The `PADDLE_RETAIN_KEY` is optional and should only be set if you're using +Paddle with [Retain](https://developer.paddle.com/concepts/retain/overview). + +### Paddle JS + +Paddle relies on its own JavaScript library to initiate the Paddle checkout +widget. You can load the JavaScript library by placing the `@paddleJS` Blade +directive right before your application layout's closing `` tag: + + + + 1 + + 2 ... + + 3  + + 4 @paddleJS + + 5 + + + + ... + + @paddleJS + + +### Currency Configuration + +You can specify a locale to be used when formatting money values for display +on invoices. Internally, Cashier utilizes [PHP's `NumberFormatter` +class](https://www.php.net/manual/en/class.numberformatter.php) to set the +currency locale: + + + + 1CASHIER_CURRENCY_LOCALE=nl_BE + + + CASHIER_CURRENCY_LOCALE=nl_BE + +In order to use locales other than `en`, ensure the `ext-intl` PHP extension +is installed and configured on your server. + +### Overriding Default Models + +You are free to extend the models used internally by Cashier by defining your +own model and extending the corresponding Cashier model: + + + + 1use Laravel\Paddle\Subscription as CashierSubscription; + + 2  + + 3class Subscription extends CashierSubscription + + 4{ + + 5 // ... + + 6} + + + use Laravel\Paddle\Subscription as 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: + + + + 1use App\Models\Cashier\Subscription; + + 2use App\Models\Cashier\Transaction; + + 3  + + 4/** + + 5 * Bootstrap any application services. + + 6 */ + + 7public function boot(): void + + 8{ + + 9 Cashier::useSubscriptionModel(Subscription::class); + + 10 Cashier::useTransactionModel(Transaction::class); + + 11} + + + 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 + +### Selling Products + +Before utilizing Paddle Checkout, you should define Products with fixed prices +in your Paddle dashboard. In addition, you should configure Paddle's webhook +handling. + +Offering product and subscription billing via your application can be +intimidating. However, thanks to Cashier and [Paddle's Checkout +Overlay](https://developer.paddle.com/concepts/sell/overlay-checkout), you can +easily build modern, robust payment integrations. + +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: + + + + 1use Illuminate\Http\Request; + + 2  + + 3Route::get('/buy', function (Request $request) { + + 4 $checkout = $request->user()->checkout('pri_deluxe_album') + + 5 ->returnTo(route('dashboard')); + + 6  + + 7 return view('buy', ['checkout' => $checkout]); + + 8})->name('checkout'); + + + use Illuminate\Http\Request; + + Route::get('/buy', function (Request $request) { + $checkout = $request->user()->checkout('pri_deluxe_album') + ->returnTo(route('dashboard')); + + 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). + +If necessary, the `checkout` method will automatically create a customer in +Paddle and connect that Paddle customer record to the corresponding user in +your application's database. After completing the checkout session, the +customer will be redirected to a dedicated success page where you can display +an informational message to the customer. + +In the `buy` 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: + + + + 1 + + 2 Buy Product + + 3 + + + + Buy Product + + +#### Providing Meta Data to Paddle Checkout + +When selling products, it's common to keep track of completed orders and +purchased products via `Cart` and `Order` models defined by your own +application. When redirecting customers to Paddle's Checkout Overlay to +complete a purchase, you may need to provide an existing order identifier so +that you can associate the completed purchase with the corresponding order +when the customer is redirected back to your application. + +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: + + + + 1use App\Models\Cart; + + 2use App\Models\Order; + + 3use Illuminate\Http\Request; + + 4  + + 5Route::get('/cart/{cart}/checkout', function (Request $request, Cart $cart) { + + 6 $order = Order::create([ + + 7 'cart_id' => $cart->id, + + 8 'price_ids' => $cart->price_ids, + + 9 'status' => 'incomplete', + + 10 ]); + + 11  + + 12 $checkout = $request->user()->checkout($order->price_ids) + + 13 ->customData(['order_id' => $order->id]); + + 14  + + 15 return view('billing', ['checkout' => $checkout]); + + 16})->name('checkout'); + + + 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', + ]); + + $checkout = $request->user()->checkout($order->price_ids) + ->customData(['order_id' => $order->id]); + + 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. + +Of course, you will likely want to mark the order as "complete" once the +customer has finished the checkout process. To accomplish this, you may listen +to the webhooks dispatched by Paddle and raised via events by Cashier to store +order information in your database. + +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`: + + + + 1use App\Listeners\CompleteOrder; + + 2use Illuminate\Support\Facades\Event; + + 3use Laravel\Paddle\Events\TransactionCompleted; + + 4  + + 5/** + + 6 * Bootstrap any application services. + + 7 */ + + 8public function boot(): void + + 9{ + + 10 Event::listen(TransactionCompleted::class, CompleteOrder::class); + + 11} + + + 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: + + + + 1namespace App\Listeners; + + 2  + + 3use App\Models\Order; + + 4use Laravel\Paddle\Cashier; + + 5use Laravel\Paddle\Events\TransactionCompleted; + + 6  + + 7class CompleteOrder + + 8{ + + 9 /** + + 10 * Handle the incoming Cashier webhook event. + + 11 */ + + 12 public function handle(TransactionCompleted $event): void + + 13 { + + 14 $orderId = $event->payload['data']['custom_data']['order_id'] ?? null; + + 15  + + 16 $order = Order::findOrFail($orderId); + + 17  + + 18 $order->update(['status' => 'completed']); + + 19 } + + 20} + + + namespace App\Listeners; + + use App\Models\Order; + use Laravel\Paddle\Cashier; + use Laravel\Paddle\Events\TransactionCompleted; + + class CompleteOrder + { + /** + * Handle the incoming Cashier webhook event. + */ + public function handle(TransactionCompleted $event): void + { + $orderId = $event->payload['data']['custom_data']['order_id'] ?? null; + + $order = Order::findOrFail($orderId); + + $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). + +### Selling Subscriptions + +Before utilizing Paddle Checkout, you should define Products with fixed prices +in your Paddle dashboard. In addition, you should configure Paddle's webhook +handling. + +Offering product and subscription billing via your application can be +intimidating. However, thanks to Cashier and [Paddle's Checkout +Overlay](https://developer.paddle.com/concepts/sell/overlay-checkout), you can +easily build modern, robust payment integrations. + +To learn how to sell subscriptions using Cashier and Paddle's Checkout +Overlay, let's consider the simple scenario of a subscription service with a +basic monthly (`price_basic_monthly`) and yearly (`price_basic_yearly`) plan. +These two prices could be grouped under a "Basic" product (`pro_basic`) in our +Paddle dashboard. In addition, our subscription service might offer an +"Expert" plan as `pro_expert`. + +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: + + + + 1use Illuminate\Http\Request; + + 2  + + 3Route::get('/subscribe', function (Request $request) { + + 4 $checkout = $request->user()->checkout('price_basic_monthly') + + 5 ->returnTo(route('dashboard')); + + 6  + + 7 return view('subscribe', ['checkout' => $checkout]); + + 8})->name('subscribe'); + + + use Illuminate\Http\Request; + + Route::get('/subscribe', function (Request $request) { + $checkout = $request->user()->checkout('price_basic_monthly') + ->returnTo(route('dashboard')); + + 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: + + + + 1 + + 2 Subscribe + + 3 + + + + Subscribe + + +Now, when the Subscribe button is clicked, the customer will be able to enter +their payment details and initiate their subscription. To know when their +subscription has actually started (since some payment methods require a few +seconds to process), you should also configure Cashier's webhook handling. + +Now that customers can start subscriptions, we need to restrict certain +portions of our application so that only subscribed users can access them. Of +course, we can always determine a user's current subscription status via the +`subscribed` method provided by Cashier's `Billable` trait: + + + + 1@if ($user->subscribed()) + + 2

    You are subscribed.

    + + 3@endif + + + @if ($user->subscribed()) +

    You are subscribed.

    + @endif + +We can even easily determine if a user is subscribed to specific product or +price: + + + + 1@if ($user->subscribedToProduct('pro_basic')) + + 2

    You are subscribed to our Basic product.

    + + 3@endif + + 4  + + 5@if ($user->subscribedToPrice('price_basic_monthly')) + + 6

    You are subscribed to our monthly Basic plan.

    + + 7@endif + + + @if ($user->subscribedToProduct('pro_basic')) +

    You are subscribed to our Basic product.

    + @endif + + @if ($user->subscribedToPrice('price_basic_monthly')) +

    You are subscribed to our monthly Basic plan.

    + @endif + +#### Building a Subscribed Middleware + +For convenience, you may wish to create a [middleware](/docs/12.x/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: + + + + 1user()?->subscribed()) { + + 17 // Redirect user to billing page and ask them to subscribe... + + 18 return redirect('/subscribe'); + + 19 } + + 20  + + 21 return $next($request); + + 22 } + + 23} + + + 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: + + + + 1use App\Http\Middleware\Subscribed; + + 2  + + 3Route::get('/dashboard', function () { + + 4 // ... + + 5})->middleware([Subscribed::class]); + + + use App\Http\Middleware\Subscribed; + + 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: + + + + 1use Illuminate\Http\Request; + + 2  + + 3Route::put('/subscription/{price}/swap', function (Request $request, $price) { + + 4 $user->subscription()->swap($price); // With "$price" being "price_basic_yearly" for this example. + + 5  + + 6 return redirect()->route('dashboard'); + + 7})->name('subscription.swap'); + + + 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. + + 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: + + + + 1use Illuminate\Http\Request; + + 2  + + 3Route::put('/subscription/cancel', function (Request $request, $price) { + + 4 $user->subscription()->cancel(); + + 5  + + 6 return redirect()->route('dashboard'); + + 7})->name('subscription.cancel'); + + + use Illuminate\Http\Request; + + Route::put('/subscription/cancel', function (Request $request, $price) { + $user->subscription()->cancel(); + + return redirect()->route('dashboard'); + })->name('subscription.cancel'); + +And now your subscription will get canceled at the end of its billing period. + +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 Paddle. So, for example, when you +cancel a customer's subscription via Paddle's dashboard, Cashier will receive +the corresponding webhook and mark the subscription as "canceled" in your +application's database. + +## Checkout Sessions + +Most operations to bill customers are performed using "checkouts" via Paddle's +[Checkout Overlay widget](https://developer.paddle.com/build/checkout/build- +overlay-checkout) or by utilizing [inline +checkout](https://developer.paddle.com/build/checkout/build-branded-inline- +checkout). + +Before processing checkout payments using Paddle, you should define your +application's [default payment +link](https://developer.paddle.com/build/transactions/default-payment- +link#set-default-link) in your Paddle checkout settings dashboard. + +### Overlay Checkout + +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: + + + + 1use Illuminate\Http\Request; + + 2  + + 3Route::get('/buy', function (Request $request) { + + 4 $checkout = $user->checkout('pri_34567') + + 5 ->returnTo(route('dashboard')); + + 6  + + 7 return view('billing', ['checkout' => $checkout]); + + 8}); + + + use Illuminate\Http\Request; + + Route::get('/buy', function (Request $request) { + $checkout = $user->checkout('pri_34567') + ->returnTo(route('dashboard')); + + return view('billing', ['checkout' => $checkout]); + }); + +Cashier includes a `paddle-button` [Blade +component](/docs/12.x/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: + + + + 1 + + 2 Subscribe + + 3 + + + + Subscribe + + +By default, this will display the widget using Paddle's default styling. You +can customize the widget by adding [Paddle supported +attributes](https://developer.paddle.com/paddlejs/html-data-attributes) like +the `data-theme='light'` attribute to the component: + + + + 1 + + 2 Subscribe + + 3 + + + + Subscribe + + +The Paddle checkout widget is asynchronous. Once the user creates a +subscription within the widget, Paddle will send your application a webhook so +that you may properly update the subscription state in your application's +database. Therefore, it's important that you properly set up webhooks to +accommodate for state changes from Paddle. + +After a subscription state change, the delay for receiving the corresponding +webhook is typically minimal but you should account for this in your +application by considering that your user's subscription might not be +immediately available after completing the checkout. + +#### Manually Rendering an Overlay Checkout + +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: + + + + 1use Illuminate\Http\Request; + + 2  + + 3Route::get('/buy', function (Request $request) { + + 4 $checkout = $user->checkout('pri_34567') + + 5 ->returnTo(route('dashboard')); + + 6  + + 7 return view('billing', ['checkout' => $checkout]); + + 8}); + + + use Illuminate\Http\Request; + + Route::get('/buy', function (Request $request) { + $checkout = $user->checkout('pri_34567') + ->returnTo(route('dashboard')); + + 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: + + + + 1getItems(); + + 3$customer = $checkout->getCustomer(); + + 4$custom = $checkout->getCustomData(); + + 5?> + + 6  + + 7getReturnUrl()) data-success-url='{{ $returnUrl }}' @endif + + 14> + + 15 Buy Product + + 16 + + + getItems(); + $customer = $checkout->getCustomer(); + $custom = $checkout->getCustomData(); + ?> + + getReturnUrl()) data-success-url='{{ $returnUrl }}' @endif + > + Buy Product + + +### Inline Checkout + +If you don't want to make use of Paddle's "overlay" style checkout widget, +Paddle also provides the option to display the widget inline. While this +approach does not allow you to adjust any of the checkout's HTML fields, it +allows you to embed the widget within your application. + +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: + + + + 1use Illuminate\Http\Request; + + 2  + + 3Route::get('/buy', function (Request $request) { + + 4 $checkout = $user->checkout('pri_34567') + + 5 ->returnTo(route('dashboard')); + + 6  + + 7 return view('billing', ['checkout' => $checkout]); + + 8}); + + + use Illuminate\Http\Request; + + Route::get('/buy', function (Request $request) { + $checkout = $user->checkout('pri_34567') + ->returnTo(route('dashboard')); + + return view('billing', ['checkout' => $checkout]); + }); + +Then, you may pass the checkout session to the component's `checkout` +attribute: + + + + 1 + + + + +To adjust the height of the inline checkout component, you may pass the +`height` attribute to the Blade component: + + + + 1 + + + + +Please consult Paddle's [guide on Inline +Checkout](https://developer.paddle.com/build/checkout/build-branded-inline- +checkout) and [available checkout +settings](https://developer.paddle.com/build/checkout/set-up-checkout-default- +settings) for further details on the inline checkout's customization options. + +#### Manually Rendering an Inline Checkout + +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: + + + + 1use Illuminate\Http\Request; + + 2  + + 3Route::get('/buy', function (Request $request) { + + 4 $checkout = $user->checkout('pri_34567') + + 5 ->returnTo(route('dashboard')); + + 6  + + 7 return view('billing', ['checkout' => $checkout]); + + 8}); + + + use Illuminate\Http\Request; + + Route::get('/buy', function (Request $request) { + $checkout = $user->checkout('pri_34567') + ->returnTo(route('dashboard')); + + 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: + + + + 1options(); + + 3  + + 4$options['settings']['frameTarget'] = 'paddle-checkout'; + + 5$options['settings']['frameInitialHeight'] = 366; + + 6?> + + 7  + + 8
    + + 11
    + + + options(); + + $options['settings']['frameTarget'] = 'paddle-checkout'; + $options['settings']['frameInitialHeight'] = 366; + ?> + +
    +
    + +### Guest Checkouts + +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: + + + + 1use Illuminate\Http\Request; + + 2use Laravel\Paddle\Checkout; + + 3  + + 4Route::get('/buy', function (Request $request) { + + 5 $checkout = Checkout::guest(['pri_34567']) + + 6 ->returnTo(route('home')); + + 7  + + 8 return view('billing', ['checkout' => $checkout]); + + 9}); + + + use Illuminate\Http\Request; + use Laravel\Paddle\Checkout; + + Route::get('/buy', function (Request $request) { + $checkout = Checkout::guest(['pri_34567']) + ->returnTo(route('home')); + + return view('billing', ['checkout' => $checkout]); + }); + +Then, you may provide the checkout session to the Paddle button or inline +checkout Blade components. + +## Price Previews + +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: + + + + 1use Laravel\Paddle\Cashier; + + 2  + + 3$prices = Cashier::previewPrices(['pri_123', 'pri_456']); + + + use Laravel\Paddle\Cashier; + + $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: + + + + 1use Laravel\Paddle\Cashier; + + 2  + + 3$prices = Cashier::previewPrices(['pri_123', 'pri_456'], ['address' => [ + + 4 'country_code' => 'BE', + + 5 'postal_code' => '1234', + + 6]]); + + + use Laravel\Paddle\Cashier; + + $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: + + + + 1
      + + 2 @foreach ($prices as $price) + + 3
    • {{ $price->product['name'] }} - {{ $price->total() }}
    • + + 4 @endforeach + + 5
    + + +
      + @foreach ($prices as $price) +
    • {{ $price->product['name'] }} - {{ $price->total() }}
    • + @endforeach +
    + +You may also display the subtotal price and tax amount separately: + + + + 1
      + + 2 @foreach ($prices as $price) + + 3
    • {{ $price->product['name'] }} - {{ $price->subtotal() }} (+ {{ $price->tax() }} tax)
    • + + 4 @endforeach + + 5
    + + +
      + @foreach ($prices as $price) +
    • {{ $price->product['name'] }} - {{ $price->subtotal() }} (+ {{ $price->tax() }} tax)
    • + @endforeach +
    + +For more information, [checkout Paddle's API documentation regarding price +previews](https://developer.paddle.com/api-reference/pricing-preview/preview- +prices). + +### Customer Price Previews + +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: + + + + 1use App\Models\User; + + 2  + + 3$prices = User::find(1)->previewPrices(['pri_123', 'pri_456']); + + + use App\Models\User; + + $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. + +### Discounts + +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: + + + + 1use Laravel\Paddle\Cashier; + + 2  + + 3$prices = Cashier::previewPrices(['pri_123', 'pri_456'], [ + + 4 'discount_id' => 'dsc_123' + + 5]); + + + use Laravel\Paddle\Cashier; + + $prices = Cashier::previewPrices(['pri_123', 'pri_456'], [ + 'discount_id' => 'dsc_123' + ]); + +Then, display the calculated prices: + + + + 1
      + + 2 @foreach ($prices as $price) + + 3
    • {{ $price->product['name'] }} - {{ $price->total() }}
    • + + 4 @endforeach + + 5
    + + +
      + @foreach ($prices as $price) +
    • {{ $price->product['name'] }} - {{ $price->total() }}
    • + @endforeach +
    + +## Customers + +### Customer Defaults + +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: + + + + 1/** + + 2 * Get the customer's name to associate with Paddle. + + 3 */ + + 4public function paddleName(): string|null + + 5{ + + 6 return $this->name; + + 7} + + 8  + + 9/** + + 10 * Get the customer's email address to associate with Paddle. + + 11 */ + + 12public function paddleEmail(): string|null + + 13{ + + 14 return $this->email; + + 15} + + + /** + * 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. + +### Retrieving Customers + +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: + + + + 1use Laravel\Paddle\Cashier; + + 2  + + 3$user = Cashier::findBillable($customerId); + + + use Laravel\Paddle\Cashier; + + $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: + + + + 1$customer = $user->createAsCustomer(); + + + $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): + + + + 1$customer = $user->createAsCustomer($options); + + + $customer = $user->createAsCustomer($options); + +## Subscriptions + +### Creating Subscriptions + +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: + + + + 1use Illuminate\Http\Request; + + 2  + + 3Route::get('/user/subscribe', function (Request $request) { + + 4 $checkout = $request->user()->subscribe($premium = 'pri_123', 'default') + + 5 ->returnTo(route('home')); + + 6  + + 7 return view('billing', ['checkout' => $checkout]); + + 8}); + + + use Illuminate\Http\Request; + + Route::get('/user/subscribe', function (Request $request) { + $checkout = $request->user()->subscribe($premium = 'pri_123', 'default') + ->returnTo(route('home')); + + 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: + + + + 1$checkout = $request->user()->subscribe($premium = 'pri_123', 'default') + + 2 ->customData(['key' => 'value']) + + 3 ->returnTo(route('home')); + + + $checkout = $request->user()->subscribe($premium = 'pri_123', '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 that is included with +Cashier Paddle: + + + + 1 + + 2 Subscribe + + 3 + + + + Subscribe + + +After the user has finished their checkout, a `subscription_created` webhook +will be dispatched from Paddle. Cashier will receive this webhook and setup +the subscription for your customer. In order to make sure all webhooks are +properly received and handled by your application, ensure you have properly +setup webhook handling. + +### Checking Subscription Status + +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: + + + + 1if ($user->subscribed()) { + + 2 // ... + + 3} + + + if ($user->subscribed()) { + // ... + } + +If your application offers multiple subscriptions, you may specify the +subscription when invoking the `subscribed` method: + + + + 1if ($user->subscribed('default')) { + + 2 // ... + + 3} + + + if ($user->subscribed('default')) { + // ... + } + +The `subscribed` method also makes a great candidate for a [route +middleware](/docs/12.x/middleware), allowing you to filter access to routes +and controllers based on the user's subscription status: + + + + 1user() && ! $request->user()->subscribed()) { + + 19 // This user is not a paying customer... + + 20 return redirect('/billing'); + + 21 } + + 22  + + 23 return $next($request); + + 24 } + + 25} + + + 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: + + + + 1if ($user->subscription()->onTrial()) { + + 2 // ... + + 3} + + + 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: + + + + 1if ($user->subscribedToPrice($monthly = 'pri_123', 'default')) { + + 2 // ... + + 3} + + + 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: + + + + 1if ($user->subscription()->recurring()) { + + 2 // ... + + 3} + + + 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: + + + + 1if ($user->subscription()->canceled()) { + + 2 // ... + + 3} + + + 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: + + + + 1if ($user->subscription()->onGracePeriod()) { + + 2 // ... + + 3} + + + 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: + + + + 1if ($user->subscription()->pastDue()) { + + 2 // ... + + 3} + + + if ($user->subscription()->pastDue()) { + // ... + } + +When a subscription is past due, you should instruct the user to update their +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`: + + + + 1use Laravel\Paddle\Cashier; + + 2  + + 3/** + + 4 * Register any application services. + + 5 */ + + 6public function register(): void + + 7{ + + 8 Cashier::keepPastDueSubscriptionsActive(); + + 9} + + + use Laravel\Paddle\Cashier; + + /** + * Register any application services. + */ + public function register(): void + { + Cashier::keepPastDueSubscriptionsActive(); + } + +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. + +#### Subscription Scopes + +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: + + + + 1// Get all valid subscriptions... + + 2$subscriptions = Subscription::query()->valid()->get(); + + 3  + + 4// Get all of the canceled subscriptions for a user... + + 5$subscriptions = $user->subscriptions()->canceled()->get(); + + + // Get all valid subscriptions... + $subscriptions = Subscription::query()->valid()->get(); + + // Get all of the canceled subscriptions for a user... + $subscriptions = $user->subscriptions()->canceled()->get(); + +A complete list of available scopes is available below: + + + + 1Subscription::query()->valid(); + + 2Subscription::query()->onTrial(); + + 3Subscription::query()->expiredTrial(); + + 4Subscription::query()->notOnTrial(); + + 5Subscription::query()->active(); + + 6Subscription::query()->recurring(); + + 7Subscription::query()->pastDue(); + + 8Subscription::query()->paused(); + + 9Subscription::query()->notPaused(); + + 10Subscription::query()->onPausedGracePeriod(); + + 11Subscription::query()->notOnPausedGracePeriod(); + + 12Subscription::query()->canceled(); + + 13Subscription::query()->notCanceled(); + + 14Subscription::query()->onGracePeriod(); + + 15Subscription::query()->notOnGracePeriod(); + + + 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: + + + + 1// Charge a single price... + + 2$response = $user->subscription()->charge('pri_123'); + + 3  + + 4// Charge multiple prices at once... + + 5$response = $user->subscription()->charge(['pri_123', 'pri_456']); + + + // Charge a single price... + $response = $user->subscription()->charge('pri_123'); + + // 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: + + + + 1$response = $user->subscription()->chargeAndInvoice('pri_123'); + + + $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: + + + + 1use Illuminate\Http\Request; + + 2  + + 3Route::get('/update-payment-method', function (Request $request) { + + 4 $user = $request->user(); + + 5  + + 6 return $user->subscription()->redirectToUpdatePaymentMethod(); + + 7}); + + + use Illuminate\Http\Request; + + Route::get('/update-payment-method', function (Request $request) { + $user = $request->user(); + + 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. + +### Changing Plans + +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: + + + + 1use App\Models\User; + + 2  + + 3$user = User::find(1); + + 4  + + 5$user->subscription()->swap($premium = 'pri_456'); + + + use App\Models\User; + + $user = User::find(1); + + $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: + + + + 1$user = User::find(1); + + 2  + + 3$user->subscription()->swapAndInvoice($premium = 'pri_456'); + + + $user = User::find(1); + + $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: + + + + 1$user->subscription('default')->noProrate()->swap($premium = 'pri_456'); + + + $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`: + + + + 1$user->subscription('default')->noProrate()->swapAndInvoice($premium = 'pri_456'); + + + $user->subscription('default')->noProrate()->swapAndInvoice($premium = 'pri_456'); + +Or, to not bill your customer for a subscription change, you may utilize the +`doNotBill` method: + + + + 1$user->subscription('default')->doNotBill()->swap($premium = 'pri_456'); + + + $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). + +### Subscription Quantity + +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: + + + + 1$user = User::find(1); + + 2  + + 3$user->subscription()->incrementQuantity(); + + 4  + + 5// Add five to the subscription's current quantity... + + 6$user->subscription()->incrementQuantity(5); + + 7  + + 8$user->subscription()->decrementQuantity(); + + 9  + + 10// Subtract five from the subscription's current quantity... + + 11$user->subscription()->decrementQuantity(5); + + + $user = User::find(1); + + $user->subscription()->incrementQuantity(); + + // Add five to the subscription's current quantity... + $user->subscription()->incrementQuantity(5); + + $user->subscription()->decrementQuantity(); + + // Subtract five from the subscription's current quantity... + $user->subscription()->decrementQuantity(5); + +Alternatively, you may set a specific quantity using the `updateQuantity` +method: + + + + 1$user->subscription()->updateQuantity(10); + + + $user->subscription()->updateQuantity(10); + +The `noProrate` method may be used to update the subscription's quantity +without prorating the charges: + + + + 1$user->subscription()->noProrate()->updateQuantity(10); + + + $user->subscription()->noProrate()->updateQuantity(10); + +#### Quantities for Subscriptions With Multiple Products + +If your subscription is a subscription 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: + + + + 1$user->subscription()->incrementQuantity(1, 'price_chat'); + + + $user->subscription()->incrementQuantity(1, 'price_chat'); + +### Subscriptions With Multiple Products + +[Subscription with multiple +products](https://developer.paddle.com/build/subscriptions/add-remove- +products-prices-addons) allow you to assign multiple billing products to a +single subscription. For example, imagine you are building a customer service +"helpdesk" application that has a base subscription price of $10 per month but +offers a live chat add-on product for an additional $15 per month. + +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: + + + + 1use Illuminate\Http\Request; + + 2  + + 3Route::post('/user/subscribe', function (Request $request) { + + 4 $checkout = $request->user()->subscribe([ + + 5 'price_monthly', + + 6 'price_chat', + + 7 ]); + + 8  + + 9 return view('billing', ['checkout' => $checkout]); + + 10}); + + + use Illuminate\Http\Request; + + Route::post('/user/subscribe', function (Request $request) { + $checkout = $request->user()->subscribe([ + 'price_monthly', + 'price_chat', + ]); + + 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: + + + + 1$user = User::find(1); + + 2  + + 3$checkout = $user->subscribe('default', ['price_monthly', 'price_chat' => 5]); + + + $user = User::find(1); + + $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: + + + + 1$user = User::find(1); + + 2  + + 3$user->subscription()->swap(['price_chat', 'price_original' => 2]); + + + $user = User::find(1); + + $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: + + + + 1$user->subscription()->swapAndInvoice(['price_chat', 'price_original' => 2]); + + + $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: + + + + 1$user->subscription()->swap(['price_original' => 2]); + + + $user->subscription()->swap(['price_original' => 2]); + +You may not remove the last price on a subscription. Instead, you should +simply cancel the subscription. + +### Multiple Subscriptions + +Paddle allows your customers to have multiple subscriptions simultaneously. +For example, you may run a gym that offers a swimming subscription and a +weight-lifting subscription, and each subscription may have different pricing. +Of course, customers should be able to subscribe to either or both plans. + +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: + + + + 1use Illuminate\Http\Request; + + 2  + + 3Route::post('/swimming/subscribe', function (Request $request) { + + 4 $checkout = $request->user()->subscribe($swimmingMonthly = 'pri_123', 'swimming'); + + 5  + + 6 return view('billing', ['checkout' => $checkout]); + + 7}); + + + use Illuminate\Http\Request; + + Route::post('/swimming/subscribe', function (Request $request) { + $checkout = $request->user()->subscribe($swimmingMonthly = 'pri_123', 'swimming'); + + 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: + + + + 1$user->subscription('swimming')->swap($swimmingYearly = 'pri_456'); + + + $user->subscription('swimming')->swap($swimmingYearly = 'pri_456'); + +Of course, you may also cancel the subscription entirely: + + + + 1$user->subscription('swimming')->cancel(); + + + $user->subscription('swimming')->cancel(); + +### Pausing Subscriptions + +To pause a subscription, call the `pause` method on the user's subscription: + + + + 1$user->subscription()->pause(); + + + $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: + + + + 1$user->subscription()->pauseNow(); + + + $user->subscription()->pauseNow(); + +Using the `pauseUntil` method, you can pause the subscription until a specific +moment in time: + + + + 1$user->subscription()->pauseUntil(now()->addMonth()); + + + $user->subscription()->pauseUntil(now()->addMonth()); + +Or, you may use the `pauseNowUntil` method to immediately pause the +subscription until a given point in time: + + + + 1$user->subscription()->pauseNowUntil(now()->addMonth()); + + + $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: + + + + 1if ($user->subscription()->onPausedGracePeriod()) { + + 2 // ... + + 3} + + + if ($user->subscription()->onPausedGracePeriod()) { + // ... + } + +To resume a paused subscription, you may invoke the `resume` method on the +subscription: + + + + 1$user->subscription()->resume(); + + + $user->subscription()->resume(); + +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. + +### Canceling Subscriptions + +To cancel a subscription, call the `cancel` method on the user's subscription: + + + + 1$user->subscription()->cancel(); + + + $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: + + + + 1if ($user->subscription()->onGracePeriod()) { + + 2 // ... + + 3} + + + if ($user->subscription()->onGracePeriod()) { + // ... + } + +If you wish to cancel a subscription immediately, you may call the `cancelNow` +method on the subscription: + + + + 1$user->subscription()->cancelNow(); + + + $user->subscription()->cancelNow(); + +To stop a subscription on its grace period from canceling, you may invoke the +`stopCancelation` method: + + + + 1$user->subscription()->stopCancelation(); + + + $user->subscription()->stopCancelation(); + +Paddle's subscriptions cannot be resumed after cancelation. If your customer +wishes to resume their subscription, they will have to create a new +subscription. + +## Subscription Trials + +### With Payment Method Up Front + +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: + + + + 1use Illuminate\Http\Request; + + 2  + + 3Route::get('/user/subscribe', function (Request $request) { + + 4 $checkout = $request->user() + + 5 ->subscribe('pri_monthly') + + 6 ->returnTo(route('home')); + + 7  + + 8 return view('billing', ['checkout' => $checkout]); + + 9}); + + + use Illuminate\Http\Request; + + Route::get('/user/subscribe', function (Request $request) { + $checkout = $request->user() + ->subscribe('pri_monthly') + ->returnTo(route('home')); + + 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. + +If the customer's subscription is not canceled before the trial ending date +they will be charged as soon as the trial expires, so you should be sure to +notify your users of their trial ending date. + +You may determine if the user is within their trial period using either the +`onTrial` method of the user instance: + + + + 1if ($user->onTrial()) { + + 2 // ... + + 3} + + + if ($user->onTrial()) { + // ... + } + +To determine if an existing trial has expired, you may use the +`hasExpiredTrial` methods: + + + + 1if ($user->hasExpiredTrial()) { + + 2 // ... + + 3} + + + if ($user->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: + + + + 1if ($user->onTrial('default')) { + + 2 // ... + + 3} + + 4  + + 5if ($user->hasExpiredTrial('default')) { + + 6 // ... + + 7} + + + if ($user->onTrial('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: + + + + 1use App\Models\User; + + 2  + + 3$user = User::create([ + + 4 // ... + + 5]); + + 6  + + 7$user->createAsCustomer([ + + 8 'trial_ends_at' => now()->addDays(10) + + 9]); + + + use App\Models\User; + + $user = User::create([ + // ... + ]); + + $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`: + + + + 1if ($user->onTrial()) { + + 2 // User is within their trial period... + + 3} + + + 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: + + + + 1use Illuminate\Http\Request; + + 2  + + 3Route::get('/user/subscribe', function (Request $request) { + + 4 $checkout = $request->user() + + 5 ->subscribe('pri_monthly') + + 6 ->returnTo(route('home')); + + 7  + + 8 return view('billing', ['checkout' => $checkout]); + + 9}); + + + use Illuminate\Http\Request; + + Route::get('/user/subscribe', function (Request $request) { + $checkout = $request->user() + ->subscribe('pri_monthly') + ->returnTo(route('home')); + + 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: + + + + 1if ($user->onTrial('default')) { + + 2 $trialEndsAt = $user->trialEndsAt(); + + 3} + + + 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: + + + + 1if ($user->onGenericTrial()) { + + 2 // User is within their "generic" trial period... + + 3} + + + 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: + + + + 1$user->subscription()->extendTrial(now()->addDays(5)); + + + $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: + + + + 1$user->subscription()->activate(); + + + $user->subscription()->activate(); + +## Handling Paddle Webhooks + +Paddle can notify your application of a variety of events via webhooks. By +default, a route that points to Cashier's webhook controller is registered by +the Cashier service provider. This controller will handle all incoming webhook +requests. + +By default, this controller will automatically handle canceling subscriptions +that have too many failed charges, subscription updates, and payment method +changes; however, as we'll soon discover, you can extend this controller to +handle any Paddle webhook event you like. + +To ensure your application can handle Paddle webhooks, be sure to [configure +the webhook URL in the Paddle control +panel](https://vendors.paddle.com/notifications-v2). By default, Cashier's +webhook controller responds to the `/paddle/webhook` URL path. The full list +of all webhooks you should enable in the Paddle control panel are: + + * Customer Updated + * Transaction Completed + * Transaction Updated + * Subscription Created + * Subscription Updated + * Subscription Paused + * Subscription Canceled + +Make sure you protect incoming requests with Cashier's included [webhook +signature verification](/docs/12.x/cashier-paddle#verifying-webhook- +signatures) middleware. + +#### Webhooks and CSRF Protection + +Since Paddle webhooks need to bypass Laravel's [CSRF +protection](/docs/12.x/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: + + + + 1->withMiddleware(function (Middleware $middleware) { + + 2 $middleware->validateCsrfTokens(except: [ + + 3 'paddle/*', + + 4 ]); + + 5}) + + + ->withMiddleware(function (Middleware $middleware) { + $middleware->validateCsrfTokens(except: [ + 'paddle/*', + ]); + }) + +#### Webhooks and Local Development + +For Paddle to be able to send your application webhooks during local +development, you will need to expose your application via a site sharing +service such as [Ngrok](https://ngrok.com/) or +[Expose](https://expose.dev/docs/introduction). If you are developing your +application locally using [Laravel Sail](/docs/12.x/sail), you may use Sail's +[site sharing command](/docs/12.x/sail#sharing-your-site). + +### Defining Webhook Event Handlers + +Cashier automatically handles subscription cancelation on failed charges and +other common Paddle webhooks. However, if you have additional webhook events +you would like to handle, you may do so by listening to the following events +that are dispatched by Cashier: + + * `Laravel\Paddle\Events\WebhookReceived` + * `Laravel\Paddle\Events\WebhookHandled` + +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/12.x/events#defining-listeners) that will handle the event: + + + + 1payload['event_type'] === 'transaction.billed') { + + 15 // Handle the incoming event... + + 16 } + + 17 } + + 18} + + + 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: + + * `Laravel\Paddle\Events\CustomerUpdated` + * `Laravel\Paddle\Events\TransactionCompleted` + * `Laravel\Paddle\Events\TransactionUpdated` + * `Laravel\Paddle\Events\SubscriptionCreated` + * `Laravel\Paddle\Events\SubscriptionUpdated` + * `Laravel\Paddle\Events\SubscriptionPaused` + * `Laravel\Paddle\Events\SubscriptionCanceled` + +You can also override the default, built-in webhook route by defining the +`CASHIER_WEBHOOK` environment variable in your application's `.env` file. This +value should be the full URL to your webhook route and needs to match the URL +set in your Paddle control panel: + + + + 1CASHIER_WEBHOOK=https://example.com/my-paddle-webhook-url + + + CASHIER_WEBHOOK=https://example.com/my-paddle-webhook-url + +### Verifying Webhook Signatures + +To secure your webhooks, you may use [Paddle's webhook +signatures](https://developer.paddle.com/webhooks/signature-verification). For +convenience, Cashier automatically includes a middleware which validates that +the incoming Paddle webhook request is valid. + +To enable webhook verification, ensure that the `PADDLE_WEBHOOK_SECRET` +environment variable is defined in your application's `.env` file. The webhook +secret may be retrieved from your Paddle account dashboard. + +## Single Charges + +### Charging for Products + +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: + + + + 1use Illuminate\Http\Request; + + 2  + + 3Route::get('/buy', function (Request $request) { + + 4 $checkout = $request->user()->checkout(['pri_tshirt', 'pri_socks' => 5]); + + 5  + + 6 return view('buy', ['checkout' => $checkout]); + + 7}); + + + use Illuminate\Http\Request; + + Route::get('/buy', function (Request $request) { + $checkout = $request->user()->checkout(['pri_tshirt', 'pri_socks' => 5]); + + return view('buy', ['checkout' => $checkout]); + }); + +After generating the checkout session, you may use Cashier's provided `paddle- +button` Blade component to allow the user to view the Paddle checkout widget +and complete the purchase: + + + + 1 + + 2 Buy + + 3 + + + + Buy + + +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: + + + + 1$checkout = $user->checkout('pri_tshirt') + + 2 ->customData([ + + 3 'custom_option' => $value, + + 4 ]); + + + $checkout = $user->checkout('pri_tshirt') + ->customData([ + 'custom_option' => $value, + ]); + +### Refunding Transactions + +Refunding transactions will return the refunded amount to your customer's +payment method that was used at the time of purchase. If you need to refund a +Paddle purchase, you may use the `refund` method on a +`Cashier\Paddle\Transaction` model. This method accepts a reason as the first +argument, one or more price ID's to refund with optional amounts as an +associative array. You may retrieve the transactions for a given billable +model using the `transactions` method. + +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`: + + + + 1use App\Models\User; + + 2  + + 3$user = User::find(1); + + 4  + + 5$transaction = $user->transactions()->first(); + + 6  + + 7$response = $transaction->refund('Accidental charge', [ + + 8 'pri_123', // Fully refund this price... + + 9 'pri_456' => 200, // Only partially refund this price... + + 10]); + + + use App\Models\User; + + $user = User::find(1); + + $transaction = $user->transactions()->first(); + + $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: + + + + 1$response = $transaction->refund('Accidental charge'); + + + $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). + +Refunds must always be approved by Paddle before fully processing. + +### Crediting Transactions + +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: + + + + 1$transaction = $user->transactions()->first(); + + 2  + + 3// Credit a specific line item fully... + + 4$response = $transaction->credit('Compensation', 'pri_123'); + + + $transaction = $user->transactions()->first(); + + // 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). + +Credits can only be applied for manually-collected transactions. +Automatically-collected transactions are credited by Paddle themselves. + +## Transactions + +You may easily retrieve an array of a billable model's transactions via the +`transactions` property: + + + + 1use App\Models\User; + + 2  + + 3$user = User::find(1); + + 4  + + 5$transactions = $user->transactions; + + + use App\Models\User; + + $user = User::find(1); + + $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. + +When listing the transactions for a customer, you may use the transaction +instance's methods to display the relevant payment information. For example, +you may wish to list every transaction in a table, allowing the user to easily +download any of the invoices: + + + + 1 + + 2 @foreach ($transactions as $transaction) + + 3 + + 4 + + 5 + + 6 + + 7 + + 8 + + 9 @endforeach + + 10
    {{ $transaction->billed_at->toFormattedDateString() }}{{ $transaction->total() }}{{ $transaction->tax() }}Download
    + + + + @foreach ($transactions as $transaction) + + + + + + + @endforeach +
    {{ $transaction->billed_at->toFormattedDateString() }}{{ $transaction->total() }}{{ $transaction->tax() }}Download
    + +The `download-invoice` route may look like the following: + + + + 1use Illuminate\Http\Request; + + 2use Laravel\Paddle\Transaction; + + 3  + + 4Route::get('/download-invoice/{transaction}', function (Request $request, Transaction $transaction) { + + 5 return $transaction->redirectToInvoicePdf(); + + 6})->name('download-invoice'); + + + use Illuminate\Http\Request; + use Laravel\Paddle\Transaction; + + 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: + + + + 1use App\Models\User; + + 2  + + 3$user = User::find(1); + + 4  + + 5$subscription = $user->subscription(); + + 6  + + 7$lastPayment = $subscription->lastPayment(); + + 8$nextPayment = $subscription->nextPayment(); + + + use App\Models\User; + + $user = User::find(1); + + $subscription = $user->subscription(); + + $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): + + + + 1Next payment: {{ $nextPayment->amount() }} due on {{ $nextPayment->date()->format('d/m/Y') }} + + + Next payment: {{ $nextPayment->amount() }} due on {{ $nextPayment->date()->format('d/m/Y') }} + +## Testing + +While testing, you should manually test your billing flow to make sure your +integration works as expected. + +For automated tests, including those executed within a CI environment, you may +use [Laravel's HTTP Client](/docs/12.x/http-client#testing) to fake HTTP calls +made to Paddle. Although this does not test the actual responses from Paddle, +it does provide a way to test your application without actually calling +Paddle's API. + diff --git a/output/12.x/collections.md b/output/12.x/collections.md new file mode 100644 index 0000000..4ceb10c --- /dev/null +++ b/output/12.x/collections.md @@ -0,0 +1,8969 @@ +# Collections + + * Introduction + * Creating Collections + * Extending Collections + * Available Methods + * Higher Order Messages + * Lazy Collections + * Introduction + * Creating Lazy Collections + * The Enumerable Contract + * Lazy Collection Methods + +## Introduction + +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: + + + + 1$collection = collect(['Taylor', 'Abigail', null])->map(function (?string $name) { + + 2 return strtoupper($name); + + 3})->reject(function (string $name) { + + 4 return empty($name); + + 5}); + + + $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. + +### Creating Collections + +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: + + + + 1$collection = collect([1, 2, 3]); + + + $collection = collect([1, 2, 3]); + +You may also create a collection using the make and fromJson methods. + +The results of [Eloquent](/docs/12.x/eloquent) queries are always returned as +`Collection` instances. + +### Extending Collections + +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: + + + + 1use Illuminate\Support\Collection; + + 2use Illuminate\Support\Str; + + 3  + + 4Collection::macro('toUpper', function () { + + 5 return $this->map(function (string $value) { + + 6 return Str::upper($value); + + 7 }); + + 8}); + + 9  + + 10$collection = collect(['first', 'second']); + + 11  + + 12$upper = $collection->toUpper(); + + 13  + + 14// ['FIRST', 'SECOND'] + + + use Illuminate\Support\Collection; + use Illuminate\Support\Str; + + Collection::macro('toUpper', function () { + return $this->map(function (string $value) { + return Str::upper($value); + }); + }); + + $collection = collect(['first', 'second']); + + $upper = $collection->toUpper(); + + // ['FIRST', 'SECOND'] + +Typically, you should declare collection macros in the `boot` method of a +[service provider](/docs/12.x/providers). + +#### Macro Arguments + +If necessary, you may define macros that accept additional arguments: + + + + 1use Illuminate\Support\Collection; + + 2use Illuminate\Support\Facades\Lang; + + 3  + + 4Collection::macro('toLocale', function (string $locale) { + + 5 return $this->map(function (string $value) use ($locale) { + + 6 return Lang::get($value, [], $locale); + + 7 }); + + 8}); + + 9  + + 10$collection = collect(['first', 'second']); + + 11  + + 12$translated = $collection->toLocale('es'); + + + 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 = collect(['first', 'second']); + + $translated = $collection->toLocale('es'); + +## Available Methods + +For the majority of the remaining collection documentation, we'll discuss each +method available on the `Collection` class. Remember, all of these methods may +be chained to fluently manipulate the underlying array. Furthermore, almost +every method returns a new `Collection` instance, allowing you to preserve the +original copy of the collection when necessary: + +after all average avg before chunk chunkWhile collapse collapseWithKeys +collect combine concat contains containsOneItem containsStrict count countBy +crossJoin dd diff diffAssoc diffAssocUsing diffKeys doesntContain +doesntContainStrict dot dump duplicates duplicatesStrict each eachSpread +ensure every except filter first firstOrFail firstWhere flatMap flatten flip +forget forPage fromJson get groupBy has hasAny implode intersect +intersectUsing intersectAssoc intersectAssocUsing intersectByKeys isEmpty +isNotEmpty join keyBy keys last lazy macro make map mapInto mapSpread +mapToGroups mapWithKeys max median merge mergeRecursive min mode multiply nth +only pad partition percentage pipe pipeInto pipeThrough pluck pop prepend pull +push put random range reduce reduceSpread reject replace replaceRecursive +reverse search select shift shuffle skip skipUntil skipWhile slice sliding +sole some sort sortBy sortByDesc sortDesc sortKeys sortKeysDesc sortKeysUsing +splice split splitIn sum take takeUntil takeWhile tap times toArray toJson +toPrettyJson transform undot union unique uniqueStrict unless unlessEmpty +unlessNotEmpty unwrap value values when whenEmpty whenNotEmpty where +whereStrict whereBetween whereIn whereInStrict whereInstanceOf whereNotBetween +whereNotIn whereNotInStrict whereNotNull whereNull wrap zip + +## Method Listing + +#### `after()` + +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: + + + + 1$collection = collect([1, 2, 3, 4, 5]); + + 2  + + 3$collection->after(3); + + 4  + + 5// 4 + + 6  + + 7$collection->after(5); + + 8  + + 9// null + + + $collection = collect([1, 2, 3, 4, 5]); + + $collection->after(3); + + // 4 + + $collection->after(5); + + // 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: + + + + 1collect([2, 4, 6, 8])->after('4', strict: true); + + 2  + + 3// null + + + collect([2, 4, 6, 8])->after('4', strict: true); + + // null + +Alternatively, you may provide your own closure to search for the first item +that passes a given truth test: + + + + 1collect([2, 4, 6, 8])->after(function (int $item, int $key) { + + 2 return $item > 5; + + 3}); + + 4  + + 5// 8 + + + collect([2, 4, 6, 8])->after(function (int $item, int $key) { + return $item > 5; + }); + + // 8 + +#### `all()` + +The `all` method returns the underlying array represented by the collection: + + + + 1collect([1, 2, 3])->all(); + + 2  + + 3// [1, 2, 3] + + + collect([1, 2, 3])->all(); + + // [1, 2, 3] + +#### `average()` + +Alias for the avg method. + +#### `avg()` + +The `avg` method returns the [average +value](https://en.wikipedia.org/wiki/Average) of a given key: + + + + 1$average = collect([ + + 2 ['foo' => 10], + + 3 ['foo' => 10], + + 4 ['foo' => 20], + + 5 ['foo' => 40] + + 6])->avg('foo'); + + 7  + + 8// 20 + + 9  + + 10$average = collect([1, 1, 2, 4])->avg(); + + 11  + + 12// 2 + + + $average = collect([ + ['foo' => 10], + ['foo' => 10], + ['foo' => 20], + ['foo' => 40] + ])->avg('foo'); + + // 20 + + $average = collect([1, 1, 2, 4])->avg(); + + // 2 + +#### `before()` + +The `before` method is the opposite of the 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: + + + + 1$collection = collect([1, 2, 3, 4, 5]); + + 2  + + 3$collection->before(3); + + 4  + + 5// 2 + + 6  + + 7$collection->before(1); + + 8  + + 9// null + + 10  + + 11collect([2, 4, 6, 8])->before('4', strict: true); + + 12  + + 13// null + + 14  + + 15collect([2, 4, 6, 8])->before(function (int $item, int $key) { + + 16 return $item > 5; + + 17}); + + 18  + + 19// 4 + + + $collection = collect([1, 2, 3, 4, 5]); + + $collection->before(3); + + // 2 + + $collection->before(1); + + // null + + collect([2, 4, 6, 8])->before('4', strict: true); + + // null + + collect([2, 4, 6, 8])->before(function (int $item, int $key) { + return $item > 5; + }); + + // 4 + +#### `chunk()` + +The `chunk` method breaks the collection into multiple, smaller collections of +a given size: + + + + 1$collection = collect([1, 2, 3, 4, 5, 6, 7]); + + 2  + + 3$chunks = $collection->chunk(4); + + 4  + + 5$chunks->all(); + + 6  + + 7// [[1, 2, 3, 4], [5, 6, 7]] + + + $collection = collect([1, 2, 3, 4, 5, 6, 7]); + + $chunks = $collection->chunk(4); + + $chunks->all(); + + // [[1, 2, 3, 4], [5, 6, 7]] + +This method is especially useful in [views](/docs/12.x/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/12.x/eloquent) models you +want to display in a grid: + + + + 1@foreach ($products->chunk(3) as $chunk) + + 2
    + + 3 @foreach ($chunk as $product) + + 4
    {{ $product->name }}
    + + 5 @endforeach + + 6
    + + 7@endforeach + + + @foreach ($products->chunk(3) as $chunk) +
    + @foreach ($chunk as $product) +
    {{ $product->name }}
    + @endforeach +
    + @endforeach + +#### `chunkWhile()` + +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: + + + + 1$collection = collect(str_split('AABBCCCD')); + + 2  + + 3$chunks = $collection->chunkWhile(function (string $value, int $key, Collection $chunk) { + + 4 return $value === $chunk->last(); + + 5}); + + 6  + + 7$chunks->all(); + + 8  + + 9// [['A', 'A'], ['B', 'B'], ['C', 'C', 'C'], ['D']] + + + $collection = collect(str_split('AABBCCCD')); + + $chunks = $collection->chunkWhile(function (string $value, int $key, Collection $chunk) { + return $value === $chunk->last(); + }); + + $chunks->all(); + + // [['A', 'A'], ['B', 'B'], ['C', 'C', 'C'], ['D']] + +#### `collapse()` + +The `collapse` method collapses a collection of arrays or collections into a +single, flat collection: + + + + 1$collection = collect([ + + 2 [1, 2, 3], + + 3 [4, 5, 6], + + 4 [7, 8, 9], + + 5]); + + 6  + + 7$collapsed = $collection->collapse(); + + 8  + + 9$collapsed->all(); + + 10  + + 11// [1, 2, 3, 4, 5, 6, 7, 8, 9] + + + $collection = collect([ + [1, 2, 3], + [4, 5, 6], + [7, 8, 9], + ]); + + $collapsed = $collection->collapse(); + + $collapsed->all(); + + // [1, 2, 3, 4, 5, 6, 7, 8, 9] + +#### `collapseWithKeys()` + +The `collapseWithKeys` method flattens a collection of arrays or collections +into a single collection, keeping the original keys intact. If the collection +is already flat, it will return an empty collection: + + + + 1$collection = collect([ + + 2 ['first' => collect([1, 2, 3])], + + 3 ['second' => [4, 5, 6]], + + 4 ['third' => collect([7, 8, 9])] + + 5]); + + 6  + + 7$collapsed = $collection->collapseWithKeys(); + + 8  + + 9$collapsed->all(); + + 10  + + 11// [ + + 12// 'first' => [1, 2, 3], + + 13// 'second' => [4, 5, 6], + + 14// 'third' => [7, 8, 9], + + 15// ] + + + $collection = collect([ + ['first' => collect([1, 2, 3])], + ['second' => [4, 5, 6]], + ['third' => collect([7, 8, 9])] + ]); + + $collapsed = $collection->collapseWithKeys(); + + $collapsed->all(); + + // [ + // 'first' => [1, 2, 3], + // 'second' => [4, 5, 6], + // 'third' => [7, 8, 9], + // ] + +#### `collect()` + +The `collect` method returns a new `Collection` instance with the items +currently in the collection: + + + + 1$collectionA = collect([1, 2, 3]); + + 2  + + 3$collectionB = $collectionA->collect(); + + 4  + + 5$collectionB->all(); + + 6  + + 7// [1, 2, 3] + + + $collectionA = collect([1, 2, 3]); + + $collectionB = $collectionA->collect(); + + $collectionB->all(); + + // [1, 2, 3] + +The `collect` method is primarily useful for converting lazy collections into +standard `Collection` instances: + + + + 1$lazyCollection = LazyCollection::make(function () { + + 2 yield 1; + + 3 yield 2; + + 4 yield 3; + + 5}); + + 6  + + 7$collection = $lazyCollection->collect(); + + 8  + + 9$collection::class; + + 10  + + 11// 'Illuminate\Support\Collection' + + 12  + + 13$collection->all(); + + 14  + + 15// [1, 2, 3] + + + $lazyCollection = LazyCollection::make(function () { + yield 1; + yield 2; + yield 3; + }); + + $collection = $lazyCollection->collect(); + + $collection::class; + + // 'Illuminate\Support\Collection' + + $collection->all(); + + // [1, 2, 3] + +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. + +#### `combine()` + +The `combine` method combines the values of the collection, as keys, with the +values of another array or collection: + + + + 1$collection = collect(['name', 'age']); + + 2  + + 3$combined = $collection->combine(['George', 29]); + + 4  + + 5$combined->all(); + + 6  + + 7// ['name' => 'George', 'age' => 29] + + + $collection = collect(['name', 'age']); + + $combined = $collection->combine(['George', 29]); + + $combined->all(); + + // ['name' => 'George', 'age' => 29] + +#### `concat()` + +The `concat` method appends the given array or collection's values onto the +end of another collection: + + + + 1$collection = collect(['John Doe']); + + 2  + + 3$concatenated = $collection->concat(['Jane Doe'])->concat(['name' => 'Johnny Doe']); + + 4  + + 5$concatenated->all(); + + 6  + + 7// ['John Doe', 'Jane Doe', 'Johnny Doe'] + + + $collection = collect(['John Doe']); + + $concatenated = $collection->concat(['Jane Doe'])->concat(['name' => 'Johnny Doe']); + + $concatenated->all(); + + // ['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. + +#### `contains()` + +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: + + + + 1$collection = collect([1, 2, 3, 4, 5]); + + 2  + + 3$collection->contains(function (int $value, int $key) { + + 4 return $value > 5; + + 5}); + + 6  + + 7// false + + + $collection = collect([1, 2, 3, 4, 5]); + + $collection->contains(function (int $value, int $key) { + return $value > 5; + }); + + // false + +Alternatively, you may pass a string to the `contains` method to determine +whether the collection contains a given item value: + + + + 1$collection = collect(['name' => 'Desk', 'price' => 100]); + + 2  + + 3$collection->contains('Desk'); + + 4  + + 5// true + + 6  + + 7$collection->contains('New York'); + + 8  + + 9// false + + + $collection = collect(['name' => 'Desk', 'price' => 100]); + + $collection->contains('Desk'); + + // true + + $collection->contains('New York'); + + // false + +You may also pass a key / value pair to the `contains` method, which will +determine if the given pair exists in the collection: + + + + 1$collection = collect([ + + 2 ['product' => 'Desk', 'price' => 200], + + 3 ['product' => 'Chair', 'price' => 100], + + 4]); + + 5  + + 6$collection->contains('product', 'Bookcase'); + + 7  + + 8// false + + + $collection = collect([ + ['product' => 'Desk', 'price' => 200], + ['product' => 'Chair', 'price' => 100], + ]); + + $collection->contains('product', 'Bookcase'); + + // 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 to filter using "strict" +comparisons. + +For the inverse of `contains`, see the doesntContain method. + +#### `containsOneItem()` + +The `containsOneItem` method determines whether the collection contains a +single item: + + + + 1collect([])->containsOneItem(); + + 2  + + 3// false + + 4  + + 5collect(['1'])->containsOneItem(); + + 6  + + 7// true + + 8  + + 9collect(['1', '2'])->containsOneItem(); + + 10  + + 11// false + + 12  + + 13collect([1, 2, 3])->containsOneItem(fn (int $item) => $item === 2); + + 14  + + 15// true + + + collect([])->containsOneItem(); + + // false + + collect(['1'])->containsOneItem(); + + // true + + collect(['1', '2'])->containsOneItem(); + + // false + + collect([1, 2, 3])->containsOneItem(fn (int $item) => $item === 2); + + // true + +#### `containsStrict()` + +This method has the same signature as the contains method; however, all values +are compared using "strict" comparisons. + +This method's behavior is modified when using [Eloquent +Collections](/docs/12.x/eloquent-collections#method-contains). + +#### `count()` + +The `count` method returns the total number of items in the collection: + + + + 1$collection = collect([1, 2, 3, 4]); + + 2  + + 3$collection->count(); + + 4  + + 5// 4 + + + $collection = collect([1, 2, 3, 4]); + + $collection->count(); + + // 4 + +#### `countBy()` + +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: + + + + 1$collection = collect([1, 2, 2, 2, 3]); + + 2  + + 3$counted = $collection->countBy(); + + 4  + + 5$counted->all(); + + 6  + + 7// [1 => 1, 2 => 3, 3 => 1] + + + $collection = collect([1, 2, 2, 2, 3]); + + $counted = $collection->countBy(); + + $counted->all(); + + // [1 => 1, 2 => 3, 3 => 1] + +You may pass a closure to the `countBy` method to count all items by a custom +value: + + + + 1$collection = collect(['[[email protected]](/cdn-cgi/l/email-protection)', '[[email protected]](/cdn-cgi/l/email-protection)', '[[email protected]](/cdn-cgi/l/email-protection)']); + + 2  + + 3$counted = $collection->countBy(function (string $email) { + + 4 return substr(strrchr($email, '@'), 1); + + 5}); + + 6  + + 7$counted->all(); + + 8  + + 9// ['gmail.com' => 2, 'yahoo.com' => 1] + + + $collection = collect(['[[email protected]](/cdn-cgi/l/email-protection)', '[[email protected]](/cdn-cgi/l/email-protection)', '[[email protected]](/cdn-cgi/l/email-protection)']); + + $counted = $collection->countBy(function (string $email) { + return substr(strrchr($email, '@'), 1); + }); + + $counted->all(); + + // ['gmail.com' => 2, 'yahoo.com' => 1] + +#### `crossJoin()` + +The `crossJoin` method cross joins the collection's values among the given +arrays or collections, returning a Cartesian product with all possible +permutations: + + + + 1$collection = collect([1, 2]); + + 2  + + 3$matrix = $collection->crossJoin(['a', 'b']); + + 4  + + 5$matrix->all(); + + 6  + + 7/* + + 8 [ + + 9 [1, 'a'], + + 10 [1, 'b'], + + 11 [2, 'a'], + + 12 [2, 'b'], + + 13 ] + + 14*/ + + 15  + + 16$collection = collect([1, 2]); + + 17  + + 18$matrix = $collection->crossJoin(['a', 'b'], ['I', 'II']); + + 19  + + 20$matrix->all(); + + 21  + + 22/* + + 23 [ + + 24 [1, 'a', 'I'], + + 25 [1, 'a', 'II'], + + 26 [1, 'b', 'I'], + + 27 [1, 'b', 'II'], + + 28 [2, 'a', 'I'], + + 29 [2, 'a', 'II'], + + 30 [2, 'b', 'I'], + + 31 [2, 'b', 'II'], + + 32 ] + + 33*/ + + + $collection = collect([1, 2]); + + $matrix = $collection->crossJoin(['a', 'b']); + + $matrix->all(); + + /* + [ + [1, 'a'], + [1, 'b'], + [2, 'a'], + [2, 'b'], + ] + */ + + $collection = collect([1, 2]); + + $matrix = $collection->crossJoin(['a', 'b'], ['I', 'II']); + + $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'], + ] + */ + +#### `dd()` + +The `dd` method dumps the collection's items and ends execution of the script: + + + + 1$collection = collect(['John Doe', 'Jane Doe']); + + 2  + + 3$collection->dd(); + + 4  + + 5/* + + 6 array:2 [ + + 7 0 => "John Doe" + + 8 1 => "Jane Doe" + + 9 ] + + 10*/ + + + $collection = collect(['John Doe', 'Jane Doe']); + + $collection->dd(); + + /* + array:2 [ + 0 => "John Doe" + 1 => "Jane Doe" + ] + */ + +If you do not want to stop executing the script, use the dump method instead. + +#### `diff()` + +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: + + + + 1$collection = collect([1, 2, 3, 4, 5]); + + 2  + + 3$diff = $collection->diff([2, 4, 6, 8]); + + 4  + + 5$diff->all(); + + 6  + + 7// [1, 3, 5] + + + $collection = collect([1, 2, 3, 4, 5]); + + $diff = $collection->diff([2, 4, 6, 8]); + + $diff->all(); + + // [1, 3, 5] + +This method's behavior is modified when using [Eloquent +Collections](/docs/12.x/eloquent-collections#method-diff). + +#### `diffAssoc()` + +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: + + + + 1$collection = collect([ + + 2 'color' => 'orange', + + 3 'type' => 'fruit', + + 4 'remain' => 6, + + 5]); + + 6  + + 7$diff = $collection->diffAssoc([ + + 8 'color' => 'yellow', + + 9 'type' => 'fruit', + + 10 'remain' => 3, + + 11 'used' => 6, + + 12]); + + 13  + + 14$diff->all(); + + 15  + + 16// ['color' => 'orange', 'remain' => 6] + + + $collection = collect([ + 'color' => 'orange', + 'type' => 'fruit', + 'remain' => 6, + ]); + + $diff = $collection->diffAssoc([ + 'color' => 'yellow', + 'type' => 'fruit', + 'remain' => 3, + 'used' => 6, + ]); + + $diff->all(); + + // ['color' => 'orange', 'remain' => 6] + +#### `diffAssocUsing()` + +Unlike `diffAssoc`, `diffAssocUsing` accepts a user supplied callback function +for the indices comparison: + + + + 1$collection = collect([ + + 2 'color' => 'orange', + + 3 'type' => 'fruit', + + 4 'remain' => 6, + + 5]); + + 6  + + 7$diff = $collection->diffAssocUsing([ + + 8 'Color' => 'yellow', + + 9 'Type' => 'fruit', + + 10 'Remain' => 3, + + 11], 'strnatcasecmp'); + + 12  + + 13$diff->all(); + + 14  + + 15// ['color' => 'orange', 'remain' => 6] + + + $collection = collect([ + 'color' => 'orange', + 'type' => 'fruit', + 'remain' => 6, + ]); + + $diff = $collection->diffAssocUsing([ + 'Color' => 'yellow', + 'Type' => 'fruit', + 'Remain' => 3, + ], 'strnatcasecmp'); + + $diff->all(); + + // ['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. + +#### `diffKeys()` + +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: + + + + 1$collection = collect([ + + 2 'one' => 10, + + 3 'two' => 20, + + 4 'three' => 30, + + 5 'four' => 40, + + 6 'five' => 50, + + 7]); + + 8  + + 9$diff = $collection->diffKeys([ + + 10 'two' => 2, + + 11 'four' => 4, + + 12 'six' => 6, + + 13 'eight' => 8, + + 14]); + + 15  + + 16$diff->all(); + + 17  + + 18// ['one' => 10, 'three' => 30, 'five' => 50] + + + $collection = collect([ + 'one' => 10, + 'two' => 20, + 'three' => 30, + 'four' => 40, + 'five' => 50, + ]); + + $diff = $collection->diffKeys([ + 'two' => 2, + 'four' => 4, + 'six' => 6, + 'eight' => 8, + ]); + + $diff->all(); + + // ['one' => 10, 'three' => 30, 'five' => 50] + +#### `doesntContain()` + +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: + + + + 1$collection = collect([1, 2, 3, 4, 5]); + + 2  + + 3$collection->doesntContain(function (int $value, int $key) { + + 4 return $value < 5; + + 5}); + + 6  + + 7// false + + + $collection = collect([1, 2, 3, 4, 5]); + + $collection->doesntContain(function (int $value, int $key) { + return $value < 5; + }); + + // false + +Alternatively, you may pass a string to the `doesntContain` method to +determine whether the collection does not contain a given item value: + + + + 1$collection = collect(['name' => 'Desk', 'price' => 100]); + + 2  + + 3$collection->doesntContain('Table'); + + 4  + + 5// true + + 6  + + 7$collection->doesntContain('Desk'); + + 8  + + 9// false + + + $collection = collect(['name' => 'Desk', 'price' => 100]); + + $collection->doesntContain('Table'); + + // true + + $collection->doesntContain('Desk'); + + // 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: + + + + 1$collection = collect([ + + 2 ['product' => 'Desk', 'price' => 200], + + 3 ['product' => 'Chair', 'price' => 100], + + 4]); + + 5  + + 6$collection->doesntContain('product', 'Bookcase'); + + 7  + + 8// true + + + $collection = collect([ + ['product' => 'Desk', 'price' => 200], + ['product' => 'Chair', 'price' => 100], + ]); + + $collection->doesntContain('product', 'Bookcase'); + + // 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. + +#### `doesntContainStrict()` + +This method has the same signature as the doesntContain method; however, all +values are compared using "strict" comparisons. + +#### `dot()` + +The `dot` method flattens a multi-dimensional collection into a single level +collection that uses "dot" notation to indicate depth: + + + + 1$collection = collect(['products' => ['desk' => ['price' => 100]]]); + + 2  + + 3$flattened = $collection->dot(); + + 4  + + 5$flattened->all(); + + 6  + + 7// ['products.desk.price' => 100] + + + $collection = collect(['products' => ['desk' => ['price' => 100]]]); + + $flattened = $collection->dot(); + + $flattened->all(); + + // ['products.desk.price' => 100] + +#### `dump()` + +The `dump` method dumps the collection's items: + + + + 1$collection = collect(['John Doe', 'Jane Doe']); + + 2  + + 3$collection->dump(); + + 4  + + 5/* + + 6 array:2 [ + + 7 0 => "John Doe" + + 8 1 => "Jane Doe" + + 9 ] + + 10*/ + + + $collection = collect(['John Doe', 'Jane Doe']); + + $collection->dump(); + + /* + array:2 [ + 0 => "John Doe" + 1 => "Jane Doe" + ] + */ + +If you want to stop executing the script after dumping the collection, use the +dd method instead. + +#### `duplicates()` + +The `duplicates` method retrieves and returns duplicate values from the +collection: + + + + 1$collection = collect(['a', 'b', 'a', 'c', 'b']); + + 2  + + 3$collection->duplicates(); + + 4  + + 5// [2 => 'a', 4 => 'b'] + + + $collection = collect(['a', 'b', 'a', 'c', 'b']); + + $collection->duplicates(); + + // [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: + + + + 1$employees = collect([ + + 2 ['email' => '[[email protected]](/cdn-cgi/l/email-protection)', 'position' => 'Developer'], + + 3 ['email' => '[[email protected]](/cdn-cgi/l/email-protection)', 'position' => 'Designer'], + + 4 ['email' => '[[email protected]](/cdn-cgi/l/email-protection)', 'position' => 'Developer'], + + 5]); + + 6  + + 7$employees->duplicates('position'); + + 8  + + 9// [2 => 'Developer'] + + + $employees = collect([ + ['email' => '[[email protected]](/cdn-cgi/l/email-protection)', 'position' => 'Developer'], + ['email' => '[[email protected]](/cdn-cgi/l/email-protection)', 'position' => 'Designer'], + ['email' => '[[email protected]](/cdn-cgi/l/email-protection)', 'position' => 'Developer'], + ]); + + $employees->duplicates('position'); + + // [2 => 'Developer'] + +#### `duplicatesStrict()` + +This method has the same signature as the duplicates method; however, all +values are compared using "strict" comparisons. + +#### `each()` + +The `each` method iterates over the items in the collection and passes each +item to a closure: + + + + 1$collection = collect([1, 2, 3, 4]); + + 2  + + 3$collection->each(function (int $item, int $key) { + + 4 // ... + + 5}); + + + $collection = collect([1, 2, 3, 4]); + + $collection->each(function (int $item, int $key) { + // ... + }); + +If you would like to stop iterating through the items, you may return `false` +from your closure: + + + + 1$collection->each(function (int $item, int $key) { + + 2 if (/* condition */) { + + 3 return false; + + 4 } + + 5}); + + + $collection->each(function (int $item, int $key) { + if (/* condition */) { + return false; + } + }); + +#### `eachSpread()` + +The `eachSpread` method iterates over the collection's items, passing each +nested item value into the given callback: + + + + 1$collection = collect([['John Doe', 35], ['Jane Doe', 33]]); + + 2  + + 3$collection->eachSpread(function (string $name, int $age) { + + 4 // ... + + 5}); + + + $collection = collect([['John Doe', 35], ['Jane Doe', 33]]); + + $collection->eachSpread(function (string $name, int $age) { + // ... + }); + +You may stop iterating through the items by returning `false` from the +callback: + + + + 1$collection->eachSpread(function (string $name, int $age) { + + 2 return false; + + 3}); + + + $collection->eachSpread(function (string $name, int $age) { + return false; + }); + +#### `ensure()` + +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: + + + + 1return $collection->ensure(User::class); + + 2  + + 3return $collection->ensure([User::class, Customer::class]); + + + return $collection->ensure(User::class); + + return $collection->ensure([User::class, Customer::class]); + +Primitive types such as `string`, `int`, `float`, `bool`, and `array` may also +be specified: + + + + 1return $collection->ensure('int'); + + + return $collection->ensure('int'); + +The `ensure` method does not guarantee that elements of different types will +not be added to the collection at a later time. + +#### `every()` + +The `every` method may be used to verify that all elements of a collection +pass a given truth test: + + + + 1collect([1, 2, 3, 4])->every(function (int $value, int $key) { + + 2 return $value > 2; + + 3}); + + 4  + + 5// false + + + collect([1, 2, 3, 4])->every(function (int $value, int $key) { + return $value > 2; + }); + + // false + +If the collection is empty, the `every` method will return true: + + + + 1$collection = collect([]); + + 2  + + 3$collection->every(function (int $value, int $key) { + + 4 return $value > 2; + + 5}); + + 6  + + 7// true + + + $collection = collect([]); + + $collection->every(function (int $value, int $key) { + return $value > 2; + }); + + // true + +#### `except()` + +The `except` method returns all items in the collection except for those with +the specified keys: + + + + 1$collection = collect(['product_id' => 1, 'price' => 100, 'discount' => false]); + + 2  + + 3$filtered = $collection->except(['price', 'discount']); + + 4  + + 5$filtered->all(); + + 6  + + 7// ['product_id' => 1] + + + $collection = collect(['product_id' => 1, 'price' => 100, 'discount' => false]); + + $filtered = $collection->except(['price', 'discount']); + + $filtered->all(); + + // ['product_id' => 1] + +For the inverse of `except`, see the only method. + +This method's behavior is modified when using [Eloquent +Collections](/docs/12.x/eloquent-collections#method-except). + +#### `filter()` + +The `filter` method filters the collection using the given callback, keeping +only those items that pass a given truth test: + + + + 1$collection = collect([1, 2, 3, 4]); + + 2  + + 3$filtered = $collection->filter(function (int $value, int $key) { + + 4 return $value > 2; + + 5}); + + 6  + + 7$filtered->all(); + + 8  + + 9// [3, 4] + + + $collection = collect([1, 2, 3, 4]); + + $filtered = $collection->filter(function (int $value, int $key) { + return $value > 2; + }); + + $filtered->all(); + + // [3, 4] + +If no callback is supplied, all entries of the collection that are equivalent +to `false` will be removed: + + + + 1$collection = collect([1, 2, 3, null, false, '', 0, []]); + + 2  + + 3$collection->filter()->all(); + + 4  + + 5// [1, 2, 3] + + + $collection = collect([1, 2, 3, null, false, '', 0, []]); + + $collection->filter()->all(); + + // [1, 2, 3] + +For the inverse of `filter`, see the reject method. + +#### `first()` + +The `first` method returns the first element in the collection that passes a +given truth test: + + + + 1collect([1, 2, 3, 4])->first(function (int $value, int $key) { + + 2 return $value > 2; + + 3}); + + 4  + + 5// 3 + + + collect([1, 2, 3, 4])->first(function (int $value, int $key) { + return $value > 2; + }); + + // 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: + + + + 1collect([1, 2, 3, 4])->first(); + + 2  + + 3// 1 + + + collect([1, 2, 3, 4])->first(); + + // 1 + +#### `firstOrFail()` + +The `firstOrFail` method is identical to the `first` method; however, if no +result is found, an `Illuminate\Support\ItemNotFoundException` exception will +be thrown: + + + + 1collect([1, 2, 3, 4])->firstOrFail(function (int $value, int $key) { + + 2 return $value > 5; + + 3}); + + 4  + + 5// Throws ItemNotFoundException... + + + collect([1, 2, 3, 4])->firstOrFail(function (int $value, int $key) { + return $value > 5; + }); + + // 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: + + + + 1collect([])->firstOrFail(); + + 2  + + 3// Throws ItemNotFoundException... + + + collect([])->firstOrFail(); + + // Throws ItemNotFoundException... + +#### `firstWhere()` + +The `firstWhere` method returns the first element in the collection with the +given key / value pair: + + + + 1$collection = collect([ + + 2 ['name' => 'Regena', 'age' => null], + + 3 ['name' => 'Linda', 'age' => 14], + + 4 ['name' => 'Diego', 'age' => 23], + + 5 ['name' => 'Linda', 'age' => 84], + + 6]); + + 7  + + 8$collection->firstWhere('name', 'Linda'); + + 9  + + 10// ['name' => 'Linda', 'age' => 14] + + + $collection = collect([ + ['name' => 'Regena', 'age' => null], + ['name' => 'Linda', 'age' => 14], + ['name' => 'Diego', 'age' => 23], + ['name' => 'Linda', 'age' => 84], + ]); + + $collection->firstWhere('name', 'Linda'); + + // ['name' => 'Linda', 'age' => 14] + +You may also call the `firstWhere` method with a comparison operator: + + + + 1$collection->firstWhere('age', '>=', 18); + + 2  + + 3// ['name' => 'Diego', 'age' => 23] + + + $collection->firstWhere('age', '>=', 18); + + // ['name' => 'Diego', 'age' => 23] + +Like the 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": + + + + 1$collection->firstWhere('age'); + + 2  + + 3// ['name' => 'Linda', 'age' => 14] + + + $collection->firstWhere('age'); + + // ['name' => 'Linda', 'age' => 14] + +#### `flatMap()` + +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: + + + + 1$collection = collect([ + + 2 ['name' => 'Sally'], + + 3 ['school' => 'Arkansas'], + + 4 ['age' => 28] + + 5]); + + 6  + + 7$flattened = $collection->flatMap(function (array $values) { + + 8 return array_map('strtoupper', $values); + + 9}); + + 10  + + 11$flattened->all(); + + 12  + + 13// ['name' => 'SALLY', 'school' => 'ARKANSAS', 'age' => '28']; + + + $collection = collect([ + ['name' => 'Sally'], + ['school' => 'Arkansas'], + ['age' => 28] + ]); + + $flattened = $collection->flatMap(function (array $values) { + return array_map('strtoupper', $values); + }); + + $flattened->all(); + + // ['name' => 'SALLY', 'school' => 'ARKANSAS', 'age' => '28']; + +#### `flatten()` + +The `flatten` method flattens a multi-dimensional collection into a single +dimension: + + + + 1$collection = collect([ + + 2 'name' => 'Taylor', + + 3 'languages' => [ + + 4 'PHP', 'JavaScript' + + 5 ] + + 6]); + + 7  + + 8$flattened = $collection->flatten(); + + 9  + + 10$flattened->all(); + + 11  + + 12// ['Taylor', 'PHP', 'JavaScript']; + + + $collection = collect([ + 'name' => 'Taylor', + 'languages' => [ + 'PHP', 'JavaScript' + ] + ]); + + $flattened = $collection->flatten(); + + $flattened->all(); + + // ['Taylor', 'PHP', 'JavaScript']; + +If necessary, you may pass the `flatten` method a "depth" argument: + + + + 1$collection = collect([ + + 2 'Apple' => [ + + 3 [ + + 4 'name' => 'iPhone 6S', + + 5 'brand' => 'Apple' + + 6 ], + + 7 ], + + 8 'Samsung' => [ + + 9 [ + + 10 'name' => 'Galaxy S7', + + 11 'brand' => 'Samsung' + + 12 ], + + 13 ], + + 14]); + + 15  + + 16$products = $collection->flatten(1); + + 17  + + 18$products->values()->all(); + + 19  + + 20/* + + 21 [ + + 22 ['name' => 'iPhone 6S', 'brand' => 'Apple'], + + 23 ['name' => 'Galaxy S7', 'brand' => 'Samsung'], + + 24 ] + + 25*/ + + + $collection = collect([ + 'Apple' => [ + [ + 'name' => 'iPhone 6S', + 'brand' => 'Apple' + ], + ], + 'Samsung' => [ + [ + 'name' => 'Galaxy S7', + 'brand' => 'Samsung' + ], + ], + ]); + + $products = $collection->flatten(1); + + $products->values()->all(); + + /* + [ + ['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. + +#### `flip()` + +The `flip` method swaps the collection's keys with their corresponding values: + + + + 1$collection = collect(['name' => 'Taylor', 'framework' => 'Laravel']); + + 2  + + 3$flipped = $collection->flip(); + + 4  + + 5$flipped->all(); + + 6  + + 7// ['Taylor' => 'name', 'Laravel' => 'framework'] + + + $collection = collect(['name' => 'Taylor', 'framework' => 'Laravel']); + + $flipped = $collection->flip(); + + $flipped->all(); + + // ['Taylor' => 'name', 'Laravel' => 'framework'] + +#### `forget()` + +The `forget` method removes an item from the collection by its key: + + + + 1$collection = collect(['name' => 'Taylor', 'framework' => 'Laravel']); + + 2  + + 3// Forget a single key... + + 4$collection->forget('name'); + + 5  + + 6// ['framework' => 'Laravel'] + + 7  + + 8// Forget multiple keys... + + 9$collection->forget(['name', 'framework']); + + 10  + + 11// [] + + + $collection = collect(['name' => 'Taylor', 'framework' => 'Laravel']); + + // Forget a single key... + $collection->forget('name'); + + // ['framework' => 'Laravel'] + + // Forget multiple keys... + $collection->forget(['name', 'framework']); + + // [] + +Unlike most other collection methods, `forget` does not return a new modified +collection; it modifies and returns the collection it is called on. + +#### `forPage()` + +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: + + + + 1$collection = collect([1, 2, 3, 4, 5, 6, 7, 8, 9]); + + 2  + + 3$chunk = $collection->forPage(2, 3); + + 4  + + 5$chunk->all(); + + 6  + + 7// [4, 5, 6] + + + $collection = collect([1, 2, 3, 4, 5, 6, 7, 8, 9]); + + $chunk = $collection->forPage(2, 3); + + $chunk->all(); + + // [4, 5, 6] + +#### `fromJson()` + +The static `fromJson` method creates a new collection instance by decoding a +given JSON string using the `json_decode` PHP function: + + + + 1use Illuminate\Support\Collection; + + 2  + + 3$json = json_encode([ + + 4 'name' => 'Taylor Otwell', + + 5 'role' => 'Developer', + + 6 'status' => 'Active', + + 7]); + + 8  + + 9$collection = Collection::fromJson($json); + + + use Illuminate\Support\Collection; + + $json = json_encode([ + 'name' => 'Taylor Otwell', + 'role' => 'Developer', + 'status' => 'Active', + ]); + + $collection = Collection::fromJson($json); + +#### `get()` + +The `get` method returns the item at a given key. If the key does not exist, +`null` is returned: + + + + 1$collection = collect(['name' => 'Taylor', 'framework' => 'Laravel']); + + 2  + + 3$value = $collection->get('name'); + + 4  + + 5// Taylor + + + $collection = collect(['name' => 'Taylor', 'framework' => 'Laravel']); + + $value = $collection->get('name'); + + // Taylor + +You may optionally pass a default value as the second argument: + + + + 1$collection = collect(['name' => 'Taylor', 'framework' => 'Laravel']); + + 2  + + 3$value = $collection->get('age', 34); + + 4  + + 5// 34 + + + $collection = collect(['name' => 'Taylor', 'framework' => 'Laravel']); + + $value = $collection->get('age', 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: + + + + 1$collection->get('email', function () { + + 2 return '[[email protected]](/cdn-cgi/l/email-protection)'; + + 3}); + + 4  + + 5// [[email protected]](/cdn-cgi/l/email-protection) + + + $collection->get('email', function () { + return '[[email protected]](/cdn-cgi/l/email-protection)'; + }); + + // [[email protected]](/cdn-cgi/l/email-protection) + +#### `groupBy()` + +The `groupBy` method groups the collection's items by a given key: + + + + 1$collection = collect([ + + 2 ['account_id' => 'account-x10', 'product' => 'Chair'], + + 3 ['account_id' => 'account-x10', 'product' => 'Bookcase'], + + 4 ['account_id' => 'account-x11', 'product' => 'Desk'], + + 5]); + + 6  + + 7$grouped = $collection->groupBy('account_id'); + + 8  + + 9$grouped->all(); + + 10  + + 11/* + + 12 [ + + 13 'account-x10' => [ + + 14 ['account_id' => 'account-x10', 'product' => 'Chair'], + + 15 ['account_id' => 'account-x10', 'product' => 'Bookcase'], + + 16 ], + + 17 'account-x11' => [ + + 18 ['account_id' => 'account-x11', 'product' => 'Desk'], + + 19 ], + + 20 ] + + 21*/ + + + $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->all(); + + /* + [ + '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: + + + + 1$grouped = $collection->groupBy(function (array $item, int $key) { + + 2 return substr($item['account_id'], -3); + + 3}); + + 4  + + 5$grouped->all(); + + 6  + + 7/* + + 8 [ + + 9 'x10' => [ + + 10 ['account_id' => 'account-x10', 'product' => 'Chair'], + + 11 ['account_id' => 'account-x10', 'product' => 'Bookcase'], + + 12 ], + + 13 'x11' => [ + + 14 ['account_id' => 'account-x11', 'product' => 'Desk'], + + 15 ], + + 16 ] + + 17*/ + + + $grouped = $collection->groupBy(function (array $item, int $key) { + return substr($item['account_id'], -3); + }); + + $grouped->all(); + + /* + [ + '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: + + + + 1$data = new Collection([ + + 2 10 => ['user' => 1, 'skill' => 1, 'roles' => ['Role_1', 'Role_3']], + + 3 20 => ['user' => 2, 'skill' => 1, 'roles' => ['Role_1', 'Role_2']], + + 4 30 => ['user' => 3, 'skill' => 2, 'roles' => ['Role_1']], + + 5 40 => ['user' => 4, 'skill' => 2, 'roles' => ['Role_2']], + + 6]); + + 7  + + 8$result = $data->groupBy(['skill', function (array $item) { + + 9 return $item['roles']; + + 10}], preserveKeys: true); + + 11  + + 12/* + + 13[ + + 14 1 => [ + + 15 'Role_1' => [ + + 16 10 => ['user' => 1, 'skill' => 1, 'roles' => ['Role_1', 'Role_3']], + + 17 20 => ['user' => 2, 'skill' => 1, 'roles' => ['Role_1', 'Role_2']], + + 18 ], + + 19 'Role_2' => [ + + 20 20 => ['user' => 2, 'skill' => 1, 'roles' => ['Role_1', 'Role_2']], + + 21 ], + + 22 'Role_3' => [ + + 23 10 => ['user' => 1, 'skill' => 1, 'roles' => ['Role_1', 'Role_3']], + + 24 ], + + 25 ], + + 26 2 => [ + + 27 'Role_1' => [ + + 28 30 => ['user' => 3, 'skill' => 2, 'roles' => ['Role_1']], + + 29 ], + + 30 'Role_2' => [ + + 31 40 => ['user' => 4, 'skill' => 2, 'roles' => ['Role_2']], + + 32 ], + + 33 ], + + 34]; + + 35*/ + + + $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); + + /* + [ + 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']], + ], + 'Role_2' => [ + 40 => ['user' => 4, 'skill' => 2, 'roles' => ['Role_2']], + ], + ], + ]; + */ + +#### `has()` + +The `has` method determines if a given key exists in the collection: + + + + 1$collection = collect(['account_id' => 1, 'product' => 'Desk', 'amount' => 5]); + + 2  + + 3$collection->has('product'); + + 4  + + 5// true + + 6  + + 7$collection->has(['product', 'amount']); + + 8  + + 9// true + + 10  + + 11$collection->has(['amount', 'price']); + + 12  + + 13// false + + + $collection = collect(['account_id' => 1, 'product' => 'Desk', 'amount' => 5]); + + $collection->has('product'); + + // true + + $collection->has(['product', 'amount']); + + // true + + $collection->has(['amount', 'price']); + + // false + +#### `hasAny()` + +The `hasAny` method determines whether any of the given keys exist in the +collection: + + + + 1$collection = collect(['account_id' => 1, 'product' => 'Desk', 'amount' => 5]); + + 2  + + 3$collection->hasAny(['product', 'price']); + + 4  + + 5// true + + 6  + + 7$collection->hasAny(['name', 'price']); + + 8  + + 9// false + + + $collection = collect(['account_id' => 1, 'product' => 'Desk', 'amount' => 5]); + + $collection->hasAny(['product', 'price']); + + // true + + $collection->hasAny(['name', 'price']); + + // false + +#### `implode()` + +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: + + + + 1$collection = collect([ + + 2 ['account_id' => 1, 'product' => 'Desk'], + + 3 ['account_id' => 2, 'product' => 'Chair'], + + 4]); + + 5  + + 6$collection->implode('product', ', '); + + 7  + + 8// 'Desk, Chair' + + + $collection = collect([ + ['account_id' => 1, 'product' => 'Desk'], + ['account_id' => 2, 'product' => 'Chair'], + ]); + + $collection->implode('product', ', '); + + // 'Desk, Chair' + +If the collection contains simple strings or numeric values, you should pass +the "glue" as the only argument to the method: + + + + 1collect([1, 2, 3, 4, 5])->implode('-'); + + 2  + + 3// '1-2-3-4-5' + + + collect([1, 2, 3, 4, 5])->implode('-'); + + // '1-2-3-4-5' + +You may pass a closure to the `implode` method if you would like to format the +values being imploded: + + + + 1$collection->implode(function (array $item, int $key) { + + 2 return strtoupper($item['product']); + + 3}, ', '); + + 4  + + 5// 'DESK, CHAIR' + + + $collection->implode(function (array $item, int $key) { + return strtoupper($item['product']); + }, ', '); + + // 'DESK, CHAIR' + +#### `intersect()` + +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: + + + + 1$collection = collect(['Desk', 'Sofa', 'Chair']); + + 2  + + 3$intersect = $collection->intersect(['Desk', 'Chair', 'Bookcase']); + + 4  + + 5$intersect->all(); + + 6  + + 7// [0 => 'Desk', 2 => 'Chair'] + + + $collection = collect(['Desk', 'Sofa', 'Chair']); + + $intersect = $collection->intersect(['Desk', 'Chair', 'Bookcase']); + + $intersect->all(); + + // [0 => 'Desk', 2 => 'Chair'] + +This method's behavior is modified when using [Eloquent +Collections](/docs/12.x/eloquent-collections#method-intersect). + +#### `intersectUsing()` + +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: + + + + 1$collection = collect(['Desk', 'Sofa', 'Chair']); + + 2  + + 3$intersect = $collection->intersectUsing(['desk', 'chair', 'bookcase'], function (string $a, string $b) { + + 4 return strcasecmp($a, $b); + + 5}); + + 6  + + 7$intersect->all(); + + 8  + + 9// [0 => 'Desk', 2 => 'Chair'] + + + $collection = collect(['Desk', 'Sofa', 'Chair']); + + $intersect = $collection->intersectUsing(['desk', 'chair', 'bookcase'], function (string $a, string $b) { + return strcasecmp($a, $b); + }); + + $intersect->all(); + + // [0 => 'Desk', 2 => 'Chair'] + +#### `intersectAssoc()` + +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: + + + + 1$collection = collect([ + + 2 'color' => 'red', + + 3 'size' => 'M', + + 4 'material' => 'cotton' + + 5]); + + 6  + + 7$intersect = $collection->intersectAssoc([ + + 8 'color' => 'blue', + + 9 'size' => 'M', + + 10 'material' => 'polyester' + + 11]); + + 12  + + 13$intersect->all(); + + 14  + + 15// ['size' => 'M'] + + + $collection = collect([ + 'color' => 'red', + 'size' => 'M', + 'material' => 'cotton' + ]); + + $intersect = $collection->intersectAssoc([ + 'color' => 'blue', + 'size' => 'M', + 'material' => 'polyester' + ]); + + $intersect->all(); + + // ['size' => 'M'] + +#### `intersectAssocUsing()` + +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: + + + + 1$collection = collect([ + + 2 'color' => 'red', + + 3 'Size' => 'M', + + 4 'material' => 'cotton', + + 5]); + + 6  + + 7$intersect = $collection->intersectAssocUsing([ + + 8 'color' => 'blue', + + 9 'size' => 'M', + + 10 'material' => 'polyester', + + 11], function (string $a, string $b) { + + 12 return strcasecmp($a, $b); + + 13}); + + 14  + + 15$intersect->all(); + + 16  + + 17// ['Size' => 'M'] + + + $collection = collect([ + 'color' => 'red', + 'Size' => 'M', + 'material' => 'cotton', + ]); + + $intersect = $collection->intersectAssocUsing([ + 'color' => 'blue', + 'size' => 'M', + 'material' => 'polyester', + ], function (string $a, string $b) { + return strcasecmp($a, $b); + }); + + $intersect->all(); + + // ['Size' => 'M'] + +#### `intersectByKeys()` + +The `intersectByKeys` method removes any keys and their corresponding values +from the original collection that are not present in the given array or +collection: + + + + 1$collection = collect([ + + 2 'serial' => 'UX301', 'type' => 'screen', 'year' => 2009, + + 3]); + + 4  + + 5$intersect = $collection->intersectByKeys([ + + 6 'reference' => 'UX404', 'type' => 'tab', 'year' => 2011, + + 7]); + + 8  + + 9$intersect->all(); + + 10  + + 11// ['type' => 'screen', 'year' => 2009] + + + $collection = collect([ + 'serial' => 'UX301', 'type' => 'screen', 'year' => 2009, + ]); + + $intersect = $collection->intersectByKeys([ + 'reference' => 'UX404', 'type' => 'tab', 'year' => 2011, + ]); + + $intersect->all(); + + // ['type' => 'screen', 'year' => 2009] + +#### `isEmpty()` + +The `isEmpty` method returns `true` if the collection is empty; otherwise, +`false` is returned: + + + + 1collect([])->isEmpty(); + + 2  + + 3// true + + + collect([])->isEmpty(); + + // true + +#### `isNotEmpty()` + +The `isNotEmpty` method returns `true` if the collection is not empty; +otherwise, `false` is returned: + + + + 1collect([])->isNotEmpty(); + + 2  + + 3// false + + + collect([])->isNotEmpty(); + + // false + +#### `join()` + +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: + + + + 1collect(['a', 'b', 'c'])->join(', '); // 'a, b, c' + + 2collect(['a', 'b', 'c'])->join(', ', ', and '); // 'a, b, and c' + + 3collect(['a', 'b'])->join(', ', ' and '); // 'a and b' + + 4collect(['a'])->join(', ', ' and '); // 'a' + + 5collect([])->join(', ', ' and '); // '' + + + 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()` + +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: + + + + 1$collection = collect([ + + 2 ['product_id' => 'prod-100', 'name' => 'Desk'], + + 3 ['product_id' => 'prod-200', 'name' => 'Chair'], + + 4]); + + 5  + + 6$keyed = $collection->keyBy('product_id'); + + 7  + + 8$keyed->all(); + + 9  + + 10/* + + 11 [ + + 12 'prod-100' => ['product_id' => 'prod-100', 'name' => 'Desk'], + + 13 'prod-200' => ['product_id' => 'prod-200', 'name' => 'Chair'], + + 14 ] + + 15*/ + + + $collection = collect([ + ['product_id' => 'prod-100', 'name' => 'Desk'], + ['product_id' => 'prod-200', 'name' => 'Chair'], + ]); + + $keyed = $collection->keyBy('product_id'); + + $keyed->all(); + + /* + [ + '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: + + + + 1$keyed = $collection->keyBy(function (array $item, int $key) { + + 2 return strtoupper($item['product_id']); + + 3}); + + 4  + + 5$keyed->all(); + + 6  + + 7/* + + 8 [ + + 9 'PROD-100' => ['product_id' => 'prod-100', 'name' => 'Desk'], + + 10 'PROD-200' => ['product_id' => 'prod-200', 'name' => 'Chair'], + + 11 ] + + 12*/ + + + $keyed = $collection->keyBy(function (array $item, int $key) { + return strtoupper($item['product_id']); + }); + + $keyed->all(); + + /* + [ + 'PROD-100' => ['product_id' => 'prod-100', 'name' => 'Desk'], + 'PROD-200' => ['product_id' => 'prod-200', 'name' => 'Chair'], + ] + */ + +#### `keys()` + +The `keys` method returns all of the collection's keys: + + + + 1$collection = collect([ + + 2 'prod-100' => ['product_id' => 'prod-100', 'name' => 'Desk'], + + 3 'prod-200' => ['product_id' => 'prod-200', 'name' => 'Chair'], + + 4]); + + 5  + + 6$keys = $collection->keys(); + + 7  + + 8$keys->all(); + + 9  + + 10// ['prod-100', 'prod-200'] + + + $collection = collect([ + 'prod-100' => ['product_id' => 'prod-100', 'name' => 'Desk'], + 'prod-200' => ['product_id' => 'prod-200', 'name' => 'Chair'], + ]); + + $keys = $collection->keys(); + + $keys->all(); + + // ['prod-100', 'prod-200'] + +#### `last()` + +The `last` method returns the last element in the collection that passes a +given truth test: + + + + 1collect([1, 2, 3, 4])->last(function (int $value, int $key) { + + 2 return $value < 3; + + 3}); + + 4  + + 5// 2 + + + collect([1, 2, 3, 4])->last(function (int $value, int $key) { + return $value < 3; + }); + + // 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: + + + + 1collect([1, 2, 3, 4])->last(); + + 2  + + 3// 4 + + + collect([1, 2, 3, 4])->last(); + + // 4 + +#### `lazy()` + +The `lazy` method returns a new LazyCollection instance from the underlying +array of items: + + + + 1$lazyCollection = collect([1, 2, 3, 4])->lazy(); + + 2  + + 3$lazyCollection::class; + + 4  + + 5// Illuminate\Support\LazyCollection + + 6  + + 7$lazyCollection->all(); + + 8  + + 9// [1, 2, 3, 4] + + + $lazyCollection = collect([1, 2, 3, 4])->lazy(); + + $lazyCollection::class; + + // Illuminate\Support\LazyCollection + + $lazyCollection->all(); + + // [1, 2, 3, 4] + +This is especially useful when you need to perform transformations on a huge +`Collection` that contains many items: + + + + 1$count = $hugeCollection + + 2 ->lazy() + + 3 ->where('country', 'FR') + + 4 ->where('balance', '>', '100') + + 5 ->count(); + + + $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. + +#### `macro()` + +The static `macro` method allows you to add methods to the `Collection` class +at run time. Refer to the documentation on extending collections for more +information. + +#### `make()` + +The static `make` method creates a new collection instance. See the Creating +Collections section. + + + + 1use Illuminate\Support\Collection; + + 2  + + 3$collection = Collection::make([1, 2, 3]); + + + use Illuminate\Support\Collection; + + $collection = Collection::make([1, 2, 3]); + +#### `map()` + +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: + + + + 1$collection = collect([1, 2, 3, 4, 5]); + + 2  + + 3$multiplied = $collection->map(function (int $item, int $key) { + + 4 return $item * 2; + + 5}); + + 6  + + 7$multiplied->all(); + + 8  + + 9// [2, 4, 6, 8, 10] + + + $collection = collect([1, 2, 3, 4, 5]); + + $multiplied = $collection->map(function (int $item, int $key) { + return $item * 2; + }); + + $multiplied->all(); + + // [2, 4, 6, 8, 10] + +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. + +#### `mapInto()` + +The `mapInto()` method iterates over the collection, creating a new instance +of the given class by passing the value into the constructor: + + + + 1class Currency + + 2{ + + 3 /** + + 4 * Create a new currency instance. + + 5 */ + + 6 function __construct( + + 7 public string $code, + + 8 ) {} + + 9} + + 10  + + 11$collection = collect(['USD', 'EUR', 'GBP']); + + 12  + + 13$currencies = $collection->mapInto(Currency::class); + + 14  + + 15$currencies->all(); + + 16  + + 17// [Currency('USD'), Currency('EUR'), Currency('GBP')] + + + class Currency + { + /** + * Create a new currency instance. + */ + function __construct( + public string $code, + ) {} + } + + $collection = collect(['USD', 'EUR', 'GBP']); + + $currencies = $collection->mapInto(Currency::class); + + $currencies->all(); + + // [Currency('USD'), Currency('EUR'), Currency('GBP')] + +#### `mapSpread()` + +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: + + + + 1$collection = collect([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]); + + 2  + + 3$chunks = $collection->chunk(2); + + 4  + + 5$sequence = $chunks->mapSpread(function (int $even, int $odd) { + + 6 return $even + $odd; + + 7}); + + 8  + + 9$sequence->all(); + + 10  + + 11// [1, 5, 9, 13, 17] + + + $collection = collect([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]); + + $chunks = $collection->chunk(2); + + $sequence = $chunks->mapSpread(function (int $even, int $odd) { + return $even + $odd; + }); + + $sequence->all(); + + // [1, 5, 9, 13, 17] + +#### `mapToGroups()` + +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: + + + + 1$collection = collect([ + + 2 [ + + 3 'name' => 'John Doe', + + 4 'department' => 'Sales', + + 5 ], + + 6 [ + + 7 'name' => 'Jane Doe', + + 8 'department' => 'Sales', + + 9 ], + + 10 [ + + 11 'name' => 'Johnny Doe', + + 12 'department' => 'Marketing', + + 13 ] + + 14]); + + 15  + + 16$grouped = $collection->mapToGroups(function (array $item, int $key) { + + 17 return [$item['department'] => $item['name']]; + + 18}); + + 19  + + 20$grouped->all(); + + 21  + + 22/* + + 23 [ + + 24 'Sales' => ['John Doe', 'Jane Doe'], + + 25 'Marketing' => ['Johnny Doe'], + + 26 ] + + 27*/ + + 28  + + 29$grouped->get('Sales')->all(); + + 30  + + 31// ['John Doe', 'Jane Doe'] + + + $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->all(); + + /* + [ + 'Sales' => ['John Doe', 'Jane Doe'], + 'Marketing' => ['Johnny Doe'], + ] + */ + + $grouped->get('Sales')->all(); + + // ['John Doe', 'Jane Doe'] + +#### `mapWithKeys()` + +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: + + + + 1$collection = collect([ + + 2 [ + + 3 'name' => 'John', + + 4 'department' => 'Sales', + + 5 'email' => '[[email protected]](/cdn-cgi/l/email-protection)', + + 6 ], + + 7 [ + + 8 'name' => 'Jane', + + 9 'department' => 'Marketing', + + 10 'email' => '[[email protected]](/cdn-cgi/l/email-protection)', + + 11 ] + + 12]); + + 13  + + 14$keyed = $collection->mapWithKeys(function (array $item, int $key) { + + 15 return [$item['email'] => $item['name']]; + + 16}); + + 17  + + 18$keyed->all(); + + 19  + + 20/* + + 21 [ + + 22 '[[email protected]](/cdn-cgi/l/email-protection)' => 'John', + + 23 '[[email protected]](/cdn-cgi/l/email-protection)' => 'Jane', + + 24 ] + + 25*/ + + + $collection = collect([ + [ + 'name' => 'John', + 'department' => 'Sales', + 'email' => '[[email protected]](/cdn-cgi/l/email-protection)', + ], + [ + 'name' => 'Jane', + 'department' => 'Marketing', + 'email' => '[[email protected]](/cdn-cgi/l/email-protection)', + ] + ]); + + $keyed = $collection->mapWithKeys(function (array $item, int $key) { + return [$item['email'] => $item['name']]; + }); + + $keyed->all(); + + /* + [ + '[[email protected]](/cdn-cgi/l/email-protection)' => 'John', + '[[email protected]](/cdn-cgi/l/email-protection)' => 'Jane', + ] + */ + +#### `max()` + +The `max` method returns the maximum value of a given key: + + + + 1$max = collect([ + + 2 ['foo' => 10], + + 3 ['foo' => 20] + + 4])->max('foo'); + + 5  + + 6// 20 + + 7  + + 8$max = collect([1, 2, 3, 4, 5])->max(); + + 9  + + 10// 5 + + + $max = collect([ + ['foo' => 10], + ['foo' => 20] + ])->max('foo'); + + // 20 + + $max = collect([1, 2, 3, 4, 5])->max(); + + // 5 + +#### `median()` + +The `median` method returns the [median +value](https://en.wikipedia.org/wiki/Median) of a given key: + + + + 1$median = collect([ + + 2 ['foo' => 10], + + 3 ['foo' => 10], + + 4 ['foo' => 20], + + 5 ['foo' => 40] + + 6])->median('foo'); + + 7  + + 8// 15 + + 9  + + 10$median = collect([1, 1, 2, 4])->median(); + + 11  + + 12// 1.5 + + + $median = collect([ + ['foo' => 10], + ['foo' => 10], + ['foo' => 20], + ['foo' => 40] + ])->median('foo'); + + // 15 + + $median = collect([1, 1, 2, 4])->median(); + + // 1.5 + +#### `merge()` + +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: + + + + 1$collection = collect(['product_id' => 1, 'price' => 100]); + + 2  + + 3$merged = $collection->merge(['price' => 200, 'discount' => false]); + + 4  + + 5$merged->all(); + + 6  + + 7// ['product_id' => 1, 'price' => 200, 'discount' => false] + + + $collection = collect(['product_id' => 1, 'price' => 100]); + + $merged = $collection->merge(['price' => 200, 'discount' => false]); + + $merged->all(); + + // ['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: + + + + 1$collection = collect(['Desk', 'Chair']); + + 2  + + 3$merged = $collection->merge(['Bookcase', 'Door']); + + 4  + + 5$merged->all(); + + 6  + + 7// ['Desk', 'Chair', 'Bookcase', 'Door'] + + + $collection = collect(['Desk', 'Chair']); + + $merged = $collection->merge(['Bookcase', 'Door']); + + $merged->all(); + + // ['Desk', 'Chair', 'Bookcase', 'Door'] + +#### `mergeRecursive()` + +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: + + + + 1$collection = collect(['product_id' => 1, 'price' => 100]); + + 2  + + 3$merged = $collection->mergeRecursive([ + + 4 'product_id' => 2, + + 5 'price' => 200, + + 6 'discount' => false + + 7]); + + 8  + + 9$merged->all(); + + 10  + + 11// ['product_id' => [1, 2], 'price' => [100, 200], 'discount' => false] + + + $collection = collect(['product_id' => 1, 'price' => 100]); + + $merged = $collection->mergeRecursive([ + 'product_id' => 2, + 'price' => 200, + 'discount' => false + ]); + + $merged->all(); + + // ['product_id' => [1, 2], 'price' => [100, 200], 'discount' => false] + +#### `min()` + +The `min` method returns the minimum value of a given key: + + + + 1$min = collect([['foo' => 10], ['foo' => 20]])->min('foo'); + + 2  + + 3// 10 + + 4  + + 5$min = collect([1, 2, 3, 4, 5])->min(); + + 6  + + 7// 1 + + + $min = collect([['foo' => 10], ['foo' => 20]])->min('foo'); + + // 10 + + $min = collect([1, 2, 3, 4, 5])->min(); + + // 1 + +#### `mode()` + +The `mode` method returns the [mode +value](https://en.wikipedia.org/wiki/Mode_\(statistics\)) of a given key: + + + + 1$mode = collect([ + + 2 ['foo' => 10], + + 3 ['foo' => 10], + + 4 ['foo' => 20], + + 5 ['foo' => 40] + + 6])->mode('foo'); + + 7  + + 8// [10] + + 9  + + 10$mode = collect([1, 1, 2, 4])->mode(); + + 11  + + 12// [1] + + 13  + + 14$mode = collect([1, 1, 2, 2])->mode(); + + 15  + + 16// [1, 2] + + + $mode = collect([ + ['foo' => 10], + ['foo' => 10], + ['foo' => 20], + ['foo' => 40] + ])->mode('foo'); + + // [10] + + $mode = collect([1, 1, 2, 4])->mode(); + + // [1] + + $mode = collect([1, 1, 2, 2])->mode(); + + // [1, 2] + +#### `multiply()` + +The `multiply` method creates the specified number of copies of all items in +the collection: + + + + 1$users = collect([ + + 2 ['name' => 'User #1', 'email' => '[[email protected]](/cdn-cgi/l/email-protection)'], + + 3 ['name' => 'User #2', 'email' => '[[email protected]](/cdn-cgi/l/email-protection)'], + + 4])->multiply(3); + + 5  + + 6/* + + 7 [ + + 8 ['name' => 'User #1', 'email' => '[[email protected]](/cdn-cgi/l/email-protection)'], + + 9 ['name' => 'User #2', 'email' => '[[email protected]](/cdn-cgi/l/email-protection)'], + + 10 ['name' => 'User #1', 'email' => '[[email protected]](/cdn-cgi/l/email-protection)'], + + 11 ['name' => 'User #2', 'email' => '[[email protected]](/cdn-cgi/l/email-protection)'], + + 12 ['name' => 'User #1', 'email' => '[[email protected]](/cdn-cgi/l/email-protection)'], + + 13 ['name' => 'User #2', 'email' => '[[email protected]](/cdn-cgi/l/email-protection)'], + + 14 ] + + 15*/ + + + $users = collect([ + ['name' => 'User #1', 'email' => '[[email protected]](/cdn-cgi/l/email-protection)'], + ['name' => 'User #2', 'email' => '[[email protected]](/cdn-cgi/l/email-protection)'], + ])->multiply(3); + + /* + [ + ['name' => 'User #1', 'email' => '[[email protected]](/cdn-cgi/l/email-protection)'], + ['name' => 'User #2', 'email' => '[[email protected]](/cdn-cgi/l/email-protection)'], + ['name' => 'User #1', 'email' => '[[email protected]](/cdn-cgi/l/email-protection)'], + ['name' => 'User #2', 'email' => '[[email protected]](/cdn-cgi/l/email-protection)'], + ['name' => 'User #1', 'email' => '[[email protected]](/cdn-cgi/l/email-protection)'], + ['name' => 'User #2', 'email' => '[[email protected]](/cdn-cgi/l/email-protection)'], + ] + */ + +#### `nth()` + +The `nth` method creates a new collection consisting of every n-th element: + + + + 1$collection = collect(['a', 'b', 'c', 'd', 'e', 'f']); + + 2  + + 3$collection->nth(4); + + 4  + + 5// ['a', 'e'] + + + $collection = collect(['a', 'b', 'c', 'd', 'e', 'f']); + + $collection->nth(4); + + // ['a', 'e'] + +You may optionally pass a starting offset as the second argument: + + + + 1$collection->nth(4, 1); + + 2  + + 3// ['b', 'f'] + + + $collection->nth(4, 1); + + // ['b', 'f'] + +#### `only()` + +The `only` method returns the items in the collection with the specified keys: + + + + 1$collection = collect([ + + 2 'product_id' => 1, + + 3 'name' => 'Desk', + + 4 'price' => 100, + + 5 'discount' => false + + 6]); + + 7  + + 8$filtered = $collection->only(['product_id', 'name']); + + 9  + + 10$filtered->all(); + + 11  + + 12// ['product_id' => 1, 'name' => 'Desk'] + + + $collection = collect([ + 'product_id' => 1, + 'name' => 'Desk', + 'price' => 100, + 'discount' => false + ]); + + $filtered = $collection->only(['product_id', 'name']); + + $filtered->all(); + + // ['product_id' => 1, 'name' => 'Desk'] + +For the inverse of `only`, see the except method. + +This method's behavior is modified when using [Eloquent +Collections](/docs/12.x/eloquent-collections#method-only). + +#### `pad()` + +The `pad` method will fill the array with the given value until the array +reaches the specified size. This method behaves like the +[array_pad](https://secure.php.net/manual/en/function.array-pad.php) PHP +function. + +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: + + + + 1$collection = collect(['A', 'B', 'C']); + + 2  + + 3$filtered = $collection->pad(5, 0); + + 4  + + 5$filtered->all(); + + 6  + + 7// ['A', 'B', 'C', 0, 0] + + 8  + + 9$filtered = $collection->pad(-5, 0); + + 10  + + 11$filtered->all(); + + 12  + + 13// [0, 0, 'A', 'B', 'C'] + + + $collection = collect(['A', 'B', 'C']); + + $filtered = $collection->pad(5, 0); + + $filtered->all(); + + // ['A', 'B', 'C', 0, 0] + + $filtered = $collection->pad(-5, 0); + + $filtered->all(); + + // [0, 0, 'A', 'B', 'C'] + +#### `partition()` + +The `partition` method may be combined with PHP array destructuring to +separate elements that pass a given truth test from those that do not: + + + + 1$collection = collect([1, 2, 3, 4, 5, 6]); + + 2  + + 3[$underThree, $equalOrAboveThree] = $collection->partition(function (int $i) { + + 4 return $i < 3; + + 5}); + + 6  + + 7$underThree->all(); + + 8  + + 9// [1, 2] + + 10  + + 11$equalOrAboveThree->all(); + + 12  + + 13// [3, 4, 5, 6] + + + $collection = collect([1, 2, 3, 4, 5, 6]); + + [$underThree, $equalOrAboveThree] = $collection->partition(function (int $i) { + return $i < 3; + }); + + $underThree->all(); + + // [1, 2] + + $equalOrAboveThree->all(); + + // [3, 4, 5, 6] + +This method's behavior is modified when interacting with [Eloquent +collections](/docs/12.x/eloquent-collections#method-partition). + +#### `percentage()` + +The `percentage` method may be used to quickly determine the percentage of +items in the collection that pass a given truth test: + + + + 1$collection = collect([1, 1, 2, 2, 2, 3]); + + 2  + + 3$percentage = $collection->percentage(fn (int $value) => $value === 1); + + 4  + + 5// 33.33 + + + $collection = collect([1, 1, 2, 2, 2, 3]); + + $percentage = $collection->percentage(fn (int $value) => $value === 1); + + // 33.33 + +By default, the percentage will be rounded to two decimal places. However, you +may customize this behavior by providing a second argument to the method: + + + + 1$percentage = $collection->percentage(fn (int $value) => $value === 1, precision: 3); + + 2  + + 3// 33.333 + + + $percentage = $collection->percentage(fn (int $value) => $value === 1, precision: 3); + + // 33.333 + +#### `pipe()` + +The `pipe` method passes the collection to the given closure and returns the +result of the executed closure: + + + + 1$collection = collect([1, 2, 3]); + + 2  + + 3$piped = $collection->pipe(function (Collection $collection) { + + 4 return $collection->sum(); + + 5}); + + 6  + + 7// 6 + + + $collection = collect([1, 2, 3]); + + $piped = $collection->pipe(function (Collection $collection) { + return $collection->sum(); + }); + + // 6 + +#### `pipeInto()` + +The `pipeInto` method creates a new instance of the given class and passes the +collection into the constructor: + + + + 1class ResourceCollection + + 2{ + + 3 /** + + 4 * Create a new ResourceCollection instance. + + 5 */ + + 6 public function __construct( + + 7 public Collection $collection, + + 8 ) {} + + 9} + + 10  + + 11$collection = collect([1, 2, 3]); + + 12  + + 13$resource = $collection->pipeInto(ResourceCollection::class); + + 14  + + 15$resource->collection->all(); + + 16  + + 17// [1, 2, 3] + + + class ResourceCollection + { + /** + * Create a new ResourceCollection instance. + */ + public function __construct( + public Collection $collection, + ) {} + } + + $collection = collect([1, 2, 3]); + + $resource = $collection->pipeInto(ResourceCollection::class); + + $resource->collection->all(); + + // [1, 2, 3] + +#### `pipeThrough()` + +The `pipeThrough` method passes the collection to the given array of closures +and returns the result of the executed closures: + + + + 1use Illuminate\Support\Collection; + + 2  + + 3$collection = collect([1, 2, 3]); + + 4  + + 5$result = $collection->pipeThrough([ + + 6 function (Collection $collection) { + + 7 return $collection->merge([4, 5]); + + 8 }, + + 9 function (Collection $collection) { + + 10 return $collection->sum(); + + 11 }, + + 12]); + + 13  + + 14// 15 + + + use Illuminate\Support\Collection; + + $collection = collect([1, 2, 3]); + + $result = $collection->pipeThrough([ + function (Collection $collection) { + return $collection->merge([4, 5]); + }, + function (Collection $collection) { + return $collection->sum(); + }, + ]); + + // 15 + +#### `pluck()` + +The `pluck` method retrieves all of the values for a given key: + + + + 1$collection = collect([ + + 2 ['product_id' => 'prod-100', 'name' => 'Desk'], + + 3 ['product_id' => 'prod-200', 'name' => 'Chair'], + + 4]); + + 5  + + 6$plucked = $collection->pluck('name'); + + 7  + + 8$plucked->all(); + + 9  + + 10// ['Desk', 'Chair'] + + + $collection = collect([ + ['product_id' => 'prod-100', 'name' => 'Desk'], + ['product_id' => 'prod-200', 'name' => 'Chair'], + ]); + + $plucked = $collection->pluck('name'); + + $plucked->all(); + + // ['Desk', 'Chair'] + +You may also specify how you wish the resulting collection to be keyed: + + + + 1$plucked = $collection->pluck('name', 'product_id'); + + 2  + + 3$plucked->all(); + + 4  + + 5// ['prod-100' => 'Desk', 'prod-200' => 'Chair'] + + + $plucked = $collection->pluck('name', 'product_id'); + + $plucked->all(); + + // ['prod-100' => 'Desk', 'prod-200' => 'Chair'] + +The `pluck` method also supports retrieving nested values using "dot" +notation: + + + + 1$collection = collect([ + + 2 [ + + 3 'name' => 'Laracon', + + 4 'speakers' => [ + + 5 'first_day' => ['Rosa', 'Judith'], + + 6 ], + + 7 ], + + 8 [ + + 9 'name' => 'VueConf', + + 10 'speakers' => [ + + 11 'first_day' => ['Abigail', 'Joey'], + + 12 ], + + 13 ], + + 14]); + + 15  + + 16$plucked = $collection->pluck('speakers.first_day'); + + 17  + + 18$plucked->all(); + + 19  + + 20// [['Rosa', 'Judith'], ['Abigail', 'Joey']] + + + $collection = collect([ + [ + 'name' => 'Laracon', + 'speakers' => [ + 'first_day' => ['Rosa', 'Judith'], + ], + ], + [ + 'name' => 'VueConf', + 'speakers' => [ + 'first_day' => ['Abigail', 'Joey'], + ], + ], + ]); + + $plucked = $collection->pluck('speakers.first_day'); + + $plucked->all(); + + // [['Rosa', 'Judith'], ['Abigail', 'Joey']] + +If duplicate keys exist, the last matching element will be inserted into the +plucked collection: + + + + 1$collection = collect([ + + 2 ['brand' => 'Tesla', 'color' => 'red'], + + 3 ['brand' => 'Pagani', 'color' => 'white'], + + 4 ['brand' => 'Tesla', 'color' => 'black'], + + 5 ['brand' => 'Pagani', 'color' => 'orange'], + + 6]); + + 7  + + 8$plucked = $collection->pluck('color', 'brand'); + + 9  + + 10$plucked->all(); + + 11  + + 12// ['Tesla' => 'black', 'Pagani' => 'orange'] + + + $collection = collect([ + ['brand' => 'Tesla', 'color' => 'red'], + ['brand' => 'Pagani', 'color' => 'white'], + ['brand' => 'Tesla', 'color' => 'black'], + ['brand' => 'Pagani', 'color' => 'orange'], + ]); + + $plucked = $collection->pluck('color', 'brand'); + + $plucked->all(); + + // ['Tesla' => 'black', 'Pagani' => 'orange'] + +#### `pop()` + +The `pop` method removes and returns the last item from the collection. If the +collection is empty, `null` will be returned: + + + + 1$collection = collect([1, 2, 3, 4, 5]); + + 2  + + 3$collection->pop(); + + 4  + + 5// 5 + + 6  + + 7$collection->all(); + + 8  + + 9// [1, 2, 3, 4] + + + $collection = collect([1, 2, 3, 4, 5]); + + $collection->pop(); + + // 5 + + $collection->all(); + + // [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: + + + + 1$collection = collect([1, 2, 3, 4, 5]); + + 2  + + 3$collection->pop(3); + + 4  + + 5// collect([5, 4, 3]) + + 6  + + 7$collection->all(); + + 8  + + 9// [1, 2] + + + $collection = collect([1, 2, 3, 4, 5]); + + $collection->pop(3); + + // collect([5, 4, 3]) + + $collection->all(); + + // [1, 2] + +#### `prepend()` + +The `prepend` method adds an item to the beginning of the collection: + + + + 1$collection = collect([1, 2, 3, 4, 5]); + + 2  + + 3$collection->prepend(0); + + 4  + + 5$collection->all(); + + 6  + + 7// [0, 1, 2, 3, 4, 5] + + + $collection = collect([1, 2, 3, 4, 5]); + + $collection->prepend(0); + + $collection->all(); + + // [0, 1, 2, 3, 4, 5] + +You may also pass a second argument to specify the key of the prepended item: + + + + 1$collection = collect(['one' => 1, 'two' => 2]); + + 2  + + 3$collection->prepend(0, 'zero'); + + 4  + + 5$collection->all(); + + 6  + + 7// ['zero' => 0, 'one' => 1, 'two' => 2] + + + $collection = collect(['one' => 1, 'two' => 2]); + + $collection->prepend(0, 'zero'); + + $collection->all(); + + // ['zero' => 0, 'one' => 1, 'two' => 2] + +#### `pull()` + +The `pull` method removes and returns an item from the collection by its key: + + + + 1$collection = collect(['product_id' => 'prod-100', 'name' => 'Desk']); + + 2  + + 3$collection->pull('name'); + + 4  + + 5// 'Desk' + + 6  + + 7$collection->all(); + + 8  + + 9// ['product_id' => 'prod-100'] + + + $collection = collect(['product_id' => 'prod-100', 'name' => 'Desk']); + + $collection->pull('name'); + + // 'Desk' + + $collection->all(); + + // ['product_id' => 'prod-100'] + +#### `push()` + +The `push` method appends an item to the end of the collection: + + + + 1$collection = collect([1, 2, 3, 4]); + + 2  + + 3$collection->push(5); + + 4  + + 5$collection->all(); + + 6  + + 7// [1, 2, 3, 4, 5] + + + $collection = collect([1, 2, 3, 4]); + + $collection->push(5); + + $collection->all(); + + // [1, 2, 3, 4, 5] + +You may also provide multiple items to append to the end of the collection: + + + + 1$collection = collect([1, 2, 3, 4]); + + 2  + + 3$collection->push(5, 6, 7); + + 4  + + 5$collection->all(); + + 6  + + 7// [1, 2, 3, 4, 5, 6, 7] + + + $collection = collect([1, 2, 3, 4]); + + $collection->push(5, 6, 7); + + $collection->all(); + + // [1, 2, 3, 4, 5, 6, 7] + +#### `put()` + +The `put` method sets the given key and value in the collection: + + + + 1$collection = collect(['product_id' => 1, 'name' => 'Desk']); + + 2  + + 3$collection->put('price', 100); + + 4  + + 5$collection->all(); + + 6  + + 7// ['product_id' => 1, 'name' => 'Desk', 'price' => 100] + + + $collection = collect(['product_id' => 1, 'name' => 'Desk']); + + $collection->put('price', 100); + + $collection->all(); + + // ['product_id' => 1, 'name' => 'Desk', 'price' => 100] + +#### `random()` + +The `random` method returns a random item from the collection: + + + + 1$collection = collect([1, 2, 3, 4, 5]); + + 2  + + 3$collection->random(); + + 4  + + 5// 4 - (retrieved randomly) + + + $collection = collect([1, 2, 3, 4, 5]); + + $collection->random(); + + // 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: + + + + 1$random = $collection->random(3); + + 2  + + 3$random->all(); + + 4  + + 5// [2, 4, 5] - (retrieved randomly) + + + $random = $collection->random(3); + + $random->all(); + + // [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: + + + + 1use Illuminate\Support\Collection; + + 2  + + 3$random = $collection->random(fn (Collection $items) => min(10, count($items))); + + 4  + + 5$random->all(); + + 6  + + 7// [1, 2, 3, 4, 5] - (retrieved randomly) + + + use Illuminate\Support\Collection; + + $random = $collection->random(fn (Collection $items) => min(10, count($items))); + + $random->all(); + + // [1, 2, 3, 4, 5] - (retrieved randomly) + +#### `range()` + +The `range` method returns a collection containing integers between the +specified range: + + + + 1$collection = collect()->range(3, 6); + + 2  + + 3$collection->all(); + + 4  + + 5// [3, 4, 5, 6] + + + $collection = collect()->range(3, 6); + + $collection->all(); + + // [3, 4, 5, 6] + +#### `reduce()` + +The `reduce` method reduces the collection to a single value, passing the +result of each iteration into the subsequent iteration: + + + + 1$collection = collect([1, 2, 3]); + + 2  + + 3$total = $collection->reduce(function (?int $carry, int $item) { + + 4 return $carry + $item; + + 5}); + + 6  + + 7// 6 + + + $collection = collect([1, 2, 3]); + + $total = $collection->reduce(function (?int $carry, int $item) { + return $carry + $item; + }); + + // 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`: + + + + 1$collection->reduce(function (int $carry, int $item) { + + 2 return $carry + $item; + + 3}, 4); + + 4  + + 5// 10 + + + $collection->reduce(function (int $carry, int $item) { + return $carry + $item; + }, 4); + + // 10 + +The `reduce` method also passes array keys to the given callback: + + + + 1$collection = collect([ + + 2 'usd' => 1400, + + 3 'gbp' => 1200, + + 4 'eur' => 1000, + + 5]); + + 6  + + 7$ratio = [ + + 8 'usd' => 1, + + 9 'gbp' => 1.37, + + 10 'eur' => 1.22, + + 11]; + + 12  + + 13$collection->reduce(function (int $carry, int $value, string $key) use ($ratio) { + + 14 return $carry + ($value * $ratio[$key]); + + 15}, 0); + + 16  + + 17// 4264 + + + $collection = collect([ + 'usd' => 1400, + 'gbp' => 1200, + 'eur' => 1000, + ]); + + $ratio = [ + 'usd' => 1, + 'gbp' => 1.37, + 'eur' => 1.22, + ]; + + $collection->reduce(function (int $carry, int $value, string $key) use ($ratio) { + return $carry + ($value * $ratio[$key]); + }, 0); + + // 4264 + +#### `reduceSpread()` + +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: + + + + 1[$creditsRemaining, $batch] = Image::where('status', 'unprocessed') + + 2 ->get() + + 3 ->reduceSpread(function (int $creditsRemaining, Collection $batch, Image $image) { + + 4 if ($creditsRemaining >= $image->creditsRequired()) { + + 5 $batch->push($image); + + 6  + + 7 $creditsRemaining -= $image->creditsRequired(); + + 8 } + + 9  + + 10 return [$creditsRemaining, $batch]; + + 11 }, $creditsAvailable, collect()); + + + [$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(); + } + + return [$creditsRemaining, $batch]; + }, $creditsAvailable, collect()); + +#### `reject()` + +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: + + + + 1$collection = collect([1, 2, 3, 4]); + + 2  + + 3$filtered = $collection->reject(function (int $value, int $key) { + + 4 return $value > 2; + + 5}); + + 6  + + 7$filtered->all(); + + 8  + + 9// [1, 2] + + + $collection = collect([1, 2, 3, 4]); + + $filtered = $collection->reject(function (int $value, int $key) { + return $value > 2; + }); + + $filtered->all(); + + // [1, 2] + +For the inverse of the `reject` method, see the filter method. + +#### `replace()` + +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: + + + + 1$collection = collect(['Taylor', 'Abigail', 'James']); + + 2  + + 3$replaced = $collection->replace([1 => 'Victoria', 3 => 'Finn']); + + 4  + + 5$replaced->all(); + + 6  + + 7// ['Taylor', 'Victoria', 'James', 'Finn'] + + + $collection = collect(['Taylor', 'Abigail', 'James']); + + $replaced = $collection->replace([1 => 'Victoria', 3 => 'Finn']); + + $replaced->all(); + + // ['Taylor', 'Victoria', 'James', 'Finn'] + +#### `replaceRecursive()` + +The `replaceRecursive` method behaves similarly to `replace`, but it will +recur into arrays and apply the same replacement process to the inner values: + + + + 1$collection = collect([ + + 2 'Taylor', + + 3 'Abigail', + + 4 [ + + 5 'James', + + 6 'Victoria', + + 7 'Finn' + + 8 ] + + 9]); + + 10  + + 11$replaced = $collection->replaceRecursive([ + + 12 'Charlie', + + 13 2 => [1 => 'King'] + + 14]); + + 15  + + 16$replaced->all(); + + 17  + + 18// ['Charlie', 'Abigail', ['James', 'King', 'Finn']] + + + $collection = collect([ + 'Taylor', + 'Abigail', + [ + 'James', + 'Victoria', + 'Finn' + ] + ]); + + $replaced = $collection->replaceRecursive([ + 'Charlie', + 2 => [1 => 'King'] + ]); + + $replaced->all(); + + // ['Charlie', 'Abigail', ['James', 'King', 'Finn']] + +#### `reverse()` + +The `reverse` method reverses the order of the collection's items, preserving +the original keys: + + + + 1$collection = collect(['a', 'b', 'c', 'd', 'e']); + + 2  + + 3$reversed = $collection->reverse(); + + 4  + + 5$reversed->all(); + + 6  + + 7/* + + 8 [ + + 9 4 => 'e', + + 10 3 => 'd', + + 11 2 => 'c', + + 12 1 => 'b', + + 13 0 => 'a', + + 14 ] + + 15*/ + + + $collection = collect(['a', 'b', 'c', 'd', 'e']); + + $reversed = $collection->reverse(); + + $reversed->all(); + + /* + [ + 4 => 'e', + 3 => 'd', + 2 => 'c', + 1 => 'b', + 0 => 'a', + ] + */ + +#### `search()` + +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: + + + + 1$collection = collect([2, 4, 6, 8]); + + 2  + + 3$collection->search(4); + + 4  + + 5// 1 + + + $collection = collect([2, 4, 6, 8]); + + $collection->search(4); + + // 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: + + + + 1collect([2, 4, 6, 8])->search('4', strict: true); + + 2  + + 3// false + + + collect([2, 4, 6, 8])->search('4', strict: true); + + // false + +Alternatively, you may provide your own closure to search for the first item +that passes a given truth test: + + + + 1collect([2, 4, 6, 8])->search(function (int $item, int $key) { + + 2 return $item > 5; + + 3}); + + 4  + + 5// 2 + + + collect([2, 4, 6, 8])->search(function (int $item, int $key) { + return $item > 5; + }); + + // 2 + +#### `select()` + +The `select` method selects the given keys from the collection, similar to an +SQL `SELECT` statement: + + + + 1$users = collect([ + + 2 ['name' => 'Taylor Otwell', 'role' => 'Developer', 'status' => 'active'], + + 3 ['name' => 'Victoria Faith', 'role' => 'Researcher', 'status' => 'active'], + + 4]); + + 5  + + 6$users->select(['name', 'role']); + + 7  + + 8/* + + 9 [ + + 10 ['name' => 'Taylor Otwell', 'role' => 'Developer'], + + 11 ['name' => 'Victoria Faith', 'role' => 'Researcher'], + + 12 ], + + 13*/ + + + $users = collect([ + ['name' => 'Taylor Otwell', 'role' => 'Developer', 'status' => 'active'], + ['name' => 'Victoria Faith', 'role' => 'Researcher', 'status' => 'active'], + ]); + + $users->select(['name', 'role']); + + /* + [ + ['name' => 'Taylor Otwell', 'role' => 'Developer'], + ['name' => 'Victoria Faith', 'role' => 'Researcher'], + ], + */ + +#### `shift()` + +The `shift` method removes and returns the first item from the collection: + + + + 1$collection = collect([1, 2, 3, 4, 5]); + + 2  + + 3$collection->shift(); + + 4  + + 5// 1 + + 6  + + 7$collection->all(); + + 8  + + 9// [2, 3, 4, 5] + + + $collection = collect([1, 2, 3, 4, 5]); + + $collection->shift(); + + // 1 + + $collection->all(); + + // [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: + + + + 1$collection = collect([1, 2, 3, 4, 5]); + + 2  + + 3$collection->shift(3); + + 4  + + 5// collect([1, 2, 3]) + + 6  + + 7$collection->all(); + + 8  + + 9// [4, 5] + + + $collection = collect([1, 2, 3, 4, 5]); + + $collection->shift(3); + + // collect([1, 2, 3]) + + $collection->all(); + + // [4, 5] + +#### `shuffle()` + +The `shuffle` method randomly shuffles the items in the collection: + + + + 1$collection = collect([1, 2, 3, 4, 5]); + + 2  + + 3$shuffled = $collection->shuffle(); + + 4  + + 5$shuffled->all(); + + 6  + + 7// [3, 2, 5, 1, 4] - (generated randomly) + + + $collection = collect([1, 2, 3, 4, 5]); + + $shuffled = $collection->shuffle(); + + $shuffled->all(); + + // [3, 2, 5, 1, 4] - (generated randomly) + +#### `skip()` + +The `skip` method returns a new collection, with the given number of elements +removed from the beginning of the collection: + + + + 1$collection = collect([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); + + 2  + + 3$collection = $collection->skip(4); + + 4  + + 5$collection->all(); + + 6  + + 7// [5, 6, 7, 8, 9, 10] + + + $collection = collect([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); + + $collection = $collection->skip(4); + + $collection->all(); + + // [5, 6, 7, 8, 9, 10] + +#### `skipUntil()` + +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: + + + + 1$collection = collect([1, 2, 3, 4]); + + 2  + + 3$subset = $collection->skipUntil(function (int $item) { + + 4 return $item >= 3; + + 5}); + + 6  + + 7$subset->all(); + + 8  + + 9// [3, 4] + + + $collection = collect([1, 2, 3, 4]); + + $subset = $collection->skipUntil(function (int $item) { + return $item >= 3; + }); + + $subset->all(); + + // [3, 4] + +You may also pass a simple value to the `skipUntil` method to skip all items +until the given value is found: + + + + 1$collection = collect([1, 2, 3, 4]); + + 2  + + 3$subset = $collection->skipUntil(3); + + 4  + + 5$subset->all(); + + 6  + + 7// [3, 4] + + + $collection = collect([1, 2, 3, 4]); + + $subset = $collection->skipUntil(3); + + $subset->all(); + + // [3, 4] + +If the given value is not found or the callback never returns `true`, the +`skipUntil` method will return an empty collection. + +#### `skipWhile()` + +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: + + + + 1$collection = collect([1, 2, 3, 4]); + + 2  + + 3$subset = $collection->skipWhile(function (int $item) { + + 4 return $item <= 3; + + 5}); + + 6  + + 7$subset->all(); + + 8  + + 9// [4] + + + $collection = collect([1, 2, 3, 4]); + + $subset = $collection->skipWhile(function (int $item) { + return $item <= 3; + }); + + $subset->all(); + + // [4] + +If the callback never returns `false`, the `skipWhile` method will return an +empty collection. + +#### `slice()` + +The `slice` method returns a slice of the collection starting at the given +index: + + + + 1$collection = collect([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); + + 2  + + 3$slice = $collection->slice(4); + + 4  + + 5$slice->all(); + + 6  + + 7// [5, 6, 7, 8, 9, 10] + + + $collection = collect([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); + + $slice = $collection->slice(4); + + $slice->all(); + + // [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: + + + + 1$slice = $collection->slice(4, 2); + + 2  + + 3$slice->all(); + + 4  + + 5// [5, 6] + + + $slice = $collection->slice(4, 2); + + $slice->all(); + + // [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 to reindex them. + +#### `sliding()` + +The `sliding` method returns a new collection of chunks representing a +"sliding window" view of the items in the collection: + + + + 1$collection = collect([1, 2, 3, 4, 5]); + + 2  + + 3$chunks = $collection->sliding(2); + + 4  + + 5$chunks->toArray(); + + 6  + + 7// [[1, 2], [2, 3], [3, 4], [4, 5]] + + + $collection = collect([1, 2, 3, 4, 5]); + + $chunks = $collection->sliding(2); + + $chunks->toArray(); + + // [[1, 2], [2, 3], [3, 4], [4, 5]] + +This is especially useful in conjunction with the eachSpread method: + + + + 1$transactions->sliding(2)->eachSpread(function (Collection $previous, Collection $current) { + + 2 $current->total = $previous->total + $current->amount; + + 3}); + + + $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: + + + + 1$collection = collect([1, 2, 3, 4, 5]); + + 2  + + 3$chunks = $collection->sliding(3, step: 2); + + 4  + + 5$chunks->toArray(); + + 6  + + 7// [[1, 2, 3], [3, 4, 5]] + + + $collection = collect([1, 2, 3, 4, 5]); + + $chunks = $collection->sliding(3, step: 2); + + $chunks->toArray(); + + // [[1, 2, 3], [3, 4, 5]] + +#### `sole()` + +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: + + + + 1collect([1, 2, 3, 4])->sole(function (int $value, int $key) { + + 2 return $value === 2; + + 3}); + + 4  + + 5// 2 + + + collect([1, 2, 3, 4])->sole(function (int $value, int $key) { + return $value === 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: + + + + 1$collection = collect([ + + 2 ['product' => 'Desk', 'price' => 200], + + 3 ['product' => 'Chair', 'price' => 100], + + 4]); + + 5  + + 6$collection->sole('product', 'Chair'); + + 7  + + 8// ['product' => 'Chair', 'price' => 100] + + + $collection = collect([ + ['product' => 'Desk', 'price' => 200], + ['product' => 'Chair', 'price' => 100], + ]); + + $collection->sole('product', 'Chair'); + + // ['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: + + + + 1$collection = collect([ + + 2 ['product' => 'Desk', 'price' => 200], + + 3]); + + 4  + + 5$collection->sole(); + + 6  + + 7// ['product' => 'Desk', 'price' => 200] + + + $collection = collect([ + ['product' => 'Desk', 'price' => 200], + ]); + + $collection->sole(); + + // ['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. + +#### `some()` + +Alias for the contains method. + +#### `sort()` + +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 +to reset the keys to consecutively numbered indexes: + + + + 1$collection = collect([5, 3, 1, 2, 4]); + + 2  + + 3$sorted = $collection->sort(); + + 4  + + 5$sorted->values()->all(); + + 6  + + 7// [1, 2, 3, 4, 5] + + + $collection = collect([5, 3, 1, 2, 4]); + + $sorted = $collection->sort(); + + $sorted->values()->all(); + + // [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. + +If you need to sort a collection of nested arrays or objects, see the sortBy +and sortByDesc methods. + +#### `sortBy()` + +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 to reset the keys to consecutively numbered indexes: + + + + 1$collection = collect([ + + 2 ['name' => 'Desk', 'price' => 200], + + 3 ['name' => 'Chair', 'price' => 100], + + 4 ['name' => 'Bookcase', 'price' => 150], + + 5]); + + 6  + + 7$sorted = $collection->sortBy('price'); + + 8  + + 9$sorted->values()->all(); + + 10  + + 11/* + + 12 [ + + 13 ['name' => 'Chair', 'price' => 100], + + 14 ['name' => 'Bookcase', 'price' => 150], + + 15 ['name' => 'Desk', 'price' => 200], + + 16 ] + + 17*/ + + + $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], + ] + */ + +The `sortBy` method accepts [sort +flags](https://www.php.net/manual/en/function.sort.php) as its second +argument: + + + + 1$collection = collect([ + + 2 ['title' => 'Item 1'], + + 3 ['title' => 'Item 12'], + + 4 ['title' => 'Item 3'], + + 5]); + + 6  + + 7$sorted = $collection->sortBy('title', SORT_NATURAL); + + 8  + + 9$sorted->values()->all(); + + 10  + + 11/* + + 12 [ + + 13 ['title' => 'Item 1'], + + 14 ['title' => 'Item 3'], + + 15 ['title' => 'Item 12'], + + 16 ] + + 17*/ + + + $collection = collect([ + ['title' => 'Item 1'], + ['title' => 'Item 12'], + ['title' => 'Item 3'], + ]); + + $sorted = $collection->sortBy('title', SORT_NATURAL); + + $sorted->values()->all(); + + /* + [ + ['title' => 'Item 1'], + ['title' => 'Item 3'], + ['title' => 'Item 12'], + ] + */ + +Alternatively, you may pass your own closure to determine how to sort the +collection's values: + + + + 1$collection = collect([ + + 2 ['name' => 'Desk', 'colors' => ['Black', 'Mahogany']], + + 3 ['name' => 'Chair', 'colors' => ['Black']], + + 4 ['name' => 'Bookcase', 'colors' => ['Red', 'Beige', 'Brown']], + + 5]); + + 6  + + 7$sorted = $collection->sortBy(function (array $product, int $key) { + + 8 return count($product['colors']); + + 9}); + + 10  + + 11$sorted->values()->all(); + + 12  + + 13/* + + 14 [ + + 15 ['name' => 'Chair', 'colors' => ['Black']], + + 16 ['name' => 'Desk', 'colors' => ['Black', 'Mahogany']], + + 17 ['name' => 'Bookcase', 'colors' => ['Red', 'Beige', 'Brown']], + + 18 ] + + 19*/ + + + $collection = collect([ + ['name' => 'Desk', 'colors' => ['Black', 'Mahogany']], + ['name' => 'Chair', 'colors' => ['Black']], + ['name' => 'Bookcase', 'colors' => ['Red', 'Beige', 'Brown']], + ]); + + $sorted = $collection->sortBy(function (array $product, int $key) { + return count($product['colors']); + }); + + $sorted->values()->all(); + + /* + [ + ['name' => 'Chair', 'colors' => ['Black']], + ['name' => 'Desk', 'colors' => ['Black', 'Mahogany']], + ['name' => 'Bookcase', 'colors' => ['Red', 'Beige', 'Brown']], + ] + */ + +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: + + + + 1$collection = collect([ + + 2 ['name' => 'Taylor Otwell', 'age' => 34], + + 3 ['name' => 'Abigail Otwell', 'age' => 30], + + 4 ['name' => 'Taylor Otwell', 'age' => 36], + + 5 ['name' => 'Abigail Otwell', 'age' => 32], + + 6]); + + 7  + + 8$sorted = $collection->sortBy([ + + 9 ['name', 'asc'], + + 10 ['age', 'desc'], + + 11]); + + 12  + + 13$sorted->values()->all(); + + 14  + + 15/* + + 16 [ + + 17 ['name' => 'Abigail Otwell', 'age' => 32], + + 18 ['name' => 'Abigail Otwell', 'age' => 30], + + 19 ['name' => 'Taylor Otwell', 'age' => 36], + + 20 ['name' => 'Taylor Otwell', 'age' => 34], + + 21 ] + + 22*/ + + + $collection = collect([ + ['name' => 'Taylor Otwell', 'age' => 34], + ['name' => 'Abigail Otwell', 'age' => 30], + ['name' => 'Taylor Otwell', 'age' => 36], + ['name' => 'Abigail Otwell', 'age' => 32], + ]); + + $sorted = $collection->sortBy([ + ['name', 'asc'], + ['age', 'desc'], + ]); + + $sorted->values()->all(); + + /* + [ + ['name' => 'Abigail Otwell', 'age' => 32], + ['name' => 'Abigail Otwell', 'age' => 30], + ['name' => 'Taylor Otwell', 'age' => 36], + ['name' => 'Taylor Otwell', 'age' => 34], + ] + */ + +When sorting a collection by multiple attributes, you may also provide +closures that define each sort operation: + + + + 1$collection = collect([ + + 2 ['name' => 'Taylor Otwell', 'age' => 34], + + 3 ['name' => 'Abigail Otwell', 'age' => 30], + + 4 ['name' => 'Taylor Otwell', 'age' => 36], + + 5 ['name' => 'Abigail Otwell', 'age' => 32], + + 6]); + + 7  + + 8$sorted = $collection->sortBy([ + + 9 fn (array $a, array $b) => $a['name'] <=> $b['name'], + + 10 fn (array $a, array $b) => $b['age'] <=> $a['age'], + + 11]); + + 12  + + 13$sorted->values()->all(); + + 14  + + 15/* + + 16 [ + + 17 ['name' => 'Abigail Otwell', 'age' => 32], + + 18 ['name' => 'Abigail Otwell', 'age' => 30], + + 19 ['name' => 'Taylor Otwell', 'age' => 36], + + 20 ['name' => 'Taylor Otwell', 'age' => 34], + + 21 ] + + 22*/ + + + $collection = collect([ + ['name' => 'Taylor Otwell', 'age' => 34], + ['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], + ] + */ + +#### `sortByDesc()` + +This method has the same signature as the sortBy method, but will sort the +collection in the opposite order. + +#### `sortDesc()` + +This method will sort the collection in the opposite order as the sort method: + + + + 1$collection = collect([5, 3, 1, 2, 4]); + + 2  + + 3$sorted = $collection->sortDesc(); + + 4  + + 5$sorted->values()->all(); + + 6  + + 7// [5, 4, 3, 2, 1] + + + $collection = collect([5, 3, 1, 2, 4]); + + $sorted = $collection->sortDesc(); + + $sorted->values()->all(); + + // [5, 4, 3, 2, 1] + +Unlike `sort`, you may not pass a closure to `sortDesc`. Instead, you should +use the sort method and invert your comparison. + +#### `sortKeys()` + +The `sortKeys` method sorts the collection by the keys of the underlying +associative array: + + + + 1$collection = collect([ + + 2 'id' => 22345, + + 3 'first' => 'John', + + 4 'last' => 'Doe', + + 5]); + + 6  + + 7$sorted = $collection->sortKeys(); + + 8  + + 9$sorted->all(); + + 10  + + 11/* + + 12 [ + + 13 'first' => 'John', + + 14 'id' => 22345, + + 15 'last' => 'Doe', + + 16 ] + + 17*/ + + + $collection = collect([ + 'id' => 22345, + 'first' => 'John', + 'last' => 'Doe', + ]); + + $sorted = $collection->sortKeys(); + + $sorted->all(); + + /* + [ + 'first' => 'John', + 'id' => 22345, + 'last' => 'Doe', + ] + */ + +#### `sortKeysDesc()` + +This method has the same signature as the sortKeys method, but will sort the +collection in the opposite order. + +#### `sortKeysUsing()` + +The `sortKeysUsing` method sorts the collection by the keys of the underlying +associative array using a callback: + + + + 1$collection = collect([ + + 2 'ID' => 22345, + + 3 'first' => 'John', + + 4 'last' => 'Doe', + + 5]); + + 6  + + 7$sorted = $collection->sortKeysUsing('strnatcasecmp'); + + 8  + + 9$sorted->all(); + + 10  + + 11/* + + 12 [ + + 13 'first' => 'John', + + 14 'ID' => 22345, + + 15 'last' => 'Doe', + + 16 ] + + 17*/ + + + $collection = collect([ + 'ID' => 22345, + 'first' => 'John', + 'last' => 'Doe', + ]); + + $sorted = $collection->sortKeysUsing('strnatcasecmp'); + + $sorted->all(); + + /* + [ + '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. + +#### `splice()` + +The `splice` method removes and returns a slice of items starting at the +specified index: + + + + 1$collection = collect([1, 2, 3, 4, 5]); + + 2  + + 3$chunk = $collection->splice(2); + + 4  + + 5$chunk->all(); + + 6  + + 7// [3, 4, 5] + + 8  + + 9$collection->all(); + + 10  + + 11// [1, 2] + + + $collection = collect([1, 2, 3, 4, 5]); + + $chunk = $collection->splice(2); + + $chunk->all(); + + // [3, 4, 5] + + $collection->all(); + + // [1, 2] + +You may pass a second argument to limit the size of the resulting collection: + + + + 1$collection = collect([1, 2, 3, 4, 5]); + + 2  + + 3$chunk = $collection->splice(2, 1); + + 4  + + 5$chunk->all(); + + 6  + + 7// [3] + + 8  + + 9$collection->all(); + + 10  + + 11// [1, 2, 4, 5] + + + $collection = collect([1, 2, 3, 4, 5]); + + $chunk = $collection->splice(2, 1); + + $chunk->all(); + + // [3] + + $collection->all(); + + // [1, 2, 4, 5] + +In addition, you may pass a third argument containing the new items to replace +the items removed from the collection: + + + + 1$collection = collect([1, 2, 3, 4, 5]); + + 2  + + 3$chunk = $collection->splice(2, 1, [10, 11]); + + 4  + + 5$chunk->all(); + + 6  + + 7// [3] + + 8  + + 9$collection->all(); + + 10  + + 11// [1, 2, 10, 11, 4, 5] + + + $collection = collect([1, 2, 3, 4, 5]); + + $chunk = $collection->splice(2, 1, [10, 11]); + + $chunk->all(); + + // [3] + + $collection->all(); + + // [1, 2, 10, 11, 4, 5] + +#### `split()` + +The `split` method breaks a collection into the given number of groups: + + + + 1$collection = collect([1, 2, 3, 4, 5]); + + 2  + + 3$groups = $collection->split(3); + + 4  + + 5$groups->all(); + + 6  + + 7// [[1, 2], [3, 4], [5]] + + + $collection = collect([1, 2, 3, 4, 5]); + + $groups = $collection->split(3); + + $groups->all(); + + // [[1, 2], [3, 4], [5]] + +#### `splitIn()` + +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: + + + + 1$collection = collect([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); + + 2  + + 3$groups = $collection->splitIn(3); + + 4  + + 5$groups->all(); + + 6  + + 7// [[1, 2, 3, 4], [5, 6, 7, 8], [9, 10]] + + + $collection = collect([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); + + $groups = $collection->splitIn(3); + + $groups->all(); + + // [[1, 2, 3, 4], [5, 6, 7, 8], [9, 10]] + +#### `sum()` + +The `sum` method returns the sum of all items in the collection: + + + + 1collect([1, 2, 3, 4, 5])->sum(); + + 2  + + 3// 15 + + + collect([1, 2, 3, 4, 5])->sum(); + + // 15 + +If the collection contains nested arrays or objects, you should pass a key +that will be used to determine which values to sum: + + + + 1$collection = collect([ + + 2 ['name' => 'JavaScript: The Good Parts', 'pages' => 176], + + 3 ['name' => 'JavaScript: The Definitive Guide', 'pages' => 1096], + + 4]); + + 5  + + 6$collection->sum('pages'); + + 7  + + 8// 1272 + + + $collection = collect([ + ['name' => 'JavaScript: The Good Parts', 'pages' => 176], + ['name' => 'JavaScript: The Definitive Guide', 'pages' => 1096], + ]); + + $collection->sum('pages'); + + // 1272 + +In addition, you may pass your own closure to determine which values of the +collection to sum: + + + + 1$collection = collect([ + + 2 ['name' => 'Chair', 'colors' => ['Black']], + + 3 ['name' => 'Desk', 'colors' => ['Black', 'Mahogany']], + + 4 ['name' => 'Bookcase', 'colors' => ['Red', 'Beige', 'Brown']], + + 5]); + + 6  + + 7$collection->sum(function (array $product) { + + 8 return count($product['colors']); + + 9}); + + 10  + + 11// 6 + + + $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']); + }); + + // 6 + +#### `take()` + +The `take` method returns a new collection with the specified number of items: + + + + 1$collection = collect([0, 1, 2, 3, 4, 5]); + + 2  + + 3$chunk = $collection->take(3); + + 4  + + 5$chunk->all(); + + 6  + + 7// [0, 1, 2] + + + $collection = collect([0, 1, 2, 3, 4, 5]); + + $chunk = $collection->take(3); + + $chunk->all(); + + // [0, 1, 2] + +You may also pass a negative integer to take the specified number of items +from the end of the collection: + + + + 1$collection = collect([0, 1, 2, 3, 4, 5]); + + 2  + + 3$chunk = $collection->take(-2); + + 4  + + 5$chunk->all(); + + 6  + + 7// [4, 5] + + + $collection = collect([0, 1, 2, 3, 4, 5]); + + $chunk = $collection->take(-2); + + $chunk->all(); + + // [4, 5] + +#### `takeUntil()` + +The `takeUntil` method returns items in the collection until the given +callback returns `true`: + + + + 1$collection = collect([1, 2, 3, 4]); + + 2  + + 3$subset = $collection->takeUntil(function (int $item) { + + 4 return $item >= 3; + + 5}); + + 6  + + 7$subset->all(); + + 8  + + 9// [1, 2] + + + $collection = collect([1, 2, 3, 4]); + + $subset = $collection->takeUntil(function (int $item) { + return $item >= 3; + }); + + $subset->all(); + + // [1, 2] + +You may also pass a simple value to the `takeUntil` method to get the items +until the given value is found: + + + + 1$collection = collect([1, 2, 3, 4]); + + 2  + + 3$subset = $collection->takeUntil(3); + + 4  + + 5$subset->all(); + + 6  + + 7// [1, 2] + + + $collection = collect([1, 2, 3, 4]); + + $subset = $collection->takeUntil(3); + + $subset->all(); + + // [1, 2] + +If the given value is not found or the callback never returns `true`, the +`takeUntil` method will return all items in the collection. + +#### `takeWhile()` + +The `takeWhile` method returns items in the collection until the given +callback returns `false`: + + + + 1$collection = collect([1, 2, 3, 4]); + + 2  + + 3$subset = $collection->takeWhile(function (int $item) { + + 4 return $item < 3; + + 5}); + + 6  + + 7$subset->all(); + + 8  + + 9// [1, 2] + + + $collection = collect([1, 2, 3, 4]); + + $subset = $collection->takeWhile(function (int $item) { + return $item < 3; + }); + + $subset->all(); + + // [1, 2] + +If the callback never returns `false`, the `takeWhile` method will return all +items in the collection. + +#### `tap()` + +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: + + + + 1collect([2, 4, 3, 1, 5]) + + 2 ->sort() + + 3 ->tap(function (Collection $collection) { + + 4 Log::debug('Values after sorting', $collection->values()->all()); + + 5 }) + + 6 ->shift(); + + 7  + + 8// 1 + + + collect([2, 4, 3, 1, 5]) + ->sort() + ->tap(function (Collection $collection) { + Log::debug('Values after sorting', $collection->values()->all()); + }) + ->shift(); + + // 1 + +#### `times()` + +The static `times` method creates a new collection by invoking the given +closure a specified number of times: + + + + 1$collection = Collection::times(10, function (int $number) { + + 2 return $number * 9; + + 3}); + + 4  + + 5$collection->all(); + + 6  + + 7// [9, 18, 27, 36, 45, 54, 63, 72, 81, 90] + + + $collection = Collection::times(10, function (int $number) { + return $number * 9; + }); + + $collection->all(); + + // [9, 18, 27, 36, 45, 54, 63, 72, 81, 90] + +#### `toArray()` + +The `toArray` method converts the collection into a plain PHP `array`. If the +collection's values are [Eloquent](/docs/12.x/eloquent) models, the models +will also be converted to arrays: + + + + 1$collection = collect(['name' => 'Desk', 'price' => 200]); + + 2  + + 3$collection->toArray(); + + 4  + + 5/* + + 6 [ + + 7 ['name' => 'Desk', 'price' => 200], + + 8 ] + + 9*/ + + + $collection = collect(['name' => 'Desk', 'price' => 200]); + + $collection->toArray(); + + /* + [ + ['name' => 'Desk', 'price' => 200], + ] + */ + +`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 instead. + +#### `toJson()` + +The `toJson` method converts the collection into a JSON serialized string: + + + + 1$collection = collect(['name' => 'Desk', 'price' => 200]); + + 2  + + 3$collection->toJson(); + + 4  + + 5// '{"name":"Desk", "price":200}' + + + $collection = collect(['name' => 'Desk', 'price' => 200]); + + $collection->toJson(); + + // '{"name":"Desk", "price":200}' + +#### `toPrettyJson()` + +The `toPrettyJson` method converts the collection into a formatted JSON string +using the `JSON_PRETTY_PRINT` option: + + + + 1$collection = collect(['name' => 'Desk', 'price' => 200]); + + 2  + + 3$collection->toPrettyJson(); + + + $collection = collect(['name' => 'Desk', 'price' => 200]); + + $collection->toPrettyJson(); + +#### `transform()` + +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: + + + + 1$collection = collect([1, 2, 3, 4, 5]); + + 2  + + 3$collection->transform(function (int $item, int $key) { + + 4 return $item * 2; + + 5}); + + 6  + + 7$collection->all(); + + 8  + + 9// [2, 4, 6, 8, 10] + + + $collection = collect([1, 2, 3, 4, 5]); + + $collection->transform(function (int $item, int $key) { + return $item * 2; + }); + + $collection->all(); + + // [2, 4, 6, 8, 10] + +Unlike most other collection methods, `transform` modifies the collection +itself. If you wish to create a new collection instead, use the map method. + +#### `undot()` + +The `undot` method expands a single-dimensional collection that uses "dot" +notation into a multi-dimensional collection: + + + + 1$person = collect([ + + 2 'name.first_name' => 'Marie', + + 3 'name.last_name' => 'Valentine', + + 4 'address.line_1' => '2992 Eagle Drive', + + 5 'address.line_2' => '', + + 6 'address.suburb' => 'Detroit', + + 7 'address.state' => 'MI', + + 8 'address.postcode' => '48219' + + 9]); + + 10  + + 11$person = $person->undot(); + + 12  + + 13$person->toArray(); + + 14  + + 15/* + + 16 [ + + 17 "name" => [ + + 18 "first_name" => "Marie", + + 19 "last_name" => "Valentine", + + 20 ], + + 21 "address" => [ + + 22 "line_1" => "2992 Eagle Drive", + + 23 "line_2" => "", + + 24 "suburb" => "Detroit", + + 25 "state" => "MI", + + 26 "postcode" => "48219", + + 27 ], + + 28 ] + + 29*/ + + + $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->toArray(); + + /* + [ + "name" => [ + "first_name" => "Marie", + "last_name" => "Valentine", + ], + "address" => [ + "line_1" => "2992 Eagle Drive", + "line_2" => "", + "suburb" => "Detroit", + "state" => "MI", + "postcode" => "48219", + ], + ] + */ + +#### `union()` + +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: + + + + 1$collection = collect([1 => ['a'], 2 => ['b']]); + + 2  + + 3$union = $collection->union([3 => ['c'], 1 => ['d']]); + + 4  + + 5$union->all(); + + 6  + + 7// [1 => ['a'], 2 => ['b'], 3 => ['c']] + + + $collection = collect([1 => ['a'], 2 => ['b']]); + + $union = $collection->union([3 => ['c'], 1 => ['d']]); + + $union->all(); + + // [1 => ['a'], 2 => ['b'], 3 => ['c']] + +#### `unique()` + +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 to reset the keys to consecutively numbered +indexes: + + + + 1$collection = collect([1, 1, 2, 2, 3, 4, 2]); + + 2  + + 3$unique = $collection->unique(); + + 4  + + 5$unique->values()->all(); + + 6  + + 7// [1, 2, 3, 4] + + + $collection = collect([1, 1, 2, 2, 3, 4, 2]); + + $unique = $collection->unique(); + + $unique->values()->all(); + + // [1, 2, 3, 4] + +When dealing with nested arrays or objects, you may specify the key used to +determine uniqueness: + + + + 1$collection = collect([ + + 2 ['name' => 'iPhone 6', 'brand' => 'Apple', 'type' => 'phone'], + + 3 ['name' => 'iPhone 5', 'brand' => 'Apple', 'type' => 'phone'], + + 4 ['name' => 'Apple Watch', 'brand' => 'Apple', 'type' => 'watch'], + + 5 ['name' => 'Galaxy S6', 'brand' => 'Samsung', 'type' => 'phone'], + + 6 ['name' => 'Galaxy Gear', 'brand' => 'Samsung', 'type' => 'watch'], + + 7]); + + 8  + + 9$unique = $collection->unique('brand'); + + 10  + + 11$unique->values()->all(); + + 12  + + 13/* + + 14 [ + + 15 ['name' => 'iPhone 6', 'brand' => 'Apple', 'type' => 'phone'], + + 16 ['name' => 'Galaxy S6', 'brand' => 'Samsung', 'type' => 'phone'], + + 17 ] + + 18*/ + + + $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->values()->all(); + + /* + [ + ['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: + + + + 1$unique = $collection->unique(function (array $item) { + + 2 return $item['brand'].$item['type']; + + 3}); + + 4  + + 5$unique->values()->all(); + + 6  + + 7/* + + 8 [ + + 9 ['name' => 'iPhone 6', 'brand' => 'Apple', 'type' => 'phone'], + + 10 ['name' => 'Apple Watch', 'brand' => 'Apple', 'type' => 'watch'], + + 11 ['name' => 'Galaxy S6', 'brand' => 'Samsung', 'type' => 'phone'], + + 12 ['name' => 'Galaxy Gear', 'brand' => 'Samsung', 'type' => 'watch'], + + 13 ] + + 14*/ + + + $unique = $collection->unique(function (array $item) { + return $item['brand'].$item['type']; + }); + + $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'], + ] + */ + +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 to filter using "strict" +comparisons. + +This method's behavior is modified when using [Eloquent +Collections](/docs/12.x/eloquent-collections#method-unique). + +#### `uniqueStrict()` + +This method has the same signature as the unique method; however, all values +are compared using "strict" comparisons. + +#### `unless()` + +The `unless` method will execute the given callback unless the first argument +given to the method evaluates to `true`. The collection instance and the first +argument given to the `unless` method will be provided to the closure: + + + + 1$collection = collect([1, 2, 3]); + + 2  + + 3$collection->unless(true, function (Collection $collection, bool $value) { + + 4 return $collection->push(4); + + 5}); + + 6  + + 7$collection->unless(false, function (Collection $collection, bool $value) { + + 8 return $collection->push(5); + + 9}); + + 10  + + 11$collection->all(); + + 12  + + 13// [1, 2, 3, 5] + + + $collection = collect([1, 2, 3]); + + $collection->unless(true, function (Collection $collection, bool $value) { + return $collection->push(4); + }); + + $collection->unless(false, function (Collection $collection, bool $value) { + return $collection->push(5); + }); + + $collection->all(); + + // [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`: + + + + 1$collection = collect([1, 2, 3]); + + 2  + + 3$collection->unless(true, function (Collection $collection, bool $value) { + + 4 return $collection->push(4); + + 5}, function (Collection $collection, bool $value) { + + 6 return $collection->push(5); + + 7}); + + 8  + + 9$collection->all(); + + 10  + + 11// [1, 2, 3, 5] + + + $collection = collect([1, 2, 3]); + + $collection->unless(true, function (Collection $collection, bool $value) { + return $collection->push(4); + }, function (Collection $collection, bool $value) { + return $collection->push(5); + }); + + $collection->all(); + + // [1, 2, 3, 5] + +For the inverse of `unless`, see the when method. + +#### `unlessEmpty()` + +Alias for the whenNotEmpty method. + +#### `unlessNotEmpty()` + +Alias for the whenEmpty method. + +#### `unwrap()` + +The static `unwrap` method returns the collection's underlying items from the +given value when applicable: + + + + 1Collection::unwrap(collect('John Doe')); + + 2  + + 3// ['John Doe'] + + 4  + + 5Collection::unwrap(['John Doe']); + + 6  + + 7// ['John Doe'] + + 8  + + 9Collection::unwrap('John Doe'); + + 10  + + 11// 'John Doe' + + + Collection::unwrap(collect('John Doe')); + + // ['John Doe'] + + Collection::unwrap(['John Doe']); + + // ['John Doe'] + + Collection::unwrap('John Doe'); + + // 'John Doe' + +#### `value()` + +The `value` method retrieves a given value from the first element of the +collection: + + + + 1$collection = collect([ + + 2 ['product' => 'Desk', 'price' => 200], + + 3 ['product' => 'Speaker', 'price' => 400], + + 4]); + + 5  + + 6$value = $collection->value('price'); + + 7  + + 8// 200 + + + $collection = collect([ + ['product' => 'Desk', 'price' => 200], + ['product' => 'Speaker', 'price' => 400], + ]); + + $value = $collection->value('price'); + + // 200 + +#### `values()` + +The `values` method returns a new collection with the keys reset to +consecutive integers: + + + + 1$collection = collect([ + + 2 10 => ['product' => 'Desk', 'price' => 200], + + 3 11 => ['product' => 'Desk', 'price' => 200], + + 4]); + + 5  + + 6$values = $collection->values(); + + 7  + + 8$values->all(); + + 9  + + 10/* + + 11 [ + + 12 0 => ['product' => 'Desk', 'price' => 200], + + 13 1 => ['product' => 'Desk', 'price' => 200], + + 14 ] + + 15*/ + + + $collection = collect([ + 10 => ['product' => 'Desk', 'price' => 200], + 11 => ['product' => 'Desk', 'price' => 200], + ]); + + $values = $collection->values(); + + $values->all(); + + /* + [ + 0 => ['product' => 'Desk', 'price' => 200], + 1 => ['product' => 'Desk', 'price' => 200], + ] + */ + +#### `when()` + +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: + + + + 1$collection = collect([1, 2, 3]); + + 2  + + 3$collection->when(true, function (Collection $collection, bool $value) { + + 4 return $collection->push(4); + + 5}); + + 6  + + 7$collection->when(false, function (Collection $collection, bool $value) { + + 8 return $collection->push(5); + + 9}); + + 10  + + 11$collection->all(); + + 12  + + 13// [1, 2, 3, 4] + + + $collection = collect([1, 2, 3]); + + $collection->when(true, function (Collection $collection, bool $value) { + return $collection->push(4); + }); + + $collection->when(false, function (Collection $collection, bool $value) { + return $collection->push(5); + }); + + $collection->all(); + + // [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`: + + + + 1$collection = collect([1, 2, 3]); + + 2  + + 3$collection->when(false, function (Collection $collection, bool $value) { + + 4 return $collection->push(4); + + 5}, function (Collection $collection, bool $value) { + + 6 return $collection->push(5); + + 7}); + + 8  + + 9$collection->all(); + + 10  + + 11// [1, 2, 3, 5] + + + $collection = collect([1, 2, 3]); + + $collection->when(false, function (Collection $collection, bool $value) { + return $collection->push(4); + }, function (Collection $collection, bool $value) { + return $collection->push(5); + }); + + $collection->all(); + + // [1, 2, 3, 5] + +For the inverse of `when`, see the unless method. + +#### `whenEmpty()` + +The `whenEmpty` method will execute the given callback when the collection is +empty: + + + + 1$collection = collect(['Michael', 'Tom']); + + 2  + + 3$collection->whenEmpty(function (Collection $collection) { + + 4 return $collection->push('Adam'); + + 5}); + + 6  + + 7$collection->all(); + + 8  + + 9// ['Michael', 'Tom'] + + 10  + + 11$collection = collect(); + + 12  + + 13$collection->whenEmpty(function (Collection $collection) { + + 14 return $collection->push('Adam'); + + 15}); + + 16  + + 17$collection->all(); + + 18  + + 19// ['Adam'] + + + $collection = collect(['Michael', 'Tom']); + + $collection->whenEmpty(function (Collection $collection) { + return $collection->push('Adam'); + }); + + $collection->all(); + + // ['Michael', 'Tom'] + + $collection = collect(); + + $collection->whenEmpty(function (Collection $collection) { + return $collection->push('Adam'); + }); + + $collection->all(); + + // ['Adam'] + +A second closure may be passed to the `whenEmpty` method that will be executed +when the collection is not empty: + + + + 1$collection = collect(['Michael', 'Tom']); + + 2  + + 3$collection->whenEmpty(function (Collection $collection) { + + 4 return $collection->push('Adam'); + + 5}, function (Collection $collection) { + + 6 return $collection->push('Taylor'); + + 7}); + + 8  + + 9$collection->all(); + + 10  + + 11// ['Michael', 'Tom', 'Taylor'] + + + $collection = collect(['Michael', 'Tom']); + + $collection->whenEmpty(function (Collection $collection) { + return $collection->push('Adam'); + }, function (Collection $collection) { + return $collection->push('Taylor'); + }); + + $collection->all(); + + // ['Michael', 'Tom', 'Taylor'] + +For the inverse of `whenEmpty`, see the whenNotEmpty method. + +#### `whenNotEmpty()` + +The `whenNotEmpty` method will execute the given callback when the collection +is not empty: + + + + 1$collection = collect(['Michael', 'Tom']); + + 2  + + 3$collection->whenNotEmpty(function (Collection $collection) { + + 4 return $collection->push('Adam'); + + 5}); + + 6  + + 7$collection->all(); + + 8  + + 9// ['Michael', 'Tom', 'Adam'] + + 10  + + 11$collection = collect(); + + 12  + + 13$collection->whenNotEmpty(function (Collection $collection) { + + 14 return $collection->push('Adam'); + + 15}); + + 16  + + 17$collection->all(); + + 18  + + 19// [] + + + $collection = collect(['Michael', 'Tom']); + + $collection->whenNotEmpty(function (Collection $collection) { + return $collection->push('Adam'); + }); + + $collection->all(); + + // ['Michael', 'Tom', 'Adam'] + + $collection = collect(); + + $collection->whenNotEmpty(function (Collection $collection) { + return $collection->push('Adam'); + }); + + $collection->all(); + + // [] + +A second closure may be passed to the `whenNotEmpty` method that will be +executed when the collection is empty: + + + + 1$collection = collect(); + + 2  + + 3$collection->whenNotEmpty(function (Collection $collection) { + + 4 return $collection->push('Adam'); + + 5}, function (Collection $collection) { + + 6 return $collection->push('Taylor'); + + 7}); + + 8  + + 9$collection->all(); + + 10  + + 11// ['Taylor'] + + + $collection = collect(); + + $collection->whenNotEmpty(function (Collection $collection) { + return $collection->push('Adam'); + }, function (Collection $collection) { + return $collection->push('Taylor'); + }); + + $collection->all(); + + // ['Taylor'] + +For the inverse of `whenNotEmpty`, see the whenEmpty method. + +#### `where()` + +The `where` method filters the collection by a given key / value pair: + + + + 1$collection = collect([ + + 2 ['product' => 'Desk', 'price' => 200], + + 3 ['product' => 'Chair', 'price' => 100], + + 4 ['product' => 'Bookcase', 'price' => 150], + + 5 ['product' => 'Door', 'price' => 100], + + 6]); + + 7  + + 8$filtered = $collection->where('price', 100); + + 9  + + 10$filtered->all(); + + 11  + + 12/* + + 13 [ + + 14 ['product' => 'Chair', 'price' => 100], + + 15 ['product' => 'Door', 'price' => 100], + + 16 ] + + 17*/ + + + $collection = collect([ + ['product' => 'Desk', 'price' => 200], + ['product' => 'Chair', 'price' => 100], + ['product' => 'Bookcase', 'price' => 150], + ['product' => 'Door', 'price' => 100], + ]); + + $filtered = $collection->where('price', 100); + + $filtered->all(); + + /* + [ + ['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 to filter using "strict" comparisons. + +Optionally, you may pass a comparison operator as the second parameter. +Supported operators are: '===', '!==', '!=', '==', '=', '<>', '>', '<', '>=', +and '<=': + + + + 1$collection = collect([ + + 2 ['name' => 'Jim', 'deleted_at' => '2019-01-01 00:00:00'], + + 3 ['name' => 'Sally', 'deleted_at' => '2019-01-02 00:00:00'], + + 4 ['name' => 'Sue', 'deleted_at' => null], + + 5]); + + 6  + + 7$filtered = $collection->where('deleted_at', '!=', null); + + 8  + + 9$filtered->all(); + + 10  + + 11/* + + 12 [ + + 13 ['name' => 'Jim', 'deleted_at' => '2019-01-01 00:00:00'], + + 14 ['name' => 'Sally', 'deleted_at' => '2019-01-02 00:00:00'], + + 15 ] + + 16*/ + + + $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->all(); + + /* + [ + ['name' => 'Jim', 'deleted_at' => '2019-01-01 00:00:00'], + ['name' => 'Sally', 'deleted_at' => '2019-01-02 00:00:00'], + ] + */ + +#### `whereStrict()` + +This method has the same signature as the where method; however, all values +are compared using "strict" comparisons. + +#### `whereBetween()` + +The `whereBetween` method filters the collection by determining if a specified +item value is within a given range: + + + + 1$collection = collect([ + + 2 ['product' => 'Desk', 'price' => 200], + + 3 ['product' => 'Chair', 'price' => 80], + + 4 ['product' => 'Bookcase', 'price' => 150], + + 5 ['product' => 'Pencil', 'price' => 30], + + 6 ['product' => 'Door', 'price' => 100], + + 7]); + + 8  + + 9$filtered = $collection->whereBetween('price', [100, 200]); + + 10  + + 11$filtered->all(); + + 12  + + 13/* + + 14 [ + + 15 ['product' => 'Desk', 'price' => 200], + + 16 ['product' => 'Bookcase', 'price' => 150], + + 17 ['product' => 'Door', 'price' => 100], + + 18 ] + + 19*/ + + + $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->all(); + + /* + [ + ['product' => 'Desk', 'price' => 200], + ['product' => 'Bookcase', 'price' => 150], + ['product' => 'Door', 'price' => 100], + ] + */ + +#### `whereIn()` + +The `whereIn` method removes elements from the collection that do not have a +specified item value that is contained within the given array: + + + + 1$collection = collect([ + + 2 ['product' => 'Desk', 'price' => 200], + + 3 ['product' => 'Chair', 'price' => 100], + + 4 ['product' => 'Bookcase', 'price' => 150], + + 5 ['product' => 'Door', 'price' => 100], + + 6]); + + 7  + + 8$filtered = $collection->whereIn('price', [150, 200]); + + 9  + + 10$filtered->all(); + + 11  + + 12/* + + 13 [ + + 14 ['product' => 'Desk', 'price' => 200], + + 15 ['product' => 'Bookcase', 'price' => 150], + + 16 ] + + 17*/ + + + $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->all(); + + /* + [ + ['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 to filter using "strict" +comparisons. + +#### `whereInStrict()` + +This method has the same signature as the whereIn method; however, all values +are compared using "strict" comparisons. + +#### `whereInstanceOf()` + +The `whereInstanceOf` method filters the collection by a given class type: + + + + 1use App\Models\User; + + 2use App\Models\Post; + + 3  + + 4$collection = collect([ + + 5 new User, + + 6 new User, + + 7 new Post, + + 8]); + + 9  + + 10$filtered = $collection->whereInstanceOf(User::class); + + 11  + + 12$filtered->all(); + + 13  + + 14// [App\Models\User, App\Models\User] + + + use App\Models\User; + use App\Models\Post; + + $collection = collect([ + new User, + new User, + new Post, + ]); + + $filtered = $collection->whereInstanceOf(User::class); + + $filtered->all(); + + // [App\Models\User, App\Models\User] + +#### `whereNotBetween()` + +The `whereNotBetween` method filters the collection by determining if a +specified item value is outside of a given range: + + + + 1$collection = collect([ + + 2 ['product' => 'Desk', 'price' => 200], + + 3 ['product' => 'Chair', 'price' => 80], + + 4 ['product' => 'Bookcase', 'price' => 150], + + 5 ['product' => 'Pencil', 'price' => 30], + + 6 ['product' => 'Door', 'price' => 100], + + 7]); + + 8  + + 9$filtered = $collection->whereNotBetween('price', [100, 200]); + + 10  + + 11$filtered->all(); + + 12  + + 13/* + + 14 [ + + 15 ['product' => 'Chair', 'price' => 80], + + 16 ['product' => 'Pencil', 'price' => 30], + + 17 ] + + 18*/ + + + $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->all(); + + /* + [ + ['product' => 'Chair', 'price' => 80], + ['product' => 'Pencil', 'price' => 30], + ] + */ + +#### `whereNotIn()` + +The `whereNotIn` method removes elements from the collection that have a +specified item value that is contained within the given array: + + + + 1$collection = collect([ + + 2 ['product' => 'Desk', 'price' => 200], + + 3 ['product' => 'Chair', 'price' => 100], + + 4 ['product' => 'Bookcase', 'price' => 150], + + 5 ['product' => 'Door', 'price' => 100], + + 6]); + + 7  + + 8$filtered = $collection->whereNotIn('price', [150, 200]); + + 9  + + 10$filtered->all(); + + 11  + + 12/* + + 13 [ + + 14 ['product' => 'Chair', 'price' => 100], + + 15 ['product' => 'Door', 'price' => 100], + + 16 ] + + 17*/ + + + $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->all(); + + /* + [ + ['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 to filter using "strict" +comparisons. + +#### `whereNotInStrict()` + +This method has the same signature as the whereNotIn method; however, all +values are compared using "strict" comparisons. + +#### `whereNotNull()` + +The `whereNotNull` method returns items from the collection where the given +key is not `null`: + + + + 1$collection = collect([ + + 2 ['name' => 'Desk'], + + 3 ['name' => null], + + 4 ['name' => 'Bookcase'], + + 5]); + + 6  + + 7$filtered = $collection->whereNotNull('name'); + + 8  + + 9$filtered->all(); + + 10  + + 11/* + + 12 [ + + 13 ['name' => 'Desk'], + + 14 ['name' => 'Bookcase'], + + 15 ] + + 16*/ + + + $collection = collect([ + ['name' => 'Desk'], + ['name' => null], + ['name' => 'Bookcase'], + ]); + + $filtered = $collection->whereNotNull('name'); + + $filtered->all(); + + /* + [ + ['name' => 'Desk'], + ['name' => 'Bookcase'], + ] + */ + +#### `whereNull()` + +The `whereNull` method returns items from the collection where the given key +is `null`: + + + + 1$collection = collect([ + + 2 ['name' => 'Desk'], + + 3 ['name' => null], + + 4 ['name' => 'Bookcase'], + + 5]); + + 6  + + 7$filtered = $collection->whereNull('name'); + + 8  + + 9$filtered->all(); + + 10  + + 11/* + + 12 [ + + 13 ['name' => null], + + 14 ] + + 15*/ + + + $collection = collect([ + ['name' => 'Desk'], + ['name' => null], + ['name' => 'Bookcase'], + ]); + + $filtered = $collection->whereNull('name'); + + $filtered->all(); + + /* + [ + ['name' => null], + ] + */ + +#### `wrap()` + +The static `wrap` method wraps the given value in a collection when +applicable: + + + + 1use Illuminate\Support\Collection; + + 2  + + 3$collection = Collection::wrap('John Doe'); + + 4  + + 5$collection->all(); + + 6  + + 7// ['John Doe'] + + 8  + + 9$collection = Collection::wrap(['John Doe']); + + 10  + + 11$collection->all(); + + 12  + + 13// ['John Doe'] + + 14  + + 15$collection = Collection::wrap(collect('John Doe')); + + 16  + + 17$collection->all(); + + 18  + + 19// ['John Doe'] + + + use Illuminate\Support\Collection; + + $collection = Collection::wrap('John Doe'); + + $collection->all(); + + // ['John Doe'] + + $collection = Collection::wrap(['John Doe']); + + $collection->all(); + + // ['John Doe'] + + $collection = Collection::wrap(collect('John Doe')); + + $collection->all(); + + // ['John Doe'] + +#### `zip()` + +The `zip` method merges together the values of the given array with the values +of the original collection at their corresponding index: + + + + 1$collection = collect(['Chair', 'Desk']); + + 2  + + 3$zipped = $collection->zip([100, 200]); + + 4  + + 5$zipped->all(); + + 6  + + 7// [['Chair', 100], ['Desk', 200]] + + + $collection = collect(['Chair', 'Desk']); + + $zipped = $collection->zip([100, 200]); + + $zipped->all(); + + // [['Chair', 100], ['Desk', 200]] + +## Higher Order Messages + +Collections also provide support for "higher order messages", which are short- +cuts for performing common actions on collections. The collection methods that +provide higher order messages are: average, avg, contains, each, every, +filter, first, flatMap, groupBy, keyBy, map, max, min, partition, reject, +skipUntil, skipWhile, some, sortBy, sortByDesc, sum, takeUntil, takeWhile, and +unique. + +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: + + + + 1use App\Models\User; + + 2  + + 3$users = User::where('votes', '>', 500)->get(); + + 4  + + 5$users->each->markAsVip(); + + + use App\Models\User; + + $users = User::where('votes', '>', 500)->get(); + + $users->each->markAsVip(); + +Likewise, we can use the `sum` higher order message to gather the total number +of "votes" for a collection of users: + + + + 1$users = User::where('group', 'Development')->get(); + + 2  + + 3return $users->sum->votes; + + + $users = User::where('group', 'Development')->get(); + + return $users->sum->votes; + +## Lazy Collections + +### Introduction + +Before learning more about Laravel's lazy collections, take some time to +familiarize yourself with [PHP +generators](https://www.php.net/manual/en/language.generators.overview.php). + +To supplement the already powerful `Collection` class, the `LazyCollection` +class leverages PHP's +[generators](https://www.php.net/manual/en/language.generators.overview.php) +to allow you to work with very large datasets while keeping memory usage low. + +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: + + + + 1use App\Models\LogEntry; + + 2use Illuminate\Support\LazyCollection; + + 3  + + 4LazyCollection::make(function () { + + 5 $handle = fopen('log.txt', 'r'); + + 6  + + 7 while (($line = fgets($handle)) !== false) { + + 8 yield $line; + + 9 } + + 10  + + 11 fclose($handle); + + 12})->chunk(4)->map(function (array $lines) { + + 13 return LogEntry::fromLines($lines); + + 14})->each(function (LogEntry $logEntry) { + + 15 // Process the log entry... + + 16}); + + + use App\Models\LogEntry; + use Illuminate\Support\LazyCollection; + + LazyCollection::make(function () { + $handle = fopen('log.txt', 'r'); + + while (($line = fgets($handle)) !== false) { + yield $line; + } + + fclose($handle); + })->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: + + + + 1use App\Models\User; + + 2  + + 3$users = User::all()->filter(function (User $user) { + + 4 return $user->id > 500; + + 5}); + + + use App\Models\User; + + $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: + + + + 1use App\Models\User; + + 2  + + 3$users = User::cursor()->filter(function (User $user) { + + 4 return $user->id > 500; + + 5}); + + 6  + + 7foreach ($users as $user) { + + 8 echo $user->id; + + 9} + + + use App\Models\User; + + $users = User::cursor()->filter(function (User $user) { + return $user->id > 500; + }); + + 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: + + + + 1use Illuminate\Support\LazyCollection; + + 2  + + 3LazyCollection::make(function () { + + 4 $handle = fopen('log.txt', 'r'); + + 5  + + 6 while (($line = fgets($handle)) !== false) { + + 7 yield $line; + + 8 } + + 9  + + 10 fclose($handle); + + 11}); + + + use Illuminate\Support\LazyCollection; + + LazyCollection::make(function () { + $handle = fopen('log.txt', 'r'); + + while (($line = fgets($handle)) !== false) { + yield $line; + } + + fclose($handle); + }); + +### The Enumerable Contract + +Almost all methods available on the `Collection` class are also available on +the `LazyCollection` class. Both of these classes implement the +`Illuminate\Support\Enumerable` contract, which defines the following methods: + +all average avg chunk chunkWhile collapse collect combine concat contains +containsStrict count countBy crossJoin dd diff diffAssoc diffKeys dump +duplicates duplicatesStrict each eachSpread every except filter first +firstOrFail firstWhere flatMap flatten flip forPage get groupBy has implode +intersect intersectAssoc intersectByKeys isEmpty isNotEmpty join keyBy keys +last macro make map mapInto mapSpread mapToGroups mapWithKeys max median merge +mergeRecursive min mode nth only pad partition pipe pluck random reduce reject +replace replaceRecursive reverse search shuffle skip slice sole some sort +sortBy sortByDesc sortKeys sortKeysDesc split sum take tap times toArray +toJson union unique uniqueStrict unless unlessEmpty unlessNotEmpty unwrap +values when whenEmpty whenNotEmpty where whereStrict whereBetween whereIn +whereInStrict whereInstanceOf whereNotBetween whereNotIn whereNotInStrict wrap +zip + +Methods that mutate the collection (such as `shift`, `pop`, `prepend` etc.) +are **not** available on the `LazyCollection` class. + +### Lazy Collection Methods + +In addition to the methods defined in the `Enumerable` contract, the +`LazyCollection` class contains the following methods: + +#### `takeUntilTimeout()` + +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: + + + + 1$lazyCollection = LazyCollection::times(INF) + + 2 ->takeUntilTimeout(now()->addMinute()); + + 3  + + 4$lazyCollection->each(function (int $number) { + + 5 dump($number); + + 6  + + 7 sleep(1); + + 8}); + + 9  + + 10// 1 + + 11// 2 + + 12// ... + + 13// 58 + + 14// 59 + + + $lazyCollection = LazyCollection::times(INF) + ->takeUntilTimeout(now()->addMinute()); + + $lazyCollection->each(function (int $number) { + dump($number); + + sleep(1); + }); + + // 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/12.x/scheduling) that runs every 15 minutes and only processes +invoices for a maximum of 14 minutes: + + + + 1use App\Models\Invoice; + + 2use Illuminate\Support\Carbon; + + 3  + + 4Invoice::pending()->cursor() + + 5 ->takeUntilTimeout( + + 6 Carbon::createFromTimestamp(LARAVEL_START)->add(14, 'minutes') + + 7 ) + + 8 ->each(fn (Invoice $invoice) => $invoice->submit()); + + + 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()` + +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: + + + + 1// Nothing has been dumped so far... + + 2$lazyCollection = LazyCollection::times(INF)->tapEach(function (int $value) { + + 3 dump($value); + + 4}); + + 5  + + 6// Three items are dumped... + + 7$array = $lazyCollection->take(3)->all(); + + 8  + + 9// 1 + + 10// 2 + + 11// 3 + + + // 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(); + + // 1 + // 2 + // 3 + +#### `throttle()` + +The `throttle` method will throttle the lazy collection such that each value +is returned after the specified number of seconds. This method is especially +useful for situations where you may be interacting with external APIs that +rate limit incoming requests: + + + + 1use App\Models\User; + + 2  + + 3User::where('vip', true) + + 4 ->cursor() + + 5 ->throttle(seconds: 1) + + 6 ->each(function (User $user) { + + 7 // Call external API... + + 8 }); + + + use App\Models\User; + + User::where('vip', true) + ->cursor() + ->throttle(seconds: 1) + ->each(function (User $user) { + // Call external API... + }); + +#### `remember()` + +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: + + + + 1// No query has been executed yet... + + 2$users = User::cursor()->remember(); + + 3  + + 4// The query is executed... + + 5// The first 5 users are hydrated from the database... + + 6$users->take(5)->all(); + + 7  + + 8// First 5 users come from the collection's cache... + + 9// The rest are hydrated from the database... + + 10$users->take(20)->all(); + + + // 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(); + + // First 5 users come from the collection's cache... + // The rest are hydrated from the database... + $users->take(20)->all(); + +#### `withHeartbeat()` + +The `withHeartbeat` method allows you to execute a callback at regular time +intervals while a lazy collection is being enumerated. This is particularly +useful for long-running operations that require periodic maintenance tasks, +such as extending locks or sending progress updates: + + + + 1use Carbon\CarbonInterval; + + 2use Illuminate\Support\Facades\Cache; + + 3  + + 4$lock = Cache::lock('generate-reports', seconds: 60 * 5); + + 5  + + 6if ($lock->get()) { + + 7 try { + + 8 Report::where('status', 'pending') + + 9 ->lazy() + + 10 ->withHeartbeat( + + 11 CarbonInterval::minutes(4), + + 12 fn () => $lock->extend(CarbonInterval::minutes(5)) + + 13 ) + + 14 ->each(fn ($report) => $report->process()); + + 15 } finally { + + 16 $lock->release(); + + 17 } + + 18} + + + use Carbon\CarbonInterval; + use Illuminate\Support\Facades\Cache; + + $lock = Cache::lock('generate-reports', seconds: 60 * 5); + + if ($lock->get()) { + try { + Report::where('status', 'pending') + ->lazy() + ->withHeartbeat( + CarbonInterval::minutes(4), + fn () => $lock->extend(CarbonInterval::minutes(5)) + ) + ->each(fn ($report) => $report->process()); + } finally { + $lock->release(); + } + } + diff --git a/output/12.x/concurrency.md b/output/12.x/concurrency.md new file mode 100644 index 0000000..77f3963 --- /dev/null +++ b/output/12.x/concurrency.md @@ -0,0 +1,123 @@ +# Concurrency + + * Introduction + * Running Concurrent Tasks + * Deferring Concurrent Tasks + +## Introduction + +Sometimes you may need to execute several slow tasks which do not depend on +one another. In many cases, significant performance improvements can be +realized by executing the tasks concurrently. Laravel's `Concurrency` facade +provides a simple, convenient API for executing closures concurrently. + +#### How it Works + +Laravel achieves concurrency by serializing the given closures and dispatching +them to a hidden Artisan CLI command, which unserializes the closures and +invokes it within its own PHP process. After the closure has been invoked, the +resulting value is serialized back to the parent process. + +The `Concurrency` facade supports three drivers: `process` (the default), +`fork`, and `sync`. + +The `fork` driver offers improved performance compared to the default +`process` driver, but it may only be used within PHP's CLI context, as PHP +does not support forking during web requests. Before using the `fork` driver, +you need to install the `spatie/fork` package: + + + + 1composer require spatie/fork + + + composer require spatie/fork + +The `sync` driver is primarily useful during testing when you want to disable +all concurrency and simply execute the given closures in sequence within the +parent process. + +## Running Concurrent Tasks + +To run concurrent tasks, you may invoke the `Concurrency` facade's `run` +method. The `run` method accepts an array of closures which should be executed +simultaneously in child PHP processes: + + + + 1use Illuminate\Support\Facades\Concurrency; + + 2use Illuminate\Support\Facades\DB; + + 3  + + 4[$userCount, $orderCount] = Concurrency::run([ + + 5 fn () => DB::table('users')->count(), + + 6 fn () => DB::table('orders')->count(), + + 7]); + + + use Illuminate\Support\Facades\Concurrency; + use Illuminate\Support\Facades\DB; + + [$userCount, $orderCount] = Concurrency::run([ + fn () => DB::table('users')->count(), + fn () => DB::table('orders')->count(), + ]); + +To use a specific driver, you may use the `driver` method: + + + + 1$results = Concurrency::driver('fork')->run(...); + + + $results = Concurrency::driver('fork')->run(...); + +Or, to change the default concurrency driver, you should publish the +`concurrency` configuration file via the `config:publish` Artisan command and +update the `default` option within the file: + + + + 1php artisan config:publish concurrency + + + php artisan config:publish concurrency + +## Deferring Concurrent Tasks + +If you would like to execute an array of closures concurrently, but are not +interested in the results returned by those closures, you should consider +using the `defer` method. When the `defer` method is invoked, the given +closures are not executed immediately. Instead, Laravel will execute the +closures concurrently after the HTTP response has been sent to the user: + + + + 1use App\Services\Metrics; + + 2use Illuminate\Support\Facades\Concurrency; + + 3  + + 4Concurrency::defer([ + + 5 fn () => Metrics::report('users'), + + 6 fn () => Metrics::report('orders'), + + 7]); + + + use App\Services\Metrics; + use Illuminate\Support\Facades\Concurrency; + + Concurrency::defer([ + fn () => Metrics::report('users'), + fn () => Metrics::report('orders'), + ]); + diff --git a/output/12.x/configuration.md b/output/12.x/configuration.md new file mode 100644 index 0000000..d2d22c2 --- /dev/null +++ b/output/12.x/configuration.md @@ -0,0 +1,626 @@ +# Configuration + + * Introduction + * Environment Configuration + * Environment Variable Types + * Retrieving Environment Configuration + * Determining the Current Environment + * Encrypting Environment Files + * Accessing Configuration Values + * Configuration Caching + * Configuration Publishing + * Debug Mode + * Maintenance Mode + +## Introduction + +All of the configuration files for the Laravel framework are stored in the +`config` directory. Each option is documented, so feel free to look through +the files and get familiar with the options available to you. + +These configuration files allow you to configure things like your database +connection information, your mail server information, as well as various other +core configuration values such as your application URL and encryption key. + +#### The `about` Command + +Laravel can display an overview of your application's configuration, drivers, +and environment via the `about` Artisan command. + + + + 1php artisan about + + + php artisan about + +If you're only interested in a particular section of the application overview +output, you may filter for that section using the `--only` option: + + + + 1php artisan about --only=environment + + + php artisan about --only=environment + +Or, to explore a specific configuration file's values in detail, you may use +the `config:show` Artisan command: + + + + 1php artisan config:show database + + + php artisan config:show database + +## Environment Configuration + +It is often helpful to have different configuration values based on the +environment where the application is running. For example, you may wish to use +a different cache driver locally than you do on your production server. + +To make this a cinch, Laravel utilizes the +[DotEnv](https://github.com/vlucas/phpdotenv) PHP library. In a fresh Laravel +installation, the root directory of your application will contain a +`.env.example` file that defines many common environment variables. During the +Laravel installation process, this file will automatically be copied to +`.env`. + +Laravel's default `.env` file contains some common configuration values that +may differ based on whether your application is running locally or on a +production web server. These values are then read by the configuration files +within the `config` directory using Laravel's `env` function. + +If you are developing with a team, you may wish to continue including and +updating the `.env.example` file with your application. By putting placeholder +values in the example configuration file, other developers on your team can +clearly see which environment variables are needed to run your application. + +Any variable in your `.env` file can be overridden by external environment +variables such as server-level or system-level environment variables. + +#### Environment File Security + +Your `.env` file should not be committed to your application's source control, +since each developer / server using your application could require a different +environment configuration. Furthermore, this would be a security risk in the +event an intruder gains access to your source control repository, since any +sensitive credentials would get exposed. + +However, it is possible to encrypt your environment file using Laravel's +built-in environment encryption. Encrypted environment files may be placed in +source control safely. + +#### Additional Environment Files + +Before loading your application's environment variables, Laravel determines if +an `APP_ENV` environment variable has been externally provided or if the +`--env` CLI argument has been specified. If so, Laravel will attempt to load +an `.env.[APP_ENV]` file if it exists. If it does not exist, the default +`.env` file will be loaded. + +### Environment Variable Types + +All variables in your `.env` files are typically parsed as strings, so some +reserved values have been created to allow you to return a wider range of +types from the `env()` function: + +`.env` Value | `env()` Value +---|--- +true | (bool) true +(true) | (bool) true +false | (bool) false +(false) | (bool) false +empty | (string) '' +(empty) | (string) '' +null | (null) null +(null) | (null) null + +If you need to define an environment variable with a value that contains +spaces, you may do so by enclosing the value in double quotes: + + + + 1APP_NAME="My Application" + + + APP_NAME="My Application" + +### Retrieving Environment Configuration + +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: + + + + 1'debug' => env('APP_DEBUG', false), + + + '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. + +### Determining the Current Environment + +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/12.x/facades): + + + + 1use Illuminate\Support\Facades\App; + + 2  + + 3$environment = App::environment(); + + + use Illuminate\Support\Facades\App; + + $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: + + + + 1if (App::environment('local')) { + + 2 // The environment is local + + 3} + + 4  + + 5if (App::environment(['local', 'staging'])) { + + 6 // The environment is either local OR staging... + + 7} + + + if (App::environment('local')) { + // The environment is local + } + + if (App::environment(['local', 'staging'])) { + // The environment is either local OR staging... + } + +The current application environment detection can be overridden by defining a +server-level `APP_ENV` environment variable. + +### Encrypting Environment Files + +Unencrypted environment files should never be stored in source control. +However, Laravel allows you to encrypt your environment files so that they may +safely be added to source control with the rest of your application. + +#### Encryption + +To encrypt an environment file, you may use the `env:encrypt` command: + + + + 1php artisan env:encrypt + + + php artisan env:encrypt + +Running the `env:encrypt` command will encrypt your `.env` file and place the +encrypted contents in an `.env.encrypted` file. The decryption key is +presented in the output of the command and should be stored in a secure +password manager. If you would like to provide your own encryption key you may +use the `--key` option when invoking the command: + + + + 1php artisan env:encrypt --key=3UVsEgGVK36XN82KKeyLFMhvosbZN1aF + + + php artisan env:encrypt --key=3UVsEgGVK36XN82KKeyLFMhvosbZN1aF + +The length of the key provided should match the key length required by the +encryption cipher being used. By default, Laravel will use the `AES-256-CBC` +cipher which requires a 32 character key. You are free to use any cipher +supported by Laravel's [encrypter](/docs/12.x/encryption) by passing the +`--cipher` option when invoking the command. + +If your application has multiple environment files, such as `.env` and +`.env.staging`, you may specify the environment file that should be encrypted +by providing the environment name via the `--env` option: + + + + 1php artisan env:encrypt --env=staging + + + php artisan env:encrypt --env=staging + +#### Decryption + +To decrypt an environment file, you may use the `env:decrypt` command. This +command requires a decryption key, which Laravel will retrieve from the +`LARAVEL_ENV_ENCRYPTION_KEY` environment variable: + + + + 1php artisan env:decrypt + + + php artisan env:decrypt + +Or, the key may be provided directly to the command via the `--key` option: + + + + 1php artisan env:decrypt --key=3UVsEgGVK36XN82KKeyLFMhvosbZN1aF + + + php artisan env:decrypt --key=3UVsEgGVK36XN82KKeyLFMhvosbZN1aF + +When the `env:decrypt` command is invoked, Laravel will decrypt the contents +of the `.env.encrypted` file and place the decrypted contents in the `.env` +file. + +The `--cipher` option may be provided to the `env:decrypt` command in order to +use a custom encryption cipher: + + + + 1php artisan env:decrypt --key=qUWuNRdfuImXcKxZ --cipher=AES-128-CBC + + + php artisan env:decrypt --key=qUWuNRdfuImXcKxZ --cipher=AES-128-CBC + +If your application has multiple environment files, such as `.env` and +`.env.staging`, you may specify the environment file that should be decrypted +by providing the environment name via the `--env` option: + + + + 1php artisan env:decrypt --env=staging + + + php artisan env:decrypt --env=staging + +In order to overwrite an existing environment file, you may provide the +`--force` option to the `env:decrypt` command: + + + + 1php artisan env:decrypt --force + + + php artisan env:decrypt --force + +## Accessing Configuration Values + +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: + + + + 1use Illuminate\Support\Facades\Config; + + 2  + + 3$value = Config::get('app.timezone'); + + 4  + + 5$value = config('app.timezone'); + + 6  + + 7// Retrieve a default value if the configuration value does not exist... + + 8$value = config('app.timezone', 'Asia/Seoul'); + + + use Illuminate\Support\Facades\Config; + + $value = Config::get('app.timezone'); + + $value = config('app.timezone'); + + // 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: + + + + 1Config::set('app.timezone', 'America/Chicago'); + + 2  + + 3config(['app.timezone' => 'America/Chicago']); + + + Config::set('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: + + + + 1Config::string('config-key'); + + 2Config::integer('config-key'); + + 3Config::float('config-key'); + + 4Config::boolean('config-key'); + + 5Config::array('config-key'); + + 6Config::collection('config-key'); + + + Config::string('config-key'); + Config::integer('config-key'); + Config::float('config-key'); + Config::boolean('config-key'); + Config::array('config-key'); + Config::collection('config-key'); + +## Configuration Caching + +To give your application a speed boost, you should cache all of your +configuration files into a single file using the `config:cache` Artisan +command. This will combine all of the configuration options for your +application into a single file which can be quickly loaded by the framework. + +You should typically run the `php artisan config:cache` command as part of +your production deployment process. The command should not be run during local +development as configuration options will frequently need to be changed during +the course of your application's development. + +Once the configuration has been cached, your application's `.env` file will +not be loaded by the framework during requests or Artisan commands; therefore, +the `env` function will only return external, system level environment +variables. + +For this reason, you should ensure you are only calling the `env` function +from within your application's configuration (`config`) files. You can see +many examples of this by examining Laravel's default configuration files. +Configuration values may be accessed from anywhere in your application using +the `config` function described above. + +The `config:clear` command may be used to purge the cached configuration: + + + + 1php artisan config:clear + + + php artisan config:clear + +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; therefore, the `env` function will only return external, +system level environment variables. + +## Configuration Publishing + +Most of Laravel's configuration files are already published in your +application's `config` directory; however, certain configuration files like +`cors.php` and `view.php` are not published by default, as most applications +will never need to modify them. + +However, you may use the `config:publish` Artisan command to publish any +configuration files that are not published by default: + + + + 1php artisan config:publish + + 2  + + 3php artisan config:publish --all + + + php artisan config:publish + + php artisan config:publish --all + +## Debug Mode + +The `debug` option in your `config/app.php` configuration file determines how +much information about an error is actually displayed to the user. By default, +this option is set to respect the value of the `APP_DEBUG` environment +variable, which is stored in your `.env` file. + +For local development, you should set the `APP_DEBUG` environment variable to +`true`. **In your production environment, this value should always be`false`. +If the variable is set to `true` in production, you risk exposing sensitive +configuration values to your application's end users.** + +## Maintenance Mode + +When your application is in maintenance mode, a custom view will be displayed +for all requests into your application. This makes it easy to "disable" your +application while it is updating or when you are performing maintenance. A +maintenance mode check is included in the default middleware stack for your +application. If the application is in maintenance mode, a +`Symfony\Component\HttpKernel\Exception\HttpException` instance will be thrown +with a status code of 503. + +To enable maintenance mode, execute the `down` Artisan command: + + + + 1php artisan down + + + php artisan down + +If you would like the `Refresh` HTTP header to be sent with all maintenance +mode responses, you may provide the `refresh` option when invoking the `down` +command. The `Refresh` header will instruct the browser to automatically +refresh the page after the specified number of seconds: + + + + 1php artisan down --refresh=15 + + + php artisan down --refresh=15 + +You may also provide a `retry` option to the `down` command, which will be set +as the `Retry-After` HTTP header's value, although browsers generally ignore +this header: + + + + 1php artisan down --retry=60 + + + php artisan down --retry=60 + +#### Bypassing Maintenance Mode + +To allow maintenance mode to be bypassed using a secret token, you may use the +`secret` option to specify a maintenance mode bypass token: + + + + 1php artisan down --secret="1630542a-246b-4b66-afa1-dd72a4c43515" + + + php artisan down --secret="1630542a-246b-4b66-afa1-dd72a4c43515" + +After placing the application in maintenance mode, you may navigate to the +application URL matching this token and Laravel will issue a maintenance mode +bypass cookie to your browser: + + + + 1https://example.com/1630542a-246b-4b66-afa1-dd72a4c43515 + + + https://example.com/1630542a-246b-4b66-afa1-dd72a4c43515 + +If you would like Laravel to generate the secret token for you, you may use +the `with-secret` option. The secret will be displayed to you once the +application is in maintenance mode: + + + + 1php artisan down --with-secret + + + php artisan down --with-secret + +When accessing this hidden route, you will then be redirected to the `/` route +of the application. Once the cookie has been issued to your browser, you will +be able to browse the application normally as if it was not in maintenance +mode. + +Your maintenance mode secret should typically consist of alpha-numeric +characters and, optionally, dashes. You should avoid using characters that +have special meaning in URLs such as `?` or `&`. + +#### Maintenance Mode on Multiple Servers + +By default, Laravel determines if your application is in maintenance mode +using a file-based system. This means to activate maintenance mode, the `php +artisan down` command has to be executed on each server hosting your +application. + +Alternatively, Laravel offers a cache-based method for handling maintenance +mode. This method requires running the `php artisan down` command on just one +server. To use this approach, modify the maintenance mode variables in your +application's `.env` file. You should select a cache `store` that is +accessible by all of your servers. This ensures the maintenance mode status is +consistently maintained across every server: + + + + 1APP_MAINTENANCE_DRIVER=cache + + 2APP_MAINTENANCE_STORE=database + + + APP_MAINTENANCE_DRIVER=cache + APP_MAINTENANCE_STORE=database + +#### Pre-Rendering the Maintenance Mode View + +If you utilize the `php artisan down` command during deployment, your users +may still occasionally encounter errors if they access the application while +your Composer dependencies or other infrastructure components are updating. +This occurs because a significant part of the Laravel framework must boot in +order to determine your application is in maintenance mode and render the +maintenance mode view using the templating engine. + +For this reason, Laravel allows you to pre-render a maintenance mode view that +will be returned at the very beginning of the request cycle. This view is +rendered before any of your application's dependencies have loaded. You may +pre-render a template of your choice using the `down` command's `render` +option: + + + + 1php artisan down --render="errors::503" + + + php artisan down --render="errors::503" + +#### Redirecting Maintenance Mode Requests + +While in maintenance mode, Laravel will display the maintenance mode view for +all application URLs the user attempts to access. If you wish, you may +instruct Laravel to redirect all requests to a specific URL. This may be +accomplished using the `redirect` option. For example, you may wish to +redirect all requests to the `/` URI: + + + + 1php artisan down --redirect=/ + + + php artisan down --redirect=/ + +#### Disabling Maintenance Mode + +To disable maintenance mode, use the `up` command: + + + + 1php artisan up + + + php artisan up + +You may customize the default maintenance mode template by defining your own +template at `resources/views/errors/503.blade.php`. + +#### Maintenance Mode and Queues + +While your application is in maintenance mode, no [queued +jobs](/docs/12.x/queues) will be handled. The jobs will continue to be handled +as normal once the application is out of maintenance mode. + +#### Alternatives to Maintenance Mode + +Since maintenance mode requires your application to have several seconds of +downtime, consider running your applications on a fully-managed platform like +[Laravel Cloud](https://cloud.laravel.com) to accomplish zero-downtime +deployment with Laravel. + diff --git a/output/12.x/console-tests.md b/output/12.x/console-tests.md new file mode 100644 index 0000000..7015be9 --- /dev/null +++ b/output/12.x/console-tests.md @@ -0,0 +1,522 @@ +# Console Tests + + * Introduction + * Success / Failure Expectations + * Input / Output Expectations + * Console Events + +## Introduction + +In addition to simplifying HTTP testing, Laravel provides a simple API for +testing your application's [custom console commands](/docs/12.x/artisan). + +## Success / Failure Expectations + +To get started, let's explore how to make assertions regarding an Artisan +command's exit code. To accomplish this, we will use the `artisan` method to +invoke an Artisan command from our test. Then, we will use the +`assertExitCode` method to assert that the command completed with a given exit +code: + +Pest PHPUnit + + + + 1test('console command', function () { + + 2 $this->artisan('inspire')->assertExitCode(0); + + 3}); + + + test('console command', function () { + $this->artisan('inspire')->assertExitCode(0); + }); + + + 1/** + + 2 * Test a console command. + + 3 */ + + 4public function test_console_command(): void + + 5{ + + 6 $this->artisan('inspire')->assertExitCode(0); + + 7} + + + /** + * Test a console command. + */ + public function test_console_command(): void + { + $this->artisan('inspire')->assertExitCode(0); + } + +You may use the `assertNotExitCode` method to assert that the command did not +exit with a given exit code: + + + + 1$this->artisan('inspire')->assertNotExitCode(1); + + + $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: + + + + 1$this->artisan('inspire')->assertSuccessful(); + + 2  + + 3$this->artisan('inspire')->assertFailed(); + + + $this->artisan('inspire')->assertSuccessful(); + + $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: + + + + 1Artisan::command('question', function () { + + 2 $name = $this->ask('What is your name?'); + + 3  + + 4 $language = $this->choice('Which language do you prefer?', [ + + 5 'PHP', + + 6 'Ruby', + + 7 'Python', + + 8 ]); + + 9  + + 10 $this->line('Your name is '.$name.' and you prefer '.$language.'.'); + + 11}); + + + Artisan::command('question', function () { + $name = $this->ask('What is your name?'); + + $language = $this->choice('Which language do you prefer?', [ + 'PHP', + 'Ruby', + 'Python', + ]); + + $this->line('Your name is '.$name.' and you prefer '.$language.'.'); + }); + +You may test this command with the following test: + +Pest PHPUnit + + + + 1test('console command', function () { + + 2 $this->artisan('question') + + 3 ->expectsQuestion('What is your name?', 'Taylor Otwell') + + 4 ->expectsQuestion('Which language do you prefer?', 'PHP') + + 5 ->expectsOutput('Your name is Taylor Otwell and you prefer PHP.') + + 6 ->doesntExpectOutput('Your name is Taylor Otwell and you prefer Ruby.') + + 7 ->assertExitCode(0); + + 8}); + + + test('console command', function () { + $this->artisan('question') + ->expectsQuestion('What is your name?', 'Taylor Otwell') + ->expectsQuestion('Which language do you prefer?', 'PHP') + ->expectsOutput('Your name is Taylor Otwell and you prefer PHP.') + ->doesntExpectOutput('Your name is Taylor Otwell and you prefer Ruby.') + ->assertExitCode(0); + }); + + + 1/** + + 2 * Test a console command. + + 3 */ + + 4public function test_console_command(): void + + 5{ + + 6 $this->artisan('question') + + 7 ->expectsQuestion('What is your name?', 'Taylor Otwell') + + 8 ->expectsQuestion('Which language do you prefer?', 'PHP') + + 9 ->expectsOutput('Your name is Taylor Otwell and you prefer PHP.') + + 10 ->doesntExpectOutput('Your name is Taylor Otwell and you prefer Ruby.') + + 11 ->assertExitCode(0); + + 12} + + + /** + * Test a console command. + */ + public function test_console_command(): void + { + $this->artisan('question') + ->expectsQuestion('What is your name?', 'Taylor Otwell') + ->expectsQuestion('Which language do you prefer?', 'PHP') + ->expectsOutput('Your name is Taylor Otwell and you prefer PHP.') + ->doesntExpectOutput('Your name is Taylor Otwell and you prefer Ruby.') + ->assertExitCode(0); + } + +If you are utilizing the `search` or `multisearch` functions provided by +[Laravel Prompts](/docs/12.x/prompts), you may use the `expectsSearch` +assertion to mock the user's input, search results, and selection: + +Pest PHPUnit + + + + 1test('console command', function () { + + 2 $this->artisan('example') + + 3 ->expectsSearch('What is your name?', search: 'Tay', answers: [ + + 4 'Taylor Otwell', + + 5 'Taylor Swift', + + 6 'Darian Taylor' + + 7 ], answer: 'Taylor Otwell') + + 8 ->assertExitCode(0); + + 9}); + + + test('console command', function () { + $this->artisan('example') + ->expectsSearch('What is your name?', search: 'Tay', answers: [ + 'Taylor Otwell', + 'Taylor Swift', + 'Darian Taylor' + ], answer: 'Taylor Otwell') + ->assertExitCode(0); + }); + + + 1/** + + 2 * Test a console command. + + 3 */ + + 4public function test_console_command(): void + + 5{ + + 6 $this->artisan('example') + + 7 ->expectsSearch('What is your name?', search: 'Tay', answers: [ + + 8 'Taylor Otwell', + + 9 'Taylor Swift', + + 10 'Darian Taylor' + + 11 ], answer: 'Taylor Otwell') + + 12 ->assertExitCode(0); + + 13} + + + /** + * Test a console command. + */ + public function test_console_command(): void + { + $this->artisan('example') + ->expectsSearch('What is your name?', search: 'Tay', answers: [ + 'Taylor Otwell', + 'Taylor Swift', + 'Darian Taylor' + ], answer: 'Taylor Otwell') + ->assertExitCode(0); + } + +You may also assert that a console command does not generate any output using +the `doesntExpectOutput` method: + +Pest PHPUnit + + + + 1test('console command', function () { + + 2 $this->artisan('example') + + 3 ->doesntExpectOutput() + + 4 ->assertExitCode(0); + + 5}); + + + test('console command', function () { + $this->artisan('example') + ->doesntExpectOutput() + ->assertExitCode(0); + }); + + + 1/** + + 2 * Test a console command. + + 3 */ + + 4public function test_console_command(): void + + 5{ + + 6 $this->artisan('example') + + 7 ->doesntExpectOutput() + + 8 ->assertExitCode(0); + + 9} + + + /** + * Test a console command. + */ + public function test_console_command(): void + { + $this->artisan('example') + ->doesntExpectOutput() + ->assertExitCode(0); + } + +The `expectsOutputToContain` and `doesntExpectOutputToContain` methods may be +used to make assertions against a portion of the output: + +Pest PHPUnit + + + + 1test('console command', function () { + + 2 $this->artisan('example') + + 3 ->expectsOutputToContain('Taylor') + + 4 ->assertExitCode(0); + + 5}); + + + test('console command', function () { + $this->artisan('example') + ->expectsOutputToContain('Taylor') + ->assertExitCode(0); + }); + + + 1/** + + 2 * Test a console command. + + 3 */ + + 4public function test_console_command(): void + + 5{ + + 6 $this->artisan('example') + + 7 ->expectsOutputToContain('Taylor') + + 8 ->assertExitCode(0); + + 9} + + + /** + * Test a console command. + */ + public function test_console_command(): void + { + $this->artisan('example') + ->expectsOutputToContain('Taylor') + ->assertExitCode(0); + } + +#### Confirmation Expectations + +When writing a command which expects confirmation in the form of a "yes" or +"no" answer, you may utilize the `expectsConfirmation` method: + + + + 1$this->artisan('module:import') + + 2 ->expectsConfirmation('Do you really wish to run this command?', 'no') + + 3 ->assertExitCode(1); + + + $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: + + + + 1$this->artisan('users:all') + + 2 ->expectsTable([ + + 3 'ID', + + 4 'Email', + + 5 ], [ + + 6 [1, '[[email protected]](/cdn-cgi/l/email-protection)'], + + 7 [2, '[[email protected]](/cdn-cgi/l/email-protection)'], + + 8 ]); + + + $this->artisan('users:all') + ->expectsTable([ + 'ID', + 'Email', + ], [ + [1, '[[email protected]](/cdn-cgi/l/email-protection)'], + [2, '[[email protected]](/cdn-cgi/l/email-protection)'], + ]); + +## Console Events + +By default, the `Illuminate\Console\Events\CommandStarting` and +`Illuminate\Console\Events\CommandFinished` events are not dispatched while +running your application's tests. However, you can enable these events for a +given test class by adding the +`Illuminate\Foundation\Testing\WithConsoleEvents` trait to the class: + +Pest PHPUnit + + + + 1use(WithConsoleEvents::class); + + 6  + + 7// ... + + + use(WithConsoleEvents::class); + + // ... + + + 1 $this->apple->findPodcast($id) + + 24 ]); + + 25 } + + 26} + + + $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. + +A deep understanding of the Laravel service container is essential to building +a powerful, large application, as well as for contributing to the Laravel core +itself. + +### Zero Configuration Resolution + +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: + + + + 1app` 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: + + + + 1use App\Services\Transistor; + + 2use App\Services\PodcastParser; + + 3use Illuminate\Contracts\Foundation\Application; + + 4  + + 5$this->app->bind(Transistor::class, function (Application $app) { + + 6 return new Transistor($app->make(PodcastParser::class)); + + 7}); + + + 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)); + }); + +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/12.x/facades): + + + + 1use App\Services\Transistor; + + 2use Illuminate\Contracts\Foundation\Application; + + 3use Illuminate\Support\Facades\App; + + 4  + + 5App::bind(Transistor::class, function (Application $app) { + + 6 // ... + + 7}); + + + use App\Services\Transistor; + use Illuminate\Contracts\Foundation\Application; + use Illuminate\Support\Facades\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: + + + + 1$this->app->bindIf(Transistor::class, function (Application $app) { + + 2 return new Transistor($app->make(PodcastParser::class)); + + 3}); + + + $this->app->bindIf(Transistor::class, function (Application $app) { + return new Transistor($app->make(PodcastParser::class)); + }); + +For convenience, you may omit providing the class or interface name that you +wish to register as a separate argument and instead allow Laravel to infer the +type from the return type of the closure you provide to the `bind` method: + + + + 1App::bind(function (Application $app): Transistor { + + 2 return new Transistor($app->make(PodcastParser::class)); + + 3}); + + + App::bind(function (Application $app): Transistor { + return new Transistor($app->make(PodcastParser::class)); + }); + +There is no need to bind classes into the container if they do not depend on +any interfaces. The container does not need to be instructed on how to build +these objects, since it can automatically resolve these objects using +reflection. + +#### Binding A Singleton + +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: + + + + 1use App\Services\Transistor; + + 2use App\Services\PodcastParser; + + 3use Illuminate\Contracts\Foundation\Application; + + 4  + + 5$this->app->singleton(Transistor::class, function (Application $app) { + + 6 return new Transistor($app->make(PodcastParser::class)); + + 7}); + + + 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)); + }); + +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: + + + + 1$this->app->singletonIf(Transistor::class, function (Application $app) { + + 2 return new Transistor($app->make(PodcastParser::class)); + + 3}); + + + $this->app->singletonIf(Transistor::class, function (Application $app) { + return new Transistor($app->make(PodcastParser::class)); + }); + +#### Singleton Attribute + +Alternatively, you may mark an interface or class with the `#[Singleton]` +attribute to indicate to the container that it should be resolved one time: + + + + 1app->scoped(Transistor::class, function (Application $app) { + + 6 return new Transistor($app->make(PodcastParser::class)); + + 7}); + + + 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)); + }); + +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: + + + + 1$this->app->scopedIf(Transistor::class, function (Application $app) { + + 2 return new Transistor($app->make(PodcastParser::class)); + + 3}); + + + $this->app->scopedIf(Transistor::class, function (Application $app) { + return new Transistor($app->make(PodcastParser::class)); + }); + +#### Scoped Attribute + +Alternatively, you may mark an interface or class with the `#[Scoped]` +attribute to indicate to the container that it should be resolved one time +within a given Laravel request / job lifecycle: + + + + 1app->instance(Transistor::class, $service); + + + use App\Services\Transistor; + use App\Services\PodcastParser; + + $service = new Transistor(new PodcastParser); + + $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: + + + + 1use App\Contracts\EventPusher; + + 2use App\Services\RedisEventPusher; + + 3  + + 4$this->app->bind(EventPusher::class, RedisEventPusher::class); + + + use App\Contracts\EventPusher; + use App\Services\RedisEventPusher; + + $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: + + + + 1use App\Contracts\EventPusher; + + 2  + + 3/** + + 4 * Create a new class instance. + + 5 */ + + 6public function __construct( + + 7 protected EventPusher $pusher, + + 8) {} + + + use App\Contracts\EventPusher; + + /** + * Create a new class instance. + */ + public function __construct( + protected EventPusher $pusher, + ) {} + +#### Bind Attribute + +Laravel also provides a `Bind` attribute for added convenience. You can apply +this attribute to any interface to tell Laravel which implementation should be +automatically injected whenever that interface is requested. When using the +`Bind` attribute, there is no need to perform any additional service +registration in your application's service providers. + +In addition, multiple `Bind` attributes may be placed on an interface in order +to configure a different implementation that should be injected for a given +set of environments: + + + + 1app->when(PhotoController::class) + + 8 ->needs(Filesystem::class) + + 9 ->give(function () { + + 10 return Storage::disk('local'); + + 11 }); + + 12  + + 13$this->app->when([VideoController::class, UploadController::class]) + + 14 ->needs(Filesystem::class) + + 15 ->give(function () { + + 16 return Storage::disk('s3'); + + 17 }); + + + 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([VideoController::class, UploadController::class]) + ->needs(Filesystem::class) + ->give(function () { + return Storage::disk('s3'); + }); + +### Contextual Attributes + +Since contextual binding is often used to inject implementations of drivers or +configuration values, Laravel offers a variety of contextual binding +attributes that allow to inject these types of values without manually +defining the contextual bindings in your service providers. + +For example, the `Storage` attribute may be used to inject a specific [storage +disk](/docs/12.x/filesystem): + + + + 1middleware('auth'); + + + use App\Models\User; + use Illuminate\Container\Attributes\CurrentUser; + + Route::get('/user', function (#[CurrentUser] User $user) { + return $user; + })->middleware('auth'); + +#### Defining Custom Attributes + +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: + + + + 1make('config')->get($attribute->key, $attribute->default); + + 29 } + + 30} + + + 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: + + + + 1use App\Http\Controllers\UserController; + + 2  + + 3$this->app->when(UserController::class) + + 4 ->needs('$variableName') + + 5 ->give($value); + + + use App\Http\Controllers\UserController; + + $this->app->when(UserController::class) + ->needs('$variableName') + ->give($value); + +Sometimes a class may depend on an array of tagged instances. Using the +`giveTagged` method, you may easily inject all of the container bindings with +that tag: + + + + 1$this->app->when(ReportAggregator::class) + + 2 ->needs('$reports') + + 3 ->giveTagged('reports'); + + + $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: + + + + 1$this->app->when(ReportAggregator::class) + + 2 ->needs('$timezone') + + 3 ->giveConfig('app.timezone'); + + + $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: + + + + 1filters = $filters; + + 23 } + + 24} + + + 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: + + + + 1$this->app->when(Firewall::class) + + 2 ->needs(Filter::class) + + 3 ->give(function (Application $app) { + + 4 return [ + + 5 $app->make(NullFilter::class), + + 6 $app->make(ProfanityFilter::class), + + 7 $app->make(TooLongFilter::class), + + 8 ]; + + 9 }); + + + $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: + + + + 1$this->app->when(Firewall::class) + + 2 ->needs(Filter::class) + + 3 ->give([ + + 4 NullFilter::class, + + 5 ProfanityFilter::class, + + 6 TooLongFilter::class, + + 7 ]); + + + $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 for +the given dependency: + + + + 1$this->app->when(ReportAggregator::class) + + 2 ->needs(Report::class) + + 3 ->giveTagged('reports'); + + + $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: + + + + 1$this->app->bind(CpuReport::class, function () { + + 2 // ... + + 3}); + + 4  + + 5$this->app->bind(MemoryReport::class, function () { + + 6 // ... + + 7}); + + 8  + + 9$this->app->tag([CpuReport::class, MemoryReport::class], 'reports'); + + + $this->app->bind(CpuReport::class, function () { + // ... + }); + + $this->app->bind(MemoryReport::class, function () { + // ... + }); + + $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: + + + + 1$this->app->bind(ReportAnalyzer::class, function (Application $app) { + + 2 return new ReportAnalyzer($app->tagged('reports')); + + 3}); + + + $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: + + + + 1$this->app->extend(Service::class, function (Service $service, Application $app) { + + 2 return new DecoratedService($service); + + 3}); + + + $this->app->extend(Service::class, function (Service $service, Application $app) { + return new DecoratedService($service); + }); + +## Resolving + +### The `make` Method + +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: + + + + 1use App\Services\Transistor; + + 2  + + 3$transistor = $this->app->make(Transistor::class); + + + use App\Services\Transistor; + + $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: + + + + 1use App\Services\Transistor; + + 2  + + 3$transistor = $this->app->makeWith(Transistor::class, ['id' => 1]); + + + use App\Services\Transistor; + + $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: + + + + 1if ($this->app->bound(Transistor::class)) { + + 2 // ... + + 3} + + + 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/12.x/facades) or the `app` [helper](/docs/12.x/helpers#method- +app) to resolve a class instance from the container: + + + + 1use App\Services\Transistor; + + 2use Illuminate\Support\Facades\App; + + 3  + + 4$transistor = App::make(Transistor::class); + + 5  + + 6$transistor = app(Transistor::class); + + + use App\Services\Transistor; + use Illuminate\Support\Facades\App; + + $transistor = App::make(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: + + + + 1use Illuminate\Container\Container; + + 2  + + 3/** + + 4 * Create a new class instance. + + 5 */ + + 6public function __construct( + + 7 protected Container $container, + + 8) {} + + + use Illuminate\Container\Container; + + /** + * Create a new class instance. + */ + public function __construct( + protected Container $container, + ) {} + +### Automatic Injection + +Alternatively, and importantly, you may type-hint the dependency in the +constructor of a class that is resolved by the container, including +[controllers](/docs/12.x/controllers), [event listeners](/docs/12.x/events), +[middleware](/docs/12.x/middleware), and more. Additionally, you may type-hint +dependencies in the `handle` method of [queued jobs](/docs/12.x/queues). In +practice, this is how most of your objects should be resolved by the +container. + +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: + + + + 1apple->findPodcast($id); + + 22 } + + 23} + + + 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: + + + + 1app->resolving(Transistor::class, function (Transistor $transistor, Application $app) { + + 5 // Called when container resolves objects of type "Transistor"... + + 6}); + + 7  + + 8$this->app->resolving(function (mixed $object, Application $app) { + + 9 // Called when container resolves object of any type... + + 10}); + + + 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(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. + +### Rebinding + +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: + + + + 1use App\Contracts\PodcastPublisher; + + 2use App\Services\SpotifyPublisher; + + 3use App\Services\TransistorPublisher; + + 4use Illuminate\Contracts\Foundation\Application; + + 5  + + 6$this->app->bind(PodcastPublisher::class, SpotifyPublisher::class); + + 7  + + 8$this->app->rebinding( + + 9 PodcastPublisher::class, + + 10 function (Application $app, PodcastPublisher $newInstance) { + + 11 // + + 12 }, + + 13); + + 14  + + 15// New binding will trigger rebinding closure... + + 16$this->app->bind(PodcastPublisher::class, TransistorPublisher::class); + + + 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: + + + + 1use App\Services\Transistor; + + 2use Psr\Container\ContainerInterface; + + 3  + + 4Route::get('/', function (ContainerInterface $container) { + + 5 $service = $container->get(Transistor::class); + + 6  + + 7 // ... + + 8}); + + + use App\Services\Transistor; + use Psr\Container\ContainerInterface; + + 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/output/12.x/context.md b/output/12.x/context.md new file mode 100644 index 0000000..a89a583 --- /dev/null +++ b/output/12.x/context.md @@ -0,0 +1,1008 @@ +# Context + + * Introduction + * How it Works + * Capturing Context + * Stacks + * Retrieving Context + * Determining Item Existence + * Removing Context + * Hidden Context + * Events + * Dehydrating + * Hydrated + +## Introduction + +Laravel's "context" capabilities enable you to capture, retrieve, and share +information throughout requests, jobs, and commands executing within your +application. This captured information is also included in logs written by +your application, giving you deeper insight into the surrounding code +execution history that occurred before a log entry was written and allowing +you to trace execution flows throughout a distributed system. + +### How it Works + +The best way to understand Laravel's context capabilities is to see it in +action using the built-in logging features. To get started, you may add +information to the context using the `Context` facade. In this example, we +will use a [middleware](/docs/12.x/middleware) to add the request URL and a +unique trace ID to the context on every incoming request: + + + + 1url()); + + 19 Context::add('trace_id', Str::uuid()->toString()); + + 20  + + 21 return $next($request); + + 22 } + + 23} + + + url()); + Context::add('trace_id', Str::uuid()->toString()); + + return $next($request); + } + } + +Information added to the context is automatically appended as metadata to any +[log entries](/docs/12.x/logging) that are written throughout the request. +Appending context as metadata allows information passed to individual log +entries to be differentiated from the information shared via `Context`. For +example, imagine we write the following log entry: + + + + 1Log::info('User authenticated.', ['auth_id' => Auth::id()]); + + + Log::info('User authenticated.', ['auth_id' => Auth::id()]); + +The written log will contain the `auth_id` passed to the log entry, but it +will also contain the context's `url` and `trace_id` as metadata: + + + + 1User authenticated. {"auth_id":27} {"url":"https://example.com/login","trace_id":"e04e1a11-e75c-4db3-b5b5-cfef4ef56697"} + + + User authenticated. {"auth_id":27} {"url":"https://example.com/login","trace_id":"e04e1a11-e75c-4db3-b5b5-cfef4ef56697"} + +Information added to the context is also made available to jobs dispatched to +the queue. For example, imagine we dispatch a `ProcessPodcast` job to the +queue after adding some information to the context: + + + + 1// In our middleware... + + 2Context::add('url', $request->url()); + + 3Context::add('trace_id', Str::uuid()->toString()); + + 4  + + 5// In our controller... + + 6ProcessPodcast::dispatch($podcast); + + + // In our middleware... + Context::add('url', $request->url()); + Context::add('trace_id', Str::uuid()->toString()); + + // In our controller... + ProcessPodcast::dispatch($podcast); + +When the job is dispatched, any information currently stored in the context is +captured and shared with the job. The captured information is then hydrated +back into the current context while the job is executing. So, if our job's +handle method was to write to the log: + + + + 1class ProcessPodcast implements ShouldQueue + + 2{ + + 3 use Queueable; + + 4  + + 5 // ... + + 6  + + 7 /** + + 8 * Execute the job. + + 9 */ + + 10 public function handle(): void + + 11 { + + 12 Log::info('Processing podcast.', [ + + 13 'podcast_id' => $this->podcast->id, + + 14 ]); + + 15  + + 16 // ... + + 17 } + + 18} + + + class ProcessPodcast implements ShouldQueue + { + use Queueable; + + // ... + + /** + * Execute the job. + */ + public function handle(): void + { + Log::info('Processing podcast.', [ + 'podcast_id' => $this->podcast->id, + ]); + + // ... + } + } + +The resulting log entry would contain the information that was added to the +context during the request that originally dispatched the job: + + + + 1Processing podcast. {"podcast_id":95} {"url":"https://example.com/login","trace_id":"e04e1a11-e75c-4db3-b5b5-cfef4ef56697"} + + + Processing podcast. {"podcast_id":95} {"url":"https://example.com/login","trace_id":"e04e1a11-e75c-4db3-b5b5-cfef4ef56697"} + +Although we have focused on the built-in logging related features of Laravel's +context, the following documentation will illustrate how context allows you to +share information across the HTTP request / queued job boundary and even how +to add hidden context data that is not written with log entries. + +## Capturing Context + +You may store information in the current context using the `Context` facade's +`add` method: + + + + 1use Illuminate\Support\Facades\Context; + + 2  + + 3Context::add('key', 'value'); + + + use Illuminate\Support\Facades\Context; + + Context::add('key', 'value'); + +To add multiple items at once, you may pass an associative array to the `add` +method: + + + + 1Context::add([ + + 2 'first_key' => 'value', + + 3 'second_key' => 'value', + + 4]); + + + Context::add([ + 'first_key' => 'value', + 'second_key' => 'value', + ]); + +The `add` method will override any existing value that shares the same key. If +you only wish to add information to the context if the key does not already +exist, you may use the `addIf` method: + + + + 1Context::add('key', 'first'); + + 2  + + 3Context::get('key'); + + 4// "first" + + 5  + + 6Context::addIf('key', 'second'); + + 7  + + 8Context::get('key'); + + 9// "first" + + + Context::add('key', 'first'); + + Context::get('key'); + // "first" + + Context::addIf('key', 'second'); + + Context::get('key'); + // "first" + +Context also provides convenient methods for incrementing or decrementing a +given key. Both of these methods accept at least one argument: the key to +track. A second argument may be provided to specify the amount by which the +key should be incremented or decremented: + + + + 1Context::increment('records_added'); + + 2Context::increment('records_added', 5); + + 3  + + 4Context::decrement('records_added'); + + 5Context::decrement('records_added', 5); + + + Context::increment('records_added'); + Context::increment('records_added', 5); + + Context::decrement('records_added'); + Context::decrement('records_added', 5); + +#### Conditional Context + +The `when` method may be used to add data to the context based on a given +condition. The first closure provided to the `when` method will be invoked if +the given condition evaluates to `true`, while the second closure will be +invoked if the condition evaluates to `false`: + + + + 1use Illuminate\Support\Facades\Auth; + + 2use Illuminate\Support\Facades\Context; + + 3  + + 4Context::when( + + 5 Auth::user()->isAdmin(), + + 6 fn ($context) => $context->add('permissions', Auth::user()->permissions), + + 7 fn ($context) => $context->add('permissions', []), + + 8); + + + use Illuminate\Support\Facades\Auth; + use Illuminate\Support\Facades\Context; + + Context::when( + Auth::user()->isAdmin(), + fn ($context) => $context->add('permissions', Auth::user()->permissions), + fn ($context) => $context->add('permissions', []), + ); + +#### Scoped Context + +The `scope` method provides a way to temporarily modify the context during the +execution of a given callback and restore the context to its original state +when the callback finishes executing. Additionally, you can pass extra data +that should be merged into the context (as the second and third arguments) +while the closure executes. + + + + 1use Illuminate\Support\Facades\Context; + + 2use Illuminate\Support\Facades\Log; + + 3  + + 4Context::add('trace_id', 'abc-999'); + + 5Context::addHidden('user_id', 123); + + 6  + + 7Context::scope( + + 8 function () { + + 9 Context::add('action', 'adding_friend'); + + 10  + + 11 $userId = Context::getHidden('user_id'); + + 12  + + 13 Log::debug("Adding user [{$userId}] to friends list."); + + 14 // Adding user [987] to friends list. {"trace_id":"abc-999","user_name":"taylor_otwell","action":"adding_friend"} + + 15 }, + + 16 data: ['user_name' => 'taylor_otwell'], + + 17 hidden: ['user_id' => 987], + + 18); + + 19  + + 20Context::all(); + + 21// [ + + 22// 'trace_id' => 'abc-999', + + 23// ] + + 24  + + 25Context::allHidden(); + + 26// [ + + 27// 'user_id' => 123, + + 28// ] + + + use Illuminate\Support\Facades\Context; + use Illuminate\Support\Facades\Log; + + Context::add('trace_id', 'abc-999'); + Context::addHidden('user_id', 123); + + Context::scope( + function () { + Context::add('action', 'adding_friend'); + + $userId = Context::getHidden('user_id'); + + Log::debug("Adding user [{$userId}] to friends list."); + // Adding user [987] to friends list. {"trace_id":"abc-999","user_name":"taylor_otwell","action":"adding_friend"} + }, + data: ['user_name' => 'taylor_otwell'], + hidden: ['user_id' => 987], + ); + + Context::all(); + // [ + // 'trace_id' => 'abc-999', + // ] + + Context::allHidden(); + // [ + // 'user_id' => 123, + // ] + +If an object within the context is modified inside the scoped closure, that +mutation will be reflected outside of the scope. + +### Stacks + +Context offers the ability to create "stacks", which are lists of data stored +in the order that they were added. You can add information to a stack by +invoking the `push` method: + + + + 1use Illuminate\Support\Facades\Context; + + 2  + + 3Context::push('breadcrumbs', 'first_value'); + + 4  + + 5Context::push('breadcrumbs', 'second_value', 'third_value'); + + 6  + + 7Context::get('breadcrumbs'); + + 8// [ + + 9// 'first_value', + + 10// 'second_value', + + 11// 'third_value', + + 12// ] + + + use Illuminate\Support\Facades\Context; + + Context::push('breadcrumbs', 'first_value'); + + Context::push('breadcrumbs', 'second_value', 'third_value'); + + Context::get('breadcrumbs'); + // [ + // 'first_value', + // 'second_value', + // 'third_value', + // ] + +Stacks can be useful to capture historical information about a request, such +as events that are happening throughout your application. For example, you +could create an event listener to push to a stack every time a query is +executed, capturing the query SQL and duration as a tuple: + + + + 1use Illuminate\Support\Facades\Context; + + 2use Illuminate\Support\Facades\DB; + + 3  + + 4// In AppServiceProvider.php... + + 5DB::listen(function ($event) { + + 6 Context::push('queries', [$event->time, $event->sql]); + + 7}); + + + use Illuminate\Support\Facades\Context; + use Illuminate\Support\Facades\DB; + + // In AppServiceProvider.php... + DB::listen(function ($event) { + Context::push('queries', [$event->time, $event->sql]); + }); + +You may determine if a value is in a stack using the `stackContains` and +`hiddenStackContains` methods: + + + + 1if (Context::stackContains('breadcrumbs', 'first_value')) { + + 2 // + + 3} + + 4  + + 5if (Context::hiddenStackContains('secrets', 'first_value')) { + + 6 // + + 7} + + + if (Context::stackContains('breadcrumbs', 'first_value')) { + // + } + + if (Context::hiddenStackContains('secrets', 'first_value')) { + // + } + +The `stackContains` and `hiddenStackContains` methods also accept a closure as +their second argument, allowing more control over the value comparison +operation: + + + + 1use Illuminate\Support\Facades\Context; + + 2use Illuminate\Support\Str; + + 3  + + 4return Context::stackContains('breadcrumbs', function ($value) { + + 5 return Str::startsWith($value, 'query_'); + + 6}); + + + use Illuminate\Support\Facades\Context; + use Illuminate\Support\Str; + + return Context::stackContains('breadcrumbs', function ($value) { + return Str::startsWith($value, 'query_'); + }); + +## Retrieving Context + +You may retrieve information from the context using the `Context` facade's +`get` method: + + + + 1use Illuminate\Support\Facades\Context; + + 2  + + 3$value = Context::get('key'); + + + use Illuminate\Support\Facades\Context; + + $value = Context::get('key'); + +The `only` and `except` methods may be used to retrieve a subset of the +information in the context: + + + + 1$data = Context::only(['first_key', 'second_key']); + + 2  + + 3$data = Context::except(['first_key']); + + + $data = Context::only(['first_key', 'second_key']); + + $data = Context::except(['first_key']); + +The `pull` method may be used to retrieve information from the context and +immediately remove it from the context: + + + + 1$value = Context::pull('key'); + + + $value = Context::pull('key'); + +If context data is stored in a stack, you may pop items from the stack using +the `pop` method: + + + + 1Context::push('breadcrumbs', 'first_value', 'second_value'); + + 2  + + 3Context::pop('breadcrumbs'); + + 4// second_value + + 5  + + 6Context::get('breadcrumbs'); + + 7// ['first_value'] + + + Context::push('breadcrumbs', 'first_value', 'second_value'); + + Context::pop('breadcrumbs'); + // second_value + + Context::get('breadcrumbs'); + // ['first_value'] + +The `remember` and `rememberHidden` methods may be used to retrieve +information from the context, while setting the context value to the value +returned by the given closure if the requested information doesn't exist: + + + + 1$permissions = Context::remember( + + 2 'user-permissions', + + 3 fn () => $user->permissions, + + 4); + + + $permissions = Context::remember( + 'user-permissions', + fn () => $user->permissions, + ); + +If you would like to retrieve all of the information stored in the context, +you may invoke the `all` method: + + + + 1$data = Context::all(); + + + $data = Context::all(); + +### Determining Item Existence + +You may use the `has` and `missing` methods to determine if the context has +any value stored for the given key: + + + + 1use Illuminate\Support\Facades\Context; + + 2  + + 3if (Context::has('key')) { + + 4 // ... + + 5} + + 6  + + 7if (Context::missing('key')) { + + 8 // ... + + 9} + + + use Illuminate\Support\Facades\Context; + + if (Context::has('key')) { + // ... + } + + if (Context::missing('key')) { + // ... + } + +The `has` method will return `true` regardless of the value stored. So, for +example, a key with a `null` value will be considered present: + + + + 1Context::add('key', null); + + 2  + + 3Context::has('key'); + + 4// true + + + Context::add('key', null); + + Context::has('key'); + // true + +## Removing Context + +The `forget` method may be used to remove a key and its value from the current +context: + + + + 1use Illuminate\Support\Facades\Context; + + 2  + + 3Context::add(['first_key' => 1, 'second_key' => 2]); + + 4  + + 5Context::forget('first_key'); + + 6  + + 7Context::all(); + + 8  + + 9// ['second_key' => 2] + + + use Illuminate\Support\Facades\Context; + + Context::add(['first_key' => 1, 'second_key' => 2]); + + Context::forget('first_key'); + + Context::all(); + + // ['second_key' => 2] + +You may forget several keys at once by providing an array to the `forget` +method: + + + + 1Context::forget(['first_key', 'second_key']); + + + Context::forget(['first_key', 'second_key']); + +## Hidden Context + +Context offers the ability to store "hidden" data. This hidden information is +not appended to logs, and is not accessible via the data retrieval methods +documented above. Context provides a different set of methods to interact with +hidden context information: + + + + 1use Illuminate\Support\Facades\Context; + + 2  + + 3Context::addHidden('key', 'value'); + + 4  + + 5Context::getHidden('key'); + + 6// 'value' + + 7  + + 8Context::get('key'); + + 9// null + + + use Illuminate\Support\Facades\Context; + + Context::addHidden('key', 'value'); + + Context::getHidden('key'); + // 'value' + + Context::get('key'); + // null + +The "hidden" methods mirror the functionality of the non-hidden methods +documented above: + + + + 1Context::addHidden(/* ... */); + + 2Context::addHiddenIf(/* ... */); + + 3Context::pushHidden(/* ... */); + + 4Context::getHidden(/* ... */); + + 5Context::pullHidden(/* ... */); + + 6Context::popHidden(/* ... */); + + 7Context::onlyHidden(/* ... */); + + 8Context::exceptHidden(/* ... */); + + 9Context::allHidden(/* ... */); + + 10Context::hasHidden(/* ... */); + + 11Context::missingHidden(/* ... */); + + 12Context::forgetHidden(/* ... */); + + + Context::addHidden(/* ... */); + Context::addHiddenIf(/* ... */); + Context::pushHidden(/* ... */); + Context::getHidden(/* ... */); + Context::pullHidden(/* ... */); + Context::popHidden(/* ... */); + Context::onlyHidden(/* ... */); + Context::exceptHidden(/* ... */); + Context::allHidden(/* ... */); + Context::hasHidden(/* ... */); + Context::missingHidden(/* ... */); + Context::forgetHidden(/* ... */); + +## Events + +Context dispatches two events that allow you to hook into the hydration and +dehydration process of the context. + +To illustrate how these events may be used, imagine that in a middleware of +your application you set the `app.locale` configuration value based on the +incoming HTTP request's `Accept-Language` header. Context's events allow you +to capture this value during the request and restore it on the queue, ensuring +notifications sent on the queue have the correct `app.locale` value. We can +use context's events and hidden data to achieve this, which the following +documentation will illustrate. + +### Dehydrating + +Whenever a job is dispatched to the queue the data in the context is +"dehydrated" and captured alongside the job's payload. The +`Context::dehydrating` method allows you to register a closure that will be +invoked during the dehydration process. Within this closure, you may make +changes to the data that will be shared with the queued job. + +Typically, you should register `dehydrating` callbacks within the `boot` +method of your application's `AppServiceProvider` class: + + + + 1use Illuminate\Log\Context\Repository; + + 2use Illuminate\Support\Facades\Config; + + 3use Illuminate\Support\Facades\Context; + + 4  + + 5/** + + 6 * Bootstrap any application services. + + 7 */ + + 8public function boot(): void + + 9{ + + 10 Context::dehydrating(function (Repository $context) { + + 11 $context->addHidden('locale', Config::get('app.locale')); + + 12 }); + + 13} + + + use Illuminate\Log\Context\Repository; + use Illuminate\Support\Facades\Config; + use Illuminate\Support\Facades\Context; + + /** + * Bootstrap any application services. + */ + public function boot(): void + { + Context::dehydrating(function (Repository $context) { + $context->addHidden('locale', Config::get('app.locale')); + }); + } + +You should not use the `Context` facade within the `dehydrating` callback, as +that will change the context of the current process. Ensure you only make +changes to the repository passed to the callback. + +### Hydrated + +Whenever a queued job begins executing on the queue, any context that was +shared with the job will be "hydrated" back into the current context. The +`Context::hydrated` method allows you to register a closure that will be +invoked during the hydration process. + +Typically, you should register `hydrated` callbacks within the `boot` method +of your application's `AppServiceProvider` class: + + + + 1use Illuminate\Log\Context\Repository; + + 2use Illuminate\Support\Facades\Config; + + 3use Illuminate\Support\Facades\Context; + + 4  + + 5/** + + 6 * Bootstrap any application services. + + 7 */ + + 8public function boot(): void + + 9{ + + 10 Context::hydrated(function (Repository $context) { + + 11 if ($context->hasHidden('locale')) { + + 12 Config::set('app.locale', $context->getHidden('locale')); + + 13 } + + 14 }); + + 15} + + + use Illuminate\Log\Context\Repository; + use Illuminate\Support\Facades\Config; + use Illuminate\Support\Facades\Context; + + /** + * Bootstrap any application services. + */ + public function boot(): void + { + Context::hydrated(function (Repository $context) { + if ($context->hasHidden('locale')) { + Config::set('app.locale', $context->getHidden('locale')); + } + }); + } + +You should not use the `Context` facade within the `hydrated` callback and +instead ensure you only make changes to the repository passed to the callback. + diff --git a/output/12.x/contracts.md b/output/12.x/contracts.md new file mode 100644 index 0000000..1531b35 --- /dev/null +++ b/output/12.x/contracts.md @@ -0,0 +1,240 @@ +# Contracts + + * Introduction + * Contracts vs. Facades + * When to Use Contracts + * How to Use Contracts + * Contract Reference + +## Introduction + +Laravel's "contracts" are a set of interfaces that define the core services +provided by the framework. For example, an `Illuminate\Contracts\Queue\Queue` +contract defines the methods needed for queueing jobs, while the +`Illuminate\Contracts\Mail\Mailer` contract defines the methods needed for +sending e-mail. + +Each contract has a corresponding implementation provided by the framework. +For example, Laravel provides a queue implementation with a variety of +drivers, and a mailer implementation that is powered by [Symfony +Mailer](https://symfony.com/doc/current/mailer.html). + +All of the Laravel contracts live in [their own GitHub +repository](https://github.com/illuminate/contracts). This provides a quick +reference point for all available contracts, as well as a single, decoupled +package that may be utilized when building packages that interact with Laravel +services. + +### Contracts vs. Facades + +Laravel's [facades](/docs/12.x/facades) and helper functions provide a simple +way of utilizing Laravel's services without needing to type-hint and resolve +contracts out of the service container. In most cases, each facade has an +equivalent contract. + +Unlike facades, which do not require you to require them in your class' +constructor, contracts allow you to define explicit dependencies for your +classes. Some developers prefer to explicitly define their dependencies in +this way and therefore prefer to use contracts, while other developers enjoy +the convenience of facades. **In general, most applications can use facades +without issue during development.** + +## When to Use Contracts + +The decision to use contracts or facades will come down to personal taste and +the tastes of your development team. Both contracts and facades can be used to +create robust, well-tested Laravel applications. Contracts and facades are not +mutually exclusive. Some parts of your applications may use facades while +others depend on contracts. As long as you are keeping your class' +responsibilities focused, you will notice very few practical differences +between using contracts and facades. + +In general, most applications can use facades without issue during +development. If you are building a package that integrates with multiple PHP +frameworks you may wish to use the `illuminate/contracts` package to define +your integration with Laravel's services without the need to require Laravel's +concrete implementations in your package's `composer.json` file. + +## How to Use Contracts + +So, how do you get an implementation of a contract? It's actually quite +simple. + +Many types of classes in Laravel are resolved through the [service +container](/docs/12.x/container), including controllers, event listeners, +middleware, queued jobs, and even route closures. So, to get an implementation +of a contract, you can just "type-hint" the interface in the constructor of +the class being resolved. + +For example, take a look at this event listener: + + + + 1 + + 5 */ + + 6public function attachments(): array + + 7{ + + 8 return [ + + 9 Attachment::fromStorage('/path/to/file'), + + 10 ]; + + 11} + + + /** + * Get the attachments for the message. + * + * @return array + */ + public function attachments(): array + { + return [ + Attachment::fromStorage('/path/to/file'), + ]; + } + +### StyleCI + +Don't worry if your code styling isn't perfect! [StyleCI](https://styleci.io/) +will automatically merge any style fixes into the Laravel repository after +pull requests are merged. This allows us to focus on the content of the +contribution and not the code style. + +## Code of Conduct + +The Laravel code of conduct is derived from the Ruby code of conduct. Any +violations of the code of conduct may be reported to Taylor Otwell ([[email +protected]](/cdn-cgi/l/email- +protection#b0c4d1c9dcdfc2f0dcd1c2d1c6d5dc9ed3dfdd)): + + * Participants will be tolerant of opposing views. + * Participants must ensure that their language and actions are free of personal attacks and disparaging personal remarks. + * When interpreting the words and actions of others, participants should always assume good intentions. + * Behavior that can be reasonably considered harassment will not be tolerated. + diff --git a/output/12.x/controllers.md b/output/12.x/controllers.md new file mode 100644 index 0000000..82ede72 --- /dev/null +++ b/output/12.x/controllers.md @@ -0,0 +1,1358 @@ +# Controllers + + * Introduction + * Writing Controllers + * Basic Controllers + * Single Action Controllers + * Controller Middleware + * Resource Controllers + * Partial Resource Routes + * Nested Resources + * Naming Resource Routes + * Naming Resource Route Parameters + * Scoping Resource Routes + * Localizing Resource URIs + * Supplementing Resource Controllers + * Singleton Resource Controllers + * Middleware and Resource Controllers + * Dependency Injection and Controllers + +## Introduction + +Instead of defining all of your request handling logic as closures in your +route files, you may wish to organize this behavior using "controller" +classes. Controllers can group related request handling logic into a single +class. For example, a `UserController` class might handle all incoming +requests related to users, including showing, creating, updating, and deleting +users. By default, controllers are stored in the `app/Http/Controllers` +directory. + +## Writing Controllers + +### Basic Controllers + +To quickly generate a new controller, you may run the `make:controller` +Artisan command. By default, all of the controllers for your application are +stored in the `app/Http/Controllers` directory: + + + + 1php artisan make:controller UserController + + + 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: + + + + 1 User::findOrFail($id) + + 17 ]); + + 18 } + + 19} + + + User::findOrFail($id) + ]); + } + } + +Once you have written a controller class and method, you may define a route to +the controller method like so: + + + + 1use App\Http\Controllers\UserController; + + 2  + + 3Route::get('/user/{id}', [UserController::class, 'show']); + + + use App\Http\Controllers\UserController; + + 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. + +Controllers are not **required** to extend a base class. However, it is +sometimes convenient to extend a base controller class that contains methods +that should be shared across all of your controllers. + +### Single Action Controllers + +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: + + + + 1middleware('auth'); + + + 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: + + + + 1 PhotoController::class, + + 3 'posts' => PostController::class, + + 4]); + + + Route::resources([ + 'photos' => PhotoController::class, + 'posts' => PostController::class, + ]); + +The `softDeletableResources` method registers many resources controllers that +all use the `withTrashed` method: + + + + 1Route::softDeletableResources([ + + 2 'photos' => PhotoController::class, + + 3 'posts' => PostController::class, + + 4]); + + + Route::softDeletableResources([ + 'photos' => PhotoController::class, + 'posts' => PostController::class, + ]); + +#### Actions Handled by Resource Controllers + +Verb | URI | Action | Route Name +---|---|---|--- +GET | `/photos` | index | photos.index +GET | `/photos/create` | create | photos.create +POST | `/photos` | store | photos.store +GET | `/photos/{photo}` | show | photos.show +GET | `/photos/{photo}/edit` | edit | photos.edit +PUT/PATCH | `/photos/{photo}` | update | photos.update +DELETE | `/photos/{photo}` | destroy | photos.destroy + +#### Customizing Missing Model Behavior + +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: + + + + 1use App\Http\Controllers\PhotoController; + + 2use Illuminate\Http\Request; + + 3use Illuminate\Support\Facades\Redirect; + + 4  + + 5Route::resource('photos', PhotoController::class) + + 6 ->missing(function (Request $request) { + + 7 return Redirect::route('photos.index'); + + 8 }); + + + 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/12.x/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: + + + + 1use App\Http\Controllers\PhotoController; + + 2  + + 3Route::resource('photos', PhotoController::class)->withTrashed(); + + + use App\Http\Controllers\PhotoController; + + 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: + + + + 1Route::resource('photos', PhotoController::class)->withTrashed(['show']); + + + Route::resource('photos', PhotoController::class)->withTrashed(['show']); + +#### Specifying the Resource Model + +If you are using [route model binding](/docs/12.x/routing#route-model-binding) +and would like the resource controller's methods to type-hint a model +instance, you may use the `--model` option when generating the controller: + + + + 1php artisan make:controller PhotoController --model=Photo --resource + + + php artisan make:controller PhotoController --model=Photo --resource + +#### Generating Form Requests + +You may provide the `--requests` option when generating a resource controller +to instruct Artisan to generate [form request +classes](/docs/12.x/validation#form-request-validation) for the controller's +storage and update methods: + + + + 1php artisan make:controller PhotoController --model=Photo --resource --requests + + + php artisan make:controller PhotoController --model=Photo --resource --requests + +### Partial Resource Routes + +When declaring a resource route, you may specify a subset of actions the +controller should handle instead of the full set of default actions: + + + + 1use App\Http\Controllers\PhotoController; + + 2  + + 3Route::resource('photos', PhotoController::class)->only([ + + 4 'index', 'show' + + 5]); + + 6  + + 7Route::resource('photos', PhotoController::class)->except([ + + 8 'create', 'store', 'update', 'destroy' + + 9]); + + + use App\Http\Controllers\PhotoController; + + Route::resource('photos', PhotoController::class)->only([ + 'index', 'show' + ]); + + 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: + + + + 1use App\Http\Controllers\PhotoController; + + 2  + + 3Route::apiResource('photos', PhotoController::class); + + + use App\Http\Controllers\PhotoController; + + Route::apiResource('photos', PhotoController::class); + +You may register many API resource controllers at once by passing an array to +the `apiResources` method: + + + + 1use App\Http\Controllers\PhotoController; + + 2use App\Http\Controllers\PostController; + + 3  + + 4Route::apiResources([ + + 5 'photos' => PhotoController::class, + + 6 'posts' => PostController::class, + + 7]); + + + use App\Http\Controllers\PhotoController; + use App\Http\Controllers\PostController; + + 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: + + + + 1php artisan make:controller PhotoController --api + + + php artisan make:controller PhotoController --api + +### Nested Resources + +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: + + + + 1use App\Http\Controllers\PhotoCommentController; + + 2  + + 3Route::resource('photos.comments', PhotoCommentController::class); + + + use App\Http\Controllers\PhotoCommentController; + + Route::resource('photos.comments', PhotoCommentController::class); + +This route will register a nested resource that may be accessed with URIs like +the following: + + + + 1/photos/{photo}/comments/{comment} + + + /photos/{photo}/comments/{comment} + +#### Scoping Nested Resources + +Laravel's [implicit model binding](/docs/12.x/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. For more information on how to accomplish this, please see the +documentation on scoping resource routes. + +#### Shallow Nesting + +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": + + + + 1use App\Http\Controllers\CommentController; + + 2  + + 3Route::resource('photos.comments', CommentController::class)->shallow(); + + + use App\Http\Controllers\CommentController; + + Route::resource('photos.comments', CommentController::class)->shallow(); + +This route definition will define the following routes: + +Verb | URI | Action | Route Name +---|---|---|--- +GET | `/photos/{photo}/comments` | index | photos.comments.index +GET | `/photos/{photo}/comments/create` | create | photos.comments.create +POST | `/photos/{photo}/comments` | store | photos.comments.store +GET | `/comments/{comment}` | show | comments.show +GET | `/comments/{comment}/edit` | edit | comments.edit +PUT/PATCH | `/comments/{comment}` | update | comments.update +DELETE | `/comments/{comment}` | destroy | comments.destroy + +### Naming Resource 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: + + + + 1use App\Http\Controllers\PhotoController; + + 2  + + 3Route::resource('photos', PhotoController::class)->names([ + + 4 'create' => 'photos.build' + + 5]); + + + use App\Http\Controllers\PhotoController; + + 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: + + + + 1use App\Http\Controllers\AdminUserController; + + 2  + + 3Route::resource('users', AdminUserController::class)->parameters([ + + 4 'users' => 'admin_user' + + 5]); + + + use App\Http\Controllers\AdminUserController; + + Route::resource('users', AdminUserController::class)->parameters([ + 'users' => 'admin_user' + ]); + +The example above generates the following URI for the resource's `show` route: + + + + 1/users/{admin_user} + + + /users/{admin_user} + +### Scoping Resource Routes + +Laravel's [scoped implicit model binding](/docs/12.x/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: + + + + 1use App\Http\Controllers\PhotoCommentController; + + 2  + + 3Route::resource('photos.comments', PhotoCommentController::class)->scoped([ + + 4 'comment' => 'slug', + + 5]); + + + use App\Http\Controllers\PhotoCommentController; + + 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: + + + + 1/photos/{photo}/comments/{comment:slug} + + + /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. + +### Localizing Resource URIs + +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`: + + + + 1/** + + 2 * Bootstrap any application services. + + 3 */ + + 4public function boot(): void + + 5{ + + 6 Route::resourceVerbs([ + + 7 'create' => 'crear', + + 8 'edit' => 'editar', + + 9 ]); + + 10} + + + /** + * 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/12.x/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: + + + + 1/publicacion/crear + + 2 + + 3/publicacion/{publicaciones}/editar + + + /publicacion/crear + + /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: + + + + 1use App\Http\Controller\PhotoController; + + 2  + + 3Route::get('/photos/popular', [PhotoController::class, 'popular']); + + 4Route::resource('photos', PhotoController::class); + + + use App\Http\Controller\PhotoController; + + Route::get('/photos/popular', [PhotoController::class, 'popular']); + Route::resource('photos', PhotoController::class); + +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. + +### Singleton Resource Controllers + +Sometimes, your application will have resources that may only have a single +instance. For example, a user's "profile" can be edited or updated, but a user +may not have more than one "profile". Likewise, an image may have a single +"thumbnail". These resources are called "singleton resources", meaning one and +only one instance of the resource may exist. In these scenarios, you may +register a "singleton" resource controller: + + + + 1use App\Http\Controllers\ProfileController; + + 2use Illuminate\Support\Facades\Route; + + 3  + + 4Route::singleton('profile', ProfileController::class); + + + use App\Http\Controllers\ProfileController; + use Illuminate\Support\Facades\Route; + + Route::singleton('profile', ProfileController::class); + +The singleton resource definition above will register the following routes. As +you can see, "creation" routes are not registered for singleton resources, and +the registered routes do not accept an identifier since only one instance of +the resource may exist: + +Verb | URI | Action | Route Name +---|---|---|--- +GET | `/profile` | show | profile.show +GET | `/profile/edit` | edit | profile.edit +PUT/PATCH | `/profile` | update | profile.update + +Singleton resources may also be nested within a standard resource: + + + + 1Route::singleton('photos.thumbnail', ThumbnailController::class); + + + Route::singleton('photos.thumbnail', ThumbnailController::class); + +In this example, the `photos` resource would receive all of the standard +resource routes; however, the `thumbnail` resource would be a singleton +resource with the following routes: + +Verb | URI | Action | Route Name +---|---|---|--- +GET | `/photos/{photo}/thumbnail` | show | photos.thumbnail.show +GET | `/photos/{photo}/thumbnail/edit` | edit | photos.thumbnail.edit +PUT/PATCH | `/photos/{photo}/thumbnail` | update | photos.thumbnail.update + +#### Creatable Singleton Resources + +Occasionally, you may want to define creation and storage routes for a +singleton resource. To accomplish this, you may invoke the `creatable` method +when registering the singleton resource route: + + + + 1Route::singleton('photos.thumbnail', ThumbnailController::class)->creatable(); + + + Route::singleton('photos.thumbnail', ThumbnailController::class)->creatable(); + +In this example, the following routes will be registered. As you can see, a +`DELETE` route will also be registered for creatable singleton resources: + +Verb | URI | Action | Route Name +---|---|---|--- +GET | `/photos/{photo}/thumbnail/create` | create | photos.thumbnail.create +POST | `/photos/{photo}/thumbnail` | store | photos.thumbnail.store +GET | `/photos/{photo}/thumbnail` | show | photos.thumbnail.show +GET | `/photos/{photo}/thumbnail/edit` | edit | photos.thumbnail.edit +PUT/PATCH | `/photos/{photo}/thumbnail` | update | photos.thumbnail.update +DELETE | `/photos/{photo}/thumbnail` | destroy | photos.thumbnail.destroy + +If you would like Laravel to register the `DELETE` route for a singleton +resource but not register the creation or storage routes, you may utilize the +`destroyable` method: + + + + 1Route::singleton(...)->destroyable(); + + + Route::singleton(...)->destroyable(); + +#### API Singleton Resources + +The `apiSingleton` method may be used to register a singleton resource that +will be manipulated via an API, thus rendering the `create` and `edit` routes +unnecessary: + + + + 1Route::apiSingleton('profile', ProfileController::class); + + + Route::apiSingleton('profile', ProfileController::class); + +Of course, API singleton resources may also be `creatable`, which will +register `store` and `destroy` routes for the resource: + + + + 1Route::apiSingleton('photos.thumbnail', ProfileController::class)->creatable(); + + + Route::apiSingleton('photos.thumbnail', ProfileController::class)->creatable(); + +### Middleware and Resource Controllers + +Laravel allows you to assign middleware to all, or only specific, methods of +resource routes using the `middleware`, `middlewareFor`, and +`withoutMiddlewareFor` methods. These methods provide fine-grained control +over which middleware is applied to each resource action. + +#### Applying Middleware to all Methods + +You may use the `middleware` method to assign middleware to all routes +generated by a resource or singleton resource route: + + + + 1Route::resource('users', UserController::class) + + 2 ->middleware(['auth', 'verified']); + + 3  + + 4Route::singleton('profile', ProfileController::class) + + 5 ->middleware('auth'); + + + Route::resource('users', UserController::class) + ->middleware(['auth', 'verified']); + + Route::singleton('profile', ProfileController::class) + ->middleware('auth'); + +#### Applying Middleware to Specific Methods + +You may use the `middlewareFor` method to assign middleware to one or more +specific methods of a given resource controller: + + + + 1Route::resource('users', UserController::class) + + 2 ->middlewareFor('show', 'auth'); + + 3  + + 4Route::apiResource('users', UserController::class) + + 5 ->middlewareFor(['show', 'update'], 'auth'); + + 6  + + 7Route::resource('users', UserController::class) + + 8 ->middlewareFor('show', 'auth') + + 9 ->middlewareFor('update', 'auth'); + + 10  + + 11Route::apiResource('users', UserController::class) + + 12 ->middlewareFor(['show', 'update'], ['auth', 'verified']); + + + Route::resource('users', UserController::class) + ->middlewareFor('show', 'auth'); + + Route::apiResource('users', UserController::class) + ->middlewareFor(['show', 'update'], 'auth'); + + Route::resource('users', UserController::class) + ->middlewareFor('show', 'auth') + ->middlewareFor('update', 'auth'); + + Route::apiResource('users', UserController::class) + ->middlewareFor(['show', 'update'], ['auth', 'verified']); + +The `middlewareFor` method may also be used in conjunction with singleton and +API singleton resource controllers: + + + + 1Route::singleton('profile', ProfileController::class) + + 2 ->middlewareFor('show', 'auth'); + + 3  + + 4Route::apiSingleton('profile', ProfileController::class) + + 5 ->middlewareFor(['show', 'update'], 'auth'); + + + Route::singleton('profile', ProfileController::class) + ->middlewareFor('show', 'auth'); + + Route::apiSingleton('profile', ProfileController::class) + ->middlewareFor(['show', 'update'], 'auth'); + +#### Excluding Middleware from Specific Methods + +You may use the `withoutMiddlewareFor` method to exclude middleware from +specific methods of a resource controller: + + + + 1Route::middleware(['auth', 'verified', 'subscribed'])->group(function () { + + 2 Route::resource('users', UserController::class) + + 3 ->withoutMiddlewareFor('index', ['auth', 'verified']) + + 4 ->withoutMiddlewareFor(['create', 'store'], 'verified') + + 5 ->withoutMiddlewareFor('destroy', 'subscribed'); + + 6}); + + + Route::middleware(['auth', 'verified', 'subscribed'])->group(function () { + Route::resource('users', UserController::class) + ->withoutMiddlewareFor('index', ['auth', 'verified']) + ->withoutMiddlewareFor(['create', 'store'], 'verified') + ->withoutMiddlewareFor('destroy', 'subscribed'); + }); + +## Dependency Injection and Controllers + +#### Constructor Injection + +The Laravel [service container](/docs/12.x/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: + + + + 1name; + + 16  + + 17 // Store the user... + + 18  + + 19 return redirect('/users'); + + 20 } + + 21} + + + name; + + // Store the user... + + 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: + + + + 1use App\Http\Controllers\UserController; + + 2  + + 3Route::put('/user/{id}', [UserController::class, 'update']); + + + use App\Http\Controllers\UserController; + + 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: + + + + 1 + + 2 + + 3 + + 4  + + 5 + + +
    + +
    + + + +If the malicious website automatically submits the form when the page is +loaded, the malicious user only needs to lure an unsuspecting user of your +application to visit their website and their email address will be changed in +your application. + +To prevent this vulnerability, we need to inspect every incoming `POST`, +`PUT`, `PATCH`, or `DELETE` request for a secret session value that the +malicious application is unable to access. + +## Preventing CSRF Requests + +Laravel automatically generates a CSRF "token" for each active [user +session](/docs/12.x/session) managed by the application. This token is used to +verify that the authenticated user is the person actually making the requests +to the application. Since this token is stored in the user's session and +changes each time the session is regenerated, a malicious application is +unable to access it. + +The current session's CSRF token can be accessed via the request's session or +via the `csrf_token` helper function: + + + + 1use Illuminate\Http\Request; + + 2  + + 3Route::get('/token', function (Request $request) { + + 4 $token = $request->session()->token(); + + 5  + + 6 $token = csrf_token(); + + 7  + + 8 // ... + + 9}); + + + use Illuminate\Http\Request; + + Route::get('/token', function (Request $request) { + $token = $request->session()->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: + + + + 1
    + + 2 @csrf + + 3  + + 4 + + 5 + + 6
    + + +
    + @csrf + + + +
    + +The `Illuminate\Foundation\Http\Middleware\ValidateCsrfToken` +[middleware](/docs/12.x/middleware), which is included in the `web` middleware +group by default, will automatically verify that the token in the request +input matches the token stored in the session. When these two tokens match, we +know that the authenticated user is the one initiating the request. + +### CSRF Tokens & SPAs + +If you are building an SPA that is utilizing Laravel as an API backend, you +should consult the [Laravel Sanctum documentation](/docs/12.x/sanctum) for +information on authenticating with your API and protecting against CSRF +vulnerabilities. + +### Excluding URIs From CSRF Protection + +Sometimes you may wish to exclude a set of URIs from CSRF protection. For +example, if you are using [Stripe](https://stripe.com) to process payments and +are utilizing their webhook system, you will need to exclude your Stripe +webhook handler route from CSRF protection since Stripe will not know what +CSRF token to send to your routes. + +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: + + + + 1->withMiddleware(function (Middleware $middleware) { + + 2 $middleware->validateCsrfTokens(except: [ + + 3 'stripe/*', + + 4 'http://example.com/foo/bar', + + 5 'http://example.com/foo/*', + + 6 ]); + + 7}) + + + ->withMiddleware(function (Middleware $middleware) { + $middleware->validateCsrfTokens(except: [ + 'stripe/*', + 'http://example.com/foo/bar', + 'http://example.com/foo/*', + ]); + }) + +For convenience, the CSRF middleware is automatically disabled for all routes +when [running tests](/docs/12.x/testing). + +## X-CSRF-TOKEN + +In addition to checking for the CSRF token as a POST parameter, the +`Illuminate\Foundation\Http\Middleware\ValidateCsrfToken` middleware, which is +included in the `web` middleware group by default, will also check for the +`X-CSRF-TOKEN` request header. You could, for example, store the token in an +HTML `meta` tag: + + + + 1 + + + + +Then, you can instruct a library like jQuery to automatically add the token to +all request headers. This provides simple, convenient CSRF protection for your +AJAX based applications using legacy JavaScript technology: + + + + 1$.ajaxSetup({ + + 2 headers: { + + 3 'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content') + + 4 } + + 5}); + + + $.ajaxSetup({ + headers: { + 'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content') + } + }); + +## X-XSRF-TOKEN + +Laravel stores the current CSRF token in an encrypted `XSRF-TOKEN` cookie that +is included with each response generated by the framework. You can use the +cookie value to set the `X-XSRF-TOKEN` request header. + +This cookie is primarily sent as a developer convenience since some JavaScript +frameworks and libraries, like Angular and Axios, automatically place its +value in the `X-XSRF-TOKEN` header on same-origin requests. + +By default, the `resources/js/bootstrap.js` file includes the Axios HTTP +library which will automatically send the `X-XSRF-TOKEN` header for you. + diff --git a/output/12.x/database-testing.md b/output/12.x/database-testing.md new file mode 100644 index 0000000..5a38410 --- /dev/null +++ b/output/12.x/database-testing.md @@ -0,0 +1,646 @@ +# Database Testing + + * Introduction + * Resetting the Database After Each Test + * Model Factories + * Running Seeders + * Available Assertions + +## Introduction + +Laravel provides a variety of helpful tools and assertions to make it easier +to test your database driven applications. In addition, Laravel model +factories and seeders make it painless to create test database records using +your application's Eloquent models and relationships. We'll discuss all of +these powerful features in the following documentation. + +### Resetting the Database After Each Test + +Before proceeding much further, let's discuss how to reset your database after +each of your tests so that data from a previous test does not interfere with +subsequent tests. Laravel's included +`Illuminate\Foundation\Testing\RefreshDatabase` trait will take care of this +for you. Simply use the trait on your test class: + +Pest PHPUnit + + + + 1use(RefreshDatabase::class); + + 6  + + 7test('basic example', function () { + + 8 $response = $this->get('/'); + + 9  + + 10 // ... + + 11}); + + + use(RefreshDatabase::class); + + test('basic example', function () { + $response = $this->get('/'); + + // ... + }); + + + 1get('/'); + + 18  + + 19 // ... + + 20 } + + 21} + + + get('/'); + + // ... + } + } + +The `Illuminate\Foundation\Testing\RefreshDatabase` trait does not migrate +your database if your schema is up to date. Instead, it will only execute the +test within a database transaction. Therefore, any records added to the +database by test cases that do not use this trait may still exist in the +database. + +If you would like to totally reset the database, you may use the +`Illuminate\Foundation\Testing\DatabaseMigrations` or +`Illuminate\Foundation\Testing\DatabaseTruncation` traits instead. However, +both of these options are significantly slower than the `RefreshDatabase` +trait. + +## Model Factories + +When testing, you may need to insert a few records into your database before +executing your test. Instead of manually specifying the value of each column +when you create this test data, Laravel allows you to define a set of default +attributes for each of your [Eloquent models](/docs/12.x/eloquent) using +[model factories](/docs/12.x/eloquent-factories). + +To learn more about creating and utilizing model factories to create models, +please consult the complete [model factory documentation](/docs/12.x/eloquent- +factories). Once you have defined a model factory, you may utilize the factory +within your test to create models: + +Pest PHPUnit + + + + 1use App\Models\User; + + 2  + + 3test('models can be instantiated', function () { + + 4 $user = User::factory()->create(); + + 5  + + 6 // ... + + 7}); + + + use App\Models\User; + + test('models can be instantiated', function () { + $user = User::factory()->create(); + + // ... + }); + + + 1use App\Models\User; + + 2  + + 3public function test_models_can_be_instantiated(): void + + 4{ + + 5 $user = User::factory()->create(); + + 6  + + 7 // ... + + 8} + + + use App\Models\User; + + public function test_models_can_be_instantiated(): void + { + $user = User::factory()->create(); + + // ... + } + +## Running Seeders + +If you would like to use [database seeders](/docs/12.x/seeding) to populate +your database during a feature test, you may invoke the `seed` method. By +default, the `seed` method will execute the `DatabaseSeeder`, which should +execute all of your other seeders. Alternatively, you pass a specific seeder +class name to the `seed` method: + +Pest PHPUnit + + + + 1use(RefreshDatabase::class); + + 8  + + 9test('orders can be created', function () { + + 10 // Run the DatabaseSeeder... + + 11 $this->seed(); + + 12  + + 13 // Run a specific seeder... + + 14 $this->seed(OrderStatusSeeder::class); + + 15  + + 16 // ... + + 17  + + 18 // Run an array of specific seeders... + + 19 $this->seed([ + + 20 OrderStatusSeeder::class, + + 21 TransactionStatusSeeder::class, + + 22 // ... + + 23 ]); + + 24}); + + + use(RefreshDatabase::class); + + test('orders can be created', function () { + // Run the DatabaseSeeder... + $this->seed(); + + // Run a specific seeder... + $this->seed(OrderStatusSeeder::class); + + // ... + + // Run an array of specific seeders... + $this->seed([ + OrderStatusSeeder::class, + TransactionStatusSeeder::class, + // ... + ]); + }); + + + 1seed(); + + 21  + + 22 // Run a specific seeder... + + 23 $this->seed(OrderStatusSeeder::class); + + 24  + + 25 // ... + + 26  + + 27 // Run an array of specific seeders... + + 28 $this->seed([ + + 29 OrderStatusSeeder::class, + + 30 TransactionStatusSeeder::class, + + 31 // ... + + 32 ]); + + 33 } + + 34} + + + seed(); + + // Run a specific seeder... + $this->seed(OrderStatusSeeder::class); + + // ... + + // Run an array of specific seeders... + $this->seed([ + OrderStatusSeeder::class, + TransactionStatusSeeder::class, + // ... + ]); + } + } + +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: + + + + 1assertDatabaseCount('users', 5); + + + $this->assertDatabaseCount('users', 5); + +#### assertDatabaseEmpty + +Assert that a table in the database contains no records: + + + + 1$this->assertDatabaseEmpty('users'); + + + $this->assertDatabaseEmpty('users'); + +#### assertDatabaseHas + +Assert that a table in the database contains records matching the given key / +value query constraints: + + + + 1$this->assertDatabaseHas('users', [ + + 2 'email' => '[[email protected]](/cdn-cgi/l/email-protection)', + + 3]); + + + $this->assertDatabaseHas('users', [ + 'email' => '[[email protected]](/cdn-cgi/l/email-protection)', + ]); + +#### assertDatabaseMissing + +Assert that a table in the database does not contain records matching the +given key / value query constraints: + + + + 1$this->assertDatabaseMissing('users', [ + + 2 'email' => '[[email protected]](/cdn-cgi/l/email-protection)', + + 3]); + + + $this->assertDatabaseMissing('users', [ + 'email' => '[[email protected]](/cdn-cgi/l/email-protection)', + ]); + +#### assertSoftDeleted + +The `assertSoftDeleted` method may be used to assert a given Eloquent model +has been "soft deleted": + + + + 1$this->assertSoftDeleted($user); + + + $this->assertSoftDeleted($user); + +#### assertNotSoftDeleted + +The `assertNotSoftDeleted` method may be used to assert a given Eloquent model +hasn't been "soft deleted": + + + + 1$this->assertNotSoftDeleted($user); + + + $this->assertNotSoftDeleted($user); + +#### assertModelExists + +Assert that a given model or collection of models exist in the database: + + + + 1use App\Models\User; + + 2  + + 3$user = User::factory()->create(); + + 4  + + 5$this->assertModelExists($user); + + + use App\Models\User; + + $user = User::factory()->create(); + + $this->assertModelExists($user); + +#### assertModelMissing + +Assert that a given model or collection of models do not exist in the +database: + + + + 1use App\Models\User; + + 2  + + 3$user = User::factory()->create(); + + 4  + + 5$user->delete(); + + 6  + + 7$this->assertModelMissing($user); + + + use App\Models\User; + + $user = User::factory()->create(); + + $user->delete(); + + $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: + + + + 1$this->expectsDatabaseQueryCount(5); + + 2  + + 3// Test... + + + $this->expectsDatabaseQueryCount(5); + + // Test... + diff --git a/output/12.x/database.md b/output/12.x/database.md new file mode 100644 index 0000000..9ad93a2 --- /dev/null +++ b/output/12.x/database.md @@ -0,0 +1,1051 @@ +# Database: Getting Started + + * Introduction + * Configuration + * Read and Write Connections + * Running SQL Queries + * Using Multiple Database Connections + * Listening for Query Events + * Monitoring Cumulative Query Time + * Database Transactions + * Connecting to the Database CLI + * Inspecting Your Databases + * Monitoring Your Databases + +## Introduction + +Almost every modern web application interacts with a database. Laravel makes +interacting with databases extremely simple across a variety of supported +databases using raw SQL, a [fluent query builder](/docs/12.x/queries), and the +[Eloquent ORM](/docs/12.x/eloquent). Currently, Laravel provides first-party +support for five databases: + + * MariaDB 10.3+ ([Version Policy](https://mariadb.org/about/#maintenance-policy)) + * MySQL 5.7+ ([Version Policy](https://en.wikipedia.org/wiki/MySQL#Release_history)) + * PostgreSQL 10.0+ ([Version Policy](https://www.postgresql.org/support/versioning/)) + * SQLite 3.26.0+ + * SQL Server 2017+ ([Version Policy](https://docs.microsoft.com/en-us/lifecycle/products/?products=sql-server)) + +Additionally, MongoDB is supported via the `mongodb/laravel-mongodb` package, +which is officially maintained by MongoDB. Check out the [Laravel +MongoDB](https://www.mongodb.com/docs/drivers/php/laravel-mongodb/) +documentation for more information. + +### Configuration + +The configuration for Laravel's database services is located in your +application's `config/database.php` configuration file. In this file, you may +define all of your database connections, as well as specify which connection +should be used by default. Most of the configuration options within this file +are driven by the values of your application's environment variables. Examples +for most of Laravel's supported database systems are provided in this file. + +By default, Laravel's sample [environment +configuration](/docs/12.x/configuration#environment-configuration) is ready to +use with [Laravel Sail](/docs/12.x/sail), which is a Docker configuration for +developing Laravel applications on your local machine. However, you are free +to modify your database configuration as needed for your local database. + +#### SQLite Configuration + +SQLite databases are contained within a single file on your filesystem. You +can create a new SQLite database using the `touch` command in your terminal: +`touch database/database.sqlite`. After the database has been created, you may +easily configure your environment variables to point to this database by +placing the absolute path to the database in the `DB_DATABASE` environment +variable: + + + + 1DB_CONNECTION=sqlite + + 2DB_DATABASE=/absolute/path/to/database.sqlite + + + DB_CONNECTION=sqlite + DB_DATABASE=/absolute/path/to/database.sqlite + +By default, foreign key constraints are enabled for SQLite connections. If you +would like to disable them, you should set the `DB_FOREIGN_KEYS` environment +variable to `false`: + + + + 1DB_FOREIGN_KEYS=false + + + DB_FOREIGN_KEYS=false + +If you use the [Laravel installer](/docs/12.x/installation#creating-a-laravel- +project) to create your Laravel application and select SQLite as your +database, Laravel will automatically create a `database/database.sqlite` file +and run the default [database migrations](/docs/12.x/migrations) for you. + +#### Microsoft SQL Server Configuration + +To use a Microsoft SQL Server database, you should ensure that you have the +`sqlsrv` and `pdo_sqlsrv` PHP extensions installed as well as any dependencies +they may require such as the Microsoft SQL ODBC driver. + +#### Configuration Using URLs + +Typically, database connections are configured using multiple configuration +values such as `host`, `database`, `username`, `password`, etc. Each of these +configuration values has its own corresponding environment variable. This +means that when configuring your database connection information on a +production server, you need to manage several environment variables. + +Some managed database providers such as AWS and Heroku provide a single +database "URL" that contains all of the connection information for the +database in a single string. An example database URL may look something like +the following: + + + + 1mysql://root:[[email protected]](/cdn-cgi/l/email-protection)/forge?charset=UTF-8 + + + mysql://root:[[email protected]](/cdn-cgi/l/email-protection)/forge?charset=UTF-8 + +These URLs typically follow a standard schema convention: + + + + 1driver://username:password@host:port/database?options + + + driver://username:password@host:port/database?options + +For convenience, Laravel supports these URLs as an alternative to configuring +your database with multiple configuration options. If the `url` (or +corresponding `DB_URL` environment variable) configuration option is present, +it will be used to extract the database connection and credential information. + +### Read and Write Connections + +Sometimes you may wish to use one database connection for SELECT statements, +and another for INSERT, UPDATE, and DELETE statements. Laravel makes this a +breeze, and the proper connections will always be used whether you are using +raw queries, the query builder, or the Eloquent ORM. + +To see how read / write connections should be configured, let's look at this +example: + + + + 1'mysql' => [ + + 2 'read' => [ + + 3 'host' => [ + + 4 '192.168.1.1', + + 5 '196.168.1.2', + + 6 ], + + 7 ], + + 8 'write' => [ + + 9 'host' => [ + + 10 '196.168.1.3', + + 11 ], + + 12 ], + + 13 'sticky' => true, + + 14  + + 15 'database' => env('DB_DATABASE', 'laravel'), + + 16 'username' => env('DB_USERNAME', 'root'), + + 17 'password' => env('DB_PASSWORD', ''), + + 18 'unix_socket' => env('DB_SOCKET', ''), + + 19 'charset' => env('DB_CHARSET', 'utf8mb4'), + + 20 'collation' => env('DB_COLLATION', 'utf8mb4_unicode_ci'), + + 21 'prefix' => '', + + 22 'prefix_indexes' => true, + + 23 'strict' => true, + + 24 'engine' => null, + + 25 'options' => extension_loaded('pdo_mysql') ? array_filter([ + + 26 PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'), + + 27 ]) : [], + + 28], + + + 'mysql' => [ + 'read' => [ + 'host' => [ + '192.168.1.1', + '196.168.1.2', + ], + ], + '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'), + ]) : [], + ], + +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. + +You only need to place items in the `read` and `write` arrays if you wish to +override the values from the main `mysql` array. So, in this case, +`192.168.1.1` will be used as the host for the "read" connection, while +`192.168.1.3` will be used for the "write" connection. The database +credentials, prefix, character set, and all other options in the main `mysql` +array will be shared across both connections. When multiple values exist in +the `host` configuration array, a database host will be randomly chosen for +each request. + +#### The `sticky` Option + +The `sticky` option is an _optional_ value that can be used to allow the +immediate reading of records that have been written to the database during the +current request cycle. If the `sticky` option is enabled and a "write" +operation has been performed against the database during the current request +cycle, any further "read" operations will use the "write" connection. This +ensures that any data written during the request cycle can be immediately read +back from the database during that same request. It is up to you to decide if +this is the desired behavior for your application. + +## Running SQL Queries + +Once you have configured your database connection, you may run queries using +the `DB` facade. The `DB` facade provides methods for each type of query: +`select`, `update`, `insert`, `delete`, and `statement`. + +#### Running a Select Query + +To run a basic SELECT query, you may use the `select` method on the `DB` +facade: + + + + 1 $users]); + + 18 } + + 19} + + + $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: + + + + 1use Illuminate\Support\Facades\DB; + + 2  + + 3$users = DB::select('select * from users'); + + 4  + + 5foreach ($users as $user) { + + 6 echo $user->name; + + 7} + + + use Illuminate\Support\Facades\DB; + + $users = DB::select('select * from users'); + + 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: + + + + 1$burgers = DB::scalar( + + 2 "select count(case when food = 'burger' then 1 end) as burgers from menu" + + 3); + + + $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: + + + + 1[$options, $notifications] = DB::selectResultSets( + + 2 "CALL get_user_options_and_notifications(?)", $request->user()->id + + 3); + + + [$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: + + + + 1$results = DB::select('select * from users where id = :id', ['id' => 1]); + + + $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: + + + + 1use Illuminate\Support\Facades\DB; + + 2  + + 3DB::insert('insert into users (id, name) values (?, ?)', [1, 'Marc']); + + + use Illuminate\Support\Facades\DB; + + 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: + + + + 1use Illuminate\Support\Facades\DB; + + 2  + + 3$affected = DB::update( + + 4 'update users set votes = 100 where name = ?', + + 5 ['Anita'] + + 6); + + + use Illuminate\Support\Facades\DB; + + $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: + + + + 1use Illuminate\Support\Facades\DB; + + 2  + + 3$deleted = DB::delete('delete from users'); + + + use Illuminate\Support\Facades\DB; + + $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: + + + + 1DB::statement('drop table users'); + + + 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: + + + + 1DB::unprepared('update users set votes = 100 where name = "Dries"'); + + + DB::unprepared('update users set votes = 100 where name = "Dries"'); + +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. + +#### Implicit Commits + +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: + + + + 1DB::unprepared('create table a (col varchar(1) null)'); + + + 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. + +### Using Multiple Database Connections + +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: + + + + 1use Illuminate\Support\Facades\DB; + + 2  + + 3$users = DB::connection('sqlite')->select(/* ... */); + + + use Illuminate\Support\Facades\DB; + + $users = DB::connection('sqlite')->select(/* ... */); + +You may access the raw, underlying PDO instance of a connection using the +`getPdo` method on a connection instance: + + + + 1$pdo = DB::connection()->getPdo(); + + + $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/12.x/providers): + + + + 1sql; + + 26 // $query->bindings; + + 27 // $query->time; + + 28 // $query->toRawSql(); + + 29 }); + + 30 } + + 31} + + + 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/12.x/providers): + + + + 1getColumns('users'); + + + $columns = Schema::connection('sqlite')->getColumns('users'); + +#### Table Overview + +If you would like to get an overview of an individual table within your +database, you may execute the `db:table` Artisan command. This command +provides a general overview of a database table, including its columns, types, +attributes, keys, and indexes: + + + + 1php artisan db:table users + + + php artisan db:table users + +## Monitoring Your Databases + +Using the `db:monitor` Artisan command, you can instruct Laravel to dispatch +an `Illuminate\Database\Events\DatabaseBusy` event if your database is +managing more than a specified number of open connections. + +To get started, you should schedule the `db:monitor` command to [run every +minute](/docs/12.x/scheduling). The command accepts the names of the database +connection configurations that you wish to monitor as well as the maximum +number of open connections that should be tolerated before dispatching an +event: + + + + 1php artisan db:monitor --databases=mysql,pgsql --max=100 + + + php artisan db:monitor --databases=mysql,pgsql --max=100 + +Scheduling this command alone is not enough to trigger a notification alerting +you of the number of open connections. When the command encounters a database +that has an open connection count that exceeds your threshold, a +`DatabaseBusy` event will be dispatched. You should listen for this event +within your application's `AppServiceProvider` in order to send a notification +to you or your development team: + + + + 1use App\Notifications\DatabaseApproachingMaxConnections; + + 2use Illuminate\Database\Events\DatabaseBusy; + + 3use Illuminate\Support\Facades\Event; + + 4use Illuminate\Support\Facades\Notification; + + 5  + + 6/** + + 7 * Bootstrap any application services. + + 8 */ + + 9public function boot(): void + + 10{ + + 11 Event::listen(function (DatabaseBusy $event) { + + 12 Notification::route('mail', '[[email protected]](/cdn-cgi/l/email-protection)') + + 13 ->notify(new DatabaseApproachingMaxConnections( + + 14 $event->connectionName, + + 15 $event->connections + + 16 )); + + 17 }); + + 18} + + + use App\Notifications\DatabaseApproachingMaxConnections; + use Illuminate\Database\Events\DatabaseBusy; + use Illuminate\Support\Facades\Event; + use Illuminate\Support\Facades\Notification; + + /** + * Bootstrap any application services. + */ + public function boot(): void + { + Event::listen(function (DatabaseBusy $event) { + Notification::route('mail', '[[email protected]](/cdn-cgi/l/email-protection)') + ->notify(new DatabaseApproachingMaxConnections( + $event->connectionName, + $event->connections + )); + }); + } + diff --git a/output/12.x/deployment.md b/output/12.x/deployment.md new file mode 100644 index 0000000..67d0c21 --- /dev/null +++ b/output/12.x/deployment.md @@ -0,0 +1,364 @@ +# Deployment + + * Introduction + * Server Requirements + * Server Configuration + * Nginx + * FrankenPHP + * Directory Permissions + * Optimization + * Caching Configuration + * Caching Events + * Caching Routes + * Caching Views + * Debug Mode + * The Health Route + * Deploying With Laravel Cloud or Forge + +## Introduction + +When you're ready to deploy your Laravel application to production, there are +some important things you can do to make sure your application is running as +efficiently as possible. In this document, we'll cover some great starting +points for making sure your Laravel application is deployed properly. + +## Server Requirements + +The Laravel framework has a few system requirements. You should ensure that +your web server has the following minimum PHP version and extensions: + + * PHP >= 8.2 + * Ctype PHP Extension + * cURL PHP Extension + * DOM PHP Extension + * Fileinfo PHP Extension + * Filter PHP Extension + * Hash PHP Extension + * Mbstring PHP Extension + * OpenSSL PHP Extension + * PCRE PHP Extension + * PDO PHP Extension + * Session PHP Extension + * Tokenizer PHP Extension + * XML PHP Extension + +## Server Configuration + +### Nginx + +If you are deploying your application to a server that is running Nginx, you +may use the following configuration file as a starting point for configuring +your web server. Most likely, this file will need to be customized depending +on your server's configuration. **If you would like assistance in managing +your server, consider using a fully-managed Laravel platform like[Laravel +Cloud](https://cloud.laravel.com).** + +Please ensure, like the configuration below, your web server directs all +requests to your application's `public/index.php` file. You should never +attempt to move the `index.php` file to your project's root, as serving the +application from the project root will expose many sensitive configuration +files to the public Internet: + + + + 1server { + + 2 listen 80; + + 3 listen [::]:80; + + 4 server_name example.com; + + 5 root /srv/example.com/public; + + 6  + + 7 add_header X-Frame-Options "SAMEORIGIN"; + + 8 add_header X-Content-Type-Options "nosniff"; + + 9  + + 10 index index.php; + + 11  + + 12 charset utf-8; + + 13  + + 14 location / { + + 15 try_files $uri $uri/ /index.php?$query_string; + + 16 } + + 17  + + 18 location = /favicon.ico { access_log off; log_not_found off; } + + 19 location = /robots.txt { access_log off; log_not_found off; } + + 20  + + 21 error_page 404 /index.php; + + 22  + + 23 location ~ ^/index\.php(/|$) { + + 24 fastcgi_pass unix:/var/run/php/php8.2-fpm.sock; + + 25 fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name; + + 26 include fastcgi_params; + + 27 fastcgi_hide_header X-Powered-By; + + 28 } + + 29  + + 30 location ~ /\.(?!well-known).* { + + 31 deny all; + + 32 } + + 33} + + + server { + listen 80; + listen [::]:80; + server_name example.com; + root /srv/example.com/public; + + add_header X-Frame-Options "SAMEORIGIN"; + add_header X-Content-Type-Options "nosniff"; + + index index.php; + + charset utf-8; + + location / { + try_files $uri $uri/ /index.php?$query_string; + } + + location = /favicon.ico { access_log off; log_not_found off; } + location = /robots.txt { access_log off; log_not_found off; } + + error_page 404 /index.php; + + location ~ ^/index\.php(/|$) { + fastcgi_pass unix:/var/run/php/php8.2-fpm.sock; + fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name; + include fastcgi_params; + fastcgi_hide_header X-Powered-By; + } + + location ~ /\.(?!well-known).* { + deny all; + } + } + +### FrankenPHP + +[FrankenPHP](https://frankenphp.dev/) may also be used to serve your Laravel +applications. FrankenPHP is a modern PHP application server written in Go. To +serve a Laravel PHP application using FrankenPHP, you may simply invoke its +`php-server` command: + + + + 1frankenphp php-server -r public/ + + + frankenphp php-server -r public/ + +To take advantage of more powerful features supported by FrankenPHP, such as +its [Laravel Octane](/docs/12.x/octane) integration, HTTP/3, modern +compression, or the ability to package Laravel applications as standalone +binaries, please consult FrankenPHP's [Laravel +documentation](https://frankenphp.dev/docs/laravel/). + +### Directory Permissions + +Laravel will need to write to the `bootstrap/cache` and `storage` directories, +so you should ensure the web server process owner has permission to write to +these directories. + +## Optimization + +When deploying your application to production, there are a variety of files +that should be cached, including your configuration, events, routes, and +views. Laravel provides a single, convenient `optimize` Artisan command that +will cache all of these files. This command should typically be invoked as +part of your application's deployment process: + + + + 1php artisan optimize + + + php artisan optimize + +The `optimize:clear` method may be used to remove all of the cache files +generated by the `optimize` command as well as all keys in the default cache +driver: + + + + 1php artisan optimize:clear + + + php artisan optimize:clear + +In the following documentation, we will discuss each of the granular +optimization commands that are executed by the `optimize` command. + +### Caching Configuration + +When deploying your application to production, you should make sure that you +run the `config:cache` Artisan command during your deployment process: + + + + 1php artisan config:cache + + + php artisan config:cache + +This command will combine all of Laravel's configuration files into a single, +cached file, which greatly reduces the number of trips the framework must make +to the filesystem when loading your configuration values. + +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 for `.env` variables +will return `null`. + +### Caching Events + +You should cache your application's auto-discovered event to listener mappings +during your deployment process. This can be accomplished by invoking the +`event:cache` Artisan command during deployment: + + + + 1php artisan event:cache + + + php artisan event:cache + +### Caching Routes + +If you are building a large application with many routes, you should make sure +that you are running the `route:cache` Artisan command during your deployment +process: + + + + 1php artisan route:cache + + + php artisan route:cache + +This command reduces all of your route registrations into a single method call +within a cached file, improving the performance of route registration when +registering hundreds of routes. + +### Caching Views + +When deploying your application to production, you should make sure that you +run the `view:cache` Artisan command during your deployment process: + + + + 1php artisan view:cache + + + php artisan view:cache + +This command precompiles all your Blade views so they are not compiled on +demand, improving the performance of each request that returns a view. + +## Debug Mode + +The debug option in your `config/app.php` configuration file determines how +much information about an error is actually displayed to the user. By default, +this option is set to respect the value of the `APP_DEBUG` environment +variable, which is stored in your application's `.env` file. + +**In your production environment, this value should always be`false`. If the +`APP_DEBUG` variable is set to `true` in production, you risk exposing +sensitive configuration values to your application's end users.** + +## The Health Route + +Laravel includes a built-in health check route that can be used to monitor the +status of your application. In production, this route may be used to report +the status of your application to an uptime monitor, load balancer, or +orchestration system such as Kubernetes. + +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: + + + + 1->withRouting( + + 2 web: __DIR__.'/../routes/web.php', + + 3 commands: __DIR__.'/../routes/console.php', + + 4 health: '/up', + + 5 health: '/status', + + 6) + + + ->withRouting( + web: __DIR__.'/../routes/web.php', + commands: __DIR__.'/../routes/console.php', + health: '/up', + health: '/status', + ) + +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/12.x/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. + +## Deploying With Laravel Cloud or Forge + +#### Laravel Cloud + +If you would like a fully-managed, auto-scaling deployment platform tuned for +Laravel, check out [Laravel Cloud](https://cloud.laravel.com). Laravel Cloud +is a robust deployment platform for Laravel, offering managed compute, +databases, caches, and object storage. + +Launch your Laravel application on Cloud and fall in love with the scalable +simplicity. Laravel Cloud is fine-tuned by Laravel's creators to work +seamlessly with the framework so you can keep writing your Laravel +applications exactly like you're used to. + +#### Laravel Forge + +If you prefer to manage your own servers but aren't comfortable configuring +all of the various services needed to run a robust Laravel application, +[Laravel Forge](https://forge.laravel.com) is a VPS server management platform +for Laravel applications. + +Laravel Forge can create servers on various infrastructure providers such as +DigitalOcean, Linode, AWS, and more. In addition, Forge installs and manages +all of the tools needed to build robust Laravel applications, such as Nginx, +MySQL, Redis, Memcached, Beanstalk, and more. + diff --git a/output/12.x/dusk.md b/output/12.x/dusk.md new file mode 100644 index 0000000..9798b11 --- /dev/null +++ b/output/12.x/dusk.md @@ -0,0 +1,4794 @@ +# Laravel Dusk + + * Introduction + * Installation + * Managing ChromeDriver Installations + * Using Other Browsers + * Getting Started + * Generating Tests + * Resetting the Database After Each Test + * Running Tests + * Environment Handling + * Browser Basics + * Creating Browsers + * Navigation + * Resizing Browser Windows + * Browser Macros + * Authentication + * Cookies + * Executing JavaScript + * Taking a Screenshot + * Storing Console Output to Disk + * Storing Page Source to Disk + * Interacting With Elements + * Dusk Selectors + * Text, Values, and Attributes + * Interacting With Forms + * Attaching Files + * Pressing Buttons + * Clicking Links + * Using the Keyboard + * Using the Mouse + * JavaScript Dialogs + * Interacting With Inline Frames + * Scoping Selectors + * Waiting for Elements + * Scrolling an Element Into View + * Available Assertions + * Pages + * Generating Pages + * Configuring Pages + * Navigating to Pages + * Shorthand Selectors + * Page Methods + * Components + * Generating Components + * Using Components + * Continuous Integration + * Heroku CI + * Travis CI + * GitHub Actions + * Chipper CI + +## Introduction + +[Pest 4](https://pestphp.com/) now includes automated browser testing which +offers significant performance and usability improvements compared to Laravel +Dusk. For new projects, we recommend using Pest for browser testing. + +[Laravel Dusk](https://github.com/laravel/dusk) provides an expressive, easy- +to-use browser automation and testing API. By default, Dusk does not require +you to install JDK or Selenium on your local computer. Instead, Dusk uses a +standalone [ChromeDriver](https://sites.google.com/chromium.org/driver) +installation. However, you are free to utilize any other Selenium compatible +driver you wish. + +## Installation + +To get started, you should install [Google +Chrome](https://www.google.com/chrome) and add the `laravel/dusk` Composer +dependency to your project: + + + + 1composer require laravel/dusk --dev + + + composer require laravel/dusk --dev + +If you are manually registering Dusk's service provider, you should **never** +register it in your production environment, as doing so could lead to +arbitrary users being able to authenticate with your application. + +After installing the Dusk package, execute the `dusk:install` Artisan command. +The `dusk:install` command will create a `tests/Browser` directory, an example +Dusk test, and install the Chrome Driver binary for your operating system: + + + + 1php artisan dusk:install + + + php artisan dusk:install + +Next, set the `APP_URL` environment variable in your application's `.env` +file. This value should match the URL you use to access your application in a +browser. + +If you are using [Laravel Sail](/docs/12.x/sail) to manage your local +development environment, please also consult the Sail documentation on +[configuring and running Dusk tests](/docs/12.x/sail#laravel-dusk). + +### Managing ChromeDriver Installations + +If you would like to install a different version of ChromeDriver than what is +installed by Laravel Dusk via the `dusk:install` command, you may use the +`dusk:chrome-driver` command: + + + + 1# Install the latest version of ChromeDriver for your OS... + + 2php artisan dusk:chrome-driver + + 3  + + 4# Install a given version of ChromeDriver for your OS... + + 5php artisan dusk:chrome-driver 86 + + 6  + + 7# Install a given version of ChromeDriver for all supported OSs... + + 8php artisan dusk:chrome-driver --all + + 9  + + 10# Install the version of ChromeDriver that matches the detected version of Chrome / Chromium for your OS... + + 11php artisan dusk:chrome-driver --detect + + + # Install the latest version of ChromeDriver for your OS... + php artisan dusk:chrome-driver + + # Install a given version of ChromeDriver for your OS... + php artisan dusk:chrome-driver 86 + + # Install a given version of ChromeDriver for all supported OSs... + php artisan dusk:chrome-driver --all + + # Install the version of ChromeDriver that matches the detected version of Chrome / Chromium for your OS... + php artisan dusk:chrome-driver --detect + +Dusk requires the `chromedriver` binaries to be executable. If you're having +problems running Dusk, you should ensure the binaries are executable using the +following command: `chmod -R 0755 vendor/laravel/dusk/bin/`. + +### Using Other Browsers + +By default, Dusk uses Google Chrome and a standalone +[ChromeDriver](https://sites.google.com/chromium.org/driver) installation to +run your browser tests. However, you may start your own Selenium server and +run your tests against any browser you wish. + +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: + + + + 1/** + + 2 * Prepare for Dusk test execution. + + 3 * + + 4 * @beforeClass + + 5 */ + + 6public static function prepare(): void + + 7{ + + 8 // static::startChromeDriver(); + + 9} + + + /** + * 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: + + + + 1use Facebook\WebDriver\Remote\RemoteWebDriver; + + 2  + + 3/** + + 4 * Create the RemoteWebDriver instance. + + 5 */ + + 6protected function driver(): RemoteWebDriver + + 7{ + + 8 return RemoteWebDriver::create( + + 9 'http://localhost:4444/wd/hub', DesiredCapabilities::phantomjs() + + 10 ); + + 11} + + + use Facebook\WebDriver\Remote\RemoteWebDriver; + + /** + * Create the RemoteWebDriver instance. + */ + protected function driver(): RemoteWebDriver + { + return RemoteWebDriver::create( + 'http://localhost:4444/wd/hub', DesiredCapabilities::phantomjs() + ); + } + +## Getting Started + +### Generating Tests + +To generate a Dusk test, use the `dusk:make` Artisan command. The generated +test will be placed in the `tests/Browser` directory: + + + + 1php artisan dusk:make LoginTest + + + php artisan dusk:make LoginTest + +### Resetting the Database After Each Test + +Most of the tests you write will interact with pages that retrieve data from +your application's database; however, your Dusk tests should never use the +`RefreshDatabase` trait. The `RefreshDatabase` trait leverages database +transactions which will not be applicable or available across HTTP requests. +Instead, you have two options: the `DatabaseMigrations` trait and the +`DatabaseTruncation` trait. + +#### Using Database Migrations + +The `DatabaseMigrations` trait will run your database migrations before each +test. However, dropping and re-creating your database tables for each test is +typically slower than truncating the tables: + +Pest PHPUnit + + + + 1use(DatabaseMigrations::class); + + 7  + + 8// + + + use(DatabaseMigrations::class); + + // + + + 1use(DatabaseTruncation::class); + + 7  + + 8// + + + use(DatabaseTruncation::class); + + // + + + 1use(DatabaseMigrations::class); + + 8  + + 9test('basic example', function () { + + 10 $user = User::factory()->create([ + + 11 'email' => '[[email protected]](/cdn-cgi/l/email-protection)', + + 12 ]); + + 13  + + 14 $this->browse(function (Browser $browser) use ($user) { + + 15 $browser->visit('/login') + + 16 ->type('email', $user->email) + + 17 ->type('password', 'password') + + 18 ->press('Login') + + 19 ->assertPathIs('/home'); + + 20 }); + + 21}); + + + use(DatabaseMigrations::class); + + test('basic example', function () { + $user = User::factory()->create([ + 'email' => '[[email protected]](/cdn-cgi/l/email-protection)', + ]); + + $this->browse(function (Browser $browser) use ($user) { + $browser->visit('/login') + ->type('email', $user->email) + ->type('password', 'password') + ->press('Login') + ->assertPathIs('/home'); + }); + }); + + + 1create([ + + 20 'email' => '[[email protected]](/cdn-cgi/l/email-protection)', + + 21 ]); + + 22  + + 23 $this->browse(function (Browser $browser) use ($user) { + + 24 $browser->visit('/login') + + 25 ->type('email', $user->email) + + 26 ->type('password', 'password') + + 27 ->press('Login') + + 28 ->assertPathIs('/home'); + + 29 }); + + 30 } + + 31} + + + create([ + 'email' => '[[email protected]](/cdn-cgi/l/email-protection)', + ]); + + $this->browse(function (Browser $browser) use ($user) { + $browser->visit('/login') + ->type('email', $user->email) + ->type('password', 'password') + ->press('Login') + ->assertPathIs('/home'); + }); + } + } + +As you can see in the example above, the `browse` method accepts a closure. A +browser instance will automatically be passed to this closure by Dusk and is +the main object used to interact with and make assertions against your +application. + +#### Creating Multiple Browsers + +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: + + + + 1$this->browse(function (Browser $first, Browser $second) { + + 2 $first->loginAs(User::find(1)) + + 3 ->visit('/home') + + 4 ->waitForText('Message'); + + 5  + + 6 $second->loginAs(User::find(2)) + + 7 ->visit('/home') + + 8 ->waitForText('Message') + + 9 ->type('message', 'Hey Taylor') + + 10 ->press('Send'); + + 11  + + 12 $first->waitForText('Hey Taylor') + + 13 ->assertSee('Jeffrey Way'); + + 14}); + + + $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'); + + $first->waitForText('Hey Taylor') + ->assertSee('Jeffrey Way'); + }); + +### Navigation + +The `visit` method may be used to navigate to a given URI within your +application: + + + + 1$browser->visit('/login'); + + + $browser->visit('/login'); + +You may use the `visitRoute` method to navigate to a [named +route](/docs/12.x/routing#named-routes): + + + + 1$browser->visitRoute($routeName, $parameters); + + + $browser->visitRoute($routeName, $parameters); + +You may navigate "back" and "forward" using the `back` and `forward` methods: + + + + 1$browser->back(); + + 2  + + 3$browser->forward(); + + + $browser->back(); + + $browser->forward(); + +You may use the `refresh` method to refresh the page: + + + + 1$browser->refresh(); + + + $browser->refresh(); + +### Resizing Browser Windows + +You may use the `resize` method to adjust the size of the browser window: + + + + 1$browser->resize(1920, 1080); + + + $browser->resize(1920, 1080); + +The `maximize` method may be used to maximize the browser window: + + + + 1$browser->maximize(); + + + $browser->maximize(); + +The `fitContent` method will resize the browser window to match the size of +its content: + + + + 1$browser->fitContent(); + + + $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: + + + + 1$browser->disableFitOnFailure(); + + + $browser->disableFitOnFailure(); + +You may use the `move` method to move the browser window to a different +position on your screen: + + + + 1$browser->move($x = 100, $y = 100); + + + $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/12.x/providers) `boot` method: + + + + 1script("$('html, body').animate({ scrollTop: $('$element').offset().top }, 0);"); + + 17  + + 18 return $this; + + 19 }); + + 20 } + + 21} + + + 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: + + + + 1$this->browse(function (Browser $browser) use ($user) { + + 2 $browser->visit('/pay') + + 3 ->scrollToElement('#credit-card-details') + + 4 ->assertSee('Enter Credit Card Details'); + + 5}); + + + $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: + + + + 1use App\Models\User; + + 2use Laravel\Dusk\Browser; + + 3  + + 4$this->browse(function (Browser $browser) { + + 5 $browser->loginAs(User::find(1)) + + 6 ->visit('/home'); + + 7}); + + + use App\Models\User; + use Laravel\Dusk\Browser; + + $this->browse(function (Browser $browser) { + $browser->loginAs(User::find(1)) + ->visit('/home'); + }); + +After using the `loginAs` method, the user session will be maintained for all +tests within the file. + +### Cookies + +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: + + + + 1$browser->cookie('name'); + + 2  + + 3$browser->cookie('name', 'Taylor'); + + + $browser->cookie('name'); + + $browser->cookie('name', 'Taylor'); + +You may use the `plainCookie` method to get or set an unencrypted cookie's +value: + + + + 1$browser->plainCookie('name'); + + 2  + + 3$browser->plainCookie('name', 'Taylor'); + + + $browser->plainCookie('name'); + + $browser->plainCookie('name', 'Taylor'); + +You may use the `deleteCookie` method to delete the given cookie: + + + + 1$browser->deleteCookie('name'); + + + $browser->deleteCookie('name'); + +### Executing JavaScript + +You may use the `script` method to execute arbitrary JavaScript statements +within the browser: + + + + 1$browser->script('document.documentElement.scrollTop = 0'); + + 2  + + 3$browser->script([ + + 4 'document.body.scrollTop = 0', + + 5 'document.documentElement.scrollTop = 0', + + 6]); + + 7  + + 8$output = $browser->script('return window.location.pathname'); + + + $browser->script('document.documentElement.scrollTop = 0'); + + $browser->script([ + 'document.body.scrollTop = 0', + 'document.documentElement.scrollTop = 0', + ]); + + $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: + + + + 1$browser->screenshot('filename'); + + + $browser->screenshot('filename'); + +The `responsiveScreenshots` method may be used to take a series of screenshots +at various breakpoints: + + + + 1$browser->responsiveScreenshots('filename'); + + + $browser->responsiveScreenshots('filename'); + +The `screenshotElement` method may be used to take a screenshot of a specific +element on the page: + + + + 1$browser->screenshotElement('#selector', 'filename'); + + + $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: + + + + 1$browser->storeConsoleLog('filename'); + + + $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: + + + + 1$browser->storeSource('filename'); + + + $browser->storeSource('filename'); + +## Interacting With Elements + +### Dusk Selectors + +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: + + + + 1// HTML... + + 2  + + 3 + + + // HTML... + + + + + 1// Test... + + 2  + + 3$browser->click('.login-page .container div > button'); + + + // Test... + + $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: + + + + 1// HTML... + + 2  + + 3 + + + // HTML... + + + + + 1// Test... + + 2  + + 3$browser->click('@login-button'); + + + // Test... + + $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`: + + + + 1use Laravel\Dusk\Dusk; + + 2  + + 3Dusk::selectorHtmlAttribute('data-dusk'); + + + use Laravel\Dusk\Dusk; + + Dusk::selectorHtmlAttribute('data-dusk'); + +### Text, Values, and Attributes + +#### Retrieving and Setting Values + +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: + + + + 1// Retrieve the value... + + 2$value = $browser->value('selector'); + + 3  + + 4// Set the value... + + 5$browser->value('selector', 'value'); + + + // Retrieve the value... + $value = $browser->value('selector'); + + // 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: + + + + 1$value = $browser->inputValue('field'); + + + $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: + + + + 1$text = $browser->text('selector'); + + + $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: + + + + 1$attribute = $browser->attribute('selector', 'value'); + + + $attribute = $browser->attribute('selector', 'value'); + +### Interacting With Forms + +#### Typing Values + +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: + + + + 1$browser->type('email', '[[email protected]](/cdn-cgi/l/email-protection)'); + + + $browser->type('email', '[[email protected]](/cdn-cgi/l/email-protection)'); + +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: + + + + 1$browser->type('tags', 'foo') + + 2 ->append('tags', ', bar, baz'); + + + $browser->type('tags', 'foo') + ->append('tags', ', bar, baz'); + +You may clear the value of an input using the `clear` method: + + + + 1$browser->clear('email'); + + + $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: + + + + 1$browser->typeSlowly('mobile', '+1 (202) 555-5555'); + + 2  + + 3$browser->typeSlowly('mobile', '+1 (202) 555-5555', 300); + + + $browser->typeSlowly('mobile', '+1 (202) 555-5555'); + + $browser->typeSlowly('mobile', '+1 (202) 555-5555', 300); + +You may use the `appendSlowly` method to append text slowly: + + + + 1$browser->type('tags', 'foo') + + 2 ->appendSlowly('tags', ', bar, baz'); + + + $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: + + + + 1$browser->select('size', 'Large'); + + + $browser->select('size', 'Large'); + +You may select a random option by omitting the second argument: + + + + 1$browser->select('size'); + + + $browser->select('size'); + +By providing an array as the second argument to the `select` method, you can +instruct the method to select multiple options: + + + + 1$browser->select('categories', ['Art', 'Music']); + + + $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: + + + + 1$browser->check('terms'); + + + $browser->check('terms'); + +The `uncheck` method may be used to "uncheck" a checkbox input: + + + + 1$browser->uncheck('terms'); + + + $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: + + + + 1$browser->radio('size', 'large'); + + + $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: + + + + 1$browser->attach('photo', __DIR__.'/photos/mountains.png'); + + + $browser->attach('photo', __DIR__.'/photos/mountains.png'); + +The attach function requires the `Zip` PHP extension to be installed and +enabled on your server. + +### Pressing Buttons + +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: + + + + 1$browser->press('Login'); + + + $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: + + + + 1// Press the button and wait a maximum of 5 seconds for it to be enabled... + + 2$browser->pressAndWaitFor('Save'); + + 3  + + 4// Press the button and wait a maximum of 1 second for it to be enabled... + + 5$browser->pressAndWaitFor('Save', 1); + + + // 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); + +### 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: + + + + 1$browser->clickLink($linkText); + + + $browser->clickLink($linkText); + +You may use the `seeLink` method to determine if a link with the given display +text is visible on the page: + + + + 1if ($browser->seeLink($linkText)) { + + 2 // ... + + 3} + + + if ($browser->seeLink($linkText)) { + // ... + } + +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. + +### Using the Keyboard + +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: + + + + 1$browser->keys('selector', ['{shift}', 'taylor'], 'swift'); + + + $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: + + + + 1$browser->keys('.app', ['{command}', 'j']); + + + $browser->keys('.app', ['{command}', 'j']); + +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). + +#### Fluent Keyboard Interactions + +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: + + + + 1use Laravel\Dusk\Keyboard; + + 2  + + 3$browser->withKeyboard(function (Keyboard $keyboard) { + + 4 $keyboard->press('c') + + 5 ->pause(1000) + + 6 ->release('c') + + 7 ->type(['c', 'e', 'o']); + + 8}); + + + use Laravel\Dusk\Keyboard; + + $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/12.x/providers) `boot` method: + + + + 1type([ + + 19 OperatingSystem::onMac() ? WebDriverKeys::META : WebDriverKeys::CONTROL, 'c', + + 20 ]); + + 21  + + 22 return $this; + + 23 }); + + 24  + + 25 Keyboard::macro('paste', function (string $element = null) { + + 26 $this->type([ + + 27 OperatingSystem::onMac() ? WebDriverKeys::META : WebDriverKeys::CONTROL, 'v', + + 28 ]); + + 29  + + 30 return $this; + + 31 }); + + 32 } + + 33} + + + type([ + OperatingSystem::onMac() ? WebDriverKeys::META : WebDriverKeys::CONTROL, 'c', + ]); + + 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: + + + + 1$browser->click('@textarea') + + 2 ->withKeyboard(fn (Keyboard $keyboard) => $keyboard->copy()) + + 3 ->click('@another-textarea') + + 4 ->withKeyboard(fn (Keyboard $keyboard) => $keyboard->paste()); + + + $browser->click('@textarea') + ->withKeyboard(fn (Keyboard $keyboard) => $keyboard->copy()) + ->click('@another-textarea') + ->withKeyboard(fn (Keyboard $keyboard) => $keyboard->paste()); + +### Using the Mouse + +#### Clicking on Elements + +The `click` method may be used to click on an element matching the given CSS +or Dusk selector: + + + + 1$browser->click('.selector'); + + + $browser->click('.selector'); + +The `clickAtXPath` method may be used to click on an element matching the +given XPath expression: + + + + 1$browser->clickAtXPath('//div[@class = "selector"]'); + + + $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: + + + + 1$browser->clickAtPoint($x = 0, $y = 0); + + + $browser->clickAtPoint($x = 0, $y = 0); + +The `doubleClick` method may be used to simulate the double click of a mouse: + + + + 1$browser->doubleClick(); + + 2  + + 3$browser->doubleClick('.selector'); + + + $browser->doubleClick(); + + $browser->doubleClick('.selector'); + +The `rightClick` method may be used to simulate the right click of a mouse: + + + + 1$browser->rightClick(); + + 2  + + 3$browser->rightClick('.selector'); + + + $browser->rightClick(); + + $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: + + + + 1$browser->clickAndHold('.selector'); + + 2  + + 3$browser->clickAndHold() + + 4 ->pause(1000) + + 5 ->releaseMouse(); + + + $browser->clickAndHold('.selector'); + + $browser->clickAndHold() + ->pause(1000) + ->releaseMouse(); + +The `controlClick` method may be used to simulate the `ctrl+click` event +within the browser: + + + + 1$browser->controlClick(); + + 2  + + 3$browser->controlClick('.selector'); + + + $browser->controlClick(); + + $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: + + + + 1$browser->mouseover('.selector'); + + + $browser->mouseover('.selector'); + +#### Drag and Drop + +The `drag` method may be used to drag an element matching the given selector +to another element: + + + + 1$browser->drag('.from-selector', '.to-selector'); + + + $browser->drag('.from-selector', '.to-selector'); + +Or, you may drag an element in a single direction: + + + + 1$browser->dragLeft('.selector', $pixels = 10); + + 2$browser->dragRight('.selector', $pixels = 10); + + 3$browser->dragUp('.selector', $pixels = 10); + + 4$browser->dragDown('.selector', $pixels = 10); + + + $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: + + + + 1$browser->dragOffset('.selector', $x = 10, $y = 10); + + + $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: + + + + 1$browser->waitForDialog($seconds = null); + + + $browser->waitForDialog($seconds = null); + +The `assertDialogOpened` method may be used to assert that a dialog has been +displayed and contains the given message: + + + + 1$browser->assertDialogOpened('Dialog message'); + + + $browser->assertDialogOpened('Dialog message'); + +If the JavaScript dialog contains a prompt, you may use the `typeInDialog` +method to type a value into the prompt: + + + + 1$browser->typeInDialog('Hello World'); + + + $browser->typeInDialog('Hello World'); + +To close an open JavaScript dialog by clicking the "OK" button, you may invoke +the `acceptDialog` method: + + + + 1$browser->acceptDialog(); + + + $browser->acceptDialog(); + +To close an open JavaScript dialog by clicking the "Cancel" button, you may +invoke the `dismissDialog` method: + + + + 1$browser->dismissDialog(); + + + $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: + + + + 1$browser->withinFrame('#credit-card-details', function ($browser) { + + 2 $browser->type('input[name="cardnumber"]', '4242424242424242') + + 3 ->type('input[name="exp-date"]', '1224') + + 4 ->type('input[name="cvc"]', '123') + + 5 ->press('Pay'); + + 6}); + + + $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: + + + + 1$browser->with('.table', function (Browser $table) { + + 2 $table->assertSee('Hello World') + + 3 ->clickLink('Delete'); + + 4}); + + + $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: + + + + 1$browser->with('.table', function (Browser $table) { + + 2 // Current scope is `body .table`... + + 3  + + 4 $browser->elsewhere('.page-title', function (Browser $title) { + + 5 // Current scope is `body .page-title`... + + 6 $title->assertSee('Hello World'); + + 7 }); + + 8  + + 9 $browser->elsewhereWhenAvailable('.page-title', function (Browser $title) { + + 10 // Current scope is `body .page-title`... + + 11 $title->assertSee('Hello World'); + + 12 }); + + 13}); + + + $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->elsewhereWhenAvailable('.page-title', function (Browser $title) { + // Current scope is `body .page-title`... + $title->assertSee('Hello World'); + }); + }); + +### Waiting for Elements + +When testing applications that use JavaScript extensively, it often becomes +necessary to "wait" for certain elements or data to be available before +proceeding with a test. Dusk makes this a cinch. Using a variety of methods, +you may wait for elements to become visible on the page or even wait until a +given JavaScript expression evaluates to `true`. + +#### Waiting + +If you just need to pause the test for a given number of milliseconds, use the +`pause` method: + + + + 1$browser->pause(1000); + + + $browser->pause(1000); + +If you need to pause the test only if a given condition is `true`, use the +`pauseIf` method: + + + + 1$browser->pauseIf(App::environment('production'), 1000); + + + $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: + + + + 1$browser->pauseUnless(App::environment('testing'), 1000); + + + $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: + + + + 1// Wait a maximum of five seconds for the selector... + + 2$browser->waitFor('.selector'); + + 3  + + 4// Wait a maximum of one second for the selector... + + 5$browser->waitFor('.selector', 1); + + + // 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); + +You may also wait until the element matching the given selector contains the +given text: + + + + 1// Wait a maximum of five seconds for the selector to contain the given text... + + 2$browser->waitForTextIn('.selector', 'Hello World'); + + 3  + + 4// Wait a maximum of one second for the selector to contain the given text... + + 5$browser->waitForTextIn('.selector', 'Hello World', 1); + + + // 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); + +You may also wait until the element matching the given selector is missing +from the page: + + + + 1// Wait a maximum of five seconds until the selector is missing... + + 2$browser->waitUntilMissing('.selector'); + + 3  + + 4// Wait a maximum of one second until the selector is missing... + + 5$browser->waitUntilMissing('.selector', 1); + + + // 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); + +Or, you may wait until the element matching the given selector is enabled or +disabled: + + + + 1// Wait a maximum of five seconds until the selector is enabled... + + 2$browser->waitUntilEnabled('.selector'); + + 3  + + 4// Wait a maximum of one second until the selector is enabled... + + 5$browser->waitUntilEnabled('.selector', 1); + + 6  + + 7// Wait a maximum of five seconds until the selector is disabled... + + 8$browser->waitUntilDisabled('.selector'); + + 9  + + 10// Wait a maximum of one second until the selector is disabled... + + 11$browser->waitUntilDisabled('.selector', 1); + + + // 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 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); + +#### 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: + + + + 1$browser->whenAvailable('.modal', function (Browser $modal) { + + 2 $modal->assertSee('Hello World') + + 3 ->press('OK'); + + 4}); + + + $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: + + + + 1// Wait a maximum of five seconds for the text... + + 2$browser->waitForText('Hello World'); + + 3  + + 4// Wait a maximum of one second for the text... + + 5$browser->waitForText('Hello World', 1); + + + // 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); + +You may use the `waitUntilMissingText` method to wait until the displayed text +has been removed from the page: + + + + 1// Wait a maximum of five seconds for the text to be removed... + + 2$browser->waitUntilMissingText('Hello World'); + + 3  + + 4// Wait a maximum of one second for the text to be removed... + + 5$browser->waitUntilMissingText('Hello World', 1); + + + // 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); + +#### Waiting for Links + +The `waitForLink` method may be used to wait until the given link text is +displayed on the page: + + + + 1// Wait a maximum of five seconds for the link... + + 2$browser->waitForLink('Create'); + + 3  + + 4// Wait a maximum of one second for the link... + + 5$browser->waitForLink('Create', 1); + + + // 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); + +#### Waiting for Inputs + +The `waitForInput` method may be used to wait until the given input field is +visible on the page: + + + + 1// Wait a maximum of five seconds for the input... + + 2$browser->waitForInput($field); + + 3  + + 4// Wait a maximum of one second for the input... + + 5$browser->waitForInput($field, 1); + + + // 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); + +#### 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: + + + + 1$browser->waitForLocation('/secret'); + + + $browser->waitForLocation('/secret'); + +The `waitForLocation` method can also be used to wait for the current window +location to be a fully qualified URL: + + + + 1$browser->waitForLocation('https://example.com/path'); + + + $browser->waitForLocation('https://example.com/path'); + +You may also wait for a [named route's](/docs/12.x/routing#named-routes) +location: + + + + 1$browser->waitForRoute($routeName, $parameters); + + + $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: + + + + 1use Laravel\Dusk\Browser; + + 2  + + 3$browser->waitForReload(function (Browser $browser) { + + 4 $browser->press('Submit'); + + 5}) + + 6->assertSee('Success!'); + + + use Laravel\Dusk\Browser; + + $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: + + + + 1$browser->clickAndWaitForReload('.selector') + + 2 ->assertSee('something'); + + + $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: + + + + 1// Wait a maximum of five seconds for the expression to be true... + + 2$browser->waitUntil('App.data.servers.length > 0'); + + 3  + + 4// Wait a maximum of one second for the expression to be true... + + 5$browser->waitUntil('App.data.servers.length > 0', 1); + + + // 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); + +#### 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: + + + + 1// Wait until the component attribute contains the given value... + + 2$browser->waitUntilVue('user.name', 'Taylor', '@user'); + + 3  + + 4// Wait until the component attribute doesn't contain the given value... + + 5$browser->waitUntilVueIsNot('user.name', null, '@user'); + + + // 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'); + +#### Waiting for JavaScript Events + +The `waitForEvent` method can be used to pause the execution of a test until a +JavaScript event occurs: + + + + 1$browser->waitForEvent('load'); + + + $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: + + + + 1$browser->with('iframe', function (Browser $iframe) { + + 2 // Wait for the iframe's load event... + + 3 $iframe->waitForEvent('load'); + + 4}); + + + $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: + + + + 1$browser->waitForEvent('load', '.selector'); + + + $browser->waitForEvent('load', '.selector'); + +You may also wait for events on the `document` and `window` objects: + + + + 1// Wait until the document is scrolled... + + 2$browser->waitForEvent('scroll', 'document'); + + 3  + + 4// Wait a maximum of five seconds until the window is resized... + + 5$browser->waitForEvent('resize', 'window', 5); + + + // 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); + +#### 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: + + + + 1$browser->waitUsing(10, 1, function () use ($something) { + + 2 return $something->isReady(); + + 3}, "Something wasn't ready in time."); + + + $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: + + + + 1$browser->scrollIntoView('.selector') + + 2 ->click('.selector'); + + + $browser->scrollIntoView('.selector') + ->click('.selector'); + +## Available Assertions + +Dusk provides a variety of assertions that you may make against your +application. All of the available assertions are documented in the list below: + +assertTitle assertTitleContains assertUrlIs assertSchemeIs assertSchemeIsNot +assertHostIs assertHostIsNot assertPortIs assertPortIsNot assertPathBeginsWith +assertPathEndsWith assertPathContains assertPathIs assertPathIsNot +assertRouteIs assertQueryStringHas assertQueryStringMissing assertFragmentIs +assertFragmentBeginsWith assertFragmentIsNot assertHasCookie +assertHasPlainCookie assertCookieMissing assertPlainCookieMissing +assertCookieValue assertPlainCookieValue assertSee assertDontSee assertSeeIn +assertDontSeeIn assertSeeAnythingIn assertSeeNothingIn assertCount +assertScript assertSourceHas assertSourceMissing assertSeeLink +assertDontSeeLink assertInputValue assertInputValueIsNot assertChecked +assertNotChecked assertIndeterminate assertRadioSelected +assertRadioNotSelected assertSelected assertNotSelected assertSelectHasOptions +assertSelectMissingOptions assertSelectHasOption assertSelectMissingOption +assertValue assertValueIsNot assertAttribute assertAttributeMissing +assertAttributeContains assertAttributeDoesntContain assertAriaAttribute +assertDataAttribute assertVisible assertPresent assertNotPresent assertMissing +assertInputPresent assertInputMissing assertDialogOpened assertEnabled +assertDisabled assertButtonEnabled assertButtonDisabled assertFocused +assertNotFocused assertAuthenticated assertGuest assertAuthenticatedAs +assertVue assertVueIsNot assertVueContains assertVueDoesntContain + +#### assertTitle + +Assert that the page title matches the given text: + + + + 1$browser->assertTitle($title); + + + $browser->assertTitle($title); + +#### assertTitleContains + +Assert that the page title contains the given text: + + + + 1$browser->assertTitleContains($title); + + + $browser->assertTitleContains($title); + +#### assertUrlIs + +Assert that the current URL (without the query string) matches the given +string: + + + + 1$browser->assertUrlIs($url); + + + $browser->assertUrlIs($url); + +#### assertSchemeIs + +Assert that the current URL scheme matches the given scheme: + + + + 1$browser->assertSchemeIs($scheme); + + + $browser->assertSchemeIs($scheme); + +#### assertSchemeIsNot + +Assert that the current URL scheme does not match the given scheme: + + + + 1$browser->assertSchemeIsNot($scheme); + + + $browser->assertSchemeIsNot($scheme); + +#### assertHostIs + +Assert that the current URL host matches the given host: + + + + 1$browser->assertHostIs($host); + + + $browser->assertHostIs($host); + +#### assertHostIsNot + +Assert that the current URL host does not match the given host: + + + + 1$browser->assertHostIsNot($host); + + + $browser->assertHostIsNot($host); + +#### assertPortIs + +Assert that the current URL port matches the given port: + + + + 1$browser->assertPortIs($port); + + + $browser->assertPortIs($port); + +#### assertPortIsNot + +Assert that the current URL port does not match the given port: + + + + 1$browser->assertPortIsNot($port); + + + $browser->assertPortIsNot($port); + +#### assertPathBeginsWith + +Assert that the current URL path begins with the given path: + + + + 1$browser->assertPathBeginsWith('/home'); + + + $browser->assertPathBeginsWith('/home'); + +#### assertPathEndsWith + +Assert that the current URL path ends with the given path: + + + + 1$browser->assertPathEndsWith('/home'); + + + $browser->assertPathEndsWith('/home'); + +#### assertPathContains + +Assert that the current URL path contains the given path: + + + + 1$browser->assertPathContains('/home'); + + + $browser->assertPathContains('/home'); + +#### assertPathIs + +Assert that the current path matches the given path: + + + + 1$browser->assertPathIs('/home'); + + + $browser->assertPathIs('/home'); + +#### assertPathIsNot + +Assert that the current path does not match the given path: + + + + 1$browser->assertPathIsNot('/home'); + + + $browser->assertPathIsNot('/home'); + +#### assertRouteIs + +Assert that the current URL matches the given [named +route's](/docs/12.x/routing#named-routes) URL: + + + + 1$browser->assertRouteIs($name, $parameters); + + + $browser->assertRouteIs($name, $parameters); + +#### assertQueryStringHas + +Assert that the given query string parameter is present: + + + + 1$browser->assertQueryStringHas($name); + + + $browser->assertQueryStringHas($name); + +Assert that the given query string parameter is present and has a given value: + + + + 1$browser->assertQueryStringHas($name, $value); + + + $browser->assertQueryStringHas($name, $value); + +#### assertQueryStringMissing + +Assert that the given query string parameter is missing: + + + + 1$browser->assertQueryStringMissing($name); + + + $browser->assertQueryStringMissing($name); + +#### assertFragmentIs + +Assert that the URL's current hash fragment matches the given fragment: + + + + 1$browser->assertFragmentIs('anchor'); + + + $browser->assertFragmentIs('anchor'); + +#### assertFragmentBeginsWith + +Assert that the URL's current hash fragment begins with the given fragment: + + + + 1$browser->assertFragmentBeginsWith('anchor'); + + + $browser->assertFragmentBeginsWith('anchor'); + +#### assertFragmentIsNot + +Assert that the URL's current hash fragment does not match the given fragment: + + + + 1$browser->assertFragmentIsNot('anchor'); + + + $browser->assertFragmentIsNot('anchor'); + +#### assertHasCookie + +Assert that the given encrypted cookie is present: + + + + 1$browser->assertHasCookie($name); + + + $browser->assertHasCookie($name); + +#### assertHasPlainCookie + +Assert that the given unencrypted cookie is present: + + + + 1$browser->assertHasPlainCookie($name); + + + $browser->assertHasPlainCookie($name); + +#### assertCookieMissing + +Assert that the given encrypted cookie is not present: + + + + 1$browser->assertCookieMissing($name); + + + $browser->assertCookieMissing($name); + +#### assertPlainCookieMissing + +Assert that the given unencrypted cookie is not present: + + + + 1$browser->assertPlainCookieMissing($name); + + + $browser->assertPlainCookieMissing($name); + +#### assertCookieValue + +Assert that an encrypted cookie has a given value: + + + + 1$browser->assertCookieValue($name, $value); + + + $browser->assertCookieValue($name, $value); + +#### assertPlainCookieValue + +Assert that an unencrypted cookie has a given value: + + + + 1$browser->assertPlainCookieValue($name, $value); + + + $browser->assertPlainCookieValue($name, $value); + +#### assertSee + +Assert that the given text is present on the page: + + + + 1$browser->assertSee($text); + + + $browser->assertSee($text); + +#### assertDontSee + +Assert that the given text is not present on the page: + + + + 1$browser->assertDontSee($text); + + + $browser->assertDontSee($text); + +#### assertSeeIn + +Assert that the given text is present within the selector: + + + + 1$browser->assertSeeIn($selector, $text); + + + $browser->assertSeeIn($selector, $text); + +#### assertDontSeeIn + +Assert that the given text is not present within the selector: + + + + 1$browser->assertDontSeeIn($selector, $text); + + + $browser->assertDontSeeIn($selector, $text); + +#### assertSeeAnythingIn + +Assert that any text is present within the selector: + + + + 1$browser->assertSeeAnythingIn($selector); + + + $browser->assertSeeAnythingIn($selector); + +#### assertSeeNothingIn + +Assert that no text is present within the selector: + + + + 1$browser->assertSeeNothingIn($selector); + + + $browser->assertSeeNothingIn($selector); + +#### assertCount + +Assert that elements matching the given selector appear the specified number +of times: + + + + 1$browser->assertCount($selector, $count); + + + $browser->assertCount($selector, $count); + +#### assertScript + +Assert that the given JavaScript expression evaluates to the given value: + + + + 1$browser->assertScript('window.isLoaded') + + 2 ->assertScript('document.readyState', 'complete'); + + + $browser->assertScript('window.isLoaded') + ->assertScript('document.readyState', 'complete'); + +#### assertSourceHas + +Assert that the given source code is present on the page: + + + + 1$browser->assertSourceHas($code); + + + $browser->assertSourceHas($code); + +#### assertSourceMissing + +Assert that the given source code is not present on the page: + + + + 1$browser->assertSourceMissing($code); + + + $browser->assertSourceMissing($code); + +#### assertSeeLink + +Assert that the given link is present on the page: + + + + 1$browser->assertSeeLink($linkText); + + + $browser->assertSeeLink($linkText); + +#### assertDontSeeLink + +Assert that the given link is not present on the page: + + + + 1$browser->assertDontSeeLink($linkText); + + + $browser->assertDontSeeLink($linkText); + +#### assertInputValue + +Assert that the given input field has the given value: + + + + 1$browser->assertInputValue($field, $value); + + + $browser->assertInputValue($field, $value); + +#### assertInputValueIsNot + +Assert that the given input field does not have the given value: + + + + 1$browser->assertInputValueIsNot($field, $value); + + + $browser->assertInputValueIsNot($field, $value); + +#### assertChecked + +Assert that the given checkbox is checked: + + + + 1$browser->assertChecked($field); + + + $browser->assertChecked($field); + +#### assertNotChecked + +Assert that the given checkbox is not checked: + + + + 1$browser->assertNotChecked($field); + + + $browser->assertNotChecked($field); + +#### assertIndeterminate + +Assert that the given checkbox is in an indeterminate state: + + + + 1$browser->assertIndeterminate($field); + + + $browser->assertIndeterminate($field); + +#### assertRadioSelected + +Assert that the given radio field is selected: + + + + 1$browser->assertRadioSelected($field, $value); + + + $browser->assertRadioSelected($field, $value); + +#### assertRadioNotSelected + +Assert that the given radio field is not selected: + + + + 1$browser->assertRadioNotSelected($field, $value); + + + $browser->assertRadioNotSelected($field, $value); + +#### assertSelected + +Assert that the given dropdown has the given value selected: + + + + 1$browser->assertSelected($field, $value); + + + $browser->assertSelected($field, $value); + +#### assertNotSelected + +Assert that the given dropdown does not have the given value selected: + + + + 1$browser->assertNotSelected($field, $value); + + + $browser->assertNotSelected($field, $value); + +#### assertSelectHasOptions + +Assert that the given array of values are available to be selected: + + + + 1$browser->assertSelectHasOptions($field, $values); + + + $browser->assertSelectHasOptions($field, $values); + +#### assertSelectMissingOptions + +Assert that the given array of values are not available to be selected: + + + + 1$browser->assertSelectMissingOptions($field, $values); + + + $browser->assertSelectMissingOptions($field, $values); + +#### assertSelectHasOption + +Assert that the given value is available to be selected on the given field: + + + + 1$browser->assertSelectHasOption($field, $value); + + + $browser->assertSelectHasOption($field, $value); + +#### assertSelectMissingOption + +Assert that the given value is not available to be selected: + + + + 1$browser->assertSelectMissingOption($field, $value); + + + $browser->assertSelectMissingOption($field, $value); + +#### assertValue + +Assert that the element matching the given selector has the given value: + + + + 1$browser->assertValue($selector, $value); + + + $browser->assertValue($selector, $value); + +#### assertValueIsNot + +Assert that the element matching the given selector does not have the given +value: + + + + 1$browser->assertValueIsNot($selector, $value); + + + $browser->assertValueIsNot($selector, $value); + +#### assertAttribute + +Assert that the element matching the given selector has the given value in the +provided attribute: + + + + 1$browser->assertAttribute($selector, $attribute, $value); + + + $browser->assertAttribute($selector, $attribute, $value); + +#### assertAttributeMissing + +Assert that the element matching the given selector is missing the provided +attribute: + + + + 1$browser->assertAttributeMissing($selector, $attribute); + + + $browser->assertAttributeMissing($selector, $attribute); + +#### assertAttributeContains + +Assert that the element matching the given selector contains the given value +in the provided attribute: + + + + 1$browser->assertAttributeContains($selector, $attribute, $value); + + + $browser->assertAttributeContains($selector, $attribute, $value); + +#### assertAttributeDoesntContain + +Assert that the element matching the given selector does not contain the given +value in the provided attribute: + + + + 1$browser->assertAttributeDoesntContain($selector, $attribute, $value); + + + $browser->assertAttributeDoesntContain($selector, $attribute, $value); + +#### assertAriaAttribute + +Assert that the element matching the given selector has the given value in the +provided aria attribute: + + + + 1$browser->assertAriaAttribute($selector, $attribute, $value); + + + $browser->assertAriaAttribute($selector, $attribute, $value); + +For example, given the markup ``, you may +assert against the `aria-label` attribute like so: + + + + 1$browser->assertAriaAttribute('button', 'label', 'Add') + + + $browser->assertAriaAttribute('button', 'label', 'Add') + +#### assertDataAttribute + +Assert that the element matching the given selector has the given value in the +provided data attribute: + + + + 1$browser->assertDataAttribute($selector, $attribute, $value); + + + $browser->assertDataAttribute($selector, $attribute, $value); + +For example, given the markup ``, +you may assert against the `data-label` attribute like so: + + + + 1$browser->assertDataAttribute('#row-1', 'content', 'attendees') + + + $browser->assertDataAttribute('#row-1', 'content', 'attendees') + +#### assertVisible + +Assert that the element matching the given selector is visible: + + + + 1$browser->assertVisible($selector); + + + $browser->assertVisible($selector); + +#### assertPresent + +Assert that the element matching the given selector is present in the source: + + + + 1$browser->assertPresent($selector); + + + $browser->assertPresent($selector); + +#### assertNotPresent + +Assert that the element matching the given selector is not present in the +source: + + + + 1$browser->assertNotPresent($selector); + + + $browser->assertNotPresent($selector); + +#### assertMissing + +Assert that the element matching the given selector is not visible: + + + + 1$browser->assertMissing($selector); + + + $browser->assertMissing($selector); + +#### assertInputPresent + +Assert that an input with the given name is present: + + + + 1$browser->assertInputPresent($name); + + + $browser->assertInputPresent($name); + +#### assertInputMissing + +Assert that an input with the given name is not present in the source: + + + + 1$browser->assertInputMissing($name); + + + $browser->assertInputMissing($name); + +#### assertDialogOpened + +Assert that a JavaScript dialog with the given message has been opened: + + + + 1$browser->assertDialogOpened($message); + + + $browser->assertDialogOpened($message); + +#### assertEnabled + +Assert that the given field is enabled: + + + + 1$browser->assertEnabled($field); + + + $browser->assertEnabled($field); + +#### assertDisabled + +Assert that the given field is disabled: + + + + 1$browser->assertDisabled($field); + + + $browser->assertDisabled($field); + +#### assertButtonEnabled + +Assert that the given button is enabled: + + + + 1$browser->assertButtonEnabled($button); + + + $browser->assertButtonEnabled($button); + +#### assertButtonDisabled + +Assert that the given button is disabled: + + + + 1$browser->assertButtonDisabled($button); + + + $browser->assertButtonDisabled($button); + +#### assertFocused + +Assert that the given field is focused: + + + + 1$browser->assertFocused($field); + + + $browser->assertFocused($field); + +#### assertNotFocused + +Assert that the given field is not focused: + + + + 1$browser->assertNotFocused($field); + + + $browser->assertNotFocused($field); + +#### assertAuthenticated + +Assert that the user is authenticated: + + + + 1$browser->assertAuthenticated(); + + + $browser->assertAuthenticated(); + +#### assertGuest + +Assert that the user is not authenticated: + + + + 1$browser->assertGuest(); + + + $browser->assertGuest(); + +#### assertAuthenticatedAs + +Assert that the user is authenticated as the given user: + + + + 1$browser->assertAuthenticatedAs($user); + + + $browser->assertAuthenticatedAs($user); + +#### assertVue + +Dusk even allows you to make assertions on the state of [Vue +component](https://vuejs.org) data. For example, imagine your application +contains the following Vue component: + + + + 1// HTML... + + 2  + + 3 + + 4  + + 5// Component Definition... + + 6  + + 7Vue.component('profile', { + + 8 template: '
    {{ user.name }}
    ', + + 9  + + 10 data: function () { + + 11 return { + + 12 user: { + + 13 name: 'Taylor' + + 14 } + + 15 }; + + 16 } + + 17}); + + + // HTML... + + + + // Component Definition... + + Vue.component('profile', { + template: '
    {{ user.name }}
    ', + + data: function () { + return { + user: { + name: 'Taylor' + } + }; + } + }); + +You may assert on the state of the Vue component like so: + +Pest PHPUnit + + + + 1test('vue', function () { + + 2 $this->browse(function (Browser $browser) { + + 3 $browser->visit('/') + + 4 ->assertVue('user.name', 'Taylor', '@profile-component'); + + 5 }); + + 6}); + + + test('vue', function () { + $this->browse(function (Browser $browser) { + $browser->visit('/') + ->assertVue('user.name', 'Taylor', '@profile-component'); + }); + }); + + + 1/** + + 2 * A basic Vue test example. + + 3 */ + + 4public function test_vue(): void + + 5{ + + 6 $this->browse(function (Browser $browser) { + + 7 $browser->visit('/') + + 8 ->assertVue('user.name', 'Taylor', '@profile-component'); + + 9 }); + + 10} + + + /** + * A basic Vue test example. + */ + public function test_vue(): void + { + $this->browse(function (Browser $browser) { + $browser->visit('/') + ->assertVue('user.name', 'Taylor', '@profile-component'); + }); + } + +#### assertVueIsNot + +Assert that a given Vue component data property does not match the given +value: + + + + 1$browser->assertVueIsNot($property, $value, $componentSelector = null); + + + $browser->assertVueIsNot($property, $value, $componentSelector = null); + +#### assertVueContains + +Assert that a given Vue component data property is an array and contains the +given value: + + + + 1$browser->assertVueContains($property, $value, $componentSelector = null); + + + $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: + + + + 1$browser->assertVueDoesntContain($property, $value, $componentSelector = null); + + + $browser->assertVueDoesntContain($property, $value, $componentSelector = null); + +## Pages + +Sometimes, tests require several complicated actions to be performed in +sequence. This can make your tests harder to read and understand. Dusk Pages +allow you to define expressive actions that may then be performed on a given +page via a single method. Pages also allow you to define short-cuts to common +selectors for your application or for a single page. + +### Generating Pages + +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: + + + + 1php artisan dusk:page Login + + + php artisan dusk:page Login + +### Configuring Pages + +By default, pages have three methods: `url`, `assert`, and `elements`. We will +discuss the `url` and `assert` methods now. The `elements` method will be +discussed in more detail below. + +#### The `url` Method + +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: + + + + 1/** + + 2 * Get the URL for the page. + + 3 */ + + 4public function url(): string + + 5{ + + 6 return '/login'; + + 7} + + + /** + * 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: + + + + 1/** + + 2 * Assert that the browser is on the page. + + 3 */ + + 4public function assert(Browser $browser): void + + 5{ + + 6 $browser->assertPathIs($this->url()); + + 7} + + + /** + * 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: + + + + 1use Tests\Browser\Pages\Login; + + 2  + + 3$browser->visit(new Login); + + + use Tests\Browser\Pages\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: + + + + 1use Tests\Browser\Pages\CreatePlaylist; + + 2  + + 3$browser->visit('/dashboard') + + 4 ->clickLink('Create Playlist') + + 5 ->on(new CreatePlaylist) + + 6 ->assertSee('@create'); + + + use Tests\Browser\Pages\CreatePlaylist; + + $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: + + + + 1/** + + 2 * Get the element shortcuts for the page. + + 3 * + + 4 * @return array + + 5 */ + + 6public function elements(): array + + 7{ + + 8 return [ + + 9 '@email' => 'input[name=email]', + + 10 ]; + + 11} + + + /** + * 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: + + + + 1$browser->type('@email', '[[email protected]](/cdn-cgi/l/email-protection)'); + + + $browser->type('@email', '[[email protected]](/cdn-cgi/l/email-protection)'); + +#### 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: + + + + 1/** + + 2 * Get the global element shortcuts for the site. + + 3 * + + 4 * @return array + + 5 */ + + 6public static function siteElements(): array + + 7{ + + 8 return [ + + 9 '@element' => '#selector', + + 10 ]; + + 11} + + + /** + * 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: + + + + 1type('name', $name) + + 18 ->check('share') + + 19 ->press('Create Playlist'); + + 20 } + + 21} + + + 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: + + + + 1use Tests\Browser\Pages\Dashboard; + + 2  + + 3$browser->visit(new Dashboard) + + 4 ->createPlaylist('My Playlist') + + 5 ->assertSee('My Playlist'); + + + use Tests\Browser\Pages\Dashboard; + + $browser->visit(new Dashboard) + ->createPlaylist('My Playlist') + ->assertSee('My Playlist'); + +## Components + +Components are similar to Dusk's “page objects”, but are intended for pieces +of UI and functionality that are re-used throughout your application, such as +a navigation bar or notification window. As such, components are not bound to +specific URLs. + +### Generating Components + +To generate a component, execute the `dusk:component` Artisan command. New +components are placed in the `tests/Browser/Components` directory: + + + + 1php artisan dusk:component DatePicker + + + 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: + + + + 1assertVisible($this->selector()); + + 24 } + + 25  + + 26 /** + + 27 * Get the element shortcuts for the component. + + 28 * + + 29 * @return array + + 30 */ + + 31 public function elements(): array + + 32 { + + 33 return [ + + 34 '@date-field' => 'input.datepicker-input', + + 35 '@year-list' => 'div > div.datepicker-years', + + 36 '@month-list' => 'div > div.datepicker-months', + + 37 '@day-list' => 'div > div.datepicker-days', + + 38 ]; + + 39 } + + 40  + + 41 /** + + 42 * Select the given date. + + 43 */ + + 44 public function selectDate(Browser $browser, int $year, int $month, int $day): void + + 45 { + + 46 $browser->click('@date-field') + + 47 ->within('@year-list', function (Browser $browser) use ($year) { + + 48 $browser->click($year); + + 49 }) + + 50 ->within('@month-list', function (Browser $browser) use ($month) { + + 51 $browser->click($month); + + 52 }) + + 53 ->within('@day-list', function (Browser $browser) use ($day) { + + 54 $browser->click($day); + + 55 }); + + 56 } + + 57} + + + 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', + ]; + } + + /** + * 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 + +Once the component has been defined, we can easily select a date within the +date picker from any test. And, if the logic necessary to select a date +changes, we only need to update the component: + +Pest PHPUnit + + + + 1use(DatabaseMigrations::class); + + 8  + + 9test('basic example', function () { + + 10 $this->browse(function (Browser $browser) { + + 11 $browser->visit('/') + + 12 ->within(new DatePicker, function (Browser $browser) { + + 13 $browser->selectDate(2019, 1, 30); + + 14 }) + + 15 ->assertSee('January'); + + 16 }); + + 17}); + + + use(DatabaseMigrations::class); + + test('basic example', function () { + $this->browse(function (Browser $browser) { + $browser->visit('/') + ->within(new DatePicker, function (Browser $browser) { + $browser->selectDate(2019, 1, 30); + }) + ->assertSee('January'); + }); + }); + + + 1browse(function (Browser $browser) { + + 18 $browser->visit('/') + + 19 ->within(new DatePicker, function (Browser $browser) { + + 20 $browser->selectDate(2019, 1, 30); + + 21 }) + + 22 ->assertSee('January'); + + 23 }); + + 24 } + + 25} + + + browse(function (Browser $browser) { + $browser->visit('/') + ->within(new DatePicker, function (Browser $browser) { + $browser->selectDate(2019, 1, 30); + }) + ->assertSee('January'); + }); + } + } + +The `component` method may be used to retrieve a browser instance scoped to +the given component: + + + + 1$datePicker = $browser->component(new DatePickerComponent); + + 2  + + 3$datePicker->selectDate(2019, 1, 30); + + 4  + + 5$datePicker->assertSee('January'); + + + $datePicker = $browser->component(new DatePickerComponent); + + $datePicker->selectDate(2019, 1, 30); + + $datePicker->assertSee('January'); + +## Continuous Integration + +Most Dusk continuous integration configurations expect your Laravel +application to be served using the built-in PHP development server on port +8000. Therefore, before continuing, you should ensure that your continuous +integration environment has an `APP_URL` environment variable value of +`http://127.0.0.1:8000`. + +### Heroku CI + +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: + + + + 1{ + + 2 "environments": { + + 3 "test": { + + 4 "buildpacks": [ + + 5 { "url": "heroku/php" }, + + 6 { "url": "https://github.com/heroku/heroku-buildpack-chrome-for-testing" } + + 7 ], + + 8 "scripts": { + + 9 "test-setup": "cp .env.testing .env", + + 10 "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" + + 11 } + + 12 } + + 13 } + + 14} + + + { + "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 + +To run your Dusk tests on [Travis CI](https://travis-ci.org), use the +following `.travis.yml` configuration. Since Travis CI is not a graphical +environment, we will need to take some extra steps in order to launch a Chrome +browser. In addition, we will use `php artisan serve` to launch PHP's built-in +web server: + + + + 1language: php + + 2  + + 3php: + + 4 - 8.2 + + 5  + + 6addons: + + 7 chrome: stable + + 8  + + 9install: + + 10 - cp .env.testing .env + + 11 - travis_retry composer install --no-interaction --prefer-dist + + 12 - php artisan key:generate + + 13 - php artisan dusk:chrome-driver + + 14  + + 15before_script: + + 16 - google-chrome-stable --headless --disable-gpu --remote-debugging-port=9222 http://localhost & + + 17 - php artisan serve --no-reload & + + 18  + + 19script: + + 20 - php artisan dusk + + + language: php + + php: + - 8.2 + + addons: + chrome: stable + + install: + - cp .env.testing .env + - travis_retry composer install --no-interaction --prefer-dist + - php artisan key:generate + - php artisan dusk:chrome-driver + + before_script: + - google-chrome-stable --headless --disable-gpu --remote-debugging-port=9222 http://localhost & + - php artisan serve --no-reload & + + script: + - php artisan dusk + +### GitHub Actions + +If you are using [GitHub Actions](https://github.com/features/actions) to run +your Dusk tests, you may use the following configuration file as a starting +point. Like TravisCI, we will use the `php artisan serve` command to launch +PHP's built-in web server: + + + + 1name: CI + + 2on: [push] + + 3jobs: + + 4  + + 5 dusk-php: + + 6 runs-on: ubuntu-latest + + 7 env: + + 8 APP_URL: "http://127.0.0.1:8000" + + 9 DB_USERNAME: root + + 10 DB_PASSWORD: root + + 11 MAIL_MAILER: log + + 12 steps: + + 13 - uses: actions/checkout@v4 + + 14 - name: Prepare The Environment + + 15 run: cp .env.example .env + + 16 - name: Create Database + + 17 run: | + + 18 sudo systemctl start mysql + + 19 mysql --user="root" --password="root" -e "CREATE DATABASE \`my-database\` character set UTF8mb4 collate utf8mb4_bin;" + + 20 - name: Install Composer Dependencies + + 21 run: composer install --no-progress --prefer-dist --optimize-autoloader + + 22 - name: Generate Application Key + + 23 run: php artisan key:generate + + 24 - name: Upgrade Chrome Driver + + 25 run: php artisan dusk:chrome-driver --detect + + 26 - name: Start Chrome Driver + + 27 run: ./vendor/laravel/dusk/bin/chromedriver-linux --port=9515 & + + 28 - name: Run Laravel Server + + 29 run: php artisan serve --no-reload & + + 30 - name: Run Dusk Tests + + 31 run: php artisan dusk + + 32 - name: Upload Screenshots + + 33 if: failure() + + 34 uses: actions/upload-artifact@v4 + + 35 with: + + 36 name: screenshots + + 37 path: tests/Browser/screenshots + + 38 - name: Upload Console Logs + + 39 if: failure() + + 40 uses: actions/upload-artifact@v4 + + 41 with: + + 42 name: console + + 43 path: tests/Browser/console + + + name: CI + on: [push] + jobs: + + dusk-php: + runs-on: ubuntu-latest + env: + APP_URL: "http://127.0.0.1:8000" + DB_USERNAME: root + DB_PASSWORD: root + MAIL_MAILER: log + steps: + - uses: actions/checkout@v4 + - name: Prepare The Environment + run: cp .env.example .env + - name: Create Database + run: | + sudo systemctl start mysql + mysql --user="root" --password="root" -e "CREATE DATABASE \`my-database\` character set UTF8mb4 collate utf8mb4_bin;" + - name: Install Composer Dependencies + run: composer install --no-progress --prefer-dist --optimize-autoloader + - name: Generate Application Key + run: php artisan key:generate + - name: Upgrade Chrome Driver + run: php artisan dusk:chrome-driver --detect + - name: Start Chrome Driver + run: ./vendor/laravel/dusk/bin/chromedriver-linux --port=9515 & + - name: Run Laravel Server + run: php artisan serve --no-reload & + - name: Run Dusk Tests + run: php artisan dusk + - name: Upload Screenshots + if: failure() + uses: actions/upload-artifact@v4 + with: + name: screenshots + path: tests/Browser/screenshots + - name: Upload Console Logs + if: failure() + uses: actions/upload-artifact@v4 + with: + name: console + path: tests/Browser/console + +### Chipper CI + +If you are using [Chipper CI](https://chipperci.com) to run your Dusk tests, +you may use the following configuration file as a starting point. We will use +PHP's built-in server to run Laravel so we can listen for requests: + + + + 1# file .chipperci.yml + + 2version: 1 + + 3  + + 4environment: + + 5 php: 8.2 + + 6 node: 16 + + 7  + + 8# Include Chrome in the build environment + + 9services: + + 10 - dusk + + 11  + + 12# Build all commits + + 13on: + + 14 push: + + 15 branches: .* + + 16  + + 17pipeline: + + 18 - name: Setup + + 19 cmd: | + + 20 cp -v .env.example .env + + 21 composer install --no-interaction --prefer-dist --optimize-autoloader + + 22 php artisan key:generate + + 23  + + 24 # Create a dusk env file, ensuring APP_URL uses BUILD_HOST + + 25 cp -v .env .env.dusk.ci + + 26 sed -i "s@APP_URL=.*@APP_URL=http://$BUILD_HOST:8000@g" .env.dusk.ci + + 27  + + 28 - name: Compile Assets + + 29 cmd: | + + 30 npm ci --no-audit + + 31 npm run build + + 32  + + 33 - name: Browser Tests + + 34 cmd: | + + 35 php -S [::0]:8000 -t public 2>server.log & + + 36 sleep 2 + + 37 php artisan dusk:chrome-driver $CHROME_DRIVER + + 38 php artisan dusk --env=ci + + + # file .chipperci.yml + version: 1 + + environment: + php: 8.2 + node: 16 + + # Include Chrome in the build environment + services: + - dusk + + # Build all commits + on: + push: + branches: .* + + pipeline: + - name: Setup + cmd: | + cp -v .env.example .env + composer install --no-interaction --prefer-dist --optimize-autoloader + php artisan key:generate + + # Create a dusk env file, ensuring APP_URL uses BUILD_HOST + cp -v .env .env.dusk.ci + sed -i "s@APP_URL=.*@APP_URL=http://$BUILD_HOST:8000@g" .env.dusk.ci + + - name: Compile Assets + cmd: | + npm ci --no-audit + npm run build + + - name: Browser Tests + cmd: | + php -S [::0]:8000 -t public 2>server.log & + sleep 2 + php artisan dusk:chrome-driver $CHROME_DRIVER + php artisan dusk --env=ci + +To learn more about running Dusk tests on Chipper CI, including how to use +databases, consult the [official Chipper CI +documentation](https://chipperci.com/docs/testing/laravel-dusk-new/). + diff --git a/output/12.x/eloquent-collections.md b/output/12.x/eloquent-collections.md new file mode 100644 index 0000000..bc55882 --- /dev/null +++ b/output/12.x/eloquent-collections.md @@ -0,0 +1,577 @@ +# Eloquent: Collections + + * Introduction + * Available Methods + * Custom Collections + +## Introduction + +All Eloquent methods that return more than one model result will return +instances of the `Illuminate\Database\Eloquent\Collection` class, including +results retrieved via the `get` method or accessed via a relationship. The +Eloquent collection object extends Laravel's [base +collection](/docs/12.x/collections), so it naturally inherits dozens of +methods used to fluently work with the underlying array of Eloquent models. Be +sure to review the Laravel collection documentation to learn all about these +helpful methods! + +All collections also serve as iterators, allowing you to loop over them as if +they were simple PHP arrays: + + + + 1use App\Models\User; + + 2  + + 3$users = User::where('active', 1)->get(); + + 4  + + 5foreach ($users as $user) { + + 6 echo $user->name; + + 7} + + + use App\Models\User; + + $users = User::where('active', 1)->get(); + + 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: + + + + 1$names = User::all()->reject(function (User $user) { + + 2 return $user->active === false; + + 3})->map(function (User $user) { + + 4 return $user->name; + + 5}); + + + $names = User::all()->reject(function (User $user) { + return $user->active === false; + })->map(function (User $user) { + return $user->name; + }); + +#### Eloquent Collection Conversion + +While most Eloquent collection methods return a new instance of an Eloquent +collection, the `collapse`, `flatten`, `flip`, `keys`, `pluck`, and `zip` +methods return a [base collection](/docs/12.x/collections) instance. Likewise, +if a `map` operation returns a collection that does not contain any Eloquent +models, it will be converted to a base collection instance. + +## Available Methods + +All Eloquent collections extend the base [Laravel +collection](/docs/12.x/collections#available-methods) object; therefore, they +inherit all of the powerful methods provided by the base collection class. + +In addition, the `Illuminate\Database\Eloquent\Collection` class provides a +superset of methods to aid with managing your model collections. Most methods +return `Illuminate\Database\Eloquent\Collection` instances; however, some +methods, like `modelKeys`, return an `Illuminate\Support\Collection` instance. + +append contains diff except find findOrFail fresh intersect load loadMissing +modelKeys makeVisible makeHidden only partition setVisible setHidden toQuery +unique + +#### `append($attributes)` + +The `append` method may be used to indicate that an attribute should be +[appended](/docs/12.x/eloquent-serialization#appending-values-to-json) for +every model in the collection. This method accepts an array of attributes or a +single attribute: + + + + 1$users->append('team'); + + 2  + + 3$users->append(['team', 'is_admin']); + + + $users->append('team'); + + $users->append(['team', 'is_admin']); + +#### `contains($key, $operator = null, $value = null)` + +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: + + + + 1$users->contains(1); + + 2  + + 3$users->contains(User::find(1)); + + + $users->contains(1); + + $users->contains(User::find(1)); + +#### `diff($items)` + +The `diff` method returns all of the models that are not present in the given +collection: + + + + 1use App\Models\User; + + 2  + + 3$users = $users->diff(User::whereIn('id', [1, 2, 3])->get()); + + + use App\Models\User; + + $users = $users->diff(User::whereIn('id', [1, 2, 3])->get()); + +#### `except($keys)` + +The `except` method returns all of the models that do not have the given +primary keys: + + + + 1$users = $users->except([1, 2, 3]); + + + $users = $users->except([1, 2, 3]); + +#### `find($key)` + +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: + + + + 1$users = User::all(); + + 2  + + 3$user = $users->find(1); + + + $users = User::all(); + + $user = $users->find(1); + +#### `findOrFail($key)` + +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: + + + + 1$users = User::all(); + + 2  + + 3$user = $users->findOrFail(1); + + + $users = User::all(); + + $user = $users->findOrFail(1); + +#### `fresh($with = [])` + +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: + + + + 1$users = $users->fresh(); + + 2  + + 3$users = $users->fresh('comments'); + + + $users = $users->fresh(); + + $users = $users->fresh('comments'); + +#### `intersect($items)` + +The `intersect` method returns all of the models that are also present in the +given collection: + + + + 1use App\Models\User; + + 2  + + 3$users = $users->intersect(User::whereIn('id', [1, 2, 3])->get()); + + + use App\Models\User; + + $users = $users->intersect(User::whereIn('id', [1, 2, 3])->get()); + +#### `load($relations)` + +The `load` method eager loads the given relationships for all models in the +collection: + + + + 1$users->load(['comments', 'posts']); + + 2  + + 3$users->load('comments.author'); + + 4  + + 5$users->load(['comments', 'posts' => fn ($query) => $query->where('active', 1)]); + + + $users->load(['comments', 'posts']); + + $users->load('comments.author'); + + $users->load(['comments', 'posts' => fn ($query) => $query->where('active', 1)]); + +#### `loadMissing($relations)` + +The `loadMissing` method eager loads the given relationships for all models in +the collection if the relationships are not already loaded: + + + + 1$users->loadMissing(['comments', 'posts']); + + 2  + + 3$users->loadMissing('comments.author'); + + 4  + + 5$users->loadMissing(['comments', 'posts' => fn ($query) => $query->where('active', 1)]); + + + $users->loadMissing(['comments', 'posts']); + + $users->loadMissing('comments.author'); + + $users->loadMissing(['comments', 'posts' => fn ($query) => $query->where('active', 1)]); + +#### `modelKeys()` + +The `modelKeys` method returns the primary keys for all models in the +collection: + + + + 1$users->modelKeys(); + + 2  + + 3// [1, 2, 3, 4, 5] + + + $users->modelKeys(); + + // [1, 2, 3, 4, 5] + +#### `makeVisible($attributes)` + +The `makeVisible` method [makes attributes visible](/docs/12.x/eloquent- +serialization#hiding-attributes-from-json) that are typically "hidden" on each +model in the collection: + + + + 1$users = $users->makeVisible(['address', 'phone_number']); + + + $users = $users->makeVisible(['address', 'phone_number']); + +#### `makeHidden($attributes)` + +The `makeHidden` method [hides attributes](/docs/12.x/eloquent- +serialization#hiding-attributes-from-json) that are typically "visible" on +each model in the collection: + + + + 1$users = $users->makeHidden(['address', 'phone_number']); + + + $users = $users->makeHidden(['address', 'phone_number']); + +#### `only($keys)` + +The `only` method returns all of the models that have the given primary keys: + + + + 1$users = $users->only([1, 2, 3]); + + + $users = $users->only([1, 2, 3]); + +#### `partition` + +The `partition` method returns an instance of `Illuminate\Support\Collection` +containing `Illuminate\Database\Eloquent\Collection` collection instances: + + + + 1$partition = $users->partition(fn ($user) => $user->age > 18); + + 2  + + 3dump($partition::class); // Illuminate\Support\Collection + + 4dump($partition[0]::class); // Illuminate\Database\Eloquent\Collection + + 5dump($partition[1]::class); // Illuminate\Database\Eloquent\Collection + + + $partition = $users->partition(fn ($user) => $user->age > 18); + + dump($partition::class); // Illuminate\Support\Collection + dump($partition[0]::class); // Illuminate\Database\Eloquent\Collection + dump($partition[1]::class); // Illuminate\Database\Eloquent\Collection + +#### `setVisible($attributes)` + +The `setVisible` method [temporarily overrides](/docs/12.x/eloquent- +serialization#temporarily-modifying-attribute-visibility) all of the visible +attributes on each model in the collection: + + + + 1$users = $users->setVisible(['id', 'name']); + + + $users = $users->setVisible(['id', 'name']); + +#### `setHidden($attributes)` + +The `setHidden` method [temporarily overrides](/docs/12.x/eloquent- +serialization#temporarily-modifying-attribute-visibility) all of the hidden +attributes on each model in the collection: + + + + 1$users = $users->setHidden(['email', 'password', 'remember_token']); + + + $users = $users->setHidden(['email', 'password', 'remember_token']); + +#### `toQuery()` + +The `toQuery` method returns an Eloquent query builder instance containing a +`whereIn` constraint on the collection model's primary keys: + + + + 1use App\Models\User; + + 2  + + 3$users = User::where('status', 'VIP')->get(); + + 4  + + 5$users->toQuery()->update([ + + 6 'status' => 'Administrator', + + 7]); + + + use App\Models\User; + + $users = User::where('status', 'VIP')->get(); + + $users->toQuery()->update([ + 'status' => 'Administrator', + ]); + +#### `unique($key = null, $strict = false)` + +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: + + + + 1$users = $users->unique(); + + + $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: + + + + 1 $models + + 15 * @return \Illuminate\Database\Eloquent\Collection + + 16 */ + + 17 public function newCollection(array $models = []): Collection + + 18 { + + 19 $collection = new UserCollection($models); + + 20  + + 21 if (Model::isAutomaticallyEagerLoadingRelationships()) { + + 22 $collection->withRelationshipAutoloading(); + + 23 } + + 24  + + 25 return $collection; + + 26 } + + 27} + + + $models + * @return \Illuminate\Database\Eloquent\Collection + */ + public function newCollection(array $models = []): Collection + { + $collection = new UserCollection($models); + + if (Model::isAutomaticallyEagerLoadingRelationships()) { + $collection->withRelationshipAutoloading(); + } + + return $collection; + } + } + +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. + +If you would like to use a custom collection for every model in your +application, you should define the `newCollection` method on a base model +class that is extended by all of your application's models. + diff --git a/output/12.x/eloquent-factories.md b/output/12.x/eloquent-factories.md new file mode 100644 index 0000000..2189350 --- /dev/null +++ b/output/12.x/eloquent-factories.md @@ -0,0 +1,1397 @@ +# Eloquent: Factories + + * Introduction + * Defining Model Factories + * Generating Factories + * Factory States + * Factory Callbacks + * Creating Models Using Factories + * Instantiating Models + * Persisting Models + * Sequences + * Factory Relationships + * Has Many Relationships + * Belongs To Relationships + * Many to Many Relationships + * Polymorphic Relationships + * Defining Relationships Within Factories + * Recycling an Existing Model for Relationships + +## Introduction + +When testing your application or seeding your database, you may need to insert +a few records into your database. Instead of manually specifying the value of +each column, Laravel allows you to define a set of default attributes for each +of your [Eloquent models](/docs/12.x/eloquent) using model factories. + +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: + + + + 1namespace Database\Factories; + + 2  + + 3use Illuminate\Database\Eloquent\Factories\Factory; + + 4use Illuminate\Support\Facades\Hash; + + 5use Illuminate\Support\Str; + + 6  + + 7/** + + 8 * @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\User> + + 9 */ + + 10class UserFactory extends Factory + + 11{ + + 12 /** + + 13 * The current password being used by the factory. + + 14 */ + + 15 protected static ?string $password; + + 16  + + 17 /** + + 18 * Define the model's default state. + + 19 * + + 20 * @return array + + 21 */ + + 22 public function definition(): array + + 23 { + + 24 return [ + + 25 'name' => fake()->name(), + + 26 'email' => fake()->unique()->safeEmail(), + + 27 'email_verified_at' => now(), + + 28 'password' => static::$password ??= Hash::make('password'), + + 29 'remember_token' => Str::random(10), + + 30 ]; + + 31 } + + 32  + + 33 /** + + 34 * Indicate that the model's email address should be unverified. + + 35 */ + + 36 public function unverified(): static + + 37 { + + 38 return $this->state(fn (array $attributes) => [ + + 39 'email_verified_at' => null, + + 40 ]); + + 41 } + + 42} + + + 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; + + /** + * 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, + ]); + } + } + +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. + +Via the `fake` helper, factories have access to the +[Faker](https://github.com/FakerPHP/Faker) PHP library, which allows you to +conveniently generate various kinds of random data for testing and seeding. + +You can change your application's Faker locale by updating the `faker_locale` +option in your `config/app.php` configuration file. + +## Defining Model Factories + +### Generating Factories + +To create a factory, execute the `make:factory` [Artisan +command](/docs/12.x/artisan): + + + + 1php artisan make:factory PostFactory + + + php artisan make:factory PostFactory + +The new factory class will be placed in your `database/factories` directory. + +#### Model and Factory Discovery Conventions + +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. + +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: + + + + 1use Database\Factories\Administration\FlightFactory; + + 2  + + 3/** + + 4 * Create a new factory instance for the model. + + 5 */ + + 6protected static function newFactory() + + 7{ + + 8 return FlightFactory::new(); + + 9} + + + use Database\Factories\Administration\FlightFactory; + + /** + * Create a new factory instance for the model. + */ + protected static function newFactory() + { + return FlightFactory::new(); + } + +Then, define a `model` property on the corresponding factory: + + + + 1use App\Administration\Flight; + + 2use Illuminate\Database\Eloquent\Factories\Factory; + + 3  + + 4class FlightFactory extends Factory + + 5{ + + 6 /** + + 7 * The name of the factory's corresponding model. + + 8 * + + 9 * @var class-string<\Illuminate\Database\Eloquent\Model> + + 10 */ + + 11 protected $model = Flight::class; + + 12} + + + 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; + } + +### Factory States + +State manipulation methods allow you to define discrete modifications that can +be applied to your model factories in any combination. For example, your +`Database\Factories\UserFactory` factory might contain a `suspended` state +method that modifies one of its default attribute values. + +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: + + + + 1use Illuminate\Database\Eloquent\Factories\Factory; + + 2  + + 3/** + + 4 * Indicate that the user is suspended. + + 5 */ + + 6public function suspended(): Factory + + 7{ + + 8 return $this->state(function (array $attributes) { + + 9 return [ + + 10 'account_status' => 'suspended', + + 11 ]; + + 12 }); + + 13} + + + 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', + ]; + }); + } + +#### "Trashed" State + +If your Eloquent model can be [soft deleted](/docs/12.x/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: + + + + 1use App\Models\User; + + 2  + + 3$user = User::factory()->trashed()->create(); + + + use App\Models\User; + + $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: + + + + 1namespace Database\Factories; + + 2  + + 3use App\Models\User; + + 4use Illuminate\Database\Eloquent\Factories\Factory; + + 5  + + 6class UserFactory extends Factory + + 7{ + + 8 /** + + 9 * Configure the model factory. + + 10 */ + + 11 public function configure(): static + + 12 { + + 13 return $this->afterMaking(function (User $user) { + + 14 // ... + + 15 })->afterCreating(function (User $user) { + + 16 // ... + + 17 }); + + 18 } + + 19  + + 20 // ... + + 21} + + + 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) { + // ... + }); + } + + // ... + } + +You may also register factory callbacks within state methods to perform +additional tasks that are specific to a given state: + + + + 1use App\Models\User; + + 2use Illuminate\Database\Eloquent\Factories\Factory; + + 3  + + 4/** + + 5 * Indicate that the user is suspended. + + 6 */ + + 7public function suspended(): Factory + + 8{ + + 9 return $this->state(function (array $attributes) { + + 10 return [ + + 11 'account_status' => 'suspended', + + 12 ]; + + 13 })->afterMaking(function (User $user) { + + 14 // ... + + 15 })->afterCreating(function (User $user) { + + 16 // ... + + 17 }); + + 18} + + + 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 + +### Instantiating Models + +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: + + + + 1use App\Models\User; + + 2  + + 3$user = User::factory()->make(); + + + use App\Models\User; + + $user = User::factory()->make(); + +You may create a collection of many models using the `count` method: + + + + 1$users = User::factory()->count(3)->make(); + + + $users = User::factory()->count(3)->make(); + +#### Applying States + +You may also apply any of your 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: + + + + 1$users = User::factory()->count(5)->suspended()->make(); + + + $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: + + + + 1$user = User::factory()->make([ + + 2 'name' => 'Abigail Otwell', + + 3]); + + + $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: + + + + 1$user = User::factory()->state([ + + 2 'name' => 'Abigail Otwell', + + 3])->make(); + + + $user = User::factory()->state([ + 'name' => 'Abigail Otwell', + ])->make(); + +[Mass assignment protection](/docs/12.x/eloquent#mass-assignment) is +automatically disabled when creating models using factories. + +### Persisting Models + +The `create` method instantiates model instances and persists them to the +database using Eloquent's `save` method: + + + + 1use App\Models\User; + + 2  + + 3// Create a single App\Models\User instance... + + 4$user = User::factory()->create(); + + 5  + + 6// Create three App\Models\User instances... + + 7$users = User::factory()->count(3)->create(); + + + use App\Models\User; + + // Create a single App\Models\User instance... + $user = User::factory()->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: + + + + 1$user = User::factory()->create([ + + 2 'name' => 'Abigail', + + 3]); + + + $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: + + + + 1use App\Models\User; + + 2use Illuminate\Database\Eloquent\Factories\Sequence; + + 3  + + 4$users = User::factory() + + 5 ->count(10) + + 6 ->state(new Sequence( + + 7 ['admin' => 'Y'], + + 8 ['admin' => 'N'], + + 9 )) + + 10 ->create(); + + + 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: + + + + 1use Illuminate\Database\Eloquent\Factories\Sequence; + + 2  + + 3$users = User::factory() + + 4 ->count(10) + + 5 ->state(new Sequence( + + 6 fn (Sequence $sequence) => ['role' => UserRoles::all()->random()], + + 7 )) + + 8 ->create(); + + + use Illuminate\Database\Eloquent\Factories\Sequence; + + $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: + + + + 1$users = User::factory() + + 2 ->count(10) + + 3 ->sequence(fn (Sequence $sequence) => ['name' => 'Name '.$sequence->index]) + + 4 ->create(); + + + $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: + + + + 1$users = User::factory() + + 2 ->count(2) + + 3 ->sequence( + + 4 ['name' => 'First User'], + + 5 ['name' => 'Second User'], + + 6 ) + + 7 ->create(); + + + $users = User::factory() + ->count(2) + ->sequence( + ['name' => 'First User'], + ['name' => 'Second User'], + ) + ->create(); + +## Factory Relationships + +### Has Many Relationships + +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: + + + + 1use App\Models\Post; + + 2use App\Models\User; + + 3  + + 4$user = User::factory() + + 5 ->has(Post::factory()->count(3)) + + 6 ->create(); + + + use App\Models\Post; + use App\Models\User; + + $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: + + + + 1$user = User::factory() + + 2 ->has(Post::factory()->count(3), 'posts') + + 3 ->create(); + + + $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: + + + + 1$user = User::factory() + + 2 ->has( + + 3 Post::factory() + + 4 ->count(3) + + 5 ->state(function (array $attributes, User $user) { + + 6 return ['user_type' => $user->type]; + + 7 }) + + 8 ) + + 9 ->create(); + + + $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: + + + + 1$user = User::factory() + + 2 ->hasPosts(3) + + 3 ->create(); + + + $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: + + + + 1$user = User::factory() + + 2 ->hasPosts(3, [ + + 3 'published' => false, + + 4 ]) + + 5 ->create(); + + + $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: + + + + 1$user = User::factory() + + 2 ->hasPosts(3, function (array $attributes, User $user) { + + 3 return ['user_type' => $user->type]; + + 4 }) + + 5 ->create(); + + + $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: + + + + 1use App\Models\Post; + + 2use App\Models\User; + + 3  + + 4$posts = Post::factory() + + 5 ->count(3) + + 6 ->for(User::factory()->state([ + + 7 'name' => 'Jessica Archer', + + 8 ])) + + 9 ->create(); + + + use App\Models\Post; + use App\Models\User; + + $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: + + + + 1$user = User::factory()->create(); + + 2  + + 3$posts = Post::factory() + + 4 ->count(3) + + 5 ->for($user) + + 6 ->create(); + + + $user = User::factory()->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: + + + + 1$posts = Post::factory() + + 2 ->count(3) + + 3 ->forUser([ + + 4 'name' => 'Jessica Archer', + + 5 ]) + + 6 ->create(); + + + $posts = Post::factory() + ->count(3) + ->forUser([ + 'name' => 'Jessica Archer', + ]) + ->create(); + +### Many to Many Relationships + +Like has many relationships, "many to many" relationships may be created using +the `has` method: + + + + 1use App\Models\Role; + + 2use App\Models\User; + + 3  + + 4$user = User::factory() + + 5 ->has(Role::factory()->count(3)) + + 6 ->create(); + + + use App\Models\Role; + use App\Models\User; + + $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: + + + + 1use App\Models\Role; + + 2use App\Models\User; + + 3  + + 4$user = User::factory() + + 5 ->hasAttached( + + 6 Role::factory()->count(3), + + 7 ['active' => true] + + 8 ) + + 9 ->create(); + + + use App\Models\Role; + use App\Models\User; + + $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: + + + + 1$user = User::factory() + + 2 ->hasAttached( + + 3 Role::factory() + + 4 ->count(3) + + 5 ->state(function (array $attributes, User $user) { + + 6 return ['name' => $user->name.' Role']; + + 7 }), + + 8 ['active' => true] + + 9 ) + + 10 ->create(); + + + $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: + + + + 1$roles = Role::factory()->count(3)->create(); + + 2  + + 3$user = User::factory() + + 4 ->count(3) + + 5 ->hasAttached($roles, ['active' => true]) + + 6 ->create(); + + + $roles = Role::factory()->count(3)->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: + + + + 1$user = User::factory() + + 2 ->hasRoles(1, [ + + 3 'name' => 'Editor' + + 4 ]) + + 5 ->create(); + + + $user = User::factory() + ->hasRoles(1, [ + 'name' => 'Editor' + ]) + ->create(); + +### Polymorphic Relationships + +[Polymorphic relationships](/docs/12.x/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: + + + + 1use App\Models\Post; + + 2  + + 3$post = Post::factory()->hasComments(3)->create(); + + + use App\Models\Post; + + $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: + + + + 1$comments = Comment::factory()->count(3)->for( + + 2 Post::factory(), 'commentable' + + 3)->create(); + + + $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: + + + + 1use App\Models\Tag; + + 2use App\Models\Video; + + 3  + + 4$videos = Video::factory() + + 5 ->hasAttached( + + 6 Tag::factory()->count(3), + + 7 ['public' => true] + + 8 ) + + 9 ->create(); + + + use App\Models\Tag; + use App\Models\Video; + + $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: + + + + 1$videos = Video::factory() + + 2 ->hasTags(3, ['public' => true]) + + 3 ->create(); + + + $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: + + + + 1use App\Models\User; + + 2  + + 3/** + + 4 * Define the model's default state. + + 5 * + + 6 * @return array + + 7 */ + + 8public function definition(): array + + 9{ + + 10 return [ + + 11 'user_id' => User::factory(), + + 12 'title' => fake()->title(), + + 13 'content' => fake()->paragraph(), + + 14 ]; + + 15} + + + 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: + + + + 1/** + + 2 * Define the model's default state. + + 3 * + + 4 * @return array + + 5 */ + + 6public function definition(): array + + 7{ + + 8 return [ + + 9 'user_id' => User::factory(), + + 10 'user_type' => function (array $attributes) { + + 11 return User::find($attributes['user_id'])->type; + + 12 }, + + 13 'title' => fake()->title(), + + 14 'content' => fake()->paragraph(), + + 15 ]; + + 16} + + + /** + * 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 + +If you have models that share a common relationship with another model, you +may use the `recycle` method to ensure a single instance of the related model +is recycled for all of the relationships created by the factory. + +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: + + + + 1Ticket::factory() + + 2 ->recycle(Airline::factory()->create()) + + 3 ->create(); + + + 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: + + + + 1Ticket::factory() + + 2 ->recycle($airlines) + + 3 ->create(); + + + Ticket::factory() + ->recycle($airlines) + ->create(); + diff --git a/output/12.x/eloquent-mutators.md b/output/12.x/eloquent-mutators.md new file mode 100644 index 0000000..f588727 --- /dev/null +++ b/output/12.x/eloquent-mutators.md @@ -0,0 +1,2583 @@ +# Eloquent: Mutators & Casting + + * Introduction + * Accessors and Mutators + * Defining an Accessor + * Defining a Mutator + * Attribute Casting + * Array and JSON Casting + * Date Casting + * Enum Casting + * Encrypted Casting + * Query Time Casting + * Custom Casts + * Value Object Casting + * Array / JSON Serialization + * Inbound Casting + * Cast Parameters + * Comparing Cast Values + * Castables + +## Introduction + +Accessors, mutators, and attribute casting allow you to transform Eloquent +attribute values when you retrieve or set them on model instances. For +example, you may want to use the [Laravel encrypter](/docs/12.x/encryption) to +encrypt a value while it is stored in the database, and then automatically +decrypt the attribute when you access it on an Eloquent model. Or, you may +want to convert a JSON string that is stored in your database to an array when +it is accessed via your Eloquent model. + +## Accessors and Mutators + +### Defining an Accessor + +An accessor transforms an Eloquent attribute value when it is accessed. To +define an accessor, create a protected method on your model to represent the +accessible attribute. This method name should correspond to the "camel case" +representation of the true underlying model attribute / database column when +applicable. + +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`: + + + + 1 ucfirst($value), + + 17 ); + + 18 } + + 19} + + + 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: + + + + 1use App\Models\User; + + 2  + + 3$user = User::find(1); + + 4  + + 5$firstName = $user->first_name; + + + use App\Models\User; + + $user = User::find(1); + + $firstName = $user->first_name; + +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/12.x/eloquent-serialization#appending-values-to-json). + +#### Building Value Objects From Multiple Attributes + +Sometimes your accessor may need to transform multiple model attributes into a +single "value object". To do so, your `get` closure may accept a second +argument of `$attributes`, which will be automatically supplied to the closure +and will contain an array of all of the model's current attributes: + + + + 1use App\Support\Address; + + 2use Illuminate\Database\Eloquent\Casts\Attribute; + + 3  + + 4/** + + 5 * Interact with the user's address. + + 6 */ + + 7protected function address(): Attribute + + 8{ + + 9 return Attribute::make( + + 10 get: fn (mixed $value, array $attributes) => new Address( + + 11 $attributes['address_line_one'], + + 12 $attributes['address_line_two'], + + 13 ), + + 14 ); + + 15} + + + use App\Support\Address; + use Illuminate\Database\Eloquent\Casts\Attribute; + + /** + * Interact with the user's address. + */ + protected function address(): Attribute + { + return Attribute::make( + get: fn (mixed $value, array $attributes) => new Address( + $attributes['address_line_one'], + $attributes['address_line_two'], + ), + ); + } + +#### Accessor Caching + +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: + + + + 1use App\Models\User; + + 2  + + 3$user = User::find(1); + + 4  + + 5$user->address->lineOne = 'Updated Address Line 1 Value'; + + 6$user->address->lineTwo = 'Updated Address Line 2 Value'; + + 7  + + 8$user->save(); + + + use App\Models\User; + + $user = User::find(1); + + $user->address->lineOne = 'Updated Address Line 1 Value'; + $user->address->lineTwo = 'Updated Address Line 2 Value'; + + $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: + + + + 1protected function hash(): Attribute + + 2{ + + 3 return Attribute::make( + + 4 get: fn (string $value) => bcrypt(gzuncompress($value)), + + 5 )->shouldCache(); + + 6} + + + protected function hash(): Attribute + { + return Attribute::make( + get: fn (string $value) => bcrypt(gzuncompress($value)), + )->shouldCache(); + } + +If you would like to disable the object caching behavior of attributes, you +may invoke the `withoutObjectCaching` method when defining the attribute: + + + + 1/** + + 2 * Interact with the user's address. + + 3 */ + + 4protected function address(): Attribute + + 5{ + + 6 return Attribute::make( + + 7 get: fn (mixed $value, array $attributes) => new Address( + + 8 $attributes['address_line_one'], + + 9 $attributes['address_line_two'], + + 10 ), + + 11 )->withoutObjectCaching(); + + 12} + + + /** + * Interact with the user's address. + */ + protected function address(): Attribute + { + return Attribute::make( + get: fn (mixed $value, array $attributes) => new Address( + $attributes['address_line_one'], + $attributes['address_line_two'], + ), + )->withoutObjectCaching(); + } + +### Defining a Mutator + +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: + + + + 1 ucfirst($value), + + 17 set: fn (string $value) => strtolower($value), + + 18 ); + + 19 } + + 20} + + + 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: + + + + 1use App\Models\User; + + 2  + + 3$user = User::find(1); + + 4  + + 5$user->first_name = 'Sally'; + + + use App\Models\User; + + $user = User::find(1); + + $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. + +#### Mutating Multiple Attributes + +Sometimes your mutator may need to set multiple attributes on the underlying +model. To do so, you may return an array from the `set` closure. Each key in +the array should correspond with an underlying attribute / database column +associated with the model: + + + + 1use App\Support\Address; + + 2use Illuminate\Database\Eloquent\Casts\Attribute; + + 3  + + 4/** + + 5 * Interact with the user's address. + + 6 */ + + 7protected function address(): Attribute + + 8{ + + 9 return Attribute::make( + + 10 get: fn (mixed $value, array $attributes) => new Address( + + 11 $attributes['address_line_one'], + + 12 $attributes['address_line_two'], + + 13 ), + + 14 set: fn (Address $value) => [ + + 15 'address_line_one' => $value->lineOne, + + 16 'address_line_two' => $value->lineTwo, + + 17 ], + + 18 ); + + 19} + + + use App\Support\Address; + use Illuminate\Database\Eloquent\Casts\Attribute; + + /** + * Interact with the user's address. + */ + protected function address(): Attribute + { + return Attribute::make( + get: fn (mixed $value, array $attributes) => new Address( + $attributes['address_line_one'], + $attributes['address_line_two'], + ), + set: fn (Address $value) => [ + 'address_line_one' => $value->lineOne, + 'address_line_two' => $value->lineTwo, + ], + ); + } + +## Attribute Casting + +Attribute casting provides functionality similar to accessors and mutators +without requiring you to define any additional methods on your model. Instead, +your model's `casts` method provides a convenient way of converting attributes +to common data types. + +The `casts` method should return an array where the key is the name of the +attribute being cast and the value is the type you wish to cast the column to. +The supported cast types are: + + * `array` + * `AsFluent::class` + * `AsStringable::class` + * `AsUri::class` + * `boolean` + * `collection` + * `date` + * `datetime` + * `immutable_date` + * `immutable_datetime` + * `decimal:` + * `double` + * `encrypted` + * `encrypted:array` + * `encrypted:collection` + * `encrypted:object` + * `float` + * `hashed` + * `integer` + * `object` + * `real` + * `string` + * `timestamp` + +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: + + + + 1 + + 13 */ + + 14 protected function casts(): array + + 15 { + + 16 return [ + + 17 'is_admin' => 'boolean', + + 18 ]; + + 19 } + + 20} + + + + */ + protected function casts(): array + { + 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: + + + + 1$user = App\Models\User::find(1); + + 2  + + 3if ($user->is_admin) { + + 4 // ... + + 5} + + + $user = App\Models\User::find(1); + + 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: + + + + 1$user->mergeCasts([ + + 2 'is_admin' => 'integer', + + 3 'options' => 'object', + + 4]); + + + $user->mergeCasts([ + 'is_admin' => 'integer', + 'options' => 'object', + ]); + +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. + +#### Stringable Casting + +You may use the `Illuminate\Database\Eloquent\Casts\AsStringable` cast class +to cast a model attribute to a [fluent Illuminate\Support\Stringable +object](/docs/12.x/strings#fluent-strings-method-list): + + + + 1 + + 14 */ + + 15 protected function casts(): array + + 16 { + + 17 return [ + + 18 'directory' => AsStringable::class, + + 19 ]; + + 20 } + + 21} + + + + */ + protected function casts(): array + { + 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: + + + + 1 + + 13 */ + + 14 protected function casts(): array + + 15 { + + 16 return [ + + 17 'options' => 'array', + + 18 ]; + + 19 } + + 20} + + + + */ + protected function casts(): 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: + + + + 1use App\Models\User; + + 2  + + 3$user = User::find(1); + + 4  + + 5$options = $user->options; + + 6  + + 7$options['key'] = 'value'; + + 8  + + 9$user->options = $options; + + 10  + + 11$user->save(); + + + use App\Models\User; + + $user = User::find(1); + + $options = $user->options; + + $options['key'] = 'value'; + + $user->options = $options; + + $user->save(); + +To update a single field of a JSON attribute with a more terse syntax, you may +[make the attribute mass assignable](/docs/12.x/eloquent#mass-assignment-json- +columns) and use the `->` operator when calling the `update` method: + + + + 1$user = User::find(1); + + 2  + + 3$user->update(['options->key' => 'value']); + + + $user = User::find(1); + + $user->update(['options->key' => 'value']); + +#### JSON and Unicode + +If you would like to store an array attribute as JSON with unescaped Unicode +characters, you may use the `json:unicode` cast: + + + + 1/** + + 2 * Get the attributes that should be cast. + + 3 * + + 4 * @return array + + 5 */ + + 6protected function casts(): array + + 7{ + + 8 return [ + + 9 'options' => 'json:unicode', + + 10 ]; + + 11} + + + /** + * Get the attributes that should be cast. + * + * @return array + */ + protected function casts(): array + { + return [ + 'options' => 'json:unicode', + ]; + } + +#### 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: + + + + 1$user = User::find(1); + + 2  + + 3$user->options['key'] = $value; + + + $user = User::find(1); + + $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 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: + + + + 1use Illuminate\Database\Eloquent\Casts\AsArrayObject; + + 2  + + 3/** + + 4 * Get the attributes that should be cast. + + 5 * + + 6 * @return array + + 7 */ + + 8protected function casts(): array + + 9{ + + 10 return [ + + 11 'options' => AsArrayObject::class, + + 12 ]; + + 13} + + + use Illuminate\Database\Eloquent\Casts\AsArrayObject; + + /** + * 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/12.x/collections) instance: + + + + 1use Illuminate\Database\Eloquent\Casts\AsCollection; + + 2  + + 3/** + + 4 * Get the attributes that should be cast. + + 5 * + + 6 * @return array + + 7 */ + + 8protected function casts(): array + + 9{ + + 10 return [ + + 11 'options' => AsCollection::class, + + 12 ]; + + 13} + + + use Illuminate\Database\Eloquent\Casts\AsCollection; + + /** + * 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: + + + + 1use App\Collections\OptionCollection; + + 2use Illuminate\Database\Eloquent\Casts\AsCollection; + + 3  + + 4/** + + 5 * Get the attributes that should be cast. + + 6 * + + 7 * @return array + + 8 */ + + 9protected function casts(): array + + 10{ + + 11 return [ + + 12 'options' => AsCollection::using(OptionCollection::class), + + 13 ]; + + 14} + + + 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), + ]; + } + +The `of` method may be used to indicate collection items should be mapped into +a given class via the collection's [mapInto +method](/docs/12.x/collections#method-mapinto): + + + + 1use App\ValueObjects\Option; + + 2use Illuminate\Database\Eloquent\Casts\AsCollection; + + 3  + + 4/** + + 5 * Get the attributes that should be cast. + + 6 * + + 7 * @return array + + 8 */ + + 9protected function casts(): array + + 10{ + + 11 return [ + + 12 'options' => AsCollection::of(Option::class) + + 13 ]; + + 14} + + + use App\ValueObjects\Option; + use Illuminate\Database\Eloquent\Casts\AsCollection; + + /** + * Get the attributes that should be cast. + * + * @return array + */ + protected function casts(): array + { + return [ + 'options' => AsCollection::of(Option::class) + ]; + } + +When mapping collections to objects, the object should implement the +`Illuminate\Contracts\Support\Arrayable` and `JsonSerializable` interfaces to +define how their instances should be serialized into the database as JSON: + + + + 1name = $data['name']; + + 20 $this->value = $data['value']; + + 21 $this->isLocked = $data['is_locked']; + + 22 } + + 23  + + 24 /** + + 25 * Get the instance as an array. + + 26 * + + 27 * @return array{name: string, data: string, is_locked: bool} + + 28 */ + + 29 public function toArray(): array + + 30 { + + 31 return [ + + 32 'name' => $this->name, + + 33 'value' => $this->value, + + 34 'is_locked' => $this->isLocked, + + 35 ]; + + 36 } + + 37  + + 38 /** + + 39 * Specify the data which should be serialized to JSON. + + 40 * + + 41 * @return array{name: string, data: string, is_locked: bool} + + 42 */ + + 43 public function jsonSerialize(): array + + 44 { + + 45 return $this->toArray(); + + 46 } + + 47} + + + name = $data['name']; + $this->value = $data['value']; + $this->isLocked = $data['is_locked']; + } + + /** + * Get the instance as an array. + * + * @return array{name: string, data: string, is_locked: bool} + */ + public function toArray(): array + { + return [ + 'name' => $this->name, + 'value' => $this->value, + 'is_locked' => $this->isLocked, + ]; + } + + /** + * Specify the data which should be serialized to JSON. + * + * @return array{name: string, data: string, is_locked: bool} + */ + public function jsonSerialize(): array + { + return $this->toArray(); + } + } + +### Date Casting + +By default, Eloquent will cast the `created_at` and `updated_at` columns to +instances of [Carbon](https://github.com/briannesbitt/Carbon), which extends +the PHP `DateTime` class and provides an assortment of helpful methods. You +may cast additional date attributes by defining additional date casts within +your model's `casts` method. Typically, dates should be cast using the +`datetime` or `immutable_datetime` cast types. + +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/12.x/eloquent-serialization): + + + + 1/** + + 2 * Get the attributes that should be cast. + + 3 * + + 4 * @return array + + 5 */ + + 6protected function casts(): array + + 7{ + + 8 return [ + + 9 'created_at' => 'datetime:Y-m-d', + + 10 ]; + + 11} + + + /** + * 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: + + + + 1/** + + 2 * Prepare a date for array / JSON serialization. + + 3 */ + + 4protected function serializeDate(DateTimeInterface $date): string + + 5{ + + 6 return $date->format('Y-m-d'); + + 7} + + + /** + * 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: + + + + 1/** + + 2 * The storage format of the model's date columns. + + 3 * + + 4 * @var string + + 5 */ + + 6protected $dateFormat = 'U'; + + + /** + * The storage format of the model's date columns. + * + * @var string + */ + protected $dateFormat = 'U'; + +#### Date Casting, Serialization, and Timezones + +By default, the `date` and `datetime` casts will serialize dates to a UTC +ISO-8601 date string (`YYYY-MM-DDTHH:MM:SS.uuuuuuZ`), regardless of the +timezone specified in your application's `timezone` configuration option. You +are strongly encouraged to always use this serialization format, as well as to +store your application's dates in the UTC timezone by not changing your +application's `timezone` configuration option from its default `UTC` value. +Consistently using the UTC timezone throughout your application will provide +the maximum level of interoperability with other date manipulation libraries +written in PHP and JavaScript. + +If a custom format is applied to the `date` or `datetime` cast, such as +`datetime:Y-m-d H:i:s`, the inner timezone of the Carbon instance will be used +during date serialization. Typically, this will be the timezone specified in +your application's `timezone` configuration option. However, it's important to +note that `timestamp` columns such as `created_at` and `updated_at` are exempt +from this behavior and are always formatted in UTC, regardless of the +application's timezone setting. + +### Enum Casting + +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: + + + + 1use App\Enums\ServerStatus; + + 2  + + 3/** + + 4 * Get the attributes that should be cast. + + 5 * + + 6 * @return array + + 7 */ + + 8protected function casts(): array + + 9{ + + 10 return [ + + 11 'status' => ServerStatus::class, + + 12 ]; + + 13} + + + use App\Enums\ServerStatus; + + /** + * 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: + + + + 1if ($server->status == ServerStatus::Provisioned) { + + 2 $server->status = ServerStatus::Ready; + + 3  + + 4 $server->save(); + + 5} + + + if ($server->status == ServerStatus::Provisioned) { + $server->status = ServerStatus::Ready; + + $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: + + + + 1use App\Enums\ServerStatus; + + 2use Illuminate\Database\Eloquent\Casts\AsEnumCollection; + + 3  + + 4/** + + 5 * Get the attributes that should be cast. + + 6 * + + 7 * @return array + + 8 */ + + 9protected function casts(): array + + 10{ + + 11 return [ + + 12 'statuses' => AsEnumCollection::of(ServerStatus::class), + + 13 ]; + + 14} + + + 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), + ]; + } + +### Encrypted Casting + +The `encrypted` cast will encrypt a model's attribute value using Laravel's +built-in [encryption](/docs/12.x/encryption) features. In addition, the +`encrypted:array`, `encrypted:collection`, `encrypted:object`, +`AsEncryptedArrayObject`, and `AsEncryptedCollection` casts work like their +unencrypted counterparts; however, as you might expect, the underlying value +is encrypted when stored in your database. + +As the final length of the encrypted text is not predictable and is longer +than its plain text counterpart, make sure the associated database column is +of `TEXT` type or larger. In addition, since the values are encrypted in the +database, you will not be able to query or search encrypted attribute values. + +#### Key Rotation + +As you may know, Laravel encrypts strings using the `key` configuration value +specified in your application's `app` configuration file. Typically, this +value corresponds to the value of the `APP_KEY` environment variable. If you +need to rotate your application's encryption key, you will need to manually +re-encrypt your encrypted attributes using the new key. + +### Query Time Casting + +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: + + + + 1use App\Models\Post; + + 2use App\Models\User; + + 3  + + 4$users = User::select([ + + 5 'users.*', + + 6 'last_posted_at' => Post::selectRaw('MAX(created_at)') + + 7 ->whereColumn('user_id', 'users.id') + + 8])->get(); + + + 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: + + + + 1$users = User::select([ + + 2 'users.*', + + 3 'last_posted_at' => Post::selectRaw('MAX(created_at)') + + 4 ->whereColumn('user_id', 'users.id') + + 5])->withCasts([ + + 6 'last_posted_at' => 'datetime' + + 7])->get(); + + + $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 + +Laravel has a variety of built-in, helpful cast types; however, you may +occasionally need to define your own cast types. To create a cast, execute the +`make:cast` Artisan command. The new cast class will be placed in your +`app/Casts` directory: + + + + 1php artisan make:cast AsJson + + + php artisan make:cast AsJson + +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: + + + + 1 $attributes + + 14 * @return array + + 15 */ + + 16 public function get( + + 17 Model $model, + + 18 string $key, + + 19 mixed $value, + + 20 array $attributes, + + 21 ): array { + + 22 return json_decode($value, true); + + 23 } + + 24  + + 25 /** + + 26 * Prepare the given value for storage. + + 27 * + + 28 * @param array $attributes + + 29 */ + + 30 public function set( + + 31 Model $model, + + 32 string $key, + + 33 mixed $value, + + 34 array $attributes, + + 35 ): string { + + 36 return json_encode($value); + + 37 } + + 38} + + + $attributes + * @return array + */ + public function get( + Model $model, + string $key, + mixed $value, + array $attributes, + ): array { + 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); + } + } + +Once you have defined a custom cast type, you may attach it to a model +attribute using its class name: + + + + 1 + + 14 */ + + 15 protected function casts(): array + + 16 { + + 17 return [ + + 18 'options' => AsJson::class, + + 19 ]; + + 20 } + + 21} + + + + */ + protected function casts(): array + { + return [ + 'options' => AsJson::class, + ]; + } + } + +### Value Object Casting + +You are not limited to casting values to primitive types. You may also cast +values to objects. Defining custom casts that cast values to objects is very +similar to casting to primitive types; however, if your value object +encompasses more than one database column, the `set` method must return an +array of key / value pairs that will be used to set raw, storable values on +the model. If your value object only affects a single column, you should +simply return the storable value. + +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 object has two public properties: `lineOne` and `lineTwo`: + + + + 1 $attributes + + 16 */ + + 17 public function get( + + 18 Model $model, + + 19 string $key, + + 20 mixed $value, + + 21 array $attributes, + + 22 ): Address { + + 23 return new Address( + + 24 $attributes['address_line_one'], + + 25 $attributes['address_line_two'] + + 26 ); + + 27 } + + 28  + + 29 /** + + 30 * Prepare the given value for storage. + + 31 * + + 32 * @param array $attributes + + 33 * @return array + + 34 */ + + 35 public function set( + + 36 Model $model, + + 37 string $key, + + 38 mixed $value, + + 39 array $attributes, + + 40 ): array { + + 41 if (! $value instanceof Address) { + + 42 throw new InvalidArgumentException('The given value is not an Address instance.'); + + 43 } + + 44  + + 45 return [ + + 46 'address_line_one' => $value->lineOne, + + 47 'address_line_two' => $value->lineTwo, + + 48 ]; + + 49 } + + 50} + + + $attributes + */ + public function get( + Model $model, + string $key, + mixed $value, + array $attributes, + ): Address { + return new Address( + $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 Address) { + 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: + + + + 1use App\Models\User; + + 2  + + 3$user = User::find(1); + + 4  + + 5$user->address->lineOne = 'Updated Address Value'; + + 6  + + 7$user->save(); + + + use App\Models\User; + + $user = User::find(1); + + $user->address->lineOne = 'Updated Address Value'; + + $user->save(); + +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. + +#### Value Object Caching + +When attributes that are cast to value objects are resolved, they are cached +by Eloquent. Therefore, the same object instance will be returned if the +attribute is accessed again. + +If you would like to disable the object caching behavior of custom cast +classes, you may declare a public `withoutObjectCaching` property on your +custom cast class: + + + + 1class AsAddress implements CastsAttributes + + 2{ + + 3 public bool $withoutObjectCaching = true; + + 4  + + 5 // ... + + 6} + + + class AsAddress implements CastsAttributes + { + public bool $withoutObjectCaching = true; + + // ... + } + +### Array / JSON Serialization + +When an Eloquent model is converted to an array or JSON using the `toArray` +and `toJson` methods, your custom cast value objects will typically be +serialized as well as long as they implement the +`Illuminate\Contracts\Support\Arrayable` and `JsonSerializable` interfaces. +However, when using value objects provided by third-party libraries, you may +not have the ability to add these interfaces to the object. + +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: + + + + 1/** + + 2 * Get the serialized representation of the value. + + 3 * + + 4 * @param array $attributes + + 5 */ + + 6public function serialize( + + 7 Model $model, + + 8 string $key, + + 9 mixed $value, + + 10 array $attributes, + + 11): string { + + 12 return (string) $value; + + 13} + + + /** + * 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 + +Occasionally, you may need to write a custom cast class that only transforms +values that are being set on the model and does not perform any operations +when attributes are being retrieved from the model. + +Inbound only custom casts should implement the `CastsInboundAttributes` +interface, which only requires a `set` method to be defined. The `make:cast` +Artisan command may be invoked with the `--inbound` option to generate an +inbound only cast class: + + + + 1php artisan make:cast AsHash --inbound + + + php artisan make:cast AsHash --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: + + + + 1 $attributes + + 21 */ + + 22 public function set( + + 23 Model $model, + + 24 string $key, + + 25 mixed $value, + + 26 array $attributes, + + 27 ): string { + + 28 return is_null($this->algorithm) + + 29 ? bcrypt($value) + + 30 : hash($this->algorithm, $value); + + 31 } + + 32} + + + $attributes + */ + public function set( + Model $model, + string $key, + mixed $value, + array $attributes, + ): string { + 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: + + + + 1/** + + 2 * Get the attributes that should be cast. + + 3 * + + 4 * @return array + + 5 */ + + 6protected function casts(): array + + 7{ + + 8 return [ + + 9 'secret' => AsHash::class.':sha256', + + 10 ]; + + 11} + + + /** + * Get the attributes that should be cast. + * + * @return array + */ + protected function casts(): array + { + return [ + 'secret' => AsHash::class.':sha256', + ]; + } + +### Comparing Cast Values + +If you would like to define how two given cast values should be compared to +determine if they have been changed, your custom cast class may implement the +`Illuminate\Contracts\Database\Eloquent\ComparesCastableAttributes` interface. +This allows you to have fine-grained control over which values Eloquent +considers changed and thus saves to the database when a model is updated. + +This interface states that your class should contain a `compare` method which +should return `true` if the given values are considered equal: + + + + 1/** + + 2 * Determine if the given values are equal. + + 3 * + + 4 * @param \Illuminate\Database\Eloquent\Model $model + + 5 * @param string $key + + 6 * @param mixed $firstValue + + 7 * @param mixed $secondValue + + 8 * @return bool + + 9 */ + + 10public function compare( + + 11 Model $model, + + 12 string $key, + + 13 mixed $firstValue, + + 14 mixed $secondValue + + 15): bool { + + 16 return $firstValue === $secondValue; + + 17} + + + /** + * Determine if the given values are equal. + * + * @param \Illuminate\Database\Eloquent\Model $model + * @param string $key + * @param mixed $firstValue + * @param mixed $secondValue + * @return bool + */ + public function compare( + Model $model, + string $key, + mixed $firstValue, + mixed $secondValue + ): bool { + return $firstValue === $secondValue; + } + +### 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: + + + + 1use App\ValueObjects\Address; + + 2  + + 3protected function casts(): array + + 4{ + + 5 return [ + + 6 'address' => Address::class, + + 7 ]; + + 8} + + + use App\ValueObjects\Address; + + 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: + + + + 1 $arguments + + 14 */ + + 15 public static function castUsing(array $arguments): string + + 16 { + + 17 return AsAddress::class; + + 18 } + + 19} + + + $arguments + */ + public static function castUsing(array $arguments): string + { + return AsAddress::class; + } + } + +When using `Castable` classes, you may still provide arguments in the `casts` +method definition. The arguments will be passed to the `castUsing` method: + + + + 1use App\ValueObjects\Address; + + 2  + + 3protected function casts(): array + + 4{ + + 5 return [ + + 6 'address' => Address::class.':argument', + + 7 ]; + + 8} + + + use App\ValueObjects\Address; + + 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: + + + + 1 $arguments + + 16 */ + + 17 public static function castUsing(array $arguments): CastsAttributes + + 18 { + + 19 return new class implements CastsAttributes + + 20 { + + 21 public function get( + + 22 Model $model, + + 23 string $key, + + 24 mixed $value, + + 25 array $attributes, + + 26 ): Address { + + 27 return new Address( + + 28 $attributes['address_line_one'], + + 29 $attributes['address_line_two'] + + 30 ); + + 31 } + + 32  + + 33 public function set( + + 34 Model $model, + + 35 string $key, + + 36 mixed $value, + + 37 array $attributes, + + 38 ): array { + + 39 return [ + + 40 'address_line_one' => $value->lineOne, + + 41 'address_line_two' => $value->lineTwo, + + 42 ]; + + 43 } + + 44 }; + + 45 } + + 46} + + + $arguments + */ + public static function castUsing(array $arguments): CastsAttributes + { + return new class implements CastsAttributes + { + 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, + ]; + } + }; + } + } + diff --git a/output/12.x/eloquent-relationships.md b/output/12.x/eloquent-relationships.md new file mode 100644 index 0000000..889fd8a --- /dev/null +++ b/output/12.x/eloquent-relationships.md @@ -0,0 +1,5877 @@ +# Eloquent: Relationships + + * Introduction + * Defining Relationships + * One to One / Has One + * One to Many / Has Many + * One to Many (Inverse) / Belongs To + * Has One of Many + * Has One Through + * Has Many Through + * Scoped Relationships + * Many to Many Relationships + * Retrieving Intermediate Table Columns + * Filtering Queries via Intermediate Table Columns + * Ordering Queries via Intermediate Table Columns + * Defining Custom Intermediate Table Models + * Polymorphic Relationships + * One to One + * One to Many + * One of Many + * Many to Many + * Custom Polymorphic Types + * Dynamic Relationships + * Querying Relations + * Relationship Methods vs. Dynamic Properties + * Querying Relationship Existence + * Querying Relationship Absence + * Querying Morph To Relationships + * Aggregating Related Models + * Counting Related Models + * Other Aggregate Functions + * Counting Related Models on Morph To Relationships + * Eager Loading + * Constraining Eager Loads + * Lazy Eager Loading + * Automatic Eager Loading + * Preventing Lazy Loading + * Inserting and Updating Related Models + * The `save` Method + * The `create` Method + * Belongs To Relationships + * Many to Many Relationships + * Touching Parent Timestamps + +## Introduction + +Database tables are often related to one another. For example, a blog post may +have many comments or an order could be related to the user who placed it. +Eloquent makes managing and working with these relationships easy, and +supports a variety of common relationships: + + * One To One + * One To Many + * Many To Many + * Has One Through + * Has Many Through + * One To One (Polymorphic) + * One To Many (Polymorphic) + * Many To Many (Polymorphic) + +## Defining Relationships + +Eloquent relationships are defined as methods on your Eloquent model classes. +Since relationships also serve as powerful [query +builders](/docs/12.x/queries), defining relationships as methods provides +powerful method chaining and querying capabilities. For example, we may chain +additional query constraints on this `posts` relationship: + + + + 1$user->posts()->where('active', 1)->get(); + + + $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. + +### One to One / Has One + +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: + + + + 1hasOne(Phone::class); + + 16 } + + 17} + + + 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: + + + + 1$phone = User::find(1)->phone; + + + $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: + + + + 1return $this->hasOne(Phone::class, 'foreign_key'); + + + 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: + + + + 1return $this->hasOne(Phone::class, 'foreign_key', 'local_key'); + + + 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: + + + + 1belongsTo(User::class); + + 16 } + + 17} + + + 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: + + + + 1/** + + 2 * Get the user that owns the phone. + + 3 */ + + 4public function user(): BelongsTo + + 5{ + + 6 return $this->belongsTo(User::class, 'foreign_key'); + + 7} + + + /** + * 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: + + + + 1/** + + 2 * Get the user that owns the phone. + + 3 */ + + 4public function user(): BelongsTo + + 5{ + + 6 return $this->belongsTo(User::class, 'foreign_key', 'owner_key'); + + 7} + + + /** + * 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: + + + + 1hasMany(Comment::class); + + 16 } + + 17} + + + 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/12.x/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: + + + + 1use App\Models\Post; + + 2  + + 3$comments = Post::find(1)->comments; + + 4  + + 5foreach ($comments as $comment) { + + 6 // ... + + 7} + + + use App\Models\Post; + + $comments = Post::find(1)->comments; + + 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: + + + + 1$comment = Post::find(1)->comments() + + 2 ->where('title', 'foo') + + 3 ->first(); + + + $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: + + + + 1return $this->hasMany(Comment::class, 'foreign_key'); + + 2  + + 3return $this->hasMany(Comment::class, 'foreign_key', 'local_key'); + + + return $this->hasMany(Comment::class, 'foreign_key'); + + return $this->hasMany(Comment::class, 'foreign_key', 'local_key'); + +#### Automatically Hydrating Parent Models on Children + +Even when utilizing Eloquent eager loading, "N + 1" query problems can arise +if you try to access the parent model from a child model while looping through +the child models: + + + + 1$posts = Post::with('comments')->get(); + + 2  + + 3foreach ($posts as $post) { + + 4 foreach ($post->comments as $comment) { + + 5 echo $comment->post->title; + + 6 } + + 7} + + + $posts = Post::with('comments')->get(); + + foreach ($posts as $post) { + foreach ($post->comments as $comment) { + echo $comment->post->title; + } + } + +In the example above, an "N + 1" query problem has been introduced because, +even though comments were eager loaded for every `Post` model, Eloquent does +not automatically hydrate the parent `Post` on each child `Comment` model. + +If you would like Eloquent to automatically hydrate parent models onto their +children, you may invoke the `chaperone` method when defining a `hasMany` +relationship: + + + + 1hasMany(Comment::class)->chaperone(); + + 16 } + + 17} + + + 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: + + + + 1use App\Models\Post; + + 2  + + 3$posts = Post::with([ + + 4 'comments' => fn ($comments) => $comments->chaperone(), + + 5])->get(); + + + use App\Models\Post; + + $posts = Post::with([ + 'comments' => fn ($comments) => $comments->chaperone(), + ])->get(); + +### One to Many (Inverse) / Belongs To + +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: + + + + 1belongsTo(Post::class); + + 16 } + + 17} + + + belongsTo(Post::class); + } + } + +Once the relationship has been defined, we can retrieve a comment's parent +post by accessing the `post` "dynamic relationship property": + + + + 1use App\Models\Comment; + + 2  + + 3$comment = Comment::find(1); + + 4  + + 5return $comment->post->title; + + + use App\Models\Comment; + + $comment = Comment::find(1); + + 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. + +Eloquent determines the default foreign key name by examining the name of the +relationship method and suffixing the method name with a `_` followed by the +name of the parent model's primary key column. So, in this example, Eloquent +will assume the `Post` model's foreign key on the `comments` table is +`post_id`. + +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: + + + + 1/** + + 2 * Get the post that owns the comment. + + 3 */ + + 4public function post(): BelongsTo + + 5{ + + 6 return $this->belongsTo(Post::class, 'foreign_key'); + + 7} + + + /** + * 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: + + + + 1/** + + 2 * Get the post that owns the comment. + + 3 */ + + 4public function post(): BelongsTo + + 5{ + + 6 return $this->belongsTo(Post::class, 'foreign_key', 'owner_key'); + + 7} + + + /** + * 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: + + + + 1/** + + 2 * Get the author of the post. + + 3 */ + + 4public function user(): BelongsTo + + 5{ + + 6 return $this->belongsTo(User::class)->withDefault(); + + 7} + + + /** + * 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: + + + + 1/** + + 2 * Get the author of the post. + + 3 */ + + 4public function user(): BelongsTo + + 5{ + + 6 return $this->belongsTo(User::class)->withDefault([ + + 7 'name' => 'Guest Author', + + 8 ]); + + 9} + + 10  + + 11/** + + 12 * Get the author of the post. + + 13 */ + + 14public function user(): BelongsTo + + 15{ + + 16 return $this->belongsTo(User::class)->withDefault(function (User $user, Post $post) { + + 17 $user->name = 'Guest Author'; + + 18 }); + + 19} + + + /** + * 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'; + }); + } + +#### 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: + + + + 1use App\Models\Post; + + 2  + + 3$posts = Post::where('user_id', $user->id)->get(); + + + use App\Models\Post; + + $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: + + + + 1$posts = Post::whereBelongsTo($user)->get(); + + + $posts = Post::whereBelongsTo($user)->get(); + +You may also provide a [collection](/docs/12.x/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: + + + + 1$users = User::where('vip', true)->get(); + + 2  + + 3$posts = Post::whereBelongsTo($users)->get(); + + + $users = User::where('vip', true)->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: + + + + 1$posts = Post::whereBelongsTo($user, 'author')->get(); + + + $posts = Post::whereBelongsTo($user, 'author')->get(); + +### Has One of Many + +Sometimes a model may have many related models, yet you want to easily +retrieve the "latest" or "oldest" related model of the relationship. For +example, a `User` model may be related to many `Order` models, but you want to +define a convenient way to interact with the most recent order the user has +placed. You may accomplish this using the `hasOne` relationship type combined +with the `ofMany` methods: + + + + 1/** + + 2 * Get the user's most recent order. + + 3 */ + + 4public function latestOrder(): HasOne + + 5{ + + 6 return $this->hasOne(Order::class)->latestOfMany(); + + 7} + + + /** + * Get the user's most recent order. + */ + public function latestOrder(): HasOne + { + return $this->hasOne(Order::class)->latestOfMany(); + } + +Likewise, you may define a method to retrieve the "oldest", or first, related +model of a relationship: + + + + 1/** + + 2 * Get the user's oldest order. + + 3 */ + + 4public function oldestOrder(): HasOne + + 5{ + + 6 return $this->hasOne(Order::class)->oldestOfMany(); + + 7} + + + /** + * Get the user's oldest order. + */ + public function oldestOrder(): HasOne + { + return $this->hasOne(Order::class)->oldestOfMany(); + } + +By default, the `latestOfMany` and `oldestOfMany` methods will retrieve the +latest or oldest related model based on the model's primary key, which must be +sortable. However, sometimes you may wish to retrieve a single model from a +larger relationship using a different sorting criteria. + +For example, using the `ofMany` method, you may retrieve the user's most +expensive order. The `ofMany` method accepts the sortable column as its first +argument and which aggregate function (`min` or `max`) to apply when querying +for the related model: + + + + 1/** + + 2 * Get the user's largest order. + + 3 */ + + 4public function largestOrder(): HasOne + + 5{ + + 6 return $this->hasOne(Order::class)->ofMany('price', 'max'); + + 7} + + + /** + * Get the user's largest order. + */ + public function largestOrder(): HasOne + { + return $this->hasOne(Order::class)->ofMany('price', 'max'); + } + +Because PostgreSQL does not support executing the `MAX` function against UUID +columns, it is not currently possible to use one-of-many relationships in +combination with PostgreSQL UUID columns. + +#### Converting "Many" Relationships to Has One Relationships + +Often, when retrieving a single model using the `latestOfMany`, +`oldestOfMany`, or `ofMany` methods, you already have a "has many" +relationship defined for the same model. For convenience, Laravel allows you +to easily convert this relationship into a "has one" relationship by invoking +the `one` method on the relationship: + + + + 1/** + + 2 * Get the user's orders. + + 3 */ + + 4public function orders(): HasMany + + 5{ + + 6 return $this->hasMany(Order::class); + + 7} + + 8  + + 9/** + + 10 * Get the user's largest order. + + 11 */ + + 12public function largestOrder(): HasOne + + 13{ + + 14 return $this->orders()->one()->ofMany('price', 'max'); + + 15} + + + /** + * Get the user's orders. + */ + public function orders(): HasMany + { + return $this->hasMany(Order::class); + } + + /** + * Get the user's largest order. + */ + public function largestOrder(): HasOne + { + return $this->orders()->one()->ofMany('price', 'max'); + } + +You may also use the `one` method to convert `HasManyThrough` relationships to +`HasOneThrough` relationships: + + + + 1public function latestDeployment(): HasOneThrough + + 2{ + + 3 return $this->deployments()->one()->latestOfMany(); + + 4} + + + public function latestDeployment(): HasOneThrough + { + return $this->deployments()->one()->latestOfMany(); + } + +#### Advanced Has One of Many Relationships + +It is possible to construct more advanced "has one of many" relationships. For +example, a `Product` model may have many associated `Price` models that are +retained in the system even after new pricing is published. In addition, new +pricing data for the product may be able to be published in advance to take +effect at a future date via a `published_at` column. + +So, in summary, we need to retrieve the latest published pricing where the +published date is not in the future. In addition, if two prices have the same +published date, we will prefer the price with the greatest ID. To accomplish +this, we must pass an array to the `ofMany` method that contains the sortable +columns which determine the latest price. In addition, a closure will be +provided as the second argument to the `ofMany` method. This closure will be +responsible for adding additional publish date constraints to the relationship +query: + + + + 1/** + + 2 * Get the current pricing for the product. + + 3 */ + + 4public function currentPricing(): HasOne + + 5{ + + 6 return $this->hasOne(Price::class)->ofMany([ + + 7 'published_at' => 'max', + + 8 'id' => 'max', + + 9 ], function (Builder $query) { + + 10 $query->where('published_at', '<', now()); + + 11 }); + + 12} + + + /** + * Get the current pricing for the product. + */ + public function currentPricing(): HasOne + { + return $this->hasOne(Price::class)->ofMany([ + 'published_at' => 'max', + 'id' => 'max', + ], function (Builder $query) { + $query->where('published_at', '<', now()); + }); + } + +### Has One Through + +The "has-one-through" relationship defines a one-to-one relationship with +another model. However, this relationship indicates that the declaring model +can be matched with one instance of another model by proceeding _through_ a +third model. + +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: + + + + 1mechanics + + 2 id - integer + + 3 name - string + + 4 + + 5cars + + 6 id - integer + + 7 model - string + + 8 mechanic_id - integer + + 9 + + 10owners + + 11 id - integer + + 12 name - string + + 13 car_id - integer + + + mechanics + id - integer + name - string + + cars + id - integer + model - string + mechanic_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: + + + + 1hasOneThrough(Owner::class, Car::class); + + 16 } + + 17} + + + 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. + +Or, 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. For example, if the `Mechanic` model has a `cars` +relationship and the `Car` model has an `owner` relationship, you may define a +"has-one-through" relationship connecting the mechanic and the owner like so: + + + + 1// String based syntax... + + 2return $this->through('cars')->has('owner'); + + 3  + + 4// Dynamic syntax... + + 5return $this->throughCars()->hasOwner(); + + + // String based syntax... + return $this->through('cars')->has('owner'); + + // Dynamic syntax... + return $this->throughCars()->hasOwner(); + +#### Key Conventions + +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: + + + + 1class Mechanic extends Model + + 2{ + + 3 /** + + 4 * Get the car's owner. + + 5 */ + + 6 public function carOwner(): HasOneThrough + + 7 { + + 8 return $this->hasOneThrough( + + 9 Owner::class, + + 10 Car::class, + + 11 'mechanic_id', // Foreign key on the cars table... + + 12 'car_id', // Foreign key on the owners table... + + 13 'id', // Local key on the mechanics table... + + 14 'id' // Local key on the cars table... + + 15 ); + + 16 } + + 17} + + + class Mechanic extends Model + { + /** + * 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... + ); + } + } + +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: + + + + 1// String based syntax... + + 2return $this->through('cars')->has('owner'); + + 3  + + 4// Dynamic syntax... + + 5return $this->throughCars()->hasOwner(); + + + // String based syntax... + return $this->through('cars')->has('owner'); + + // Dynamic syntax... + return $this->throughCars()->hasOwner(); + +### Has Many Through + +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 +Cloud](https://cloud.laravel.com). An `Application` model might access many +`Deployment` models through an intermediate `Environment` model. Using this +example, you could easily gather all deployments for a given application. +Let's look at the tables required to define this relationship: + + + + 1applications + + 2 id - integer + + 3 name - string + + 4 + + 5environments + + 6 id - integer + + 7 application_id - integer + + 8 name - string + + 9 + + 10deployments + + 11 id - integer + + 12 environment_id - integer + + 13 commit_hash - string + + + applications + id - integer + name - string + + environments + id - integer + application_id - integer + name - 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 `Application` model: + + + + 1hasManyThrough(Deployment::class, Environment::class); + + 16 } + + 17} + + + 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. + +Or, 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. For example, if the `Application` model has a +`environments` relationship and the `Environment` model has a `deployments` +relationship, you may define a "has-many-through" relationship connecting the +application and the deployments like so: + + + + 1// String based syntax... + + 2return $this->through('environments')->has('deployments'); + + 3  + + 4// Dynamic syntax... + + 5return $this->throughEnvironments()->hasDeployments(); + + + // String based syntax... + return $this->through('environments')->has('deployments'); + + // Dynamic syntax... + return $this->throughEnvironments()->hasDeployments(); + +Though the `Deployment` model's table does not contain a `application_id` +column, the `hasManyThrough` relation provides access to a application's +deployments via `$application->deployments`. To retrieve these models, +Eloquent inspects the `application_id` column on the intermediate +`Environment` model's table. After finding the relevant environment IDs, they +are used to query the `Deployment` model's table. + +#### Key Conventions + +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: + + + + 1class Application extends Model + + 2{ + + 3 public function deployments(): HasManyThrough + + 4 { + + 5 return $this->hasManyThrough( + + 6 Deployment::class, + + 7 Environment::class, + + 8 'application_id', // Foreign key on the environments table... + + 9 'environment_id', // Foreign key on the deployments table... + + 10 'id', // Local key on the applications table... + + 11 'id' // Local key on the environments table... + + 12 ); + + 13 } + + 14} + + + class Application extends Model + { + public function deployments(): HasManyThrough + { + return $this->hasManyThrough( + Deployment::class, + Environment::class, + 'application_id', // Foreign key on the environments table... + 'environment_id', // Foreign key on the deployments table... + 'id', // Local key on the applications 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: + + + + 1// String based syntax... + + 2return $this->through('environments')->has('deployments'); + + 3  + + 4// Dynamic syntax... + + 5return $this->throughEnvironments()->hasDeployments(); + + + // String based syntax... + return $this->through('environments')->has('deployments'); + + // Dynamic syntax... + return $this->throughEnvironments()->hasDeployments(); + +### Scoped Relationships + +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: + + + + 1hasMany(Post::class)->latest(); + + 16 } + + 17  + + 18 /** + + 19 * Get the user's featured posts. + + 20 */ + + 21 public function featuredPosts(): HasMany + + 22 { + + 23 return $this->posts()->where('featured', true); + + 24 } + + 25} + + + hasMany(Post::class)->latest(); + } + + /** + * Get the user's featured posts. + */ + public function featuredPosts(): HasMany + { + 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: + + + + 1/** + + 2 * Get the user's featured posts. + + 3 */ + + 4public function featuredPosts(): HasMany + + 5{ + + 6 return $this->posts()->withAttributes(['featured' => true]); + + 7} + + + /** + * Get the user's featured posts. + */ + public function featuredPosts(): HasMany + { + return $this->posts()->withAttributes(['featured' => true]); + } + +The `withAttributes` method will add `where` conditions to the query using the +given attributes, and it will also add the given attributes to any models +created via the relationship method: + + + + 1$post = $user->featuredPosts()->create(['title' => 'Featured Post']); + + 2  + + 3$post->featured; // true + + + $post = $user->featuredPosts()->create(['title' => 'Featured Post']); + + $post->featured; // true + +To instruct the `withAttributes` method to not add `where` conditions to the +query, you may set the `asConditions` argument to `false`: + + + + 1return $this->posts()->withAttributes(['featured' => true], asConditions: false); + + + return $this->posts()->withAttributes(['featured' => true], asConditions: false); + +## Many to Many Relationships + +Many-to-many relations are slightly more complicated than `hasOne` and +`hasMany` relationships. An example of a many-to-many relationship is a user +that has many roles and those roles are also shared by other users in the +application. For example, a user may be assigned the role of "Author" and +"Editor"; however, those roles may also be assigned to other users as well. +So, a user has many roles and a role has many users. + +#### Table Structure + +To define this relationship, three database tables are needed: `users`, +`roles`, and `role_user`. The `role_user` table is derived from the +alphabetical order of the related model names and contains `user_id` and +`role_id` columns. This table is used as an intermediate table linking the +users and 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: + + + + 1users + + 2 id - integer + + 3 name - string + + 4 + + 5roles + + 6 id - integer + + 7 name - string + + 8 + + 9role_user + + 10 user_id - integer + + 11 role_id - integer + + + users + id - integer + name - string + + roles + id - integer + name - string + + 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: + + + + 1belongsToMany(Role::class); + + 16 } + + 17} + + + belongsToMany(Role::class); + } + } + +Once the relationship is defined, you may access the user's roles using the +`roles` dynamic relationship property: + + + + 1use App\Models\User; + + 2  + + 3$user = User::find(1); + + 4  + + 5foreach ($user->roles as $role) { + + 6 // ... + + 7} + + + use App\Models\User; + + $user = User::find(1); + + 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: + + + + 1$roles = User::find(1)->roles()->orderBy('name')->get(); + + + $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: + + + + 1return $this->belongsToMany(Role::class, 'role_user'); + + + 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: + + + + 1return $this->belongsToMany(Role::class, 'role_user', 'user_id', 'role_id'); + + + 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: + + + + 1belongsToMany(User::class); + + 16 } + + 17} + + + 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. + +### Retrieving Intermediate Table Columns + +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: + + + + 1use App\Models\User; + + 2  + + 3$user = User::find(1); + + 4  + + 5foreach ($user->roles as $role) { + + 6 echo $role->pivot->created_at; + + 7} + + + use App\Models\User; + + $user = User::find(1); + + 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: + + + + 1return $this->belongsToMany(Role::class)->withPivot('active', 'created_by'); + + + 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: + + + + 1return $this->belongsToMany(Role::class)->withTimestamps(); + + + return $this->belongsToMany(Role::class)->withTimestamps(); + +Intermediate tables that utilize Eloquent's automatically maintained +timestamps are required to have both `created_at` and `updated_at` timestamp +columns. + +#### Customizing the `pivot` Attribute Name + +As noted previously, attributes from the intermediate table may be accessed on +models via the `pivot` attribute. However, you are free to customize the name +of this attribute to better reflect its purpose within your application. + +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: + + + + 1return $this->belongsToMany(Podcast::class) + + 2 ->as('subscription') + + 3 ->withTimestamps(); + + + 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: + + + + 1$users = User::with('podcasts')->get(); + + 2  + + 3foreach ($users->flatMap->podcasts as $podcast) { + + 4 echo $podcast->subscription->created_at; + + 5} + + + $users = User::with('podcasts')->get(); + + 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: + + + + 1return $this->belongsToMany(Role::class) + + 2 ->wherePivot('approved', 1); + + 3  + + 4return $this->belongsToMany(Role::class) + + 5 ->wherePivotIn('priority', [1, 2]); + + 6  + + 7return $this->belongsToMany(Role::class) + + 8 ->wherePivotNotIn('priority', [1, 2]); + + 9  + + 10return $this->belongsToMany(Podcast::class) + + 11 ->as('subscriptions') + + 12 ->wherePivotBetween('created_at', ['2020-01-01 00:00:00', '2020-12-31 00:00:00']); + + 13  + + 14return $this->belongsToMany(Podcast::class) + + 15 ->as('subscriptions') + + 16 ->wherePivotNotBetween('created_at', ['2020-01-01 00:00:00', '2020-12-31 00:00:00']); + + 17  + + 18return $this->belongsToMany(Podcast::class) + + 19 ->as('subscriptions') + + 20 ->wherePivotNull('expired_at'); + + 21  + + 22return $this->belongsToMany(Podcast::class) + + 23 ->as('subscriptions') + + 24 ->wherePivotNotNull('expired_at'); + + + return $this->belongsToMany(Role::class) + ->wherePivot('approved', 1); + + return $this->belongsToMany(Role::class) + ->wherePivotIn('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') + ->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') + ->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: + + + + 1return $this->belongsToMany(Role::class) + + 2 ->withPivotValue('approved', 1); + + + 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: + + + + 1return $this->belongsToMany(Badge::class) + + 2 ->where('rank', 'gold') + + 3 ->orderByPivot('created_at', 'desc'); + + + return $this->belongsToMany(Badge::class) + ->where('rank', 'gold') + ->orderByPivot('created_at', 'desc'); + +### Defining Custom Intermediate Table Models + +If you would like to define a custom model to represent the intermediate table +of your many-to-many relationship, you may call the `using` method when +defining the relationship. Custom pivot models give you the opportunity to +define additional behavior on the pivot model, such as methods and casts. + +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: + + + + 1belongsToMany(User::class)->using(RoleUser::class); + + 16 } + + 17} + + + belongsToMany(User::class)->using(RoleUser::class); + } + } + +When defining the `RoleUser` model, you should extend the +`Illuminate\Database\Eloquent\Relations\Pivot` class: + + + + 1morphTo(); + + 16 } + + 17} + + 18  + + 19use Illuminate\Database\Eloquent\Model; + + 20use Illuminate\Database\Eloquent\Relations\MorphOne; + + 21  + + 22class Post extends Model + + 23{ + + 24 /** + + 25 * Get the post's image. + + 26 */ + + 27 public function image(): MorphOne + + 28 { + + 29 return $this->morphOne(Image::class, 'imageable'); + + 30 } + + 31} + + 32  + + 33use Illuminate\Database\Eloquent\Model; + + 34use Illuminate\Database\Eloquent\Relations\MorphOne; + + 35  + + 36class User extends Model + + 37{ + + 38 /** + + 39 * Get the user's image. + + 40 */ + + 41 public function image(): MorphOne + + 42 { + + 43 return $this->morphOne(Image::class, 'imageable'); + + 44 } + + 45} + + + morphTo(); + } + } + + use Illuminate\Database\Eloquent\Model; + use Illuminate\Database\Eloquent\Relations\MorphOne; + + class Post extends Model + { + /** + * Get the post's image. + */ + public function image(): MorphOne + { + return $this->morphOne(Image::class, 'imageable'); + } + } + + use Illuminate\Database\Eloquent\Model; + use Illuminate\Database\Eloquent\Relations\MorphOne; + + class User extends Model + { + /** + * Get the user's image. + */ + public function image(): MorphOne + { + 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: + + + + 1use App\Models\Post; + + 2  + + 3$post = Post::find(1); + + 4  + + 5$image = $post->image; + + + use App\Models\Post; + + $post = Post::find(1); + + $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: + + + + 1use App\Models\Image; + + 2  + + 3$image = Image::find(1); + + 4  + + 5$imageable = $image->imageable; + + + use App\Models\Image; + + $image = Image::find(1); + + $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. + +#### Key Conventions + +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: + + + + 1/** + + 2 * Get the model that the image belongs to. + + 3 */ + + 4public function imageable(): MorphTo + + 5{ + + 6 return $this->morphTo(__FUNCTION__, 'imageable_type', 'imageable_id'); + + 7} + + + /** + * Get the model that the image belongs to. + */ + public function imageable(): MorphTo + { + return $this->morphTo(__FUNCTION__, 'imageable_type', 'imageable_id'); + } + +### One to Many (Polymorphic) + +#### Table Structure + +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: + + + + 1posts + + 2 id - integer + + 3 title - string + + 4 body - text + + 5 + + 6videos + + 7 id - integer + + 8 title - string + + 9 url - string + + 10 + + 11comments + + 12 id - integer + + 13 body - text + + 14 commentable_id - integer + + 15 commentable_type - string + + + 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: + + + + 1morphTo(); + + 16 } + + 17} + + 18  + + 19use Illuminate\Database\Eloquent\Model; + + 20use Illuminate\Database\Eloquent\Relations\MorphMany; + + 21  + + 22class Post extends Model + + 23{ + + 24 /** + + 25 * Get all of the post's comments. + + 26 */ + + 27 public function comments(): MorphMany + + 28 { + + 29 return $this->morphMany(Comment::class, 'commentable'); + + 30 } + + 31} + + 32  + + 33use Illuminate\Database\Eloquent\Model; + + 34use Illuminate\Database\Eloquent\Relations\MorphMany; + + 35  + + 36class Video extends Model + + 37{ + + 38 /** + + 39 * Get all of the video's comments. + + 40 */ + + 41 public function comments(): MorphMany + + 42 { + + 43 return $this->morphMany(Comment::class, 'commentable'); + + 44 } + + 45} + + + morphTo(); + } + } + + use Illuminate\Database\Eloquent\Model; + use Illuminate\Database\Eloquent\Relations\MorphMany; + + class Post extends Model + { + /** + * Get all of the post's comments. + */ + public function comments(): MorphMany + { + return $this->morphMany(Comment::class, 'commentable'); + } + } + + use Illuminate\Database\Eloquent\Model; + use Illuminate\Database\Eloquent\Relations\MorphMany; + + class Video extends Model + { + /** + * Get all of the video's comments. + */ + public function comments(): MorphMany + { + 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: + + + + 1use App\Models\Post; + + 2  + + 3$post = Post::find(1); + + 4  + + 5foreach ($post->comments as $comment) { + + 6 // ... + + 7} + + + use App\Models\Post; + + $post = Post::find(1); + + 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: + + + + 1use App\Models\Comment; + + 2  + + 3$comment = Comment::find(1); + + 4  + + 5$commentable = $comment->commentable; + + + use App\Models\Comment; + + $comment = Comment::find(1); + + $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. + +#### Automatically Hydrating Parent Models on Children + +Even when utilizing Eloquent eager loading, "N + 1" query problems can arise +if you try to access the parent model from a child model while looping through +the child models: + + + + 1$posts = Post::with('comments')->get(); + + 2  + + 3foreach ($posts as $post) { + + 4 foreach ($post->comments as $comment) { + + 5 echo $comment->commentable->title; + + 6 } + + 7} + + + $posts = Post::with('comments')->get(); + + foreach ($posts as $post) { + foreach ($post->comments as $comment) { + echo $comment->commentable->title; + } + } + +In the example above, an "N + 1" query problem has been introduced because, +even though comments were eager loaded for every `Post` model, Eloquent does +not automatically hydrate the parent `Post` on each child `Comment` model. + +If you would like Eloquent to automatically hydrate parent models onto their +children, you may invoke the `chaperone` method when defining a `morphMany` +relationship: + + + + 1class Post extends Model + + 2{ + + 3 /** + + 4 * Get all of the post's comments. + + 5 */ + + 6 public function comments(): MorphMany + + 7 { + + 8 return $this->morphMany(Comment::class, 'commentable')->chaperone(); + + 9 } + + 10} + + + class Post extends Model + { + /** + * Get all of the post's comments. + */ + public function comments(): MorphMany + { + 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: + + + + 1use App\Models\Post; + + 2  + + 3$posts = Post::with([ + + 4 'comments' => fn ($comments) => $comments->chaperone(), + + 5])->get(); + + + use App\Models\Post; + + $posts = Post::with([ + 'comments' => fn ($comments) => $comments->chaperone(), + ])->get(); + +### One of Many (Polymorphic) + +Sometimes a model may have many related models, yet you want to easily +retrieve the "latest" or "oldest" related model of the relationship. For +example, a `User` model may be related to many `Image` models, but you want to +define a convenient way to interact with the most recent image the user has +uploaded. You may accomplish this using the `morphOne` relationship type +combined with the `ofMany` methods: + + + + 1/** + + 2 * Get the user's most recent image. + + 3 */ + + 4public function latestImage(): MorphOne + + 5{ + + 6 return $this->morphOne(Image::class, 'imageable')->latestOfMany(); + + 7} + + + /** + * Get the user's most recent image. + */ + public function latestImage(): MorphOne + { + return $this->morphOne(Image::class, 'imageable')->latestOfMany(); + } + +Likewise, you may define a method to retrieve the "oldest", or first, related +model of a relationship: + + + + 1/** + + 2 * Get the user's oldest image. + + 3 */ + + 4public function oldestImage(): MorphOne + + 5{ + + 6 return $this->morphOne(Image::class, 'imageable')->oldestOfMany(); + + 7} + + + /** + * Get the user's oldest image. + */ + public function oldestImage(): MorphOne + { + return $this->morphOne(Image::class, 'imageable')->oldestOfMany(); + } + +By default, the `latestOfMany` and `oldestOfMany` methods will retrieve the +latest or oldest related model based on the model's primary key, which must be +sortable. However, sometimes you may wish to retrieve a single model from a +larger relationship using a different sorting criteria. + +For example, using the `ofMany` method, you may retrieve the user's most +"liked" image. The `ofMany` method accepts the sortable column as its first +argument and which aggregate function (`min` or `max`) to apply when querying +for the related model: + + + + 1/** + + 2 * Get the user's most popular image. + + 3 */ + + 4public function bestImage(): MorphOne + + 5{ + + 6 return $this->morphOne(Image::class, 'imageable')->ofMany('likes', 'max'); + + 7} + + + /** + * Get the user's most popular image. + */ + public function bestImage(): MorphOne + { + return $this->morphOne(Image::class, 'imageable')->ofMany('likes', 'max'); + } + +It is possible to construct more advanced "one of many" relationships. For +more information, please consult the has one of many documentation. + +### Many to Many (Polymorphic) + +#### Table Structure + +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: + + + + 1posts + + 2 id - integer + + 3 name - string + + 4 + + 5videos + + 6 id - integer + + 7 name - string + + 8 + + 9tags + + 10 id - integer + + 11 name - string + + 12 + + 13taggables + + 14 tag_id - integer + + 15 taggable_id - integer + + 16 taggable_type - string + + + posts + id - integer + name - string + + videos + id - integer + name - string + + tags + id - integer + name - string + + taggables + tag_id - integer + taggable_id - integer + taggable_type - string + +Before diving into polymorphic many-to-many relationships, you may benefit +from reading the documentation on typical many-to-many relationships. + +#### Model Structure + +Next, we're ready to define the relationships on the models. The `Post` and +`Video` models will both contain a `tags` method that calls the `morphToMany` +method provided by the base Eloquent model class. + +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": + + + + 1morphToMany(Tag::class, 'taggable'); + + 16 } + + 17} + + + morphToMany(Tag::class, 'taggable'); + } + } + +#### Defining the Inverse of the Relationship + +Next, on the `Tag` model, you should define a method for each of its possible +parent models. So, in this example, we will define a `posts` method and a +`videos` method. Both of these methods should return the result of the +`morphedByMany` method. + +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": + + + + 1morphedByMany(Post::class, 'taggable'); + + 16 } + + 17  + + 18 /** + + 19 * Get all of the videos that are assigned this tag. + + 20 */ + + 21 public function videos(): MorphToMany + + 22 { + + 23 return $this->morphedByMany(Video::class, 'taggable'); + + 24 } + + 25} + + + 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: + + + + 1use App\Models\Post; + + 2  + + 3$post = Post::find(1); + + 4  + + 5foreach ($post->tags as $tag) { + + 6 // ... + + 7} + + + use App\Models\Post; + + $post = Post::find(1); + + 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: + + + + 1use App\Models\Tag; + + 2  + + 3$tag = Tag::find(1); + + 4  + + 5foreach ($tag->posts as $post) { + + 6 // ... + + 7} + + 8  + + 9foreach ($tag->videos as $video) { + + 10 // ... + + 11} + + + use App\Models\Tag; + + $tag = Tag::find(1); + + foreach ($tag->posts as $post) { + // ... + } + + foreach ($tag->videos as $video) { + // ... + } + +### Custom Polymorphic Types + +By default, Laravel will use the fully qualified class name to store the +"type" of the related model. For instance, given the one-to-many relationship +example above where a `Comment` model may belong to a `Post` or a `Video` +model, the default `commentable_type` would be either `App\Models\Post` or +`App\Models\Video`, respectively. However, you may wish to decouple these +values from your application's internal structure. + +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: + + + + 1use Illuminate\Database\Eloquent\Relations\Relation; + + 2  + + 3Relation::enforceMorphMap([ + + 4 'post' => 'App\Models\Post', + + 5 'video' => 'App\Models\Video', + + 6]); + + + use Illuminate\Database\Eloquent\Relations\Relation; + + 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: + + + + 1use Illuminate\Database\Eloquent\Relations\Relation; + + 2  + + 3$alias = $post->getMorphClass(); + + 4  + + 5$class = Relation::getMorphedModel($alias); + + + use Illuminate\Database\Eloquent\Relations\Relation; + + $alias = $post->getMorphClass(); + + $class = Relation::getMorphedModel($alias); + +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. + +### Dynamic Relationships + +You may use the `resolveRelationUsing` method to define relations between +Eloquent models at runtime. While not typically recommended for normal +application development, this may occasionally be useful when developing +Laravel packages. + +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/12.x/providers): + + + + 1use App\Models\Order; + + 2use App\Models\Customer; + + 3  + + 4Order::resolveRelationUsing('customer', function (Order $orderModel) { + + 5 return $orderModel->belongsTo(Customer::class, 'customer_id'); + + 6}); + + + use App\Models\Order; + use App\Models\Customer; + + Order::resolveRelationUsing('customer', function (Order $orderModel) { + return $orderModel->belongsTo(Customer::class, 'customer_id'); + }); + +When defining dynamic relationships, always provide explicit key name +arguments to the Eloquent relationship methods. + +## Querying Relations + +Since all Eloquent relationships are defined via methods, you may call those +methods to obtain an instance of the relationship without actually executing a +query to load the related models. In addition, all types of Eloquent +relationships also serve as [query builders](/docs/12.x/queries), allowing you +to continue to chain constraints onto the relationship query before finally +executing the SQL query against your database. + +For example, imagine a blog application in which a `User` model has many +associated `Post` models: + + + + 1hasMany(Post::class); + + 16 } + + 17} + + + hasMany(Post::class); + } + } + +You may query the `posts` relationship and add additional constraints to the +relationship like so: + + + + 1use App\Models\User; + + 2  + + 3$user = User::find(1); + + 4  + + 5$user->posts()->where('active', 1)->get(); + + + use App\Models\User; + + $user = User::find(1); + + $user->posts()->where('active', 1)->get(); + +You are able to use any of the Laravel [query builder's](/docs/12.x/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. + +#### Chaining `orWhere` Clauses After Relationships + +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: + + + + 1$user->posts() + + 2 ->where('active', 1) + + 3 ->orWhere('votes', '>=', 100) + + 4 ->get(); + + + $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: + + + + 1select * + + 2from posts + + 3where user_id = ? and active = 1 or votes >= 100 + + + select * + from posts + where user_id = ? and active = 1 or votes >= 100 + +In most situations, you should use [logical +groups](/docs/12.x/queries#logical-grouping) to group the conditional checks +between parentheses: + + + + 1use Illuminate\Database\Eloquent\Builder; + + 2  + + 3$user->posts() + + 4 ->where(function (Builder $query) { + + 5 return $query->where('active', 1) + + 6 ->orWhere('votes', '>=', 100); + + 7 }) + + 8 ->get(); + + + 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: + + + + 1select * + + 2from posts + + 3where user_id = ? and (active = 1 or votes >= 100) + + + select * + from posts + where user_id = ? and (active = 1 or votes >= 100) + +### Relationship Methods vs. Dynamic Properties + +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: + + + + 1use App\Models\User; + + 2  + + 3$user = User::find(1); + + 4  + + 5foreach ($user->posts as $post) { + + 6 // ... + + 7} + + + use App\Models\User; + + $user = User::find(1); + + 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 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. + +### Querying Relationship Existence + +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: + + + + 1use App\Models\Post; + + 2  + + 3// Retrieve all posts that have at least one comment... + + 4$posts = Post::has('comments')->get(); + + + use App\Models\Post; + + // 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: + + + + 1// Retrieve all posts that have three or more comments... + + 2$posts = Post::has('comments', '>=', 3)->get(); + + + // 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: + + + + 1// Retrieve posts that have at least one comment with images... + + 2$posts = Post::has('comments.images')->get(); + + + // 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: + + + + 1use Illuminate\Database\Eloquent\Builder; + + 2  + + 3// Retrieve posts with at least one comment containing words like code%... + + 4$posts = Post::whereHas('comments', function (Builder $query) { + + 5 $query->where('content', 'like', 'code%'); + + 6})->get(); + + 7  + + 8// Retrieve posts with at least ten comments containing words like code%... + + 9$posts = Post::whereHas('comments', function (Builder $query) { + + 10 $query->where('content', 'like', 'code%'); + + 11}, '>=', 10)->get(); + + + 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 ten comments containing words like code%... + $posts = Post::whereHas('comments', function (Builder $query) { + $query->where('content', 'like', 'code%'); + }, '>=', 10)->get(); + +Eloquent does not currently support querying for relationship existence across +databases. The relationships must exist within the same database. + +#### Many to Many Relationship Existence Queries + +The `whereAttachedTo` method may be used to query for models that have a many +to many attachment to a model or collection of models: + + + + 1$users = User::whereAttachedTo($role)->get(); + + + $users = User::whereAttachedTo($role)->get(); + +You may also provide a [collection](/docs/12.x/eloquent-collections) instance +to the `whereAttachedTo` method. When doing so, Laravel will retrieve models +that are attached to any of the models within the collection: + + + + 1$tags = Tag::whereLike('name', '%laravel%')->get(); + + 2  + + 3$posts = Post::whereAttachedTo($tags)->get(); + + + $tags = Tag::whereLike('name', '%laravel%')->get(); + + $posts = Post::whereAttachedTo($tags)->get(); + +#### Inline Relationship Existence Queries + +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: + + + + 1use App\Models\Post; + + 2  + + 3$posts = Post::whereRelation('comments', 'is_approved', false)->get(); + + + use App\Models\Post; + + $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: + + + + 1$posts = Post::whereRelation( + + 2 'comments', 'created_at', '>=', now()->subHour() + + 3)->get(); + + + $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: + + + + 1use App\Models\Post; + + 2  + + 3$posts = Post::doesntHave('comments')->get(); + + + use App\Models\Post; + + $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: + + + + 1use Illuminate\Database\Eloquent\Builder; + + 2  + + 3$posts = Post::whereDoesntHave('comments', function (Builder $query) { + + 4 $query->where('content', 'like', 'code%'); + + 5})->get(); + + + use Illuminate\Database\Eloquent\Builder; + + $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 as well as posts that have comments where none of the comments are +from banned users: + + + + 1use Illuminate\Database\Eloquent\Builder; + + 2  + + 3$posts = Post::whereDoesntHave('comments.author', function (Builder $query) { + + 4 $query->where('banned', 1); + + 5})->get(); + + + use Illuminate\Database\Eloquent\Builder; + + $posts = Post::whereDoesntHave('comments.author', function (Builder $query) { + $query->where('banned', 1); + })->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: + + + + 1use App\Models\Comment; + + 2use App\Models\Post; + + 3use App\Models\Video; + + 4use Illuminate\Database\Eloquent\Builder; + + 5  + + 6// Retrieve comments associated to posts or videos with a title like code%... + + 7$comments = Comment::whereHasMorph( + + 8 'commentable', + + 9 [Post::class, Video::class], + + 10 function (Builder $query) { + + 11 $query->where('title', 'like', 'code%'); + + 12 } + + 13)->get(); + + 14  + + 15// Retrieve comments associated to posts with a title not like code%... + + 16$comments = Comment::whereDoesntHaveMorph( + + 17 'commentable', + + 18 Post::class, + + 19 function (Builder $query) { + + 20 $query->where('title', 'like', 'code%'); + + 21 } + + 22)->get(); + + + 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: + + + + 1use Illuminate\Database\Eloquent\Builder; + + 2  + + 3$comments = Comment::whereHasMorph( + + 4 'commentable', + + 5 [Post::class, Video::class], + + 6 function (Builder $query, string $type) { + + 7 $column = $type === Post::class ? 'content' : 'title'; + + 8  + + 9 $query->where($column, 'like', 'code%'); + + 10 } + + 11)->get(); + + + use Illuminate\Database\Eloquent\Builder; + + $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(); + +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: + + + + 1$comments = Comment::whereMorphedTo('commentable', $post) + + 2 ->orWhereMorphedTo('commentable', $video) + + 3 ->get(); + + + $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: + + + + 1use Illuminate\Database\Eloquent\Builder; + + 2  + + 3$comments = Comment::whereHasMorph('commentable', '*', function (Builder $query) { + + 4 $query->where('title', 'like', 'foo%'); + + 5})->get(); + + + use Illuminate\Database\Eloquent\Builder; + + $comments = Comment::whereHasMorph('commentable', '*', function (Builder $query) { + $query->where('title', 'like', 'foo%'); + })->get(); + +## Aggregating Related Models + +### Counting Related Models + +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: + + + + 1use App\Models\Post; + + 2  + + 3$posts = Post::withCount('comments')->get(); + + 4  + + 5foreach ($posts as $post) { + + 6 echo $post->comments_count; + + 7} + + + use App\Models\Post; + + $posts = Post::withCount('comments')->get(); + + 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: + + + + 1use Illuminate\Database\Eloquent\Builder; + + 2  + + 3$posts = Post::withCount(['votes', 'comments' => function (Builder $query) { + + 4 $query->where('content', 'like', 'code%'); + + 5}])->get(); + + 6  + + 7echo $posts[0]->votes_count; + + 8echo $posts[0]->comments_count; + + + use Illuminate\Database\Eloquent\Builder; + + $posts = Post::withCount(['votes', 'comments' => function (Builder $query) { + $query->where('content', 'like', 'code%'); + }])->get(); + + 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: + + + + 1use Illuminate\Database\Eloquent\Builder; + + 2  + + 3$posts = Post::withCount([ + + 4 'comments', + + 5 'comments as pending_comments_count' => function (Builder $query) { + + 6 $query->where('approved', false); + + 7 }, + + 8])->get(); + + 9  + + 10echo $posts[0]->comments_count; + + 11echo $posts[0]->pending_comments_count; + + + use Illuminate\Database\Eloquent\Builder; + + $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; + +#### Deferred Count Loading + +Using the `loadCount` method, you may load a relationship count after the +parent model has already been retrieved: + + + + 1$book = Book::first(); + + 2  + + 3$book->loadCount('genres'); + + + $book = Book::first(); + + $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: + + + + 1$book->loadCount(['reviews' => function (Builder $query) { + + 2 $query->where('rating', 5); + + 3}]) + + + $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: + + + + 1$posts = Post::select(['title', 'body']) + + 2 ->withCount('comments') + + 3 ->get(); + + + $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: + + + + 1use App\Models\Post; + + 2  + + 3$posts = Post::withSum('comments', 'votes')->get(); + + 4  + + 5foreach ($posts as $post) { + + 6 echo $post->comments_sum_votes; + + 7} + + + use App\Models\Post; + + $posts = Post::withSum('comments', 'votes')->get(); + + 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: + + + + 1$posts = Post::withSum('comments as total_comments', 'votes')->get(); + + 2  + + 3foreach ($posts as $post) { + + 4 echo $post->total_comments; + + 5} + + + $posts = Post::withSum('comments as total_comments', 'votes')->get(); + + 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: + + + + 1$post = Post::first(); + + 2  + + 3$post->loadSum('comments', 'votes'); + + + $post = Post::first(); + + $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: + + + + 1$posts = Post::select(['title', 'body']) + + 2 ->withExists('comments') + + 3 ->get(); + + + $posts = Post::select(['title', 'body']) + ->withExists('comments') + ->get(); + +### Counting Related Models on Morph To Relationships + +If you would like to eager load a "morph to" relationship, as well as related +model counts for the various entities that may be returned by that +relationship, you may utilize the `with` method in combination with the +`morphTo` relationship's `morphWithCount` method. + +In this example, let's assume that `Photo` and `Post` models may create +`ActivityFeed` models. We will assume the `ActivityFeed` model defines a +"morph to" relationship named `parentable` that allows us to retrieve the +parent `Photo` or `Post` model for a given `ActivityFeed` instance. +Additionally, let's assume that `Photo` models "have many" `Tag` models and +`Post` models "have many" `Comment` models. + +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: + + + + 1use Illuminate\Database\Eloquent\Relations\MorphTo; + + 2  + + 3$activities = ActivityFeed::with([ + + 4 'parentable' => function (MorphTo $morphTo) { + + 5 $morphTo->morphWithCount([ + + 6 Photo::class => ['tags'], + + 7 Post::class => ['comments'], + + 8 ]); + + 9 }])->get(); + + + use Illuminate\Database\Eloquent\Relations\MorphTo; + + $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: + + + + 1$activities = ActivityFeed::with('parentable')->get(); + + 2  + + 3$activities->loadMorphCount('parentable', [ + + 4 Photo::class => ['tags'], + + 5 Post::class => ['comments'], + + 6]); + + + $activities = ActivityFeed::with('parentable')->get(); + + $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: + + + + 1belongsTo(Author::class); + + 16 } + + 17} + + + belongsTo(Author::class); + } + } + +Now, let's retrieve all books and their authors: + + + + 1use App\Models\Book; + + 2  + + 3$books = Book::all(); + + 4  + + 5foreach ($books as $book) { + + 6 echo $book->author->name; + + 7} + + + use App\Models\Book; + + $books = Book::all(); + + 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: + + + + 1$books = Book::with('author')->get(); + + 2  + + 3foreach ($books as $book) { + + 4 echo $book->author->name; + + 5} + + + $books = Book::with('author')->get(); + + 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: + + + + 1select * from books + + 2  + + 3select * from authors where id in (1, 2, 3, 4, 5, ...) + + + select * from books + + select * from authors where id in (1, 2, 3, 4, 5, ...) + +#### Eager Loading Multiple Relationships + +Sometimes you may need to eager load several different relationships. To do +so, just pass an array of relationships to the `with` method: + + + + 1$books = Book::with(['author', 'publisher'])->get(); + + + $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: + + + + 1$books = Book::with('author.contacts')->get(); + + + $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: + + + + 1$books = Book::with([ + + 2 'author' => [ + + 3 'contacts', + + 4 'publisher', + + 5 ], + + 6])->get(); + + + $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: + + + + 1morphTo(); + + 14 } + + 15} + + + 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: + + + + 1use Illuminate\Database\Eloquent\Relations\MorphTo; + + 2  + + 3$activities = ActivityFeed::query() + + 4 ->with(['parentable' => function (MorphTo $morphTo) { + + 5 $morphTo->morphWith([ + + 6 Event::class => ['calendar'], + + 7 Photo::class => ['tags'], + + 8 Post::class => ['author'], + + 9 ]); + + 10 }])->get(); + + + 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(); + +#### 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: + + + + 1$books = Book::with('author:id,name,book_id')->get(); + + + $books = Book::with('author:id,name,book_id')->get(); + +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. + +#### Eager Loading by Default + +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: + + + + 1belongsTo(Author::class); + + 23 } + + 24  + + 25 /** + + 26 * Get the genre of the book. + + 27 */ + + 28 public function genre(): BelongsTo + + 29 { + + 30 return $this->belongsTo(Genre::class); + + 31 } + + 32} + + + belongsTo(Author::class); + } + + /** + * Get the genre of the book. + */ + public function genre(): BelongsTo + { + 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: + + + + 1$books = Book::without('author')->get(); + + + $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: + + + + 1$books = Book::withOnly('genre')->get(); + + + $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: + + + + 1use App\Models\User; + + 2use Illuminate\Database\Eloquent\Builder; + + 3  + + 4$users = User::with(['posts' => function (Builder $query) { + + 5 $query->where('title', 'like', '%code%'); + + 6}])->get(); + + + use App\Models\User; + use Illuminate\Database\Eloquent\Builder; + + $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/12.x/queries) methods to further customize the eager loading +operation: + + + + 1$users = User::with(['posts' => function (Builder $query) { + + 2 $query->orderBy('created_at', 'desc'); + + 3}])->get(); + + + $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: + + + + 1use Illuminate\Database\Eloquent\Relations\MorphTo; + + 2  + + 3$comments = Comment::with(['commentable' => function (MorphTo $morphTo) { + + 4 $morphTo->constrain([ + + 5 Post::class => function ($query) { + + 6 $query->whereNull('hidden_at'); + + 7 }, + + 8 Video::class => function ($query) { + + 9 $query->where('type', 'educational'); + + 10 }, + + 11 ]); + + 12}])->get(); + + + 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(); + +In this example, Eloquent will only eager load posts that have not been hidden +and videos that have a `type` value of "educational". + +#### Constraining Eager Loads With Relationship Existence + +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: + + + + 1use App\Models\User; + + 2  + + 3$users = User::withWhereHas('posts', function ($query) { + + 4 $query->where('featured', true); + + 5})->get(); + + + use App\Models\User; + + $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: + + + + 1use App\Models\Book; + + 2  + + 3$books = Book::all(); + + 4  + + 5if ($condition) { + + 6 $books->load('author', 'publisher'); + + 7} + + + use App\Models\Book; + + $books = Book::all(); + + if ($condition) { + $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: + + + + 1$author->load(['books' => function (Builder $query) { + + 2 $query->orderBy('published_date', 'asc'); + + 3}]); + + + $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: + + + + 1$book->loadMissing('author'); + + + $book->loadMissing('author'); + +#### Nested Lazy Eager Loading and `morphTo` + +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 `loadMorph` method. + +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: + + + + 1morphTo(); + + 14 } + + 15} + + + 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: + + + + 1$activities = ActivityFeed::with('parentable') + + 2 ->get() + + 3 ->loadMorph('parentable', [ + + 4 Event::class => ['calendar'], + + 5 Photo::class => ['tags'], + + 6 Post::class => ['author'], + + 7 ]); + + + $activities = ActivityFeed::with('parentable') + ->get() + ->loadMorph('parentable', [ + Event::class => ['calendar'], + Photo::class => ['tags'], + Post::class => ['author'], + ]); + +### Automatic Eager Loading + +This feature is currently in beta in order to gather community feedback. The +behavior and functionality of this feature may change even on patch releases. + +In many cases, Laravel can automatically eager load the relationships you +access. To enable automatic eager loading, you should invoke the +`Model::automaticallyEagerLoadRelationships` method within the `boot` method +of your application's `AppServiceProvider`: + + + + 1use Illuminate\Database\Eloquent\Model; + + 2  + + 3/** + + 4 * Bootstrap any application services. + + 5 */ + + 6public function boot(): void + + 7{ + + 8 Model::automaticallyEagerLoadRelationships(); + + 9} + + + use Illuminate\Database\Eloquent\Model; + + /** + * Bootstrap any application services. + */ + public function boot(): void + { + Model::automaticallyEagerLoadRelationships(); + } + +When this feature is enabled, Laravel will attempt to automatically load any +relationships you access that have not been previously loaded. For example, +consider the following scenario: + + + + 1use App\Models\User; + + 2  + + 3$users = User::all(); + + 4  + + 5foreach ($users as $user) { + + 6 foreach ($user->posts as $post) { + + 7 foreach ($post->comments as $comment) { + + 8 echo $comment->content; + + 9 } + + 10 } + + 11} + + + use App\Models\User; + + $users = User::all(); + + foreach ($users as $user) { + foreach ($user->posts as $post) { + foreach ($post->comments as $comment) { + echo $comment->content; + } + } + } + +Typically, the code above would execute a query for each user in order to +retrieve their posts, as well as a query for each post to retrieve its +comments. However, when the `automaticallyEagerLoadRelationships` feature has +been enabled, Laravel will automatically lazy eager load the posts for all +users in the user collection when you attempt to access the posts on any of +the retrieved users. Likewise, when you attempt to access the comments for any +retrieved post, all comments will be lazy eager loaded for all posts that were +originally retrieved. + +If you do not want to globally enable automatic eager loading, you can still +enable this feature for a single Eloquent collection instance by invoking the +`withRelationshipAutoloading` method on the collection: + + + + 1$users = User::where('vip', true)->get(); + + 2  + + 3return $users->withRelationshipAutoloading(); + + + $users = User::where('vip', true)->get(); + + return $users->withRelationshipAutoloading(); + +### Preventing Lazy Loading + +As previously discussed, eager loading relationships can often provide +significant performance benefits to your application. Therefore, if you would +like, you may instruct Laravel to always prevent the lazy loading of +relationships. To accomplish this, you may invoke the `preventLazyLoading` +method offered by the base Eloquent model class. Typically, you should call +this method within the `boot` method of your application's +`AppServiceProvider` class. + +The `preventLazyLoading` method accepts an optional boolean argument that +indicates if lazy loading should be prevented. For example, you may wish to +only disable lazy loading in non-production environments so that your +production environment will continue to function normally even if a lazy +loaded relationship is accidentally present in production code: + + + + 1use Illuminate\Database\Eloquent\Model; + + 2  + + 3/** + + 4 * Bootstrap any application services. + + 5 */ + + 6public function boot(): void + + 7{ + + 8 Model::preventLazyLoading(! $this->app->isProduction()); + + 9} + + + use Illuminate\Database\Eloquent\Model; + + /** + * Bootstrap any application services. + */ + public function boot(): void + { + Model::preventLazyLoading(! $this->app->isProduction()); + } + +After preventing lazy loading, Eloquent will throw a +`Illuminate\Database\LazyLoadingViolationException` exception when your +application attempts to lazy load any Eloquent relationship. + +You may customize the behavior of lazy loading violations using the +`handleLazyLoadingViolationsUsing` method. For example, using this method, you +may instruct lazy loading violations to only be logged instead of interrupting +the application's execution with exceptions: + + + + 1Model::handleLazyLoadingViolationUsing(function (Model $model, string $relation) { + + 2 $class = $model::class; + + 3  + + 4 info("Attempted to lazy load [{$relation}] on model [{$class}]."); + + 5}); + + + Model::handleLazyLoadingViolationUsing(function (Model $model, string $relation) { + $class = $model::class; + + info("Attempted to lazy load [{$relation}] on model [{$class}]."); + }); + +## Inserting and Updating Related Models + +### The `save` Method + +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: + + + + 1use App\Models\Comment; + + 2use App\Models\Post; + + 3  + + 4$comment = new Comment(['message' => 'A new comment.']); + + 5  + + 6$post = Post::find(1); + + 7  + + 8$post->comments()->save($comment); + + + use App\Models\Comment; + use App\Models\Post; + + $comment = new Comment(['message' => 'A new comment.']); + + $post = Post::find(1); + + $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: + + + + 1$post = Post::find(1); + + 2  + + 3$post->comments()->saveMany([ + + 4 new Comment(['message' => 'A new comment.']), + + 5 new Comment(['message' => 'Another new comment.']), + + 6]); + + + $post = Post::find(1); + + $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: + + + + 1$post->comments()->save($comment); + + 2  + + 3$post->refresh(); + + 4  + + 5// All comments, including the newly saved comment... + + 6$post->comments; + + + $post->comments()->save($comment); + + $post->refresh(); + + // 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: + + + + 1$post = Post::find(1); + + 2  + + 3$post->comments[0]->message = 'Message'; + + 4$post->comments[0]->author->name = 'Author Name'; + + 5  + + 6$post->push(); + + + $post = Post::find(1); + + $post->comments[0]->message = 'Message'; + $post->comments[0]->author->name = 'Author Name'; + + $post->push(); + +The `pushQuietly` method may be used to save a model and its associated +relationships without raising any events: + + + + 1$post->pushQuietly(); + + + $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: + + + + 1use App\Models\Post; + + 2  + + 3$post = Post::find(1); + + 4  + + 5$comment = $post->comments()->create([ + + 6 'message' => 'A new comment.', + + 7]); + + + use App\Models\Post; + + $post = Post::find(1); + + $comment = $post->comments()->create([ + 'message' => 'A new comment.', + ]); + +You may use the `createMany` method to create multiple related models: + + + + 1$post = Post::find(1); + + 2  + + 3$post->comments()->createMany([ + + 4 ['message' => 'A new comment.'], + + 5 ['message' => 'Another new comment.'], + + 6]); + + + $post = Post::find(1); + + $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: + + + + 1$user = User::find(1); + + 2  + + 3$user->posts()->createQuietly([ + + 4 'title' => 'Post title.', + + 5]); + + 6  + + 7$user->posts()->createManyQuietly([ + + 8 ['title' => 'First post.'], + + 9 ['title' => 'Second post.'], + + 10]); + + + $user = User::find(1); + + $user->posts()->createQuietly([ + 'title' => 'Post title.', + ]); + + $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/12.x/eloquent#upserts). + +Before using the `create` method, be sure to review the [mass +assignment](/docs/12.x/eloquent#mass-assignment) documentation. + +### Belongs To Relationships + +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: + + + + 1use App\Models\Account; + + 2  + + 3$account = Account::find(10); + + 4  + + 5$user->account()->associate($account); + + 6  + + 7$user->save(); + + + use App\Models\Account; + + $account = Account::find(10); + + $user->account()->associate($account); + + $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`: + + + + 1$user->account()->dissociate(); + + 2  + + 3$user->save(); + + + $user->account()->dissociate(); + + $user->save(); + +### Many to Many Relationships + +#### Attaching / Detaching + +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: + + + + 1use App\Models\User; + + 2  + + 3$user = User::find(1); + + 4  + + 5$user->roles()->attach($roleId); + + + use App\Models\User; + + $user = User::find(1); + + $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: + + + + 1$user->roles()->attach($roleId, ['expires' => $expires]); + + + $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: + + + + 1// Detach a single role from the user... + + 2$user->roles()->detach($roleId); + + 3  + + 4// Detach all roles from the user... + + 5$user->roles()->detach(); + + + // Detach a single role from the user... + $user->roles()->detach($roleId); + + // Detach all roles from the user... + $user->roles()->detach(); + +For convenience, `attach` and `detach` also accept arrays of IDs as input: + + + + 1$user = User::find(1); + + 2  + + 3$user->roles()->detach([1, 2, 3]); + + 4  + + 5$user->roles()->attach([ + + 6 1 => ['expires' => $expires], + + 7 2 => ['expires' => $expires], + + 8]); + + + $user = User::find(1); + + $user->roles()->detach([1, 2, 3]); + + $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: + + + + 1$user->roles()->sync([1, 2, 3]); + + + $user->roles()->sync([1, 2, 3]); + +You may also pass additional intermediate table values with the IDs: + + + + 1$user->roles()->sync([1 => ['expires' => true], 2, 3]); + + + $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: + + + + 1$user->roles()->syncWithPivotValues([1, 2, 3], ['active' => true]); + + + $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: + + + + 1$user->roles()->syncWithoutDetaching([1, 2, 3]); + + + $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: + + + + 1$user->roles()->toggle([1, 2, 3]); + + + $user->roles()->toggle([1, 2, 3]); + +You may also pass additional intermediate table values with the IDs: + + + + 1$user->roles()->toggle([ + + 2 1 => ['expires' => true], + + 3 2 => ['expires' => true], + + 4]); + + + $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: + + + + 1$user = User::find(1); + + 2  + + 3$user->roles()->updateExistingPivot($roleId, [ + + 4 'active' => false, + + 5]); + + + $user = User::find(1); + + $user->roles()->updateExistingPivot($roleId, [ + 'active' => false, + ]); + +## Touching Parent Timestamps + +When a model defines a `belongsTo` or `belongsToMany` relationship to another +model, such as a `Comment` which belongs to a `Post`, it is sometimes helpful +to update the parent's timestamp when the child model is updated. + +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: + + + + 1belongsTo(Post::class); + + 23 } + + 24} + + + belongsTo(Post::class); + } + } + +Parent model timestamps will only be updated if the child model is updated +using Eloquent's `save` method. + diff --git a/output/12.x/eloquent-resources.md b/output/12.x/eloquent-resources.md new file mode 100644 index 0000000..258ca30 --- /dev/null +++ b/output/12.x/eloquent-resources.md @@ -0,0 +1,2183 @@ +# Eloquent: API Resources + + * Introduction + * Generating Resources + * Concept Overview + * Resource Collections + * Writing Resources + * Data Wrapping + * Pagination + * Conditional Attributes + * Conditional Relationships + * Adding Meta Data + * Resource Responses + +## Introduction + +When building an API, you may need a transformation layer that sits between +your Eloquent models and the JSON responses that are actually returned to your +application's users. For example, you may wish to display certain attributes +for a subset of users and not others, or you may wish to always include +certain relationships in the JSON representation of your models. Eloquent's +resource classes allow you to expressively and easily transform your models +and model collections into JSON. + +Of course, you may always convert Eloquent models or collections to JSON using +their `toJson` methods; however, Eloquent resources provide more granular and +robust control over the JSON serialization of your models and their +relationships. + +## Generating Resources + +To generate a resource class, you may use the `make:resource` Artisan command. +By default, resources will be placed in the `app/Http/Resources` directory of +your application. Resources extend the +`Illuminate\Http\Resources\Json\JsonResource` class: + + + + 1php artisan make:resource UserResource + + + php artisan make:resource UserResource + +#### Resource Collections + +In addition to generating resources that transform individual models, you may +generate resources that are responsible for transforming collections of +models. This allows your JSON responses to include links and other meta +information that is relevant to an entire collection of a given resource. + +To create a resource collection, you should use the `--collection` flag when +creating the resource. Or, including the word `Collection` in the resource +name will indicate to Laravel that it should create a collection resource. +Collection resources extend the +`Illuminate\Http\Resources\Json\ResourceCollection` class: + + + + 1php artisan make:resource User --collection + + 2  + + 3php artisan make:resource UserCollection + + + php artisan make:resource User --collection + + php artisan make:resource UserCollection + +## Concept Overview + +This is a high-level overview of resources and resource collections. You are +highly encouraged to read the other sections of this documentation to gain a +deeper understanding of the customization and power offered to you by +resources. + +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: + + + + 1 + + 14 */ + + 15 public function toArray(Request $request): array + + 16 { + + 17 return [ + + 18 'id' => $this->id, + + 19 'name' => $this->name, + + 20 'email' => $this->email, + + 21 'created_at' => $this->created_at, + + 22 'updated_at' => $this->updated_at, + + 23 ]; + + 24 } + + 25} + + + + */ + 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, + ]; + } + } + +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: + + + + 1use App\Http\Resources\UserResource; + + 2use App\Models\User; + + 3  + + 4Route::get('/user/{id}', function (string $id) { + + 5 return new UserResource(User::findOrFail($id)); + + 6}); + + + use App\Http\Resources\UserResource; + use App\Models\User; + + Route::get('/user/{id}', function (string $id) { + return new UserResource(User::findOrFail($id)); + }); + +For convenience, you may use the model's `toResource` method, which will use +framework conventions to automatically discover the model's underlying +resource: + + + + 1return User::findOrFail($id)->toResource(); + + + return User::findOrFail($id)->toResource(); + +When invoking the `toResource` method, Laravel will attempt to locate a +resource that matches the model's name and is optionally suffixed with +`Resource` within the `Http\Resources` namespace closest to the model's +namespace. + +### 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: + + + + 1use App\Http\Resources\UserResource; + + 2use App\Models\User; + + 3  + + 4Route::get('/users', function () { + + 5 return UserResource::collection(User::all()); + + 6}); + + + use App\Http\Resources\UserResource; + use App\Models\User; + + Route::get('/users', function () { + return UserResource::collection(User::all()); + }); + +Or, for convenience, you may use the Eloquent collection's +`toResourceCollection` method, which will use framework conventions to +automatically discover the model's underlying resource collection: + + + + 1return User::all()->toResourceCollection(); + + + return User::all()->toResourceCollection(); + +When invoking the `toResourceCollection` method, Laravel will attempt to +locate a resource collection that matches the model's name and is suffixed +with `Collection` within the `Http\Resources` namespace closest to the model's +namespace. + +#### Custom Resource Collections + +By default, resource collections do 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: + + + + 1php artisan make:resource UserCollection + + + 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: + + + + 1 + + 14 */ + + 15 public function toArray(Request $request): array + + 16 { + + 17 return [ + + 18 'data' => $this->collection, + + 19 'links' => [ + + 20 'self' => 'link-value', + + 21 ], + + 22 ]; + + 23 } + + 24} + + + + */ + public function toArray(Request $request): array + { + return [ + 'data' => $this->collection, + 'links' => [ + 'self' => 'link-value', + ], + ]; + } + } + +After defining your resource collection, it may be returned from a route or +controller: + + + + 1use App\Http\Resources\UserCollection; + + 2use App\Models\User; + + 3  + + 4Route::get('/users', function () { + + 5 return new UserCollection(User::all()); + + 6}); + + + use App\Http\Resources\UserCollection; + use App\Models\User; + + Route::get('/users', function () { + return new UserCollection(User::all()); + }); + +Or, for convenience, you may use the Eloquent collection's +`toResourceCollection` method, which will use framework conventions to +automatically discover the model's underlying resource collection: + + + + 1return User::all()->toResourceCollection(); + + + return User::all()->toResourceCollection(); + +When invoking the `toResourceCollection` method, Laravel will attempt to +locate a resource collection that matches the model's name and is suffixed +with `Collection` within the `Http\Resources` namespace closest to the model's +namespace. + +#### 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: + + + + 1keyBy->id); + + 6}); + + + use App\Http\Resources\UserResource; + use App\Models\User; + + Route::get('/users', function () { + return UserResource::collection(User::all()->keyBy->id); + }); + +#### Customizing the Underlying Resource Class + +Typically, the `$this->collection` property of a resource collection is +automatically populated with the result of mapping each item of the collection +to its singular resource class. The singular resource class is assumed to be +the collection's class name without the trailing `Collection` portion of the +class name. In addition, depending on your personal preference, the singular +resource class may or may not be suffixed with `Resource`. + +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: + + + + 1 + + 14 */ + + 15 public function toArray(Request $request): array + + 16 { + + 17 return [ + + 18 'id' => $this->id, + + 19 'name' => $this->name, + + 20 'email' => $this->email, + + 21 'created_at' => $this->created_at, + + 22 'updated_at' => $this->updated_at, + + 23 ]; + + 24 } + + 25} + + + + */ + 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: + + + + 1use App\Models\User; + + 2  + + 3Route::get('/user/{id}', function (string $id) { + + 4 return User::findOrFail($id)->toUserResource(); + + 5}); + + + use App\Models\User; + + Route::get('/user/{id}', function (string $id) { + return User::findOrFail($id)->toUserResource(); + }); + +#### 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: + + + + 1use App\Http\Resources\PostResource; + + 2use Illuminate\Http\Request; + + 3  + + 4/** + + 5 * Transform the resource into an array. + + 6 * + + 7 * @return array + + 8 */ + + 9public function toArray(Request $request): array + + 10{ + + 11 return [ + + 12 'id' => $this->id, + + 13 'name' => $this->name, + + 14 'email' => $this->email, + + 15 'posts' => PostResource::collection($this->posts), + + 16 'created_at' => $this->created_at, + + 17 'updated_at' => $this->updated_at, + + 18 ]; + + 19} + + + 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, + ]; + } + +If you would like to include relationships only when they have already been +loaded, check out the documentation on conditional relationships. + +#### Resource Collections + +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 Eloquent model collections provide a `toResourceCollection` method +to generate an "ad-hoc" resource collection on the fly: + + + + 1use App\Models\User; + + 2  + + 3Route::get('/users', function () { + + 4 return User::all()->toResourceCollection(); + + 5}); + + + use App\Models\User; + + Route::get('/users', function () { + return User::all()->toResourceCollection(); + }); + +However, if you need to customize the meta data returned with the collection, +it is necessary to define your own resource collection: + + + + 1 + + 14 */ + + 15 public function toArray(Request $request): array + + 16 { + + 17 return [ + + 18 'data' => $this->collection, + + 19 'links' => [ + + 20 'self' => 'link-value', + + 21 ], + + 22 ]; + + 23 } + + 24} + + + + */ + public function toArray(Request $request): array + { + return [ + 'data' => $this->collection, + 'links' => [ + 'self' => 'link-value', + ], + ]; + } + } + +Like singular resources, resource collections may be returned directly from +routes or controllers: + + + + 1use App\Http\Resources\UserCollection; + + 2use App\Models\User; + + 3  + + 4Route::get('/users', function () { + + 5 return new UserCollection(User::all()); + + 6}); + + + use App\Http\Resources\UserCollection; + use App\Models\User; + + Route::get('/users', function () { + return new UserCollection(User::all()); + }); + +Or, for convenience, you may use the Eloquent collection's +`toResourceCollection` method, which will use framework conventions to +automatically discover the model's underlying resource collection: + + + + 1return User::all()->toResourceCollection(); + + + return User::all()->toResourceCollection(); + +When invoking the `toResourceCollection` method, Laravel will attempt to +locate a resource collection that matches the model's name and is suffixed +with `Collection` within the `Http\Resources` namespace closest to the model's +namespace. + +### Data Wrapping + +By default, your outermost resource is wrapped in a `data` key when the +resource response is converted to JSON. So, for example, a typical resource +collection response looks like the following: + + + + 1{ + + 2 "data": [ + + 3 { + + 4 "id": 1, + + 5 "name": "Eladio Schroeder Sr.", + + 6 "email": "[[email protected]](/cdn-cgi/l/email-protection)" + + 7 }, + + 8 { + + 9 "id": 2, + + 10 "name": "Liliana Mayert", + + 11 "email": "[[email protected]](/cdn-cgi/l/email-protection)" + + 12 } + + 13 ] + + 14} + + + { + "data": [ + { + "id": 1, + "name": "Eladio Schroeder Sr.", + "email": "[[email protected]](/cdn-cgi/l/email-protection)" + }, + { + "id": 2, + "name": "Liliana Mayert", + "email": "[[email protected]](/cdn-cgi/l/email-protection)" + } + ] + } + +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/12.x/providers) that is loaded on every request to your +application: + + + + 1 + + 13 */ + + 14 public function toArray(Request $request): array + + 15 { + + 16 return ['data' => $this->collection]; + + 17 } + + 18} + + + + */ + public function toArray(Request $request): array + { + return ['data' => $this->collection]; + } + } + +#### Data Wrapping and Pagination + +When returning paginated collections via a resource response, Laravel will +wrap your resource data in a `data` key even if the `withoutWrapping` method +has been called. This is because paginated responses always contain `meta` and +`links` keys with information about the paginator's state: + + + + 1{ + + 2 "data": [ + + 3 { + + 4 "id": 1, + + 5 "name": "Eladio Schroeder Sr.", + + 6 "email": "[[email protected]](/cdn-cgi/l/email-protection)" + + 7 }, + + 8 { + + 9 "id": 2, + + 10 "name": "Liliana Mayert", + + 11 "email": "[[email protected]](/cdn-cgi/l/email-protection)" + + 12 } + + 13 ], + + 14 "links":{ + + 15 "first": "http://example.com/users?page=1", + + 16 "last": "http://example.com/users?page=1", + + 17 "prev": null, + + 18 "next": null + + 19 }, + + 20 "meta":{ + + 21 "current_page": 1, + + 22 "from": 1, + + 23 "last_page": 1, + + 24 "path": "http://example.com/users", + + 25 "per_page": 15, + + 26 "to": 10, + + 27 "total": 10 + + 28 } + + 29} + + + { + "data": [ + { + "id": 1, + "name": "Eladio Schroeder Sr.", + "email": "[[email protected]](/cdn-cgi/l/email-protection)" + }, + { + "id": 2, + "name": "Liliana Mayert", + "email": "[[email protected]](/cdn-cgi/l/email-protection)" + } + ], + "links":{ + "first": "http://example.com/users?page=1", + "last": "http://example.com/users?page=1", + "prev": null, + "next": null + }, + "meta":{ + "current_page": 1, + "from": 1, + "last_page": 1, + "path": "http://example.com/users", + "per_page": 15, + "to": 10, + "total": 10 + } + } + +### Pagination + +You may pass a Laravel paginator instance to the `collection` method of a +resource or to a custom resource collection: + + + + 1use App\Http\Resources\UserCollection; + + 2use App\Models\User; + + 3  + + 4Route::get('/users', function () { + + 5 return new UserCollection(User::paginate()); + + 6}); + + + use App\Http\Resources\UserCollection; + use App\Models\User; + + Route::get('/users', function () { + return new UserCollection(User::paginate()); + }); + +Or, for convenience, you may use the paginator's `toResourceCollection` +method, which will use framework conventions to automatically discover the +paginated model's underlying resource collection: + + + + 1return User::paginate()->toResourceCollection(); + + + return User::paginate()->toResourceCollection(); + +Paginated responses always contain `meta` and `links` keys with information +about the paginator's state: + + + + 1{ + + 2 "data": [ + + 3 { + + 4 "id": 1, + + 5 "name": "Eladio Schroeder Sr.", + + 6 "email": "[[email protected]](/cdn-cgi/l/email-protection)" + + 7 }, + + 8 { + + 9 "id": 2, + + 10 "name": "Liliana Mayert", + + 11 "email": "[[email protected]](/cdn-cgi/l/email-protection)" + + 12 } + + 13 ], + + 14 "links":{ + + 15 "first": "http://example.com/users?page=1", + + 16 "last": "http://example.com/users?page=1", + + 17 "prev": null, + + 18 "next": null + + 19 }, + + 20 "meta":{ + + 21 "current_page": 1, + + 22 "from": 1, + + 23 "last_page": 1, + + 24 "path": "http://example.com/users", + + 25 "per_page": 15, + + 26 "to": 10, + + 27 "total": 10 + + 28 } + + 29} + + + { + "data": [ + { + "id": 1, + "name": "Eladio Schroeder Sr.", + "email": "[[email protected]](/cdn-cgi/l/email-protection)" + }, + { + "id": 2, + "name": "Liliana Mayert", + "email": "[[email protected]](/cdn-cgi/l/email-protection)" + } + ], + "links":{ + "first": "http://example.com/users?page=1", + "last": "http://example.com/users?page=1", + "prev": null, + "next": null + }, + "meta":{ + "current_page": 1, + "from": 1, + "last_page": 1, + "path": "http://example.com/users", + "per_page": 15, + "to": 10, + "total": 10 + } + } + +#### Customizing the Pagination Information + +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: + + + + 1/** + + 2 * Customize the pagination information for the resource. + + 3 * + + 4 * @param \Illuminate\Http\Request $request + + 5 * @param array $paginated + + 6 * @param array $default + + 7 * @return array + + 8 */ + + 9public function paginationInformation($request, $paginated, $default) + + 10{ + + 11 $default['links']['custom'] = 'https://example.com'; + + 12  + + 13 return $default; + + 14} + + + /** + * 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; + } + +### 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: + + + + 1/** + + 2 * Transform the resource into an array. + + 3 * + + 4 * @return array + + 5 */ + + 6public function toArray(Request $request): array + + 7{ + + 8 return [ + + 9 'id' => $this->id, + + 10 'name' => $this->name, + + 11 'email' => $this->email, + + 12 'secret' => $this->when($request->user()->isAdmin(), 'secret-value'), + + 13 'created_at' => $this->created_at, + + 14 'updated_at' => $this->updated_at, + + 15 ]; + + 16} + + + /** + * 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`: + + + + 1'secret' => $this->when($request->user()->isAdmin(), function () { + + 2 return 'secret-value'; + + 3}), + + + '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: + + + + 1'name' => $this->whenHas('name'), + + + '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: + + + + 1'name' => $this->whenNotNull($this->name), + + + '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`: + + + + 1/** + + 2 * Transform the resource into an array. + + 3 * + + 4 * @return array + + 5 */ + + 6public function toArray(Request $request): array + + 7{ + + 8 return [ + + 9 'id' => $this->id, + + 10 'name' => $this->name, + + 11 'email' => $this->email, + + 12 $this->mergeWhen($request->user()->isAdmin(), [ + + 13 'first-secret' => 'value', + + 14 'second-secret' => 'value', + + 15 ]), + + 16 'created_at' => $this->created_at, + + 17 'updated_at' => $this->updated_at, + + 18 ]; + + 19} + + + /** + * 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. + +The `mergeWhen` method should not be used within arrays that mix string and +numeric keys. Furthermore, it should not be used within arrays with numeric +keys that are not ordered sequentially. + +### Conditional Relationships + +In addition to conditionally loading attributes, you may conditionally include +relationships on your resource responses based on if the relationship has +already been loaded on the model. This allows your controller to decide which +relationships should be loaded on the model and your resource can easily +include them only when they have actually been loaded. Ultimately, this makes +it easier to avoid "N+1" query problems within your resources. + +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: + + + + 1use App\Http\Resources\PostResource; + + 2  + + 3/** + + 4 * Transform the resource into an array. + + 5 * + + 6 * @return array + + 7 */ + + 8public function toArray(Request $request): array + + 9{ + + 10 return [ + + 11 'id' => $this->id, + + 12 'name' => $this->name, + + 13 'email' => $this->email, + + 14 'posts' => PostResource::collection($this->whenLoaded('posts')), + + 15 'created_at' => $this->created_at, + + 16 'updated_at' => $this->updated_at, + + 17 ]; + + 18} + + + 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. + +#### Conditional Relationship Counts + +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: + + + + 1new UserResource($user->loadCount('posts')); + + + 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: + + + + 1/** + + 2 * Transform the resource into an array. + + 3 * + + 4 * @return array + + 5 */ + + 6public function toArray(Request $request): array + + 7{ + + 8 return [ + + 9 'id' => $this->id, + + 10 'name' => $this->name, + + 11 'email' => $this->email, + + 12 'posts_count' => $this->whenCounted('posts'), + + 13 'created_at' => $this->created_at, + + 14 'updated_at' => $this->updated_at, + + 15 ]; + + 16} + + + /** + * 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. + +Other types of aggregates, such as `avg`, `sum`, `min`, and `max` may also be +conditionally loaded using the `whenAggregated` method: + + + + 1'words_avg' => $this->whenAggregated('posts', 'words', 'avg'), + + 2'words_sum' => $this->whenAggregated('posts', 'words', 'sum'), + + 3'words_min' => $this->whenAggregated('posts', 'words', 'min'), + + 4'words_max' => $this->whenAggregated('posts', 'words', 'max'), + + + 'words_avg' => $this->whenAggregated('posts', 'words', 'avg'), + 'words_sum' => $this->whenAggregated('posts', 'words', 'sum'), + 'words_min' => $this->whenAggregated('posts', 'words', 'min'), + 'words_max' => $this->whenAggregated('posts', 'words', 'max'), + +#### Conditional Pivot Information + +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: + + + + 1/** + + 2 * Transform the resource into an array. + + 3 * + + 4 * @return array + + 5 */ + + 6public function toArray(Request $request): array + + 7{ + + 8 return [ + + 9 'id' => $this->id, + + 10 'name' => $this->name, + + 11 'expires_at' => $this->whenPivotLoaded('role_user', function () { + + 12 return $this->pivot->expires_at; + + 13 }), + + 14 ]; + + 15} + + + /** + * 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/12.x/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: + + + + 1'expires_at' => $this->whenPivotLoaded(new Membership, function () { + + 2 return $this->pivot->expires_at; + + 3}), + + + '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: + + + + 1/** + + 2 * Transform the resource into an array. + + 3 * + + 4 * @return array + + 5 */ + + 6public function toArray(Request $request): array + + 7{ + + 8 return [ + + 9 'id' => $this->id, + + 10 'name' => $this->name, + + 11 'expires_at' => $this->whenPivotLoadedAs('subscription', 'role_user', function () { + + 12 return $this->subscription->expires_at; + + 13 }), + + 14 ]; + + 15} + + + /** + * 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: + + + + 1/** + + 2 * Transform the resource into an array. + + 3 * + + 4 * @return array + + 5 */ + + 6public function toArray(Request $request): array + + 7{ + + 8 return [ + + 9 'data' => $this->collection, + + 10 'links' => [ + + 11 'self' => 'link-value', + + 12 ], + + 13 ]; + + 14} + + + /** + * 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. + +#### Top Level Meta Data + +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: + + + + 1 + + 13 */ + + 14 public function toArray(Request $request): array + + 15 { + + 16 return parent::toArray($request); + + 17 } + + 18  + + 19 /** + + 20 * Get additional data that should be returned with the resource array. + + 21 * + + 22 * @return array + + 23 */ + + 24 public function with(Request $request): array + + 25 { + + 26 return [ + + 27 'meta' => [ + + 28 'key' => 'value', + + 29 ], + + 30 ]; + + 31 } + + 32} + + + + */ + public function toArray(Request $request): array + { + 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', + ], + ]; + } + } + +#### 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: + + + + 1return User::all() + + 2 ->load('roles') + + 3 ->toResourceCollection() + + 4 ->additional(['meta' => [ + + 5 'key' => 'value', + + 6 ]]); + + + return User::all() + ->load('roles') + ->toResourceCollection() + ->additional(['meta' => [ + 'key' => 'value', + ]]); + +## Resource Responses + +As you have already read, resources may be returned directly from routes and +controllers: + + + + 1use App\Models\User; + + 2  + + 3Route::get('/user/{id}', function (string $id) { + + 4 return User::findOrFail($id)->toResource(); + + 5}); + + + use App\Models\User; + + Route::get('/user/{id}', function (string $id) { + return User::findOrFail($id)->toResource(); + }); + +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: + + + + 1use App\Http\Resources\UserResource; + + 2use App\Models\User; + + 3  + + 4Route::get('/user', function () { + + 5 return User::find(1) + + 6 ->toResource() + + 7 ->response() + + 8 ->header('X-Value', 'True'); + + 9}); + + + use App\Http\Resources\UserResource; + use App\Models\User; + + Route::get('/user', function () { + return User::find(1) + ->toResource() + ->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: + + + + 1 + + 15 */ + + 16 public function toArray(Request $request): array + + 17 { + + 18 return [ + + 19 'id' => $this->id, + + 20 ]; + + 21 } + + 22  + + 23 /** + + 24 * Customize the outgoing response for the resource. + + 25 */ + + 26 public function withResponse(Request $request, JsonResponse $response): void + + 27 { + + 28 $response->header('X-Value', 'True'); + + 29 } + + 30} + + + + */ + public function toArray(Request $request): array + { + return [ + 'id' => $this->id, + ]; + } + + /** + * Customize the outgoing response for the resource. + */ + public function withResponse(Request $request, JsonResponse $response): void + { + $response->header('X-Value', 'True'); + } + } + diff --git a/output/12.x/eloquent-serialization.md b/output/12.x/eloquent-serialization.md new file mode 100644 index 0000000..1218492 --- /dev/null +++ b/output/12.x/eloquent-serialization.md @@ -0,0 +1,531 @@ +# Eloquent: Serialization + + * Introduction + * Serializing Models and Collections + * Serializing to Arrays + * Serializing to JSON + * Hiding Attributes From JSON + * Appending Values to JSON + * Date Serialization + +## Introduction + +When building APIs using Laravel, you will often need to convert your models +and relationships to arrays or JSON. Eloquent includes convenient methods for +making these conversions, as well as controlling which attributes are included +in the serialized representation of your models. + +For an even more robust way of handling Eloquent model and collection JSON +serialization, check out the documentation on [Eloquent API +resources](/docs/12.x/eloquent-resources). + +## Serializing Models and Collections + +### Serializing to Arrays + +To convert a model and its loaded [relationships](/docs/12.x/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: + + + + 1use App\Models\User; + + 2  + + 3$user = User::with('roles')->first(); + + 4  + + 5return $user->toArray(); + + + use App\Models\User; + + $user = User::with('roles')->first(); + + return $user->toArray(); + +The `attributesToArray` method may be used to convert a model's attributes to +an array but not its relationships: + + + + 1$user = User::first(); + + 2  + + 3return $user->attributesToArray(); + + + $user = User::first(); + + return $user->attributesToArray(); + +You may also convert entire [collections](/docs/12.x/eloquent-collections) of +models to arrays by calling the `toArray` method on the collection instance: + + + + 1$users = User::all(); + + 2  + + 3return $users->toArray(); + + + $users = User::all(); + + 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): + + + + 1use App\Models\User; + + 2  + + 3$user = User::find(1); + + 4  + + 5return $user->toJson(); + + 6  + + 7return $user->toJson(JSON_PRETTY_PRINT); + + + use App\Models\User; + + $user = User::find(1); + + return $user->toJson(); + + 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: + + + + 1return (string) User::find(1); + + + 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: + + + + 1Route::get('/users', function () { + + 2 return User::all(); + + 3}); + + + Route::get('/users', function () { + return User::all(); + }); + +#### Relationships + +When an Eloquent model is converted to JSON, its loaded relationships will +automatically be included as attributes on the JSON object. Also, though +Eloquent relationship methods are defined using "camel case" method names, a +relationship's JSON attribute will be "snake case". + +## Hiding Attributes From JSON + +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: + + + + 1 + + 13 */ + + 14 protected $hidden = ['password']; + + 15} + + + + */ + protected $hidden = ['password']; + } + +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: + + + + 1makeVisible('attribute')->toArray(); + + 2  + + 3return $user->mergeVisible(['name', 'email'])->toArray(); + + + return $user->makeVisible('attribute')->toArray(); + + return $user->mergeVisible(['name', 'email'])->toArray(); + +Likewise, if you would like to hide some attributes that are typically +visible, you may use the `makeHidden` or `mergeHidden` methods: + + + + 1return $user->makeHidden('attribute')->toArray(); + + 2  + + 3return $user->mergeHidden(['name', 'email'])->toArray(); + + + return $user->makeHidden('attribute')->toArray(); + + return $user->mergeHidden(['name', 'email'])->toArray(); + +If you wish to temporarily override all of the visible or hidden attributes, +you may use the `setVisible` and `setHidden` methods respectively: + + + + 1return $user->setVisible(['id', 'name'])->toArray(); + + 2  + + 3return $user->setHidden(['email', 'password', 'remember_token'])->toArray(); + + + return $user->setVisible(['id', 'name'])->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/12.x/eloquent-mutators) for the value: + + + + 1 'yes', + + 17 ); + + 18 } + + 19} + + + '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": + + + + 1append('is_admin')->toArray(); + + 2  + + 3return $user->mergeAppends(['is_admin', 'status'])->toArray(); + + 4  + + 5return $user->setAppends(['is_admin'])->toArray(); + + + return $user->append('is_admin')->toArray(); + + return $user->mergeAppends(['is_admin', 'status'])->toArray(); + + return $user->setAppends(['is_admin'])->toArray(); + +## Date Serialization + +#### Customizing the Default Date Format + +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: + + + + 1/** + + 2 * Prepare a date for array / JSON serialization. + + 3 */ + + 4protected function serializeDate(DateTimeInterface $date): string + + 5{ + + 6 return $date->format('Y-m-d'); + + 7} + + + /** + * 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/12.x/eloquent-mutators#attribute-casting): + + + + 1protected function casts(): array + + 2{ + + 3 return [ + + 4 'birthday' => 'date:Y-m-d', + + 5 'joined_at' => 'datetime:Y-m-d H:00', + + 6 ]; + + 7} + + + protected function casts(): array + { + return [ + 'birthday' => 'date:Y-m-d', + 'joined_at' => 'datetime:Y-m-d H:00', + ]; + } + diff --git a/output/12.x/eloquent.md b/output/12.x/eloquent.md new file mode 100644 index 0000000..4a7f124 --- /dev/null +++ b/output/12.x/eloquent.md @@ -0,0 +1,4298 @@ +# Eloquent: Getting Started + + * Introduction + * Generating Model Classes + * Eloquent Model Conventions + * Table Names + * Primary Keys + * UUID and ULID Keys + * Timestamps + * Database Connections + * Default Attribute Values + * Configuring Eloquent Strictness + * Retrieving Models + * Collections + * Chunking Results + * Chunk Using Lazy Collections + * Cursors + * Advanced Subqueries + * Retrieving Single Models / Aggregates + * Retrieving or Creating Models + * Retrieving Aggregates + * Inserting and Updating Models + * Inserts + * Updates + * Mass Assignment + * Upserts + * Deleting Models + * Soft Deleting + * Querying Soft Deleted Models + * Pruning Models + * Replicating Models + * Query Scopes + * Global Scopes + * Local Scopes + * Pending Attributes + * Comparing Models + * Events + * Using Closures + * Observers + * Muting Events + +## Introduction + +Laravel includes Eloquent, an object-relational mapper (ORM) that makes it +enjoyable to interact with your database. When using Eloquent, each database +table has a corresponding "Model" that is used to interact with that table. In +addition to retrieving records from the database table, Eloquent models allow +you to insert, update, and delete records from the table as well. + +Before getting started, be sure to configure a database connection in your +application's `config/database.php` configuration file. For more information +on configuring your database, check out [the database configuration +documentation](/docs/12.x/database#configuration). + +## Generating Model Classes + +To get started, let's create an Eloquent model. Models typically live in the +`app\Models` directory and extend the `Illuminate\Database\Eloquent\Model` +class. You may use the `make:model` [Artisan command](/docs/12.x/artisan) to +generate a new model: + + + + 1php artisan make:model Flight + + + php artisan make:model Flight + +If you would like to generate a [database migration](/docs/12.x/migrations) +when you generate the model, you may use the `--migration` or `-m` option: + + + + 1php artisan make:model Flight --migration + + + php artisan make:model Flight --migration + +You may generate various other types of classes when generating a model, such +as factories, seeders, policies, controllers, and form requests. In addition, +these options may be combined to create multiple classes at once: + + + + 1# Generate a model and a FlightFactory class... + + 2php artisan make:model Flight --factory + + 3php artisan make:model Flight -f + + 4  + + 5# Generate a model and a FlightSeeder class... + + 6php artisan make:model Flight --seed + + 7php artisan make:model Flight -s + + 8  + + 9# Generate a model and a FlightController class... + + 10php artisan make:model Flight --controller + + 11php artisan make:model Flight -c + + 12  + + 13# Generate a model, FlightController resource class, and form request classes... + + 14php artisan make:model Flight --controller --resource --requests + + 15php artisan make:model Flight -crR + + 16  + + 17# Generate a model and a FlightPolicy class... + + 18php artisan make:model Flight --policy + + 19  + + 20# Generate a model and a migration, factory, seeder, and controller... + + 21php artisan make:model Flight -mfsc + + 22  + + 23# Shortcut to generate a model, migration, factory, seeder, policy, controller, and form requests... + + 24php artisan make:model Flight --all + + 25php artisan make:model Flight -a + + 26  + + 27# Generate a pivot model... + + 28php artisan make:model Member --pivot + + 29php artisan make:model Member -p + + + # Generate a model and a FlightFactory class... + php artisan make:model Flight --factory + php artisan make:model Flight -f + + # Generate a model and a FlightSeeder class... + php artisan make:model Flight --seed + php artisan make:model Flight -s + + # Generate a model and a FlightController class... + php artisan make:model Flight --controller + php artisan make:model Flight -c + + # Generate a model, FlightController resource class, and form request classes... + php artisan make:model Flight --controller --resource --requests + php artisan make:model Flight -crR + + # Generate a model and a FlightPolicy class... + php artisan make:model Flight --policy + + # Generate a model and a migration, factory, seeder, and controller... + php artisan make:model Flight -mfsc + + # Shortcut to generate a model, migration, factory, seeder, policy, controller, and form requests... + php artisan make:model Flight --all + php artisan make:model Flight -a + + # Generate a pivot model... + php artisan make:model Member --pivot + php artisan make:model Member -p + +#### Inspecting Models + +Sometimes it can be difficult to determine all of a model's available +attributes and relationships just by skimming its code. Instead, try the +`model:show` Artisan command, which provides a convenient overview of all the +model's attributes and relations: + + + + 1php artisan model:show Flight + + + php artisan model:show Flight + +## Eloquent Model Conventions + +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: + + + + 1 'Traveling to Europe']); + + 12  + + 13$article->id; // "8f8e8478-9035-4d23-b9a7-62f4d2612ce5" + + + use Illuminate\Database\Eloquent\Concerns\HasUuids; + use Illuminate\Database\Eloquent\Model; + + class Article extends Model + { + use HasUuids; + + // ... + } + + $article = Article::create(['title' => 'Traveling to Europe']); + + $article->id; // "8f8e8478-9035-4d23-b9a7-62f4d2612ce5" + +By default, The `HasUuids` trait will generate ["ordered" +UUIDs](/docs/12.x/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: + + + + 1use Ramsey\Uuid\Uuid; + + 2  + + 3/** + + 4 * Generate a new UUID for the model. + + 5 */ + + 6public function newUniqueId(): string + + 7{ + + 8 return (string) Uuid::uuid4(); + + 9} + + 10  + + 11/** + + 12 * Get the columns that should receive a unique identifier. + + 13 * + + 14 * @return array + + 15 */ + + 16public function uniqueIds(): array + + 17{ + + 18 return ['id', 'discount_code']; + + 19} + + + use Ramsey\Uuid\Uuid; + + /** + * 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']; + } + +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/12.x/migrations#column-method-ulid): + + + + 1use Illuminate\Database\Eloquent\Concerns\HasUlids; + + 2use Illuminate\Database\Eloquent\Model; + + 3  + + 4class Article extends Model + + 5{ + + 6 use HasUlids; + + 7  + + 8 // ... + + 9} + + 10  + + 11$article = Article::create(['title' => 'Traveling to Asia']); + + 12  + + 13$article->id; // "01gd4d3tgrrfqeda94gdbtdk5c" + + + use Illuminate\Database\Eloquent\Concerns\HasUlids; + use Illuminate\Database\Eloquent\Model; + + class Article extends Model + { + use HasUlids; + + // ... + } + + $article = Article::create(['title' => 'Traveling to Asia']); + + $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`: + + + + 1 $post->increment('reads')); + + + 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: + + + + 1 '[]', + + 16 'delayed' => false, + + 17 ]; + + 18} + + + '[]', + 'delayed' => false, + ]; + } + +### Configuring Eloquent Strictness + +Laravel offers several methods that allow you to configure Eloquent's behavior +and "strictness" in a variety of situations. + +First, the `preventLazyLoading` method accepts an optional boolean argument +that indicates if lazy loading should be prevented. For example, you may wish +to only disable lazy loading in non-production environments so that your +production environment will continue to function normally even if a lazy +loaded relationship is accidentally present in production code. Typically, +this method should be invoked in the `boot` method of your application's +`AppServiceProvider`: + + + + 1use Illuminate\Database\Eloquent\Model; + + 2  + + 3/** + + 4 * Bootstrap any application services. + + 5 */ + + 6public function boot(): void + + 7{ + + 8 Model::preventLazyLoading(! $this->app->isProduction()); + + 9} + + + use Illuminate\Database\Eloquent\Model; + + /** + * Bootstrap any application services. + */ + public function boot(): void + { + Model::preventLazyLoading(! $this->app->isProduction()); + } + +Also, you may instruct Laravel to throw an exception when attempting to fill +an unfillable attribute by invoking the `preventSilentlyDiscardingAttributes` +method. This can help prevent unexpected errors during local development when +attempting to set an attribute that has not been added to the model's +`fillable` array: + + + + 1Model::preventSilentlyDiscardingAttributes(! $this->app->isProduction()); + + + Model::preventSilentlyDiscardingAttributes(! $this->app->isProduction()); + +## Retrieving Models + +Once you have created a model and [its associated database +table](/docs/12.x/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/12.x/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: + + + + 1use App\Models\Flight; + + 2  + + 3foreach (Flight::all() as $flight) { + + 4 echo $flight->name; + + 5} + + + use App\Models\Flight; + + 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/12.x/queries), you may add additional constraints to queries +and then invoke the `get` method to retrieve the results: + + + + 1$flights = Flight::where('active', 1) + + 2 ->orderBy('name') + + 3 ->limit(10) + + 4 ->get(); + + + $flights = Flight::where('active', 1) + ->orderBy('name') + ->limit(10) + ->get(); + +Since Eloquent models are query builders, you should review all of the methods +provided by Laravel's [query builder](/docs/12.x/queries). You may use any of +these methods when writing your Eloquent queries. + +#### Refreshing Models + +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: + + + + 1$flight = Flight::where('number', 'FR 900')->first(); + + 2  + + 3$freshFlight = $flight->fresh(); + + + $flight = Flight::where('number', 'FR 900')->first(); + + $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: + + + + 1$flight = Flight::where('number', 'FR 900')->first(); + + 2  + + 3$flight->number = 'FR 456'; + + 4  + + 5$flight->refresh(); + + 6  + + 7$flight->number; // "FR 900" + + + $flight = Flight::where('number', 'FR 900')->first(); + + $flight->number = 'FR 456'; + + $flight->refresh(); + + $flight->number; // "FR 900" + +### Collections + +As we have seen, Eloquent methods like `all` and `get` retrieve multiple +records from the database. However, these methods don't return a plain PHP +array. Instead, an instance of `Illuminate\Database\Eloquent\Collection` is +returned. + +The Eloquent `Collection` class extends Laravel's base +`Illuminate\Support\Collection` class, which provides a [variety of helpful +methods](/docs/12.x/collections#available-methods) for interacting with data +collections. For example, the `reject` method may be used to remove models +from a collection based on the results of an invoked closure: + + + + 1$flights = Flight::where('destination', 'Paris')->get(); + + 2  + + 3$flights = $flights->reject(function (Flight $flight) { + + 4 return $flight->cancelled; + + 5}); + + + $flights = Flight::where('destination', 'Paris')->get(); + + $flights = $flights->reject(function (Flight $flight) { + return $flight->cancelled; + }); + +In addition to the methods provided by Laravel's base collection class, the +Eloquent collection class provides [a few extra methods](/docs/12.x/eloquent- +collections#available-methods) that are specifically intended for interacting +with collections of Eloquent models. + +Since all of Laravel's collections implement PHP's iterable interfaces, you +may loop over collections as if they were an array: + + + + 1foreach ($flights as $flight) { + + 2 echo $flight->name; + + 3} + + + foreach ($flights as $flight) { + echo $flight->name; + } + +### Chunking Results + +Your application may run out of memory if you attempt to load tens of +thousands of Eloquent records via the `all` or `get` methods. Instead of using +these methods, the `chunk` method may be used to process large numbers of +models more efficiently. + +The `chunk` method will retrieve a subset of Eloquent models, passing them to +a closure for processing. Since only the current chunk of Eloquent models is +retrieved at a time, the `chunk` method will provide significantly reduced +memory usage when working with a large number of models: + + + + 1use App\Models\Flight; + + 2use Illuminate\Database\Eloquent\Collection; + + 3  + + 4Flight::chunk(200, function (Collection $flights) { + + 5 foreach ($flights as $flight) { + + 6 // ... + + 7 } + + 8}); + + + use App\Models\Flight; + use Illuminate\Database\Eloquent\Collection; + + Flight::chunk(200, function (Collection $flights) { + foreach ($flights as $flight) { + // ... + } + }); + +The first argument passed to the `chunk` method is the number of records you +wish to receive per "chunk". The closure passed as the second argument will be +invoked for each chunk that is retrieved from the database. A database query +will be executed to retrieve each chunk of records passed to the closure. + +If you are filtering the results of the `chunk` method based on a column that +you will also be updating while iterating over the results, you should use the +`chunkById` method. Using the `chunk` method in these scenarios could lead to +unexpected and inconsistent results. Internally, the `chunkById` method will +always retrieve models with an `id` column greater than the last model in the +previous chunk: + + + + 1Flight::where('departed', true) + + 2 ->chunkById(200, function (Collection $flights) { + + 3 $flights->each->update(['departed' => false]); + + 4 }, column: 'id'); + + + Flight::where('departed', true) + ->chunkById(200, function (Collection $flights) { + $flights->each->update(['departed' => false]); + }, column: 'id'); + +Since the `chunkById` and `lazyById` methods add their own "where" conditions +to the query being executed, you should typically [logically +group](/docs/12.x/queries#logical-grouping) your own conditions within a +closure: + + + + 1Flight::where(function ($query) { + + 2 $query->where('delayed', true)->orWhere('cancelled', true); + + 3})->chunkById(200, function (Collection $flights) { + + 4 $flights->each->update([ + + 5 'departed' => false, + + 6 'cancelled' => true + + 7 ]); + + 8}, column: 'id'); + + + Flight::where(function ($query) { + $query->where('delayed', true)->orWhere('cancelled', true); + })->chunkById(200, function (Collection $flights) { + $flights->each->update([ + 'departed' => false, + 'cancelled' => true + ]); + }, column: 'id'); + +### Chunking Using Lazy Collections + +The `lazy` method works similarly to the `chunk` method in the sense that, +behind the scenes, it executes the query in chunks. However, instead of +passing each chunk directly into a callback as is, the `lazy` method returns a +flattened [LazyCollection](/docs/12.x/collections#lazy-collections) of +Eloquent models, which lets you interact with the results as a single stream: + + + + 1use App\Models\Flight; + + 2  + + 3foreach (Flight::lazy() as $flight) { + + 4 // ... + + 5} + + + use App\Models\Flight; + + foreach (Flight::lazy() as $flight) { + // ... + } + +If you are filtering the results of the `lazy` method based on a column that +you will also be updating while iterating over the results, you should use the +`lazyById` method. Internally, the `lazyById` method will always retrieve +models with an `id` column greater than the last model in the previous chunk: + + + + 1Flight::where('departed', true) + + 2 ->lazyById(200, column: 'id') + + 3 ->each->update(['departed' => false]); + + + Flight::where('departed', true) + ->lazyById(200, column: 'id') + ->each->update(['departed' => false]); + +You may filter the results based on the descending order of the `id` using the +`lazyByIdDesc` method. + +### Cursors + +Similar to the `lazy` method, the `cursor` method may be used to significantly +reduce your application's memory consumption when iterating through tens of +thousands of Eloquent model records. + +The `cursor` method will only execute a single database query; however, the +individual Eloquent models will not be hydrated until they are actually +iterated over. Therefore, only one Eloquent model is kept in memory at any +given time while iterating over the cursor. + +Since the `cursor` method only ever holds a single Eloquent model in memory at +a time, it cannot eager load relationships. If you need to eager load +relationships, consider using the `lazy` method instead. + +Internally, the `cursor` method uses PHP +[generators](https://www.php.net/manual/en/language.generators.overview.php) +to implement this functionality: + + + + 1use App\Models\Flight; + + 2  + + 3foreach (Flight::where('destination', 'Zurich')->cursor() as $flight) { + + 4 // ... + + 5} + + + use App\Models\Flight; + + foreach (Flight::where('destination', 'Zurich')->cursor() as $flight) { + // ... + } + +The `cursor` returns an `Illuminate\Support\LazyCollection` instance. [Lazy +collections](/docs/12.x/collections#lazy-collections) allow you to use many of +the collection methods available on typical Laravel collections while only +loading a single model into memory at a time: + + + + 1use App\Models\User; + + 2  + + 3$users = User::cursor()->filter(function (User $user) { + + 4 return $user->id > 500; + + 5}); + + 6  + + 7foreach ($users as $user) { + + 8 echo $user->id; + + 9} + + + use App\Models\User; + + $users = User::cursor()->filter(function (User $user) { + return $user->id > 500; + }); + + foreach ($users as $user) { + echo $user->id; + } + +Although the `cursor` method uses far less memory than a regular query (by +only holding a single Eloquent model in memory at a time), it will still +eventually run out of memory. This is [due to PHP's PDO driver internally +caching all raw query results in its +buffer](https://www.php.net/manual/en/mysqlinfo.concepts.buffering.php). If +you're dealing with a very large number of Eloquent records, consider using +the `lazy` method instead. + +### Advanced Subqueries + +#### Subquery Selects + +Eloquent also offers advanced subquery support, which allows you to pull +information from related tables in a single query. For example, let's imagine +that we have a table of flight `destinations` and a table of `flights` to +destinations. The `flights` table contains an `arrived_at` column which +indicates when the flight arrived at the destination. + +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: + + + + 1use App\Models\Destination; + + 2use App\Models\Flight; + + 3  + + 4return Destination::addSelect(['last_flight' => Flight::select('name') + + 5 ->whereColumn('destination_id', 'destinations.id') + + 6 ->orderByDesc('arrived_at') + + 7 ->limit(1) + + 8])->get(); + + + 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(); + +#### 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: + + + + 1return Destination::orderByDesc( + + 2 Flight::select('arrived_at') + + 3 ->whereColumn('destination_id', 'destinations.id') + + 4 ->orderByDesc('arrived_at') + + 5 ->limit(1) + + 6)->get(); + + + 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: + + + + 1use App\Models\Flight; + + 2  + + 3// Retrieve a model by its primary key... + + 4$flight = Flight::find(1); + + 5  + + 6// Retrieve the first model matching the query constraints... + + 7$flight = Flight::where('active', 1)->first(); + + 8  + + 9// Alternative to retrieving the first model matching the query constraints... + + 10$flight = Flight::firstWhere('active', 1); + + + use App\Models\Flight; + + // 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(); + + // 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: + + + + 1$flight = Flight::findOr(1, function () { + + 2 // ... + + 3}); + + 4  + + 5$flight = Flight::where('legs', '>', 3)->firstOr(function () { + + 6 // ... + + 7}); + + + $flight = Flight::findOr(1, 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: + + + + 1$flight = Flight::findOrFail(1); + + 2  + + 3$flight = Flight::where('legs', '>', 3)->firstOrFail(); + + + $flight = Flight::findOrFail(1); + + $flight = Flight::where('legs', '>', 3)->firstOrFail(); + +If the `ModelNotFoundException` is not caught, a 404 HTTP response is +automatically sent back to the client: + + + + 1use App\Models\Flight; + + 2  + + 3Route::get('/api/flights/{id}', function (string $id) { + + 4 return Flight::findOrFail($id); + + 5}); + + + use App\Models\Flight; + + Route::get('/api/flights/{id}', function (string $id) { + return Flight::findOrFail($id); + }); + +### Retrieving or Creating Models + +The `firstOrCreate` method will attempt to locate a database record using the +given column / value pairs. If the model cannot be found in the database, a +record will be inserted with the attributes resulting from merging the first +array argument with the optional second array argument. + +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: + + + + 1use App\Models\Flight; + + 2  + + 3// Retrieve flight by name or create it if it doesn't exist... + + 4$flight = Flight::firstOrCreate([ + + 5 'name' => 'London to Paris' + + 6]); + + 7  + + 8// Retrieve flight by name or create it with the name, delayed, and arrival_time attributes... + + 9$flight = Flight::firstOrCreate( + + 10 ['name' => 'London to Paris'], + + 11 ['delayed' => 1, 'arrival_time' => '11:30'] + + 12); + + 13  + + 14// Retrieve flight by name or instantiate a new Flight instance... + + 15$flight = Flight::firstOrNew([ + + 16 'name' => 'London to Paris' + + 17]); + + 18  + + 19// Retrieve flight by name or instantiate with the name, delayed, and arrival_time attributes... + + 20$flight = Flight::firstOrNew( + + 21 ['name' => 'Tokyo to Sydney'], + + 22 ['delayed' => 1, 'arrival_time' => '11:30'] + + 23); + + + 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' + ]); + + // 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/12.x/queries#aggregates) provided +by the Laravel [query builder](/docs/12.x/queries). As you might expect, these +methods return a scalar value instead of an Eloquent model instance: + + + + 1$count = Flight::where('active', 1)->count(); + + 2  + + 3$max = Flight::where('active', 1)->max('price'); + + + $count = Flight::where('active', 1)->count(); + + $max = Flight::where('active', 1)->max('price'); + +## Inserting and Updating Models + +### Inserts + +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: + + + + 1name = $request->name; + + 21  + + 22 $flight->save(); + + 23  + + 24 return redirect('/flights'); + + 25 } + + 26} + + + name = $request->name; + + $flight->save(); + + 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: + + + + 1use App\Models\Flight; + + 2  + + 3$flight = Flight::create([ + + 4 'name' => 'London to Paris', + + 5]); + + + use App\Models\Flight; + + $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. + +### Updates + +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: + + + + 1use App\Models\Flight; + + 2  + + 3$flight = Flight::find(1); + + 4  + + 5$flight->name = 'Paris to London'; + + 6  + + 7$flight->save(); + + + use App\Models\Flight; + + $flight = Flight::find(1); + + $flight->name = 'Paris to London'; + + $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: + + + + 1$flight = Flight::updateOrCreate( + + 2 ['departure' => 'Oakland', 'destination' => 'San Diego'], + + 3 ['price' => 99, 'discounted' => 1] + + 4); + + + $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: + + + + 1Flight::where('active', 1) + + 2 ->where('destination', 'San Diego') + + 3 ->update(['delayed' => 1]); + + + 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. + +When issuing a mass update via Eloquent, the `saving`, `saved`, `updating`, +and `updated` model events will not be fired for the updated models. This is +because the models are never actually retrieved when issuing a mass update. + +#### Examining Attribute Changes + +Eloquent provides the `isDirty`, `isClean`, and `wasChanged` methods to +examine the internal state of your model and determine how its attributes have +changed from when the model was originally retrieved. + +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: + + + + 1use App\Models\User; + + 2  + + 3$user = User::create([ + + 4 'first_name' => 'Taylor', + + 5 'last_name' => 'Otwell', + + 6 'title' => 'Developer', + + 7]); + + 8  + + 9$user->title = 'Painter'; + + 10  + + 11$user->isDirty(); // true + + 12$user->isDirty('title'); // true + + 13$user->isDirty('first_name'); // false + + 14$user->isDirty(['first_name', 'title']); // true + + 15  + + 16$user->isClean(); // false + + 17$user->isClean('title'); // false + + 18$user->isClean('first_name'); // true + + 19$user->isClean(['first_name', 'title']); // false + + 20  + + 21$user->save(); + + 22  + + 23$user->isDirty(); // false + + 24$user->isClean(); // true + + + use App\Models\User; + + $user = User::create([ + 'first_name' => 'Taylor', + 'last_name' => 'Otwell', + 'title' => 'Developer', + ]); + + $user->title = 'Painter'; + + $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->save(); + + $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: + + + + 1$user = User::create([ + + 2 'first_name' => 'Taylor', + + 3 'last_name' => 'Otwell', + + 4 'title' => 'Developer', + + 5]); + + 6  + + 7$user->title = 'Painter'; + + 8  + + 9$user->save(); + + 10  + + 11$user->wasChanged(); // true + + 12$user->wasChanged('title'); // true + + 13$user->wasChanged(['title', 'slug']); // true + + 14$user->wasChanged('first_name'); // false + + 15$user->wasChanged(['first_name', 'title']); // true + + + $user = User::create([ + 'first_name' => 'Taylor', + 'last_name' => 'Otwell', + 'title' => 'Developer', + ]); + + $user->title = 'Painter'; + + $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 + +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: + + + + 1$user = User::find(1); + + 2  + + 3$user->name; // John + + 4$user->email; // [[email protected]](/cdn-cgi/l/email-protection) + + 5  + + 6$user->name = 'Jack'; + + 7$user->name; // Jack + + 8  + + 9$user->getOriginal('name'); // John + + 10$user->getOriginal(); // Array of original attributes... + + + $user = User::find(1); + + $user->name; // John + $user->email; // [[email protected]](/cdn-cgi/l/email-protection) + + $user->name = 'Jack'; + $user->name; // Jack + + $user->getOriginal('name'); // John + $user->getOriginal(); // Array of original attributes... + +The `getChanges` method returns an array containing the attributes that +changed when the model was last saved, while the `getPrevious` method returns +an array containing the original attribute values before the model was last +saved: + + + + 1$user = User::find(1); + + 2  + + 3$user->name; // John + + 4$user->email; // [[email protected]](/cdn-cgi/l/email-protection) + + 5  + + 6$user->update([ + + 7 'name' => 'Jack', + + 8 'email' => '[[email protected]](/cdn-cgi/l/email-protection)', + + 9]); + + 10  + + 11$user->getChanges(); + + 12  + + 13/* + + 14 [ + + 15 'name' => 'Jack', + + 16 'email' => '[[email protected]](/cdn-cgi/l/email-protection)', + + 17 ] + + 18*/ + + 19  + + 20$user->getPrevious(); + + 21  + + 22/* + + 23 [ + + 24 'name' => 'John', + + 25 'email' => '[[email protected]](/cdn-cgi/l/email-protection)', + + 26 ] + + 27*/ + + + $user = User::find(1); + + $user->name; // John + $user->email; // [[email protected]](/cdn-cgi/l/email-protection) + + $user->update([ + 'name' => 'Jack', + 'email' => '[[email protected]](/cdn-cgi/l/email-protection)', + ]); + + $user->getChanges(); + + /* + [ + 'name' => 'Jack', + 'email' => '[[email protected]](/cdn-cgi/l/email-protection)', + ] + */ + + $user->getPrevious(); + + /* + [ + 'name' => 'John', + 'email' => '[[email protected]](/cdn-cgi/l/email-protection)', + ] + */ + +### 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: + + + + 1use App\Models\Flight; + + 2  + + 3$flight = Flight::create([ + + 4 'name' => 'London to Paris', + + 5]); + + + use App\Models\Flight; + + $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. + +A mass assignment vulnerability occurs when a user passes an unexpected HTTP +request field and that field changes a column in your database that you did +not expect. For example, a malicious user might send an `is_admin` parameter +through an HTTP request, which is then passed to your model's `create` method, +allowing the user to escalate themselves to an administrator. + +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: + + + + 1 + + 13 */ + + 14 protected $fillable = ['name']; + + 15} + + + + */ + 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: + + + + 1$flight = Flight::create(['name' => 'London to Paris']); + + + $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: + + + + 1$flight->fill(['name' => 'Amsterdam to Frankfurt']); + + + $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: + + + + 1/** + + 2 * The attributes that are mass assignable. + + 3 * + + 4 * @var array + + 5 */ + + 6protected $fillable = [ + + 7 'options->enabled', + + 8]; + + + /** + * 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: + + + + 1/** + + 2 * The attributes that aren't mass assignable. + + 3 * + + 4 * @var array|bool + + 5 */ + + 6protected $guarded = []; + + + /** + * The attributes that aren't mass assignable. + * + * @var array|bool + */ + protected $guarded = []; + +#### Mass Assignment Exceptions + +By default, attributes that are not included in the `$fillable` array are +silently discarded when performing mass-assignment operations. In production, +this is expected behavior; however, during local development it can lead to +confusion as to why model changes are not taking effect. + +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: + + + + 1use Illuminate\Database\Eloquent\Model; + + 2  + + 3/** + + 4 * Bootstrap any application services. + + 5 */ + + 6public function boot(): void + + 7{ + + 8 Model::preventSilentlyDiscardingAttributes($this->app->isLocal()); + + 9} + + + use Illuminate\Database\Eloquent\Model; + + /** + * 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: + + + + 1Flight::upsert([ + + 2 ['departure' => 'Oakland', 'destination' => 'San Diego', 'price' => 99], + + 3 ['departure' => 'Chicago', 'destination' => 'New York', 'price' => 150] + + 4], uniqueBy: ['departure', 'destination'], update: ['price']); + + + Flight::upsert([ + ['departure' => 'Oakland', 'destination' => 'San Diego', 'price' => 99], + ['departure' => 'Chicago', 'destination' => 'New York', 'price' => 150] + ], uniqueBy: ['departure', 'destination'], update: ['price']); + +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. + +## Deleting Models + +To delete a model, you may call the `delete` method on the model instance: + + + + 1use App\Models\Flight; + + 2  + + 3$flight = Flight::find(1); + + 4  + + 5$flight->delete(); + + + use App\Models\Flight; + + $flight = Flight::find(1); + + $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/12.x/collections) of primary keys: + + + + 1Flight::destroy(1); + + 2  + + 3Flight::destroy(1, 2, 3); + + 4  + + 5Flight::destroy([1, 2, 3]); + + 6  + + 7Flight::destroy(collect([1, 2, 3])); + + + Flight::destroy(1); + + Flight::destroy(1, 2, 3); + + Flight::destroy([1, 2, 3]); + + Flight::destroy(collect([1, 2, 3])); + +If you are utilizing soft deleting models, you may permanently delete models +via the `forceDestroy` method: + + + + 1Flight::forceDestroy(1); + + + Flight::forceDestroy(1); + +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. + +#### Deleting Models Using Queries + +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: + + + + 1$deleted = Flight::where('active', 0)->delete(); + + + $deleted = Flight::where('active', 0)->delete(); + +To delete all models in a table, you should execute a query without adding any +conditions: + + + + 1$deleted = Flight::query()->delete(); + + + $deleted = Flight::query()->delete(); + +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. + +### Soft Deleting + +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: + + + + 1softDeletes(); + + 6}); + + 7  + + 8Schema::table('flights', function (Blueprint $table) { + + 9 $table->dropSoftDeletes(); + + 10}); + + + 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->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: + + + + 1if ($flight->trashed()) { + + 2 // ... + + 3} + + + 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`: + + + + 1$flight->restore(); + + + $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: + + + + 1Flight::withTrashed() + + 2 ->where('airline_id', 1) + + 3 ->restore(); + + + Flight::withTrashed() + ->where('airline_id', 1) + ->restore(); + +The `restore` method may also be used when building +[relationship](/docs/12.x/eloquent-relationships) queries: + + + + 1$flight->history()->restore(); + + + $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: + + + + 1$flight->forceDelete(); + + + $flight->forceDelete(); + +You may also use the `forceDelete` method when building Eloquent relationship +queries: + + + + 1$flight->history()->forceDelete(); + + + $flight->history()->forceDelete(); + +### Querying Soft Deleted Models + +#### Including Soft Deleted Models + +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: + + + + 1use App\Models\Flight; + + 2  + + 3$flights = Flight::withTrashed() + + 4 ->where('account_id', 1) + + 5 ->get(); + + + use App\Models\Flight; + + $flights = Flight::withTrashed() + ->where('account_id', 1) + ->get(); + +The `withTrashed` method may also be called when building a +[relationship](/docs/12.x/eloquent-relationships) query: + + + + 1$flight->history()->withTrashed()->get(); + + + $flight->history()->withTrashed()->get(); + +#### Retrieving Only Soft Deleted Models + +The `onlyTrashed` method will retrieve **only** soft deleted models: + + + + 1$flights = Flight::onlyTrashed() + + 2 ->where('airline_id', 1) + + 3 ->get(); + + + $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: + + + + 1subMonth()); + + 19 } + + 20} + + + 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: + + + + 1/** + + 2 * Prepare the model for pruning. + + 3 */ + + 4protected function pruning(): void + + 5{ + + 6 // ... + + 7} + + + /** + * 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: + + + + 1use Illuminate\Support\Facades\Schedule; + + 2  + + 3Schedule::command('model:prune')->daily(); + + + use Illuminate\Support\Facades\Schedule; + + 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: + + + + 1Schedule::command('model:prune', [ + + 2 '--model' => [Address::class, Flight::class], + + 3])->daily(); + + + 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: + + + + 1Schedule::command('model:prune', [ + + 2 '--except' => [Address::class, Flight::class], + + 3])->daily(); + + + 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: + + + + 1php artisan model:prune --pretend + + + php artisan model:prune --pretend + +Soft deleting models will be permanently deleted (`forceDelete`) if they match +the prunable query. + +#### Mass Pruning + +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: + + + + 1subMonth()); + + 19 } + + 20} + + + 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: + + + + 1use App\Models\Address; + + 2  + + 3$shipping = Address::create([ + + 4 'type' => 'shipping', + + 5 'line_1' => '123 Example Street', + + 6 'city' => 'Victorville', + + 7 'state' => 'CA', + + 8 'postcode' => '90001', + + 9]); + + 10  + + 11$billing = $shipping->replicate()->fill([ + + 12 'type' => 'billing' + + 13]); + + 14  + + 15$billing->save(); + + + use App\Models\Address; + + $shipping = Address::create([ + 'type' => 'shipping', + 'line_1' => '123 Example Street', + 'city' => 'Victorville', + 'state' => 'CA', + 'postcode' => '90001', + ]); + + $billing = $shipping->replicate()->fill([ + 'type' => 'billing' + ]); + + $billing->save(); + +To exclude one or more attributes from being replicated to the new model, you +may pass an array to the `replicate` method: + + + + 1$flight = Flight::create([ + + 2 'destination' => 'LAX', + + 3 'origin' => 'LHR', + + 4 'last_flown' => '2020-03-04 11:00:00', + + 5 'last_pilot_id' => 747, + + 6]); + + 7  + + 8$flight = $flight->replicate([ + + 9 'last_flown', + + 10 'last_pilot_id' + + 11]); + + + $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 + +### Global Scopes + +Global scopes allow you to add constraints to all queries for a given model. +Laravel's own soft delete functionality utilizes global scopes to only +retrieve "non-deleted" models from the database. Writing your own global +scopes can provide a convenient, easy way to make sure every query for a given +model receives certain constraints. + +#### Generating Scopes + +To generate a new global scope, you may invoke the `make:scope` Artisan +command, which will place the generated scope in your application's +`app/Models/Scopes` directory: + + + + 1php artisan make:scope AncientScope + + + php artisan make:scope AncientScope + +#### Writing Global Scopes + +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: + + + + 1where('created_at', '<', now()->subYears(2000)); + + 17 } + + 18} + + + where('created_at', '<', now()->subYears(2000)); + } + } + +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. + +#### Applying Global Scopes + +To assign a global scope to a model, you may simply place the `ScopedBy` +attribute on the model: + + + + 1where('created_at', '<', now()->subYears(2000)); + + 17 }); + + 18 } + + 19} + + + 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: + + + + 1User::withoutGlobalScope(AncientScope::class)->get(); + + + 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: + + + + 1User::withoutGlobalScope('ancient')->get(); + + + 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: + + + + 1// Remove all of the global scopes... + + 2User::withoutGlobalScopes()->get(); + + 3  + + 4// Remove some of the global scopes... + + 5User::withoutGlobalScopes([ + + 6 FirstScope::class, SecondScope::class + + 7])->get(); + + + // Remove all of the global scopes... + User::withoutGlobalScopes()->get(); + + // Remove some of the global scopes... + User::withoutGlobalScopes([ + FirstScope::class, SecondScope::class + ])->get(); + +### Local Scopes + +Local scopes allow you to define common sets of query constraints that you may +easily re-use throughout your application. For example, you may need to +frequently retrieve all users that are considered "popular". To define a +scope, add the `Scope` attribute to an Eloquent method. + +Scopes should always return the same query builder instance or `void`: + + + + 1where('votes', '>', 100); + + 18 } + + 19  + + 20 /** + + 21 * Scope a query to only include active users. + + 22 */ + + 23 #[Scope] + + 24 protected function active(Builder $query): void + + 25 { + + 26 $query->where('active', 1); + + 27 } + + 28} + + + where('votes', '>', 100); + } + + /** + * Scope a query to only include active users. + */ + #[Scope] + protected function active(Builder $query): void + { + $query->where('active', 1); + } + } + +#### Utilizing a Local Scope + +Once the scope has been defined, you may call the scope methods when querying +the model. You can even chain calls to various scopes: + + + + 1use App\Models\User; + + 2  + + 3$users = User::popular()->active()->orderBy('created_at')->get(); + + + use App\Models\User; + + $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/12.x/queries#logical-grouping): + + + + 1$users = User::popular()->orWhere(function (Builder $query) { + + 2 $query->active(); + + 3})->get(); + + + $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: + + + + 1$users = User::popular()->orWhere->active()->get(); + + + $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: + + + + 1where('type', $type); + + 18 } + + 19} + + + 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: + + + + 1$users = User::ofType('admin')->get(); + + + $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: + + + + 1withAttributes([ + + 18 'hidden' => true, + + 19 ]); + + 20 } + + 21} + + + withAttributes([ + 'hidden' => true, + ]); + } + } + +The `withAttributes` method will add `where` conditions to the query using the +given attributes, and it will also add the given attributes to any models +created via the scope: + + + + 1$draft = Post::draft()->create(['title' => 'In Progress']); + + 2  + + 3$draft->hidden; // true + + + $draft = Post::draft()->create(['title' => 'In Progress']); + + $draft->hidden; // true + +To instruct the `withAttributes` method to not add `where` conditions to the +query, you may set the `asConditions` argument to `false`: + + + + 1$query->withAttributes([ + + 2 'hidden' => true, + + 3], asConditions: false); + + + $query->withAttributes([ + 'hidden' => true, + ], asConditions: false); + +## 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: + + + + 1if ($post->is($anotherPost)) { + + 2 // ... + + 3} + + 4  + + 5if ($post->isNot($anotherPost)) { + + 6 // ... + + 7} + + + if ($post->is($anotherPost)) { + // ... + } + + if ($post->isNot($anotherPost)) { + // ... + } + +The `is` and `isNot` methods are also available when using the `belongsTo`, +`hasOne`, `morphTo`, and `morphOne` [relationships](/docs/12.x/eloquent- +relationships). This method is particularly helpful when you would like to +compare a related model without issuing a query to retrieve that model: + + + + 1if ($post->author()->is($user)) { + + 2 // ... + + 3} + + + if ($post->author()->is($user)) { + // ... + } + +## Events + +Want to broadcast your Eloquent events directly to your client-side +application? Check out Laravel's [model event +broadcasting](/docs/12.x/broadcasting#model-broadcasting). + +Eloquent models dispatch several events, allowing you to hook into the +following moments in a model's lifecycle: `retrieved`, `creating`, `created`, +`updating`, `updated`, `saving`, `saved`, `deleting`, `deleted`, `trashed`, +`forceDeleting`, `forceDeleted`, `restoring`, `restored`, and `replicating`. + +The `retrieved` event will dispatch when an existing model is retrieved from +the database. When a new model is saved for the first time, the `creating` and +`created` events will dispatch. The `updating` / `updated` events will +dispatch when an existing model is modified and the `save` method is called. +The `saving` / `saved` events will dispatch when a model is created or updated +- even if the model's attributes have not been changed. Event names ending +with `-ing` are dispatched before any changes to the model are persisted, +while events ending with `-ed` are dispatched after the changes to the model +are persisted. + +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/12.x/events). Each model event +class should expect to receive an instance of the affected model via its +constructor: + + + + 1 + + 18 */ + + 19 protected $dispatchesEvents = [ + + 20 'saved' => UserSaved::class, + + 21 'deleted' => UserDeleted::class, + + 22 ]; + + 23} + + + + */ + protected $dispatchesEvents = [ + 'saved' => UserSaved::class, + 'deleted' => UserDeleted::class, + ]; + } + +After defining and mapping your Eloquent events, you may use [event +listeners](/docs/12.x/events#defining-listeners) to handle the events. + +When issuing a mass update or delete query via Eloquent, the `saved`, +`updated`, `deleting`, and `deleted` model events will not be dispatched for +the affected models. This is because the models are never actually retrieved +when performing mass updates or deletes. + +### Using Closures + +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: + + + + 1delete(); + + 5  + + 6 return User::find(2); + + 7}); + + + use App\Models\User; + + $user = User::withoutEvents(function () { + User::findOrFail(1)->delete(); + + 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: + + + + 1$user = User::findOrFail(1); + + 2  + + 3$user->name = 'Victoria Faith'; + + 4  + + 5$user->saveQuietly(); + + + $user = User::findOrFail(1); + + $user->name = 'Victoria Faith'; + + $user->saveQuietly(); + +You may also "update", "delete", "soft delete", "restore", and "replicate" a +given model without dispatching any events: + + + + 1$user->deleteQuietly(); + + 2$user->forceDeleteQuietly(); + + 3$user->restoreQuietly(); + + + $user->deleteQuietly(); + $user->forceDeleteQuietly(); + $user->restoreQuietly(); + diff --git a/output/12.x/encryption.md b/output/12.x/encryption.md new file mode 100644 index 0000000..967e9f6 --- /dev/null +++ b/output/12.x/encryption.md @@ -0,0 +1,174 @@ +# Encryption + + * Introduction + * Configuration + * Gracefully Rotating Encryption Keys + * Using the Encrypter + +## Introduction + +Laravel's encryption services provide a simple, convenient interface for +encrypting and decrypting text via OpenSSL using AES-256 and AES-128 +encryption. All of Laravel's encrypted values are signed using a message +authentication code (MAC) so that their underlying value cannot be modified or +tampered with once encrypted. + +## Configuration + +Before using Laravel's encrypter, you must set the `key` configuration option +in your `config/app.php` configuration file. This configuration value is +driven by the `APP_KEY` environment variable. You should use the `php artisan +key:generate` command to generate this variable's value since the +`key:generate` command will use PHP's secure random bytes generator to build a +cryptographically secure key for your application. Typically, the value of the +`APP_KEY` environment variable will be generated for you during [Laravel's +installation](/docs/12.x/installation). + +### Gracefully Rotating Encryption Keys + +If you change your application's encryption key, all authenticated user +sessions will be logged out of your application. This is because every cookie, +including session cookies, are encrypted by Laravel. In addition, it will no +longer be possible to decrypt any data that was encrypted with your previous +encryption key. + +To mitigate this issue, Laravel allows you to list your previous encryption +keys in your application's `APP_PREVIOUS_KEYS` environment variable. This +variable may contain a comma-delimited list of all of your previous encryption +keys: + + + + 1APP_KEY="base64:J63qRTDLub5NuZvP+kb8YIorGS6qFYHKVo6u7179stY=" + + 2APP_PREVIOUS_KEYS="base64:2nLsGFGzyoae2ax3EF2Lyq/hH6QghBGLIq5uL+Gp8/w=" + + + APP_KEY="base64:J63qRTDLub5NuZvP+kb8YIorGS6qFYHKVo6u7179stY=" + APP_PREVIOUS_KEYS="base64:2nLsGFGzyoae2ax3EF2Lyq/hH6QghBGLIq5uL+Gp8/w=" + +When you set this environment variable, Laravel will always use the "current" +encryption key when encrypting values. However, when decrypting values, +Laravel will first try the current key, and if decryption fails using the +current key, Laravel will try all previous keys until one of the keys is able +to decrypt the value. + +This approach to graceful decryption allows users to keep using your +application uninterrupted even if your encryption key is rotated. + +## Using the Encrypter + +#### Encrypting a Value + +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: + + + + 1user()->fill([ + + 17 'token' => Crypt::encryptString($request->token), + + 18 ])->save(); + + 19  + + 20 return redirect('/secrets'); + + 21 } + + 22} + + + 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: + + + + 1use Illuminate\Contracts\Encryption\DecryptException; + + 2use Illuminate\Support\Facades\Crypt; + + 3  + + 4try { + + 5 $decrypted = Crypt::decryptString($encryptedValue); + + 6} catch (DecryptException $e) { + + 7 // ... + + 8} + + + use Illuminate\Contracts\Encryption\DecryptException; + use Illuminate\Support\Facades\Crypt; + + try { + $decrypted = Crypt::decryptString($encryptedValue); + } catch (DecryptException $e) { + // ... + } + diff --git a/output/12.x/envoy.md b/output/12.x/envoy.md new file mode 100644 index 0000000..7f8999d --- /dev/null +++ b/output/12.x/envoy.md @@ -0,0 +1,626 @@ +# Laravel Envoy + + * Introduction + * Installation + * Writing Tasks + * Defining Tasks + * Multiple Servers + * Setup + * Variables + * Stories + * Hooks + * Running Tasks + * Confirming Task Execution + * Notifications + * Slack + * Discord + * Telegram + * Microsoft Teams + +## Introduction + +[Laravel Envoy](https://github.com/laravel/envoy) is a tool for executing +common tasks you run on your remote servers. Using [Blade](/docs/12.x/blade) +style syntax, you can easily setup tasks for deployment, Artisan commands, and +more. Currently, Envoy only supports the Mac and Linux operating systems. +However, Windows support is achievable using +[WSL2](https://docs.microsoft.com/en-us/windows/wsl/install-win10). + +## Installation + +First, install Envoy into your project using the Composer package manager: + + + + 1composer require laravel/envoy --dev + + + composer require laravel/envoy --dev + +Once Envoy has been installed, the Envoy binary will be available in your +application's `vendor/bin` directory: + + + + 1php vendor/bin/envoy + + + php vendor/bin/envoy + +## Writing Tasks + +### Defining Tasks + +Tasks are the basic building block of Envoy. Tasks define the shell commands +that should execute on your remote servers when the task is invoked. For +example, you might define a task that executes the `php artisan queue:restart` +command on all of your application's queue worker servers. + +All of your Envoy tasks should be defined in an `Envoy.blade.php` file at the +root of your application. Here's an example to get you started: + + + + 1@servers(['web' => ['[[email protected]](/cdn-cgi/l/email-protection)'], 'workers' => ['[[email protected]](/cdn-cgi/l/email-protection)']]) + + 2  + + 3@task('restart-queues', ['on' => 'workers']) + + 4 cd /home/user/example.com + + 5 php artisan queue:restart + + 6@endtask + + + @servers(['web' => ['[[email protected]](/cdn-cgi/l/email-protection)'], 'workers' => ['[[email protected]](/cdn-cgi/l/email-protection)']]) + + @task('restart-queues', ['on' => 'workers']) + cd /home/user/example.com + php artisan queue:restart + @endtask + +As you can see, an array of `@servers` is defined at the top of the file, +allowing you to reference these servers via the `on` option of your task +declarations. The `@servers` declaration should always be placed on a single +line. Within your `@task` declarations, you should place the shell commands +that should execute on your servers when the task is invoked. + +#### Local Tasks + +You can force a script to run on your local computer by specifying the +server's IP address as `127.0.0.1`: + + + + 1@servers(['localhost' => '127.0.0.1']) + + + @servers(['localhost' => '127.0.0.1']) + +#### Importing Envoy Tasks + +Using the `@import` directive, you may import other Envoy files so their +stories and tasks are added to yours. After the files have been imported, you +may execute the tasks they contain as if they were defined in your own Envoy +file: + + + + 1@import('vendor/package/Envoy.blade.php') + + + @import('vendor/package/Envoy.blade.php') + +### Multiple Servers + +Envoy allows you to easily run a task across multiple servers. First, add +additional servers to your `@servers` declaration. Each server should be +assigned a unique name. Once you have defined your additional servers you may +list each of the servers in the task's `on` array: + + + + 1@servers(['web-1' => '192.168.1.1', 'web-2' => '192.168.1.2']) + + 2  + + 3@task('deploy', ['on' => ['web-1', 'web-2']]) + + 4 cd /home/user/example.com + + 5 git pull origin {{ $branch }} + + 6 php artisan migrate --force + + 7@endtask + + + @servers(['web-1' => '192.168.1.1', 'web-2' => '192.168.1.2']) + + @task('deploy', ['on' => ['web-1', 'web-2']]) + cd /home/user/example.com + git pull origin {{ $branch }} + php artisan migrate --force + @endtask + +#### Parallel Execution + +By default, tasks will be executed on each server serially. In other words, a +task will finish running on the first server before proceeding to execute on +the second server. If you would like to run a task across multiple servers in +parallel, add the `parallel` option to your task declaration: + + + + 1@servers(['web-1' => '192.168.1.1', 'web-2' => '192.168.1.2']) + + 2  + + 3@task('deploy', ['on' => ['web-1', 'web-2'], 'parallel' => true]) + + 4 cd /home/user/example.com + + 5 git pull origin {{ $branch }} + + 6 php artisan migrate --force + + 7@endtask + + + @servers(['web-1' => '192.168.1.1', 'web-2' => '192.168.1.2']) + + @task('deploy', ['on' => ['web-1', 'web-2'], 'parallel' => true]) + cd /home/user/example.com + git pull origin {{ $branch }} + php artisan migrate --force + @endtask + +### Setup + +Sometimes, you may need to execute arbitrary PHP code before running your +Envoy tasks. You may use the `@setup` directive to define a block of PHP code +that should execute before your tasks: + + + + 1@setup + + 2 $now = new DateTime; + + 3@endsetup + + + @setup + $now = new DateTime; + @endsetup + +If you need to require other PHP files before your task is executed, you may +use the `@include` directive at the top of your `Envoy.blade.php` file: + + + + 1@include('vendor/autoload.php') + + 2  + + 3@task('restart-queues') + + 4 # ... + + 5@endtask + + + @include('vendor/autoload.php') + + @task('restart-queues') + # ... + @endtask + +### Variables + +If needed, you may pass arguments to Envoy tasks by specifying them on the +command line when invoking Envoy: + + + + 1php vendor/bin/envoy run deploy --branch=master + + + php vendor/bin/envoy run deploy --branch=master + +You may access the options within your tasks using Blade's "echo" syntax. You +may also define Blade `if` statements and loops within your tasks. For +example, let's verify the presence of the `$branch` variable before executing +the `git pull` command: + + + + 1@servers(['web' => ['[[email protected]](/cdn-cgi/l/email-protection)']]) + + 2  + + 3@task('deploy', ['on' => 'web']) + + 4 cd /home/user/example.com + + 5  + + 6 @if ($branch) + + 7 git pull origin {{ $branch }} + + 8 @endif + + 9  + + 10 php artisan migrate --force + + 11@endtask + + + @servers(['web' => ['[[email protected]](/cdn-cgi/l/email-protection)']]) + + @task('deploy', ['on' => 'web']) + cd /home/user/example.com + + @if ($branch) + git pull origin {{ $branch }} + @endif + + php artisan migrate --force + @endtask + +### Stories + +Stories group a set of tasks under a single, convenient name. For instance, a +`deploy` story may run the `update-code` and `install-dependencies` tasks by +listing the task names within its definition: + + + + 1@servers(['web' => ['[[email protected]](/cdn-cgi/l/email-protection)']]) + + 2  + + 3@story('deploy') + + 4 update-code + + 5 install-dependencies + + 6@endstory + + 7  + + 8@task('update-code') + + 9 cd /home/user/example.com + + 10 git pull origin master + + 11@endtask + + 12  + + 13@task('install-dependencies') + + 14 cd /home/user/example.com + + 15 composer install + + 16@endtask + + + @servers(['web' => ['[[email protected]](/cdn-cgi/l/email-protection)']]) + + @story('deploy') + update-code + install-dependencies + @endstory + + @task('update-code') + cd /home/user/example.com + git pull origin master + @endtask + + @task('install-dependencies') + cd /home/user/example.com + composer install + @endtask + +Once the story has been written, you may invoke it in the same way you would +invoke a task: + + + + 1php vendor/bin/envoy run deploy + + + php vendor/bin/envoy run deploy + +### Hooks + +When tasks and stories run, a number of hooks are executed. The hook types +supported by Envoy are `@before`, `@after`, `@error`, `@success`, and +`@finished`. All of the code in these hooks is interpreted as PHP and executed +locally, not on the remote servers that your tasks interact with. + +You may define as many of each of these hooks as you like. They will be +executed in the order that they appear in your Envoy script. + +#### `@before` + +Before each task execution, all of the `@before` hooks registered in your +Envoy script will execute. The `@before` hooks receive the name of the task +that will be executed: + + + + 1@before + + 2 if ($task === 'deploy') { + + 3 // ... + + 4 } + + 5@endbefore + + + @before + if ($task === 'deploy') { + // ... + } + @endbefore + +#### `@after` + +After each task execution, all of the `@after` hooks registered in your Envoy +script will execute. The `@after` hooks receive the name of the task that was +executed: + + + + 1@after + + 2 if ($task === 'deploy') { + + 3 // ... + + 4 } + + 5@endafter + + + @after + if ($task === 'deploy') { + // ... + } + @endafter + +#### `@error` + +After every task failure (exits with a status code greater than `0`), all of +the `@error` hooks registered in your Envoy script will execute. The `@error` +hooks receive the name of the task that was executed: + + + + 1@error + + 2 if ($task === 'deploy') { + + 3 // ... + + 4 } + + 5@enderror + + + @error + if ($task === 'deploy') { + // ... + } + @enderror + +#### `@success` + +If all tasks have executed without errors, all of the `@success` hooks +registered in your Envoy script will execute: + + + + 1@success + + 2 // ... + + 3@endsuccess + + + @success + // ... + @endsuccess + +#### `@finished` + +After all tasks have been executed (regardless of exit status), all of the +`@finished` hooks will be executed. The `@finished` hooks receive the status +code of the completed task, which may be `null` or an `integer` greater than +or equal to `0`: + + + + 1@finished + + 2 if ($exitCode > 0) { + + 3 // There were errors in one of the tasks... + + 4 } + + 5@endfinished + + + @finished + if ($exitCode > 0) { + // There were errors in one of the tasks... + } + @endfinished + +## Running Tasks + +To run a task or story that is defined in your application's `Envoy.blade.php` +file, execute Envoy's `run` command, passing the name of the task or story you +would like to execute. Envoy will execute the task and display the output from +your remote servers as the task is running: + + + + 1php vendor/bin/envoy run deploy + + + php vendor/bin/envoy run deploy + +### Confirming Task Execution + +If you would like to be prompted for confirmation before running a given task +on your servers, you should add the `confirm` directive to your task +declaration. This option is particularly useful for destructive operations: + + + + 1@task('deploy', ['on' => 'web', 'confirm' => true]) + + 2 cd /home/user/example.com + + 3 git pull origin {{ $branch }} + + 4 php artisan migrate + + 5@endtask + + + @task('deploy', ['on' => 'web', 'confirm' => true]) + cd /home/user/example.com + git pull origin {{ $branch }} + php artisan migrate + @endtask + +## Notifications + +### Slack + +Envoy supports sending notifications to [Slack](https://slack.com) after each +task is executed. The `@slack` directive accepts a Slack hook URL and a +channel / user name. You may retrieve your webhook URL by creating an +"Incoming WebHooks" integration in your Slack control panel. + +You should pass the entire webhook URL as the first argument given to the +`@slack` directive. The second argument given to the `@slack` directive should +be a channel name (`#channel`) or a user name (`@user`): + + + + 1@finished + + 2 @slack('webhook-url', '#bots') + + 3@endfinished + + + @finished + @slack('webhook-url', '#bots') + @endfinished + +By default, Envoy notifications will send a message to the notification +channel describing the task that was executed. However, you may overwrite this +message with your own custom message by passing a third argument to the +`@slack` directive: + + + + 1@finished + + 2 @slack('webhook-url', '#bots', 'Hello, Slack.') + + 3@endfinished + + + @finished + @slack('webhook-url', '#bots', 'Hello, Slack.') + @endfinished + +### Discord + +Envoy also supports sending notifications to [Discord](https://discord.com) +after each task is executed. The `@discord` directive accepts a Discord hook +URL and a message. You may retrieve your webhook URL by creating a "Webhook" +in your Server Settings and choosing which channel the webhook should post to. +You should pass the entire Webhook URL into the `@discord` directive: + + + + 1@finished + + 2 @discord('discord-webhook-url') + + 3@endfinished + + + @finished + @discord('discord-webhook-url') + @endfinished + +### Telegram + +Envoy also supports sending notifications to [Telegram](https://telegram.org) +after each task is executed. The `@telegram` directive accepts a Telegram Bot +ID and a Chat ID. You may retrieve your Bot ID by creating a new bot using +[BotFather](https://t.me/botfather). You can retrieve a valid Chat ID using +[@username_to_id_bot](https://t.me/username_to_id_bot). You should pass the +entire Bot ID and Chat ID into the `@telegram` directive: + + + + 1@finished + + 2 @telegram('bot-id','chat-id') + + 3@endfinished + + + @finished + @telegram('bot-id','chat-id') + @endfinished + +### Microsoft Teams + +Envoy also supports sending notifications to [Microsoft +Teams](https://www.microsoft.com/en-us/microsoft-teams) after each task is +executed. The `@microsoftTeams` directive accepts a Teams Webhook (required), +a message, theme color (success, info, warning, error), and an array of +options. You may retrieve your Teams Webhook by creating a new [incoming +webhook](https://docs.microsoft.com/en-us/microsoftteams/platform/webhooks- +and-connectors/how-to/add-incoming-webhook). The Teams API has many other +attributes to customize your message box like title, summary, and sections. +You can find more information on the [Microsoft Teams +documentation](https://docs.microsoft.com/en- +us/microsoftteams/platform/webhooks-and-connectors/how-to/connectors- +using?tabs=cURL#example-of-connector-message). You should pass the entire +Webhook URL into the `@microsoftTeams` directive: + + + + 1@finished + + 2 @microsoftTeams('webhook-url') + + 3@endfinished + + + @finished + @microsoftTeams('webhook-url') + @endfinished + diff --git a/output/12.x/errors.md b/output/12.x/errors.md new file mode 100644 index 0000000..4fc858b --- /dev/null +++ b/output/12.x/errors.md @@ -0,0 +1,1144 @@ +# Error Handling + + * Introduction + * Configuration + * Handling Exceptions + * Reporting Exceptions + * Exception Log Levels + * Ignoring Exceptions by Type + * Rendering Exceptions + * Reportable and Renderable Exceptions + * Throttling Reported Exceptions + * HTTP Exceptions + * Custom HTTP Error Pages + +## Introduction + +When you start a new Laravel project, error and exception handling is already +configured for you; however, at any point, you may use the `withExceptions` +method in your application's `bootstrap/app.php` to manage how exceptions are +reported and rendered by your application. + +The `$exceptions` object provided to the `withExceptions` closure is an +instance of `Illuminate\Foundation\Configuration\Exceptions` and is +responsible for managing exception handling in your application. We'll dive +deeper into this object throughout this documentation. + +## Configuration + +The `debug` option in your `config/app.php` configuration file determines how +much information about an error is actually displayed to the user. By default, +this option is set to respect the value of the `APP_DEBUG` environment +variable, which is stored in your `.env` file. + +During local development, you should set the `APP_DEBUG` environment variable +to `true`. **In your production environment, this value should always +be`false`. If the value is set to `true` in production, you risk exposing +sensitive configuration values to your application's end users.** + +## Handling Exceptions + +### Reporting Exceptions + +In Laravel, exception reporting is used to log exceptions or send them to an +external service like [Sentry](https://github.com/getsentry/sentry-laravel) or +[Flare](https://flareapp.io). By default, exceptions will be logged based on +your [logging](/docs/12.x/logging) configuration. However, you are free to log +exceptions however you wish. + +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: + + + + 1use App\Exceptions\InvalidOrderException; + + 2  + + 3->withExceptions(function (Exceptions $exceptions) { + + 4 $exceptions->report(function (InvalidOrderException $e) { + + 5 // ... + + 6 }); + + 7}) + + + use App\Exceptions\InvalidOrderException; + + ->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: + + + + 1use App\Exceptions\InvalidOrderException; + + 2  + + 3->withExceptions(function (Exceptions $exceptions) { + + 4 $exceptions->report(function (InvalidOrderException $e) { + + 5 // ... + + 6 })->stop(); + + 7  + + 8 $exceptions->report(function (InvalidOrderException $e) { + + 9 return false; + + 10 }); + + 11}) + + + use App\Exceptions\InvalidOrderException; + + ->withExceptions(function (Exceptions $exceptions) { + $exceptions->report(function (InvalidOrderException $e) { + // ... + })->stop(); + + $exceptions->report(function (InvalidOrderException $e) { + return false; + }); + }) + +To customize the exception reporting for a given exception, you may also +utilize [reportable exceptions](/docs/12.x/errors#renderable-exceptions). + +#### Global Log Context + +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: + + + + 1->withExceptions(function (Exceptions $exceptions) { + + 2 $exceptions->context(fn () => [ + + 3 'foo' => 'bar', + + 4 ]); + + 5}) + + + ->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: + + + + 1 + + 15 */ + + 16 public function context(): array + + 17 { + + 18 return ['order_id' => $this->orderId]; + + 19 } + + 20} + + + + */ + 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: + + + + 1public function isValid(string $value): bool + + 2{ + + 3 try { + + 4 // Validate the value... + + 5 } catch (Throwable $e) { + + 6 report($e); + + 7  + + 8 return false; + + 9 } + + 10} + + + public function isValid(string $value): bool + { + try { + // Validate the value... + } catch (Throwable $e) { + report($e); + + return false; + } + } + +#### Deduplicating Reported Exceptions + +If you are using the `report` function throughout your application, you may +occasionally report the same exception multiple times, creating duplicate +entries in your logs. + +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: + + + + 1->withExceptions(function (Exceptions $exceptions) { + + 2 $exceptions->dontReportDuplicates(); + + 3}) + + + ->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: + + + + 1$original = new RuntimeException('Whoops!'); + + 2  + + 3report($original); // reported + + 4  + + 5try { + + 6 throw $original; + + 7} catch (Throwable $caught) { + + 8 report($caught); // ignored + + 9} + + 10  + + 11report($original); // ignored + + 12report($caught); // ignored + + + $original = new RuntimeException('Whoops!'); + + report($original); // reported + + try { + throw $original; + } catch (Throwable $caught) { + report($caught); // ignored + } + + report($original); // ignored + report($caught); // ignored + +### Exception Log Levels + +When messages are written to your application's [logs](/docs/12.x/logging), +the messages are written at a specified [log level](/docs/12.x/logging#log- +levels), which indicates the severity or importance of the message being +logged. + +As noted above, even 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; however, since the log +level can sometimes influence the channels on which a message is logged, you +may wish to configure the log level that certain exceptions are logged at. + +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: + + + + 1use PDOException; + + 2use Psr\Log\LogLevel; + + 3  + + 4->withExceptions(function (Exceptions $exceptions) { + + 5 $exceptions->level(PDOException::class, LogLevel::CRITICAL); + + 6}) + + + use PDOException; + use Psr\Log\LogLevel; + + ->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: + + + + 1use App\Exceptions\InvalidOrderException; + + 2  + + 3->withExceptions(function (Exceptions $exceptions) { + + 4 $exceptions->dontReport([ + + 5 InvalidOrderException::class, + + 6 ]); + + 7}) + + + use App\Exceptions\InvalidOrderException; + + ->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: + + + + 1withExceptions(function (Exceptions $exceptions) { + + 5 $exceptions->dontReportWhen(function (Throwable $e) { + + 6 return $e instanceof PodcastProcessingException && + + 7 $e->reason() === 'Subscription expired'; + + 8 }); + + 9}) + + + use App\Exceptions\InvalidOrderException; + use Throwable; + + ->withExceptions(function (Exceptions $exceptions) { + $exceptions->dontReportWhen(function (Throwable $e) { + return $e instanceof PodcastProcessingException && + $e->reason() === 'Subscription expired'; + }); + }) + +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: + + + + 1use Symfony\Component\HttpKernel\Exception\HttpException; + + 2  + + 3->withExceptions(function (Exceptions $exceptions) { + + 4 $exceptions->stopIgnoring(HttpException::class); + + 5}) + + + use Symfony\Component\HttpKernel\Exception\HttpException; + + ->withExceptions(function (Exceptions $exceptions) { + $exceptions->stopIgnoring(HttpException::class); + }) + +### Rendering Exceptions + +By default, the Laravel exception handler will convert exceptions into an HTTP +response for you. However, you are free to register a custom rendering closure +for exceptions of a given type. You may accomplish this by using the `render` +exception method in your application's `bootstrap/app.php` file. + +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: + + + + 1use App\Exceptions\InvalidOrderException; + + 2use Illuminate\Http\Request; + + 3  + + 4->withExceptions(function (Exceptions $exceptions) { + + 5 $exceptions->render(function (InvalidOrderException $e, Request $request) { + + 6 return response()->view('errors.invalid-order', status: 500); + + 7 }); + + 8}) + + + 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: + + + + 1use Illuminate\Http\Request; + + 2use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; + + 3  + + 4->withExceptions(function (Exceptions $exceptions) { + + 5 $exceptions->render(function (NotFoundHttpException $e, Request $request) { + + 6 if ($request->is('api/*')) { + + 7 return response()->json([ + + 8 'message' => 'Record not found.' + + 9 ], 404); + + 10 } + + 11 }); + + 12}) + + + 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: + + + + 1use Illuminate\Http\Request; + + 2use Throwable; + + 3  + + 4->withExceptions(function (Exceptions $exceptions) { + + 5 $exceptions->shouldRenderJsonWhen(function (Request $request, Throwable $e) { + + 6 if ($request->is('admin/*')) { + + 7 return true; + + 8 } + + 9  + + 10 return $request->expectsJson(); + + 11 }); + + 12}) + + + use Illuminate\Http\Request; + use Throwable; + + ->withExceptions(function (Exceptions $exceptions) { + $exceptions->shouldRenderJsonWhen(function (Request $request, Throwable $e) { + if ($request->is('admin/*')) { + return true; + } + + 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: + + + + 1use Symfony\Component\HttpFoundation\Response; + + 2  + + 3->withExceptions(function (Exceptions $exceptions) { + + 4 $exceptions->respond(function (Response $response) { + + 5 if ($response->getStatusCode() === 419) { + + 6 return back()->with([ + + 7 'message' => 'The page expired, please try again.', + + 8 ]); + + 9 } + + 10  + + 11 return $response; + + 12 }); + + 13}) + + + 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; + }); + }) + +### 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: + + + + 1withExceptions(function (Exceptions $exceptions) { + + 5 $exceptions->throttle(function (Throwable $e) { + + 6 return Lottery::odds(1, 1000); + + 7 }); + + 8}) + + + 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: + + + + 1use App\Exceptions\ApiMonitoringException; + + 2use Illuminate\Support\Lottery; + + 3use Throwable; + + 4  + + 5->withExceptions(function (Exceptions $exceptions) { + + 6 $exceptions->throttle(function (Throwable $e) { + + 7 if ($e instanceof ApiMonitoringException) { + + 8 return Lottery::odds(1, 1000); + + 9 } + + 10 }); + + 11}) + + + 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); + } + }); + }) + +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: + + + + 1use Illuminate\Broadcasting\BroadcastException; + + 2use Illuminate\Cache\RateLimiting\Limit; + + 3use Throwable; + + 4  + + 5->withExceptions(function (Exceptions $exceptions) { + + 6 $exceptions->throttle(function (Throwable $e) { + + 7 if ($e instanceof BroadcastException) { + + 8 return Limit::perMinute(300); + + 9 } + + 10 }); + + 11}) + + + 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`: + + + + 1use Illuminate\Broadcasting\BroadcastException; + + 2use Illuminate\Cache\RateLimiting\Limit; + + 3use Throwable; + + 4  + + 5->withExceptions(function (Exceptions $exceptions) { + + 6 $exceptions->throttle(function (Throwable $e) { + + 7 if ($e instanceof BroadcastException) { + + 8 return Limit::perMinute(300)->by($e->getMessage()); + + 9 } + + 10 }); + + 11}) + + + 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: + + + + 1use App\Exceptions\ApiMonitoringException; + + 2use Illuminate\Broadcasting\BroadcastException; + + 3use Illuminate\Cache\RateLimiting\Limit; + + 4use Illuminate\Support\Lottery; + + 5use Throwable; + + 6  + + 7->withExceptions(function (Exceptions $exceptions) { + + 8 $exceptions->throttle(function (Throwable $e) { + + 9 return match (true) { + + 10 $e instanceof BroadcastException => Limit::perMinute(300), + + 11 $e instanceof ApiMonitoringException => Lottery::odds(1, 1000), + + 12 default => Limit::none(), + + 13 }; + + 14 }); + + 15}) + + + 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: + + + + 1abort(404); + + + 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: + + + + 1

    {{ $exception->getMessage() }}

    + + +

    {{ $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: + + + + 1php artisan vendor:publish --tag=laravel-errors + + + php artisan vendor:publish --tag=laravel-errors + +#### Fallback HTTP Error Pages + +You may also define a "fallback" error page for a given series of HTTP status +codes. This page will be rendered if there is not a corresponding page for the +specific HTTP status code that occurred. To accomplish this, define a +`4xx.blade.php` template and a `5xx.blade.php` template in your application's +`resources/views/errors` directory. + +When defining fallback error pages, the fallback pages will not affect `404`, +`500`, and `503` error responses since Laravel has internal, dedicated pages +for these status codes. To customize the pages rendered for these status +codes, you should define a custom error page for each of them individually. + diff --git a/output/12.x/events.md b/output/12.x/events.md new file mode 100644 index 0000000..acd3cb5 --- /dev/null +++ b/output/12.x/events.md @@ -0,0 +1,2827 @@ +# Events + + * Introduction + * Generating Events and Listeners + * Registering Events and Listeners + * Event Discovery + * Manually Registering Events + * Closure Listeners + * Defining Events + * Defining Listeners + * Queued Event Listeners + * Manually Interacting With the Queue + * Queued Event Listeners and Database Transactions + * Queued Listener Middleware + * Encrypted Queued Listeners + * Handling Failed Jobs + * Dispatching Events + * Dispatching Events After Database Transactions + * Deferring Events + * Event Subscribers + * Writing Event Subscribers + * Registering Event Subscribers + * Testing + * Faking a Subset of Events + * Scoped Events Fakes + +## Introduction + +Laravel's events provide a simple observer pattern implementation, allowing +you to subscribe and listen for various events that occur within your +application. Event classes are typically stored in the `app/Events` directory, +while their listeners are stored in `app/Listeners`. Don't worry if you don't +see these directories in your application as they will be created for you as +you generate events and listeners using Artisan console commands. + +Events serve as a great way to decouple various aspects of your application, +since a single event can have multiple listeners that do not depend on each +other. For example, you may wish to send a Slack notification to your user +each time an order has shipped. Instead of coupling your order processing code +to your Slack notification code, you can raise an `App\Events\OrderShipped` +event which a listener can receive and use to dispatch a Slack notification. + +## Generating Events and Listeners + +To quickly generate events and listeners, you may use the `make:event` and +`make:listener` Artisan commands: + + + + 1php artisan make:event PodcastProcessed + + 2  + + 3php artisan make:listener SendPodcastNotification --event=PodcastProcessed + + + php artisan make:event PodcastProcessed + + php artisan make:listener SendPodcastNotification --event=PodcastProcessed + +For convenience, you may also invoke the `make:event` and `make:listener` +Artisan commands without additional arguments. When you do so, Laravel will +automatically prompt you for the class name and, when creating a listener, the +event it should listen to: + + + + 1php artisan make:event + + 2  + + 3php artisan make:listener + + + php artisan make:event + + php artisan make:listener + +## Registering Events and Listeners + +### Event Discovery + +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: + + + + 1use App\Events\PodcastProcessed; + + 2  + + 3class SendPodcastNotification + + 4{ + + 5 /** + + 6 * Handle the event. + + 7 */ + + 8 public function handle(PodcastProcessed $event): void + + 9 { + + 10 // ... + + 11 } + + 12} + + + use App\Events\PodcastProcessed; + + class SendPodcastNotification + { + /** + * Handle the event. + */ + public function handle(PodcastProcessed $event): void + { + // ... + } + } + +You may listen to multiple events using PHP's union types: + + + + 1/** + + 2 * Handle the event. + + 3 */ + + 4public function handle(PodcastProcessed|PodcastPublished $event): void + + 5{ + + 6 // ... + + 7} + + + /** + * Handle the 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: + + + + 1->withEvents(discover: [ + + 2 __DIR__.'/../app/Domain/Orders/Listeners', + + 3]) + + + ->withEvents(discover: [ + __DIR__.'/../app/Domain/Orders/Listeners', + ]) + +You may scan for listeners in multiple similar directories using the `*` +character as a wildcard: + + + + 1->withEvents(discover: [ + + 2 __DIR__.'/../app/Domain/*/Listeners', + + 3]) + + + ->withEvents(discover: [ + __DIR__.'/../app/Domain/*/Listeners', + ]) + +The `event:list` command may be used to list all of the listeners registered +within your application: + + + + 1php artisan event:list + + + php artisan event:list + +#### Event Discovery in Production + +To give your application a speed boost, you should cache a manifest of all of +your application's listeners using the `optimize` or `event:cache` Artisan +commands. Typically, this command should be run as part of your application's +[deployment process](/docs/12.x/deployment#optimization). This manifest will +be used by the framework to speed up the event registration process. The +`event:clear` command may be used to destroy the event cache. + +### Manually Registering Events + +Using the `Event` facade, you may manually register events and their +corresponding listeners within the `boot` method of your application's +`AppServiceProvider`: + + + + 1use App\Domain\Orders\Events\PodcastProcessed; + + 2use App\Domain\Orders\Listeners\SendPodcastNotification; + + 3use Illuminate\Support\Facades\Event; + + 4  + + 5/** + + 6 * Bootstrap any application services. + + 7 */ + + 8public function boot(): void + + 9{ + + 10 Event::listen( + + 11 PodcastProcessed::class, + + 12 SendPodcastNotification::class, + + 13 ); + + 14} + + + 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, + ); + } + +The `event:list` command may be used to list all of the listeners registered +within your application: + + + + 1php artisan event:list + + + php artisan event:list + +### Closure Listeners + +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`: + + + + 1use App\Events\PodcastProcessed; + + 2use Illuminate\Support\Facades\Event; + + 3  + + 4/** + + 5 * Bootstrap any application services. + + 6 */ + + 7public function boot(): void + + 8{ + + 9 Event::listen(function (PodcastProcessed $event) { + + 10 // ... + + 11 }); + + 12} + + + use App\Events\PodcastProcessed; + use Illuminate\Support\Facades\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/12.x/queues): + + + + 1use App\Events\PodcastProcessed; + + 2use function Illuminate\Events\queueable; + + 3use Illuminate\Support\Facades\Event; + + 4  + + 5/** + + 6 * Bootstrap any application services. + + 7 */ + + 8public function boot(): void + + 9{ + + 10 Event::listen(queueable(function (PodcastProcessed $event) { + + 11 // ... + + 12 })); + + 13} + + + 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) { + // ... + })); + } + +Like queued jobs, you may use the `onConnection`, `onQueue`, and `delay` +methods to customize the execution of the queued listener: + + + + 1Event::listen(queueable(function (PodcastProcessed $event) { + + 2 // ... + + 3})->onConnection('redis')->onQueue('podcasts')->delay(now()->addSeconds(10))); + + + 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: + + + + 1use App\Events\PodcastProcessed; + + 2use function Illuminate\Events\queueable; + + 3use Illuminate\Support\Facades\Event; + + 4use Throwable; + + 5  + + 6Event::listen(queueable(function (PodcastProcessed $event) { + + 7 // ... + + 8})->catch(function (PodcastProcessed $event, Throwable $e) { + + 9 // The queued listener failed... + + 10})); + + + 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... + })); + +#### 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: + + + + 1Event::listen('event.*', function (string $eventName, array $data) { + + 2 // ... + + 3}); + + + 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/12.x/eloquent) object: + + + + 1order... + + 20 } + + 21} + + + order... + } + } + +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/12.x/container), so dependencies will be injected +automatically. + +#### Stopping The Propagation Of An Event + +Sometimes, you may wish to stop the propagation of an event to other +listeners. You may do so by returning `false` from your listener's `handle` +method. + +## Queued Event Listeners + +Queueing listeners can be beneficial if your listener is going to perform a +slow task such as sending an email or making an HTTP request. Before using +queued listeners, make sure to [configure your queue](/docs/12.x/queues) and +start a queue worker on your server or local development environment. + +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: + + + + 1highPriority ? 0 : 60; + + 23} + + + /** + * 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: + + + + 1order->subtotal >= 5000; + + 24 } + + 25} + + + 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: + + + + 1release(30); + + 20 } + + 21 } + + 22} + + + release(30); + } + } + } + +### Queued Event Listeners and Database Transactions + +When queued listeners are dispatched within database transactions, they may be +processed by the queue before the database transaction has committed. When +this happens, any updates you have made to models or database records during +the database transaction may not yet be reflected in the database. In +addition, any models or database records created within the transaction may +not exist in the database. If your listener depends on these models, +unexpected errors can occur when the job that dispatches the queued listener +is processed. + +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: + + + + 1 + + 23 */ + + 24 public function middleware(OrderShipped $event): array + + 25 { + + 26 return [new RateLimited]; + + 27 } + + 28} + + + + */ + public function middleware(OrderShipped $event): array + { + return [new RateLimited]; + } + } + +#### Encrypted Queued Listeners + +Laravel allows you to ensure the privacy and integrity of a queued listener's +data via [encryption](/docs/12.x/encryption). To get started, simply add the +`ShouldBeEncrypted` interface to the listener class. Once this interface has +been added to the class, Laravel will automatically encrypt your listener +before pushing it onto a queue: + + + + 1addMinutes(5); + + 9} + + + use DateTime; + + /** + * Determine the time at which the listener should timeout. + */ + public function retryUntil(): DateTime + { + return now()->addMinutes(5); + } + +If both `retryUntil` and `tries` are defined, Laravel gives precedence to the +`retryUntil` method. + +#### 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: + + + + 1/** + + 2 * The number of seconds to wait before retrying the queued listener. + + 3 * + + 4 * @var int + + 5 */ + + 6public $backoff = 3; + + + /** + * 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: + + + + 1/** + + 2 * Calculate the number of seconds to wait before retrying the queued listener. + + 3 */ + + 4public function backoff(OrderShipped $event): int + + 5{ + + 6 return 3; + + 7} + + + /** + * Calculate the number of seconds to wait before retrying the queued listener. + */ + public function backoff(OrderShipped $event): 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: + + + + 1/** + + 2 * Calculate the number of seconds to wait before retrying the queued listener. + + 3 * + + 4 * @return list + + 5 */ + + 6public function backoff(OrderShipped $event): array + + 7{ + + 8 return [1, 5, 10]; + + 9} + + + /** + * Calculate the number of seconds to wait before retrying the queued listener. + * + * @return list + */ + public function backoff(OrderShipped $event): array + { + return [1, 5, 10]; + } + +#### Specifying Queued Listener Max Exceptions + +Sometimes you may wish to specify that a queued listener 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 listener class: + + + + 1order_id); + + 18  + + 19 // Order shipment logic... + + 20  + + 21 OrderShipped::dispatch($order); + + 22  + + 23 return redirect('/orders'); + + 24 } + + 25} + + + order_id); + + // Order shipment logic... + + OrderShipped::dispatch($order); + + return redirect('/orders'); + } + } + +If you would like to conditionally dispatch an event, you may use the +`dispatchIf` and `dispatchUnless` methods: + + + + 1OrderShipped::dispatchIf($condition, $order); + + 2  + + 3OrderShipped::dispatchUnless($condition, $order); + + + OrderShipped::dispatchIf($condition, $order); + + OrderShipped::dispatchUnless($condition, $order); + +When testing, it can be helpful to assert that certain events were dispatched +without actually triggering their listeners. Laravel's built-in testing +helpers make it a cinch. + +### Dispatching Events After Database Transactions + +Sometimes, you may want to instruct Laravel to only dispatch an event after +the active database transaction has committed. To do so, you may implement the +`ShouldDispatchAfterCommit` interface on the event class. + +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: + + + + 1 'Victoria Otwell']); + + 6  + + 7 $user->posts()->create(['title' => 'My first post!']); + + 8}); + + + use App\Models\User; + use Illuminate\Support\Facades\Event; + + Event::defer(function () { + $user = User::create(['name' => 'Victoria Otwell']); + + $user->posts()->create(['title' => 'My first post!']); + }); + +All events triggered within the closure will be dispatched after the closure +is executed. This ensures that event listeners have access to all related +records that were created during the deferred execution. If an exception +occurs within the closure, the deferred events will not be dispatched. + +To defer only specific events, pass an array of events as the second argument +to the `defer` method: + + + + 1use App\Models\User; + + 2use Illuminate\Support\Facades\Event; + + 3  + + 4Event::defer(function () { + + 5 $user = User::create(['name' => 'Victoria Otwell']); + + 6  + + 7 $user->posts()->create(['title' => 'My first post!']); + + 8}, ['eloquent.created: '.User::class]); + + + use App\Models\User; + use Illuminate\Support\Facades\Event; + + Event::defer(function () { + $user = User::create(['name' => 'Victoria Otwell']); + + $user->posts()->create(['title' => 'My first post!']); + }, ['eloquent.created: '.User::class]); + +## Event Subscribers + +### Writing Event Subscribers + +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 receives an event dispatcher instance. You may call the `listen` +method on the given dispatcher to register event listeners: + + + + 1listen( + + 27 Login::class, + + 28 [UserEventSubscriber::class, 'handleUserLogin'] + + 29 ); + + 30  + + 31 $events->listen( + + 32 Logout::class, + + 33 [UserEventSubscriber::class, 'handleUserLogout'] + + 34 ); + + 35 } + + 36} + + + 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: + + + + 1 + + 25 */ + + 26 public function subscribe(Dispatcher $events): array + + 27 { + + 28 return [ + + 29 Login::class => 'handleUserLogin', + + 30 Logout::class => 'handleUserLogout', + + 31 ]; + + 32 } + + 33} + + + + */ + public function subscribe(Dispatcher $events): array + { + 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. 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`: + + + + 1order->id === $order->id; + + 3}); + + + 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: + + + + 1Event::assertListening( + + 2 OrderShipped::class, + + 3 SendShipmentNotification::class + + 4); + + + Event::assertListening( + OrderShipped::class, + SendShipmentNotification::class + ); + +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. + +### Faking a Subset of Events + +If you only want to fake event listeners for a specific set of events, you may +pass them to the `fake` or `fakeFor` method: + +Pest PHPUnit + + + + 1test('orders can be processed', function () { + + 2 Event::fake([ + + 3 OrderCreated::class, + + 4 ]); + + 5  + + 6 $order = Order::factory()->create(); + + 7  + + 8 Event::assertDispatched(OrderCreated::class); + + 9  + + 10 // Other events are dispatched as normal... + + 11 $order->update([ + + 12 // ... + + 13 ]); + + 14}); + + + test('orders can be processed', function () { + Event::fake([ + OrderCreated::class, + ]); + + $order = Order::factory()->create(); + + Event::assertDispatched(OrderCreated::class); + + // Other events are dispatched as normal... + $order->update([ + // ... + ]); + }); + + + 1/** + + 2 * Test order process. + + 3 */ + + 4public function test_orders_can_be_processed(): void + + 5{ + + 6 Event::fake([ + + 7 OrderCreated::class, + + 8 ]); + + 9  + + 10 $order = Order::factory()->create(); + + 11  + + 12 Event::assertDispatched(OrderCreated::class); + + 13  + + 14 // Other events are dispatched as normal... + + 15 $order->update([ + + 16 // ... + + 17 ]); + + 18} + + + /** + * Test order process. + */ + public function test_orders_can_be_processed(): void + { + Event::fake([ + OrderCreated::class, + ]); + + $order = Order::factory()->create(); + + Event::assertDispatched(OrderCreated::class); + + // Other events are dispatched as normal... + $order->update([ + // ... + ]); + } + +You may fake all events except for a set of specified events using the +`except` method: + + + + 1Event::fake()->except([ + + 2 OrderCreated::class, + + 3]); + + + Event::fake()->except([ + OrderCreated::class, + ]); + +### Scoped Event Fakes + +If you only want to fake event listeners for a portion of your test, you may +use the `fakeFor` method: + +Pest PHPUnit + + + + 1create(); + + 10  + + 11 Event::assertDispatched(OrderCreated::class); + + 12  + + 13 return $order; + + 14 }); + + 15  + + 16 // Events are dispatched as normal and observers will run... + + 17 $order->update([ + + 18 // ... + + 19 ]); + + 20}); + + + create(); + + Event::assertDispatched(OrderCreated::class); + + return $order; + }); + + // Events are dispatched as normal and observers will run... + $order->update([ + // ... + ]); + }); + + + 1create(); + + 19  + + 20 Event::assertDispatched(OrderCreated::class); + + 21  + + 22 return $order; + + 23 }); + + 24  + + 25 // Events are dispatched as normal and observers will run... + + 26 $order->update([ + + 27 // ... + + 28 ]); + + 29 } + + 30} + + + create(); + + Event::assertDispatched(OrderCreated::class); + + return $order; + }); + + // Events are dispatched as normal and observers will run... + $order->update([ + // ... + ]); + } + } + diff --git a/output/12.x/facades.md b/output/12.x/facades.md new file mode 100644 index 0000000..7656c9f --- /dev/null +++ b/output/12.x/facades.md @@ -0,0 +1,810 @@ +# Facades + + * Introduction + * When to Utilize Facades + * Facades vs. Dependency Injection + * Facades vs. Helper Functions + * How Facades Work + * Real-Time Facades + * Facade Class Reference + +## Introduction + +Throughout the Laravel documentation, you will see examples of code that +interacts with Laravel's features via "facades". Facades provide a "static" +interface to classes that are available in the application's [service +container](/docs/12.x/container). Laravel ships with many facades which +provide access to almost all of Laravel's features. + +Laravel facades serve as "static proxies" to underlying classes in the service +container, providing the benefit of a terse, expressive syntax while +maintaining more testability and flexibility than traditional static methods. +It's perfectly fine if you don't totally understand how facades work - just go +with the flow and continue learning about Laravel. + +All of Laravel's facades are defined in the `Illuminate\Support\Facades` +namespace. So, we can easily access a facade like so: + + + + 1use Illuminate\Support\Facades\Cache; + + 2use Illuminate\Support\Facades\Route; + + 3  + + 4Route::get('/cache', function () { + + 5 return Cache::get('key'); + + 6}); + + + use Illuminate\Support\Facades\Cache; + use Illuminate\Support\Facades\Route; + + 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. + +#### Helper Functions + +To complement facades, Laravel offers a variety of global "helper functions" +that make it even easier to interact with common Laravel features. Some of the +common helper functions you may interact with are `view`, `response`, `url`, +`config`, and more. Each helper function offered by Laravel is documented with +their corresponding feature; however, a complete list is available within the +dedicated [helper documentation](/docs/12.x/helpers). + +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: + + + + 1use Illuminate\Support\Facades\Response; + + 2  + + 3Route::get('/users', function () { + + 4 return Response::json([ + + 5 // ... + + 6 ]); + + 7}); + + 8  + + 9Route::get('/users', function () { + + 10 return response()->json([ + + 11 // ... + + 12 ]); + + 13}); + + + use Illuminate\Support\Facades\Response; + + Route::get('/users', function () { + return Response::json([ + // ... + ]); + }); + + Route::get('/users', function () { + return response()->json([ + // ... + ]); + }); + +## When to Utilize Facades + +Facades have many benefits. They provide a terse, memorable syntax that allows +you to use Laravel's features without remembering long class names that must +be injected or configured manually. Furthermore, because of their unique usage +of PHP's dynamic methods, they are easy to test. + +However, some care must be taken when using facades. The primary danger of +facades is class "scope creep". Since facades are so easy to use and do not +require injection, it can be easy to let your classes continue to grow and use +many facades in a single class. Using dependency injection, this potential is +mitigated by the visual feedback a large constructor gives you that your class +is growing too large. So, when using facades, pay special attention to the +size of your class so that its scope of responsibility stays narrow. If your +class is getting too large, consider splitting it into multiple smaller +classes. + +### Facades vs. Dependency Injection + +One of the primary benefits of dependency injection is the ability to swap +implementations of the injected class. This is useful during testing since you +can inject a mock or stub and assert that various methods were called on the +stub. + +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: + + + + 1use Illuminate\Support\Facades\Cache; + + 2  + + 3Route::get('/cache', function () { + + 4 return Cache::get('key'); + + 5}); + + + use Illuminate\Support\Facades\Cache; + + 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: + +Pest PHPUnit + + + + 1use Illuminate\Support\Facades\Cache; + + 2  + + 3test('basic example', function () { + + 4 Cache::shouldReceive('get') + + 5 ->with('key') + + 6 ->andReturn('value'); + + 7  + + 8 $response = $this->get('/cache'); + + 9  + + 10 $response->assertSee('value'); + + 11}); + + + use Illuminate\Support\Facades\Cache; + + test('basic example', function () { + Cache::shouldReceive('get') + ->with('key') + ->andReturn('value'); + + $response = $this->get('/cache'); + + $response->assertSee('value'); + }); + + + 1use Illuminate\Support\Facades\Cache; + + 2  + + 3/** + + 4 * A basic functional test example. + + 5 */ + + 6public function test_basic_example(): void + + 7{ + + 8 Cache::shouldReceive('get') + + 9 ->with('key') + + 10 ->andReturn('value'); + + 11  + + 12 $response = $this->get('/cache'); + + 13  + + 14 $response->assertSee('value'); + + 15} + + + use Illuminate\Support\Facades\Cache; + + /** + * A basic functional test example. + */ + public function test_basic_example(): void + { + Cache::shouldReceive('get') + ->with('key') + ->andReturn('value'); + + $response = $this->get('/cache'); + + $response->assertSee('value'); + } + +### Facades vs. Helper Functions + +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: + + + + 1return Illuminate\Support\Facades\View::make('profile'); + + 2  + + 3return view('profile'); + + + return Illuminate\Support\Facades\View::make('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: + + + + 1Route::get('/cache', function () { + + 2 return cache('key'); + + 3}); + + + 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: + + + + 1use Illuminate\Support\Facades\Cache; + + 2  + + 3/** + + 4 * A basic functional test example. + + 5 */ + + 6public function test_basic_example(): void + + 7{ + + 8 Cache::shouldReceive('get') + + 9 ->with('key') + + 10 ->andReturn('value'); + + 11  + + 12 $response = $this->get('/cache'); + + 13  + + 14 $response->assertSee('value'); + + 15} + + + use Illuminate\Support\Facades\Cache; + + /** + * A basic functional test example. + */ + public function test_basic_example(): void + { + Cache::shouldReceive('get') + ->with('key') + ->andReturn('value'); + + $response = $this->get('/cache'); + + $response->assertSee('value'); + } + +## How Facades Work + +In a Laravel application, a facade is a class that provides access to an +object from the container. The machinery that makes this work is in the +`Facade` class. Laravel's facades, and any custom facades you create, will +extend the base `Illuminate\Support\Facades\Facade` class. + +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: + + + + 1 $user]); + + 18 } + + 19} + + + $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`: + + + + 1class Cache extends Facade + + 2{ + + 3 /** + + 4 * Get the registered name of the component. + + 5 */ + + 6 protected static function getFacadeAccessor(): string + + 7 { + + 8 return 'cache'; + + 9 } + + 10} + + + class Cache extends Facade + { + /** + * Get the registered name of the component. + */ + protected static function getFacadeAccessor(): string + { + 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/12.x/container) and runs the requested method (in this case, +`get`) against that object. + +## Real-Time Facades + +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: + + + + 1update(['publishing' => now()]); + + 16  + + 17 $publisher->publish($this); + + 18 } + + 19} + + + 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`: + + + + 1update(['publishing' => now()]); + + 18  + + 19 $publisher->publish($this); + + 20 Publisher::publish($this); + + 21 } + + 22} + + + update(['publishing' => now()]); + + $publisher->publish($this); + Publisher::publish($this); + } + } + +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: + +Pest PHPUnit + + + + 1use(RefreshDatabase::class); + + 8  + + 9test('podcast can be published', function () { + + 10 $podcast = Podcast::factory()->create(); + + 11  + + 12 Publisher::shouldReceive('publish')->once()->with($podcast); + + 13  + + 14 $podcast->publish(); + + 15}); + + + use(RefreshDatabase::class); + + test('podcast can be published', function () { + $podcast = Podcast::factory()->create(); + + Publisher::shouldReceive('publish')->once()->with($podcast); + + $podcast->publish(); + }); + + + 1create(); + + 20  + + 21 Publisher::shouldReceive('publish')->once()->with($podcast); + + 22  + + 23 $podcast->publish(); + + 24 } + + 25} + + + create(); + + Publisher::shouldReceive('publish')->once()->with($podcast); + + $podcast->publish(); + } + } + +## Facade Class Reference + +Below you will find every facade and its underlying class. This is a useful +tool for quickly digging into the API documentation for a given facade root. +The [service container binding](/docs/12.x/container) key is also included +where applicable. + +Facade | Class | Service Container Binding +---|---|--- +App | [Illuminate\Foundation\Application](https://api.laravel.com/docs/12.x/Illuminate/Foundation/Application.html) | `app` +Artisan | [Illuminate\Contracts\Console\Kernel](https://api.laravel.com/docs/12.x/Illuminate/Contracts/Console/Kernel.html) | `artisan` +Auth (Instance) | [Illuminate\Contracts\Auth\Guard](https://api.laravel.com/docs/12.x/Illuminate/Contracts/Auth/Guard.html) | `auth.driver` +Auth | [Illuminate\Auth\AuthManager](https://api.laravel.com/docs/12.x/Illuminate/Auth/AuthManager.html) | `auth` +Blade | [Illuminate\View\Compilers\BladeCompiler](https://api.laravel.com/docs/12.x/Illuminate/View/Compilers/BladeCompiler.html) | `blade.compiler` +Broadcast (Instance) | [Illuminate\Contracts\Broadcasting\Broadcaster](https://api.laravel.com/docs/12.x/Illuminate/Contracts/Broadcasting/Broadcaster.html) | +Broadcast | [Illuminate\Contracts\Broadcasting\Factory](https://api.laravel.com/docs/12.x/Illuminate/Contracts/Broadcasting/Factory.html) | +Bus | [Illuminate\Contracts\Bus\Dispatcher](https://api.laravel.com/docs/12.x/Illuminate/Contracts/Bus/Dispatcher.html) | +Cache (Instance) | [Illuminate\Cache\Repository](https://api.laravel.com/docs/12.x/Illuminate/Cache/Repository.html) | `cache.store` +Cache | [Illuminate\Cache\CacheManager](https://api.laravel.com/docs/12.x/Illuminate/Cache/CacheManager.html) | `cache` +Config | [Illuminate\Config\Repository](https://api.laravel.com/docs/12.x/Illuminate/Config/Repository.html) | `config` +Context | [Illuminate\Log\Context\Repository](https://api.laravel.com/docs/12.x/Illuminate/Log/Context/Repository.html) | +Cookie | [Illuminate\Cookie\CookieJar](https://api.laravel.com/docs/12.x/Illuminate/Cookie/CookieJar.html) | `cookie` +Crypt | [Illuminate\Encryption\Encrypter](https://api.laravel.com/docs/12.x/Illuminate/Encryption/Encrypter.html) | `encrypter` +Date | [Illuminate\Support\DateFactory](https://api.laravel.com/docs/12.x/Illuminate/Support/DateFactory.html) | `date` +DB (Instance) | [Illuminate\Database\Connection](https://api.laravel.com/docs/12.x/Illuminate/Database/Connection.html) | `db.connection` +DB | [Illuminate\Database\DatabaseManager](https://api.laravel.com/docs/12.x/Illuminate/Database/DatabaseManager.html) | `db` +Event | [Illuminate\Events\Dispatcher](https://api.laravel.com/docs/12.x/Illuminate/Events/Dispatcher.html) | `events` +Exceptions (Instance) | [Illuminate\Contracts\Debug\ExceptionHandler](https://api.laravel.com/docs/12.x/Illuminate/Contracts/Debug/ExceptionHandler.html) | +Exceptions | [Illuminate\Foundation\Exceptions\Handler](https://api.laravel.com/docs/12.x/Illuminate/Foundation/Exceptions/Handler.html) | +File | [Illuminate\Filesystem\Filesystem](https://api.laravel.com/docs/12.x/Illuminate/Filesystem/Filesystem.html) | `files` +Gate | [Illuminate\Contracts\Auth\Access\Gate](https://api.laravel.com/docs/12.x/Illuminate/Contracts/Auth/Access/Gate.html) | +Hash | [Illuminate\Contracts\Hashing\Hasher](https://api.laravel.com/docs/12.x/Illuminate/Contracts/Hashing/Hasher.html) | `hash` +Http | [Illuminate\Http\Client\Factory](https://api.laravel.com/docs/12.x/Illuminate/Http/Client/Factory.html) | +Lang | [Illuminate\Translation\Translator](https://api.laravel.com/docs/12.x/Illuminate/Translation/Translator.html) | `translator` +Log | [Illuminate\Log\LogManager](https://api.laravel.com/docs/12.x/Illuminate/Log/LogManager.html) | `log` +Mail | [Illuminate\Mail\Mailer](https://api.laravel.com/docs/12.x/Illuminate/Mail/Mailer.html) | `mailer` +Notification | [Illuminate\Notifications\ChannelManager](https://api.laravel.com/docs/12.x/Illuminate/Notifications/ChannelManager.html) | +Password (Instance) | [Illuminate\Auth\Passwords\PasswordBroker](https://api.laravel.com/docs/12.x/Illuminate/Auth/Passwords/PasswordBroker.html) | `auth.password.broker` +Password | [Illuminate\Auth\Passwords\PasswordBrokerManager](https://api.laravel.com/docs/12.x/Illuminate/Auth/Passwords/PasswordBrokerManager.html) | `auth.password` +Pipeline (Instance) | [Illuminate\Pipeline\Pipeline](https://api.laravel.com/docs/12.x/Illuminate/Pipeline/Pipeline.html) | +Process | [Illuminate\Process\Factory](https://api.laravel.com/docs/12.x/Illuminate/Process/Factory.html) | +Queue (Base Class) | [Illuminate\Queue\Queue](https://api.laravel.com/docs/12.x/Illuminate/Queue/Queue.html) | +Queue (Instance) | [Illuminate\Contracts\Queue\Queue](https://api.laravel.com/docs/12.x/Illuminate/Contracts/Queue/Queue.html) | `queue.connection` +Queue | [Illuminate\Queue\QueueManager](https://api.laravel.com/docs/12.x/Illuminate/Queue/QueueManager.html) | `queue` +RateLimiter | [Illuminate\Cache\RateLimiter](https://api.laravel.com/docs/12.x/Illuminate/Cache/RateLimiter.html) | +Redirect | [Illuminate\Routing\Redirector](https://api.laravel.com/docs/12.x/Illuminate/Routing/Redirector.html) | `redirect` +Redis (Instance) | [Illuminate\Redis\Connections\Connection](https://api.laravel.com/docs/12.x/Illuminate/Redis/Connections/Connection.html) | `redis.connection` +Redis | [Illuminate\Redis\RedisManager](https://api.laravel.com/docs/12.x/Illuminate/Redis/RedisManager.html) | `redis` +Request | [Illuminate\Http\Request](https://api.laravel.com/docs/12.x/Illuminate/Http/Request.html) | `request` +Response (Instance) | [Illuminate\Http\Response](https://api.laravel.com/docs/12.x/Illuminate/Http/Response.html) | +Response | [Illuminate\Contracts\Routing\ResponseFactory](https://api.laravel.com/docs/12.x/Illuminate/Contracts/Routing/ResponseFactory.html) | +Route | [Illuminate\Routing\Router](https://api.laravel.com/docs/12.x/Illuminate/Routing/Router.html) | `router` +Schedule | [Illuminate\Console\Scheduling\Schedule](https://api.laravel.com/docs/12.x/Illuminate/Console/Scheduling/Schedule.html) | +Schema | [Illuminate\Database\Schema\Builder](https://api.laravel.com/docs/12.x/Illuminate/Database/Schema/Builder.html) | +Session (Instance) | [Illuminate\Session\Store](https://api.laravel.com/docs/12.x/Illuminate/Session/Store.html) | `session.store` +Session | [Illuminate\Session\SessionManager](https://api.laravel.com/docs/12.x/Illuminate/Session/SessionManager.html) | `session` +Storage (Instance) | [Illuminate\Contracts\Filesystem\Filesystem](https://api.laravel.com/docs/12.x/Illuminate/Contracts/Filesystem/Filesystem.html) | `filesystem.disk` +Storage | [Illuminate\Filesystem\FilesystemManager](https://api.laravel.com/docs/12.x/Illuminate/Filesystem/FilesystemManager.html) | `filesystem` +URL | [Illuminate\Routing\UrlGenerator](https://api.laravel.com/docs/12.x/Illuminate/Routing/UrlGenerator.html) | `url` +Validator (Instance) | [Illuminate\Validation\Validator](https://api.laravel.com/docs/12.x/Illuminate/Validation/Validator.html) | +Validator | [Illuminate\Validation\Factory](https://api.laravel.com/docs/12.x/Illuminate/Validation/Factory.html) | `validator` +View (Instance) | [Illuminate\View\View](https://api.laravel.com/docs/12.x/Illuminate/View/View.html) | +View | [Illuminate\View\Factory](https://api.laravel.com/docs/12.x/Illuminate/View/Factory.html) | `view` +Vite | [Illuminate\Foundation\Vite](https://api.laravel.com/docs/12.x/Illuminate/Foundation/Vite.html) | + diff --git a/output/12.x/filesystem.md b/output/12.x/filesystem.md new file mode 100644 index 0000000..84e9c15 --- /dev/null +++ b/output/12.x/filesystem.md @@ -0,0 +1,1881 @@ +# File Storage + + * Introduction + * Configuration + * The Local Driver + * The Public Disk + * Driver Prerequisites + * Scoped and Read-Only Filesystems + * Amazon S3 Compatible Filesystems + * Obtaining Disk Instances + * On-Demand Disks + * Retrieving Files + * Downloading Files + * File URLs + * Temporary URLs + * File Metadata + * Storing Files + * Prepending and Appending To Files + * Copying and Moving Files + * Automatic Streaming + * File Uploads + * File Visibility + * Deleting Files + * Directories + * Testing + * Custom Filesystems + +## Introduction + +Laravel provides a powerful filesystem abstraction thanks to the wonderful +[Flysystem](https://github.com/thephpleague/flysystem) PHP package by Frank de +Jonge. The Laravel Flysystem integration provides simple drivers for working +with local filesystems, SFTP, and Amazon S3. Even better, it's amazingly +simple to switch between these storage options between your local development +machine and production server as the API remains the same for each system. + +## Configuration + +Laravel's filesystem configuration file is located at +`config/filesystems.php`. Within this file, you may configure all of your +filesystem "disks". Each disk represents a particular storage driver and +storage location. Example configurations for each supported driver are +included in the configuration file so you can modify the configuration to +reflect your storage preferences and credentials. + +The `local` driver interacts with files stored locally on the server running +the Laravel application, while the `sftp` storage driver is used for SSH key- +based FTP. The `s3` driver is used to write to Amazon's S3 cloud storage +service. + +You may configure as many disks as you like and may even have multiple disks +that use the same driver. + +### The Local Driver + +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`: + + + + 1use Illuminate\Support\Facades\Storage; + + 2  + + 3Storage::disk('local')->put('example.txt', 'Contents'); + + + use Illuminate\Support\Facades\Storage; + + Storage::disk('local')->put('example.txt', 'Contents'); + +### The Public Disk + +The `public` disk included in your application's `filesystems` configuration +file is intended for files that are going to be publicly accessible. By +default, the `public` disk uses the `local` driver and stores its files in +`storage/app/public`. + +If your `public` disk uses the `local` driver and you want to make these files +accessible from the web, you should create a symbolic link from source +directory `storage/app/public` to target directory `public/storage`: + +To create the symbolic link, you may use the `storage:link` Artisan command: + + + + 1php artisan storage:link + + + 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: + + + + 1echo asset('storage/file.txt'); + + + 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: + + + + 1'links' => [ + + 2 public_path('storage') => storage_path('app/public'), + + 3 public_path('images') => storage_path('app/images'), + + 4], + + + '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: + + + + 1php artisan storage:unlink + + + php artisan storage:unlink + +### Driver Prerequisites + +#### S3 Driver Configuration + +Before using the S3 driver, you will need to install the Flysystem S3 package +via the Composer package manager: + + + + 1composer require league/flysystem-aws-s3-v3 "^3.0" --with-all-dependencies + + + composer require league/flysystem-aws-s3-v3 "^3.0" --with-all-dependencies + +An S3 disk configuration array is located in your `config/filesystems.php` +configuration file. Typically, you should configure your S3 information and +credentials using the following environment variables which are referenced by +the `config/filesystems.php` configuration file: + + + + 1AWS_ACCESS_KEY_ID= + + 2AWS_SECRET_ACCESS_KEY= + + 3AWS_DEFAULT_REGION=us-east-1 + + 4AWS_BUCKET= + + 5AWS_USE_PATH_STYLE_ENDPOINT=false + + + AWS_ACCESS_KEY_ID= + AWS_SECRET_ACCESS_KEY= + AWS_DEFAULT_REGION=us-east-1 + AWS_BUCKET= + AWS_USE_PATH_STYLE_ENDPOINT=false + +For convenience, these environment variables match the naming convention used +by the AWS CLI. + +#### FTP Driver Configuration + +Before using the FTP driver, you will need to install the Flysystem FTP +package via the Composer package manager: + + + + 1composer require league/flysystem-ftp "^3.0" + + + 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: + + + + 1'ftp' => [ + + 2 'driver' => 'ftp', + + 3 'host' => env('FTP_HOST'), + + 4 'username' => env('FTP_USERNAME'), + + 5 'password' => env('FTP_PASSWORD'), + + 6  + + 7 // Optional FTP Settings... + + 8 // 'port' => env('FTP_PORT', 21), + + 9 // 'root' => env('FTP_ROOT'), + + 10 // 'passive' => true, + + 11 // 'ssl' => true, + + 12 // 'timeout' => 30, + + 13], + + + '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 + +Before using the SFTP driver, you will need to install the Flysystem SFTP +package via the Composer package manager: + + + + 1composer require league/flysystem-sftp-v3 "^3.0" + + + 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: + + + + 1'sftp' => [ + + 2 'driver' => 'sftp', + + 3 'host' => env('SFTP_HOST'), + + 4  + + 5 // Settings for basic authentication... + + 6 'username' => env('SFTP_USERNAME'), + + 7 'password' => env('SFTP_PASSWORD'), + + 8  + + 9 // Settings for SSH key-based authentication with encryption password... + + 10 'privateKey' => env('SFTP_PRIVATE_KEY'), + + 11 'passphrase' => env('SFTP_PASSPHRASE'), + + 12  + + 13 // Settings for file / directory permissions... + + 14 'visibility' => 'private', // `private` = 0600, `public` = 0644 + + 15 'directory_visibility' => 'private', // `private` = 0700, `public` = 0755 + + 16  + + 17 // Optional SFTP Settings... + + 18 // 'hostFingerprint' => env('SFTP_HOST_FINGERPRINT'), + + 19 // 'maxTries' => 4, + + 20 // 'passphrase' => env('SFTP_PASSPHRASE'), + + 21 // 'port' => env('SFTP_PORT', 22), + + 22 // 'root' => env('SFTP_ROOT', ''), + + 23 // 'timeout' => 30, + + 24 // 'useAgent' => true, + + 25], + + + '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 + +Scoped disks allow you to define a filesystem where all paths are +automatically prefixed with a given path prefix. Before creating a scoped +filesystem disk, you will need to install an additional Flysystem package via +the Composer package manager: + + + + 1composer require league/flysystem-path-prefixing "^3.0" + + + composer require league/flysystem-path-prefixing "^3.0" + +You may create a path scoped instance of any existing filesystem disk by +defining a disk that utilizes the `scoped` driver. For example, you may create +a disk which scopes your existing `s3` disk to a specific path prefix, and +then every file operation using your scoped disk will utilize the specified +prefix: + + + + 1's3-videos' => [ + + 2 'driver' => 'scoped', + + 3 'disk' => 's3', + + 4 'prefix' => 'path/to/videos', + + 5], + + + 's3-videos' => [ + 'driver' => 'scoped', + 'disk' => 's3', + 'prefix' => 'path/to/videos', + ], + +"Read-only" disks allow you to create filesystem disks that do not allow write +operations. Before using the `read-only` configuration option, you will need +to install an additional Flysystem package via the Composer package manager: + + + + 1composer require league/flysystem-read-only "^3.0" + + + composer require league/flysystem-read-only "^3.0" + +Next, you may include the `read-only` configuration option in one or more of +your disk's configuration arrays: + + + + 1's3-videos' => [ + + 2 'driver' => 's3', + + 3 // ... + + 4 'read-only' => true, + + 5], + + + 's3-videos' => [ + 'driver' => 's3', + // ... + 'read-only' => true, + ], + +### Amazon S3 Compatible Filesystems + +By default, your application's `filesystems` configuration file contains a +disk configuration for the `s3` disk. In addition to using this disk to +interact with [Amazon S3](https://aws.amazon.com/s3/), you may use it to +interact with any S3-compatible file storage service such as +[MinIO](https://github.com/minio/minio), [DigitalOcean +Spaces](https://www.digitalocean.com/products/spaces/), [Vultr Object +Storage](https://www.vultr.com/products/object-storage/), [Cloudflare +R2](https://www.cloudflare.com/developer-platform/products/r2/), or [Hetzner +Cloud Storage](https://www.hetzner.com/storage/object-storage/). + +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: + + + + 1'endpoint' => env('AWS_ENDPOINT', 'https://minio:9000'), + + + 'endpoint' => env('AWS_ENDPOINT', 'https://minio:9000'), + +#### MinIO + +In order for Laravel's Flysystem integration to generate proper URLs when +using MinIO, you should define the `AWS_URL` environment variable so that it +matches your application's local URL and includes the bucket name in the URL +path: + + + + 1AWS_URL=http://localhost:9000/local + + + AWS_URL=http://localhost:9000/local + +Generating temporary storage URLs via the `temporaryUrl` method may not work +when using MinIO if the `endpoint` is not accessible by the client. + +## Obtaining Disk Instances + +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: + + + + 1use Illuminate\Support\Facades\Storage; + + 2  + + 3Storage::put('avatars/1', $content); + + + use Illuminate\Support\Facades\Storage; + + 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: + + + + 1Storage::disk('s3')->put('avatars/1', $content); + + + Storage::disk('s3')->put('avatars/1', $content); + +### On-Demand Disks + +Sometimes you may wish to create a disk at runtime using a given configuration +without that configuration actually being present in your application's +`filesystems` configuration file. To accomplish this, you may pass a +configuration array to the `Storage` facade's `build` method: + + + + 1use Illuminate\Support\Facades\Storage; + + 2  + + 3$disk = Storage::build([ + + 4 'driver' => 'local', + + 5 'root' => '/path/to/root', + + 6]); + + 7  + + 8$disk->put('image.jpg', $content); + + + use Illuminate\Support\Facades\Storage; + + $disk = Storage::build([ + 'driver' => 'local', + 'root' => '/path/to/root', + ]); + + $disk->put('image.jpg', $content); + +## Retrieving Files + +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: + + + + 1$contents = Storage::get('file.jpg'); + + + $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: + + + + 1$orders = Storage::json('orders.json'); + + + $orders = Storage::json('orders.json'); + +The `exists` method may be used to determine if a file exists on the disk: + + + + 1if (Storage::disk('s3')->exists('file.jpg')) { + + 2 // ... + + 3} + + + if (Storage::disk('s3')->exists('file.jpg')) { + // ... + } + +The `missing` method may be used to determine if a file is missing from the +disk: + + + + 1if (Storage::disk('s3')->missing('file.jpg')) { + + 2 // ... + + 3} + + + 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: + + + + 1return Storage::download('file.jpg'); + + 2  + + 3return Storage::download('file.jpg', $name, $headers); + + + return Storage::download('file.jpg'); + + 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: + + + + 1use Illuminate\Support\Facades\Storage; + + 2  + + 3$url = Storage::url('file.jpg'); + + + use Illuminate\Support\Facades\Storage; + + $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 at `public/storage` which points to the +`storage/app/public` directory. + +When using the `local` driver, the return value of `url` is not URL encoded. +For this reason, we recommend always storing your files using names that will +create valid URLs. + +#### URL Host Customization + +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: + + + + 1'public' => [ + + 2 'driver' => 'local', + + 3 'root' => storage_path('app/public'), + + 4 'url' => env('APP_URL').'/storage', + + 5 'visibility' => 'public', + + 6 'throw' => false, + + 7], + + + '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: + + + + 1use Illuminate\Support\Facades\Storage; + + 2  + + 3$url = Storage::temporaryUrl( + + 4 'file.jpg', now()->addMinutes(5) + + 5); + + + use Illuminate\Support\Facades\Storage; + + $url = Storage::temporaryUrl( + 'file.jpg', now()->addMinutes(5) + ); + +#### Enabling Local Temporary URLs + +If you started developing your application before support for temporary URLs +was introduced to the `local` driver, you may need to enable local temporary +URLs. To do so, add the `serve` option to your `local` disk's configuration +array within the `config/filesystems.php` configuration file: + + + + 1'local' => [ + + 2 'driver' => 'local', + + 3 'root' => storage_path('app/private'), + + 4 'serve' => true, + + 5 'throw' => false, + + 6], + + + 'local' => [ + 'driver' => 'local', + 'root' => storage_path('app/private'), + 'serve' => true, + 'throw' => false, + ], + +#### S3 Request Parameters + +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: + + + + 1$url = Storage::temporaryUrl( + + 2 'file.jpg', + + 3 now()->addMinutes(5), + + 4 [ + + 5 'ResponseContentType' => 'application/octet-stream', + + 6 'ResponseContentDisposition' => 'attachment; filename=file2.jpg', + + 7 ] + + 8); + + + $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: + + + + 1buildTemporaryUrlsUsing( + + 18 function (string $path, DateTime $expiration, array $options) { + + 19 return URL::temporarySignedRoute( + + 20 'files.download', + + 21 $expiration, + + 22 array_merge($options, ['path' => $path]) + + 23 ); + + 24 } + + 25 ); + + 26 } + + 27} + + + buildTemporaryUrlsUsing( + function (string $path, DateTime $expiration, array $options) { + return URL::temporarySignedRoute( + 'files.download', + $expiration, + array_merge($options, ['path' => $path]) + ); + } + ); + } + } + +#### Temporary Upload URLs + +The ability to generate temporary upload URLs is only supported by the `s3` +driver. + +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: + + + + 1use Illuminate\Support\Facades\Storage; + + 2  + + 3['url' => $url, 'headers' => $headers] = Storage::temporaryUploadUrl( + + 4 'file.jpg', now()->addMinutes(5) + + 5); + + + use Illuminate\Support\Facades\Storage; + + ['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. + +### File Metadata + +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: + + + + 1use Illuminate\Support\Facades\Storage; + + 2  + + 3$size = Storage::size('file.jpg'); + + + use Illuminate\Support\Facades\Storage; + + $size = Storage::size('file.jpg'); + +The `lastModified` method returns the UNIX timestamp of the last time the file +was modified: + + + + 1$time = Storage::lastModified('file.jpg'); + + + $time = Storage::lastModified('file.jpg'); + +The MIME type of a given file may be obtained via the `mimeType` method: + + + + 1$mime = Storage::mimeType('file.jpg'); + + + $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: + + + + 1use Illuminate\Support\Facades\Storage; + + 2  + + 3$path = Storage::path('file.jpg'); + + + use Illuminate\Support\Facades\Storage; + + $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: + + + + 1use Illuminate\Support\Facades\Storage; + + 2  + + 3Storage::put('file.jpg', $contents); + + 4  + + 5Storage::put('file.jpg', $resource); + + + use Illuminate\Support\Facades\Storage; + + Storage::put('file.jpg', $contents); + + 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: + + + + 1if (! Storage::put('file.jpg', $contents)) { + + 2 // The file could not be written to disk... + + 3} + + + 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: + + + + 1'public' => [ + + 2 'driver' => 'local', + + 3 // ... + + 4 'throw' => true, + + 5], + + + '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: + + + + 1Storage::prepend('file.log', 'Prepended Text'); + + 2  + + 3Storage::append('file.log', 'Appended Text'); + + + Storage::prepend('file.log', 'Prepended 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: + + + + 1Storage::copy('old/file.jpg', 'new/file.jpg'); + + 2  + + 3Storage::move('old/file.jpg', 'new/file.jpg'); + + + Storage::copy('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: + + + + 1use Illuminate\Http\File; + + 2use Illuminate\Support\Facades\Storage; + + 3  + + 4// Automatically generate a unique ID for filename... + + 5$path = Storage::putFile('photos', new File('/path/to/photo')); + + 6  + + 7// Manually specify a filename... + + 8$path = Storage::putFileAs('photos', new File('/path/to/photo'), 'photo.jpg'); + + + 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')); + + // 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: + + + + 1Storage::putFile('photos', new File('/path/to/photo'), 'public'); + + + 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: + + + + 1file('avatar')->store('avatars'); + + 15  + + 16 return $path; + + 17 } + + 18} + + + 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: + + + + 1$path = Storage::putFile('avatars', $request->file('avatar')); + + + $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: + + + + 1$path = $request->file('avatar')->storeAs( + + 2 'avatars', $request->user()->id + + 3); + + + $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: + + + + 1$path = Storage::putFileAs( + + 2 'avatars', $request->file('avatar'), $request->user()->id + + 3); + + + $path = Storage::putFileAs( + 'avatars', $request->file('avatar'), $request->user()->id + ); + +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. + +#### Specifying a Disk + +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: + + + + 1$path = $request->file('avatar')->store( + + 2 'avatars/'.$request->user()->id, 's3' + + 3); + + + $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: + + + + 1$path = $request->file('avatar')->storeAs( + + 2 'avatars', + + 3 $request->user()->id, + + 4 's3' + + 5); + + + $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: + + + + 1$file = $request->file('avatar'); + + 2  + + 3$name = $file->getClientOriginalName(); + + 4$extension = $file->getClientOriginalExtension(); + + + $file = $request->file('avatar'); + + $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: + + + + 1$file = $request->file('avatar'); + + 2  + + 3$name = $file->hashName(); // Generate a unique, random name... + + 4$extension = $file->extension(); // Determine the file's extension based on the file's MIME type... + + + $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... + +### File Visibility + +In Laravel's Flysystem integration, "visibility" is an abstraction of file +permissions across multiple platforms. Files may either be declared `public` +or `private`. When a file is declared `public`, you are indicating that the +file should generally be accessible to others. For example, when using the S3 +driver, you may retrieve URLs for `public` files. + +You can set the visibility when writing the file via the `put` method: + + + + 1use Illuminate\Support\Facades\Storage; + + 2  + + 3Storage::put('file.jpg', $contents, 'public'); + + + use Illuminate\Support\Facades\Storage; + + 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: + + + + 1$visibility = Storage::getVisibility('file.jpg'); + + 2  + + 3Storage::setVisibility('file.jpg', 'public'); + + + $visibility = Storage::getVisibility('file.jpg'); + + 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: + + + + 1$path = $request->file('avatar')->storePublicly('avatars', 's3'); + + 2  + + 3$path = $request->file('avatar')->storePubliclyAs( + + 4 'avatars', + + 5 $request->user()->id, + + 6 's3' + + 7); + + + $path = $request->file('avatar')->storePublicly('avatars', 's3'); + + $path = $request->file('avatar')->storePubliclyAs( + 'avatars', + $request->user()->id, + 's3' + ); + +#### Local Files and Visibility + +When using the `local` driver, `public` 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: + + + + 1'local' => [ + + 2 'driver' => 'local', + + 3 'root' => storage_path('app'), + + 4 'permissions' => [ + + 5 'file' => [ + + 6 'public' => 0644, + + 7 'private' => 0600, + + 8 ], + + 9 'dir' => [ + + 10 'public' => 0755, + + 11 'private' => 0700, + + 12 ], + + 13 ], + + 14 'throw' => false, + + 15], + + + 'local' => [ + 'driver' => 'local', + 'root' => storage_path('app'), + 'permissions' => [ + 'file' => [ + 'public' => 0644, + 'private' => 0600, + ], + 'dir' => [ + 'public' => 0755, + 'private' => 0700, + ], + ], + 'throw' => false, + ], + +## Deleting Files + +The `delete` method accepts a single filename or an array of files to delete: + + + + 1use Illuminate\Support\Facades\Storage; + + 2  + + 3Storage::delete('file.jpg'); + + 4  + + 5Storage::delete(['file.jpg', 'file2.jpg']); + + + use Illuminate\Support\Facades\Storage; + + Storage::delete('file.jpg'); + + Storage::delete(['file.jpg', 'file2.jpg']); + +If necessary, you may specify the disk that the file should be deleted from: + + + + 1use Illuminate\Support\Facades\Storage; + + 2  + + 3Storage::disk('s3')->delete('path/file.jpg'); + + + use Illuminate\Support\Facades\Storage; + + Storage::disk('s3')->delete('path/file.jpg'); + +## Directories + +#### Get All Files Within a Directory + +The `files` method returns an array of all files within a given directory. If +you would like to retrieve a list of all files within a given directory +including subdirectories, you may use the `allFiles` method: + + + + 1use Illuminate\Support\Facades\Storage; + + 2  + + 3$files = Storage::files($directory); + + 4  + + 5$files = Storage::allFiles($directory); + + + use Illuminate\Support\Facades\Storage; + + $files = Storage::files($directory); + + $files = Storage::allFiles($directory); + +#### Get All Directories Within a Directory + +The `directories` method returns an array of all directories within a given +directory. If you would like to retrieve a list of all directories within a +given directory including subdirectories, you may use the `allDirectories` +method: + + + + 1$directories = Storage::directories($directory); + + 2  + + 3$directories = Storage::allDirectories($directory); + + + $directories = Storage::directories($directory); + + $directories = Storage::allDirectories($directory); + +#### Create a Directory + +The `makeDirectory` method will create the given directory, including any +needed subdirectories: + + + + 1Storage::makeDirectory($directory); + + + Storage::makeDirectory($directory); + +#### Delete a Directory + +Finally, the `deleteDirectory` method may be used to remove a directory and +all of its files: + + + + 1Storage::deleteDirectory($directory); + + + Storage::deleteDirectory($directory); + +## Testing + +The `Storage` facade's `fake` method allows you to easily generate a fake disk +that, combined with the file generation utilities of the +`Illuminate\Http\UploadedFile` class, greatly simplifies the testing of file +uploads. For example: + +Pest PHPUnit + + + + 1json('POST', '/photos', [ + + 10 UploadedFile::fake()->image('photo1.jpg'), + + 11 UploadedFile::fake()->image('photo2.jpg') + + 12 ]); + + 13  + + 14 // Assert one or more files were stored... + + 15 Storage::disk('photos')->assertExists('photo1.jpg'); + + 16 Storage::disk('photos')->assertExists(['photo1.jpg', 'photo2.jpg']); + + 17  + + 18 // Assert one or more files were not stored... + + 19 Storage::disk('photos')->assertMissing('missing.jpg'); + + 20 Storage::disk('photos')->assertMissing(['missing.jpg', 'non-existing.jpg']); + + 21  + + 22 // Assert that the number of files in a given directory matches the expected count... + + 23 Storage::disk('photos')->assertCount('/wallpapers', 2); + + 24  + + 25 // Assert that a given directory is empty... + + 26 Storage::disk('photos')->assertDirectoryEmpty('/wallpapers'); + + 27}); + + + json('POST', '/photos', [ + UploadedFile::fake()->image('photo1.jpg'), + UploadedFile::fake()->image('photo2.jpg') + ]); + + // Assert one or more files were stored... + Storage::disk('photos')->assertExists('photo1.jpg'); + Storage::disk('photos')->assertExists(['photo1.jpg', 'photo2.jpg']); + + // Assert one or more files were not stored... + Storage::disk('photos')->assertMissing('missing.jpg'); + Storage::disk('photos')->assertMissing(['missing.jpg', 'non-existing.jpg']); + + // Assert that the number of files in a given directory matches the expected count... + Storage::disk('photos')->assertCount('/wallpapers', 2); + + // Assert that a given directory is empty... + Storage::disk('photos')->assertDirectoryEmpty('/wallpapers'); + }); + + + 1json('POST', '/photos', [ + + 16 UploadedFile::fake()->image('photo1.jpg'), + + 17 UploadedFile::fake()->image('photo2.jpg') + + 18 ]); + + 19  + + 20 // Assert one or more files were stored... + + 21 Storage::disk('photos')->assertExists('photo1.jpg'); + + 22 Storage::disk('photos')->assertExists(['photo1.jpg', 'photo2.jpg']); + + 23  + + 24 // Assert one or more files were not stored... + + 25 Storage::disk('photos')->assertMissing('missing.jpg'); + + 26 Storage::disk('photos')->assertMissing(['missing.jpg', 'non-existing.jpg']); + + 27  + + 28 // Assert that the number of files in a given directory matches the expected count... + + 29 Storage::disk('photos')->assertCount('/wallpapers', 2); + + 30  + + 31 // Assert that a given directory is empty... + + 32 Storage::disk('photos')->assertDirectoryEmpty('/wallpapers'); + + 33 } + + 34} + + + json('POST', '/photos', [ + UploadedFile::fake()->image('photo1.jpg'), + UploadedFile::fake()->image('photo2.jpg') + ]); + + // Assert one or more files were stored... + Storage::disk('photos')->assertExists('photo1.jpg'); + Storage::disk('photos')->assertExists(['photo1.jpg', 'photo2.jpg']); + + // Assert one or more files were not stored... + Storage::disk('photos')->assertMissing('missing.jpg'); + Storage::disk('photos')->assertMissing(['missing.jpg', 'non-existing.jpg']); + + // Assert that the number of files in a given directory matches the expected count... + Storage::disk('photos')->assertCount('/wallpapers', 2); + + // Assert that a given directory is empty... + Storage::disk('photos')->assertDirectoryEmpty('/wallpapers'); + } + } + +By default, the `fake` method will delete all files in its temporary +directory. If you would like to keep these files, you may use the +"persistentFake" method instead. For more information on testing file uploads, +you may consult the [HTTP testing documentation's information on file +uploads](/docs/12.x/http-tests#testing-file-uploads). + +The `image` method requires the [GD +extension](https://www.php.net/manual/en/book.image.php). + +## Custom Filesystems + +Laravel's Flysystem integration provides support for several "drivers" out of +the box; however, Flysystem is not limited to these and has adapters for many +other storage systems. You can create a custom driver if you want to use one +of these additional adapters in your Laravel application. + +In order to define a custom filesystem you will need a Flysystem adapter. +Let's add a community maintained Dropbox adapter to our project: + + + + 1composer require spatie/flysystem-dropbox + + + composer require spatie/flysystem-dropbox + +Next, you can register the driver within the `boot` method of one of your +application's [service providers](/docs/12.x/providers). To accomplish this, +you should use the `extend` method of the `Storage` facade: + + + + 1 + + 2 Hello World + + 3 + + +
    + Hello World +
    + +## Installation + +To get started, install Folio into your project using the Composer package +manager: + + + + 1composer require laravel/folio + + + composer require laravel/folio + +After installing Folio, you may execute the `folio:install` Artisan command, +which will install Folio's service provider into your application. This +service provider registers the directory where Folio will search for routes / +pages: + + + + 1php artisan folio:install + + + php artisan folio:install + +### Page Paths / URIs + +By default, Folio serves pages from your application's `resources/views/pages` +directory, but you may customize these directories in your Folio service +provider's `boot` method. + +For example, sometimes it may be convenient to specify multiple Folio paths in +the same Laravel application. You may wish to have a separate directory of +Folio pages for your application's "admin" area, while using another directory +for the rest of your application's pages. + +You may accomplish this using the `Folio::path` and `Folio::uri` methods. The +`path` method registers a directory that Folio will scan for pages when +routing incoming HTTP requests, while the `uri` method specifies the "base +URI" for that directory of pages: + + + + 1use Laravel\Folio\Folio; + + 2  + + 3Folio::path(resource_path('views/pages/guest'))->uri('/'); + + 4  + + 5Folio::path(resource_path('views/pages/admin')) + + 6 ->uri('/admin') + + 7 ->middleware([ + + 8 '*' => [ + + 9 'auth', + + 10 'verified', + + 11  + + 12 // ... + + 13 ], + + 14 ]); + + + use Laravel\Folio\Folio; + + Folio::path(resource_path('views/pages/guest'))->uri('/'); + + Folio::path(resource_path('views/pages/admin')) + ->uri('/admin') + ->middleware([ + '*' => [ + 'auth', + 'verified', + + // ... + ], + ]); + +### Subdomain Routing + +You may also route to pages based on the incoming request's subdomain. For +example, you may wish to route requests from `admin.example.com` to a +different page directory than the rest of your Folio pages. You may accomplish +this by invoking the `domain` method after invoking the `Folio::path` method: + + + + 1use Laravel\Folio\Folio; + + 2  + + 3Folio::domain('admin.example.com') + + 4 ->path(resource_path('views/pages/admin')); + + + use Laravel\Folio\Folio; + + Folio::domain('admin.example.com') + ->path(resource_path('views/pages/admin')); + +The `domain` method also allows you to capture parts of the domain or +subdomain as parameters. These parameters will be injected into your page +template: + + + + 1use Laravel\Folio\Folio; + + 2  + + 3Folio::domain('{account}.example.com') + + 4 ->path(resource_path('views/pages/admin')); + + + use Laravel\Folio\Folio; + + Folio::domain('{account}.example.com') + ->path(resource_path('views/pages/admin')); + +## Creating Routes + +You may create a Folio route by placing a Blade template in any of your Folio +mounted directories. By default, Folio mounts the `resources/views/pages` +directory, but you may customize these directories in your Folio service +provider's `boot` method. + +Once a Blade template has been placed in a Folio mounted directory, you may +immediately access it via your browser. For example, a page placed in +`pages/schedule.blade.php` may be accessed in your browser at +`http://example.com/schedule`. + +To quickly view a list of all of your Folio pages / routes, you may invoke the +`folio:list` Artisan command: + + + + 1php artisan folio:list + + + php artisan folio:list + +### Nested Routes + +You may create a nested route by creating one or more directories within one +of Folio's directories. For instance, to create a page that is accessible via +`/user/profile`, create a `profile.blade.php` template within the `pages/user` +directory: + + + + 1php artisan folio:page user/profile + + 2  + + 3# pages/user/profile.blade.php → /user/profile + + + php artisan folio:page user/profile + + # pages/user/profile.blade.php → /user/profile + +### Index Routes + +Sometimes, you may wish to make a given page the "index" of a directory. By +placing an `index.blade.php` template within a Folio directory, any requests +to the root of that directory will be routed to that page: + + + + 1php artisan folio:page index + + 2# pages/index.blade.php → / + + 3  + + 4php artisan folio:page users/index + + 5# pages/users/index.blade.php → /users + + + php artisan folio:page index + # pages/index.blade.php → / + + php artisan folio:page users/index + # pages/users/index.blade.php → /users + +## Route Parameters + +Often, you will need to have segments of the incoming request's URL injected +into your page so that you can interact with them. For example, you may need +to access the "ID" of the user whose profile is being displayed. To accomplish +this, you may encapsulate a segment of the page's filename in square brackets: + + + + 1php artisan folio:page "users/[id]" + + 2  + + 3# pages/users/[id].blade.php → /users/1 + + + php artisan folio:page "users/[id]" + + # pages/users/[id].blade.php → /users/1 + +Captured segments can be accessed as variables within your Blade template: + + + + 1
    + + 2 User {{ $id }} + + 3
    + + +
    + User {{ $id }} +
    + +To capture multiple segments, you can prefix the encapsulated segment with +three dots `...`: + + + + 1php artisan folio:page "users/[...ids]" + + 2  + + 3# pages/users/[...ids].blade.php → /users/1/2/3 + + + php artisan folio:page "users/[...ids]" + + # pages/users/[...ids].blade.php → /users/1/2/3 + +When capturing multiple segments, the captured segments will be injected into +the page as an array: + + + + 1
      + + 2 @foreach ($ids as $id) + + 3
    • User {{ $id }}
    • + + 4 @endforeach + + 5
    + + +
      + @foreach ($ids as $id) +
    • User {{ $id }}
    • + @endforeach +
    + +## Route Model Binding + +If a wildcard segment of your page template's filename corresponds one of your +application's Eloquent models, Folio will automatically take advantage of +Laravel's route model binding capabilities and attempt to inject the resolved +model instance into your page: + + + + 1php artisan folio:page "users/[User]" + + 2  + + 3# pages/users/[User].blade.php → /users/1 + + + php artisan folio:page "users/[User]" + + # pages/users/[User].blade.php → /users/1 + +Captured models can be accessed as variables within your Blade template. The +model's variable name will be converted to "camel case": + + + + 1
    + + 2 User {{ $user->id }} + + 3
    + + +
    + User {{ $user->id }} +
    + +#### Customizing the Key + +Sometimes you may wish to resolve bound Eloquent models using a column other +than `id`. To do so, you may specify the column in the page's filename. For +example, a page with the filename `[Post:slug].blade.php` will attempt to +resolve the bound model via the `slug` column instead of the `id` column. + +On Windows, you should use `-` to separate the model name from the key: +`[Post-slug].blade.php`. + +#### Model Location + +By default, Folio will search for your model within your application's +`app/Models` directory. However, if needed, you may specify the fully- +qualified model class name in your template's filename: + + + + 1php artisan folio:page "users/[.App.Models.User]" + + 2  + + 3# pages/users/[.App.Models.User].blade.php → /users/1 + + + php artisan folio:page "users/[.App.Models.User]" + + # pages/users/[.App.Models.User].blade.php → /users/1 + +### Soft Deleted Models + +By default, models that have been soft deleted are not retrieved when +resolving implicit model bindings. However, if you wish, you can instruct +Folio to retrieve soft deleted models by invoking the `withTrashed` function +within the page's template: + + + + 1 + + 8  + + 9
    + + 10 User {{ $user->id }} + + 11
    + + + + +
    + User {{ $user->id }} +
    + +## Render Hooks + +By default, Folio will return the content of the page's Blade template as the +response to the incoming request. However, you may customize the response by +invoking the `render` function within the page's template. + +The `render` function accepts a closure which will receive the `View` instance +being rendered by Folio, allowing you to add additional data to the view or +customize the entire response. In addition to receiving the `View` instance, +any additional route parameters or model bindings will also be provided to the +`render` closure: + + + + 1can('view', $post)) { + + 11 return response('Unauthorized', 403); + + 12 } + + 13  + + 14 return $view->with('photos', $post->author->photos); + + 15}); ?> + + 16  + + 17
    + + 18 {{ $post->content }} + + 19
    + + 20  + + 21
    + + 22 This author has also taken {{ count($photos) }} photos. + + 23
    + + + can('view', $post)) { + return response('Unauthorized', 403); + } + + return $view->with('photos', $post->author->photos); + }); ?> + +
    + {{ $post->content }} +
    + +
    + This author has also taken {{ count($photos) }} photos. +
    + +## Named Routes + +You may specify a name for a given page's route using the `name` function: + + + + 1 + + 2 All Users + + 3 + + + + All Users + + +If the page has parameters, you may simply pass their values to the `route` +function: + + + + 1route('users.show', ['user' => $user]); + + + route('users.show', ['user' => $user]); + +## Middleware + +You can apply middleware to a specific page by invoking the `middleware` +function within the page's template: + + + + 1 + + 8  + + 9
    + + 10 Dashboard + + 11
    + + + + +
    + Dashboard +
    + +Or, to assign middleware to a group of pages, you may chain the `middleware` +method after invoking the `Folio::path` method. + +To specify which pages the middleware should be applied to, the array of +middleware may be keyed using the corresponding URL patterns of the pages they +should be applied to. The `*` character may be utilized as a wildcard +character: + + + + 1use Laravel\Folio\Folio; + + 2  + + 3Folio::path(resource_path('views/pages'))->middleware([ + + 4 'admin/*' => [ + + 5 'auth', + + 6 'verified', + + 7  + + 8 // ... + + 9 ], + + 10]); + + + use Laravel\Folio\Folio; + + Folio::path(resource_path('views/pages'))->middleware([ + 'admin/*' => [ + 'auth', + 'verified', + + // ... + ], + ]); + +You may include closures in the array of middleware to define inline, +anonymous middleware: + + + + 1use Closure; + + 2use Illuminate\Http\Request; + + 3use Laravel\Folio\Folio; + + 4  + + 5Folio::path(resource_path('views/pages'))->middleware([ + + 6 'admin/*' => [ + + 7 'auth', + + 8 'verified', + + 9  + + 10 function (Request $request, Closure $next) { + + 11 // ... + + 12  + + 13 return $next($request); + + 14 }, + + 15 ], + + 16]); + + + use Closure; + use Illuminate\Http\Request; + use Laravel\Folio\Folio; + + Folio::path(resource_path('views/pages'))->middleware([ + 'admin/*' => [ + 'auth', + 'verified', + + function (Request $request, Closure $next) { + // ... + + return $next($request); + }, + ], + ]); + +## Route Caching + +When using Folio, you should always take advantage of [Laravel's route caching +capabilities](/docs/12.x/routing#route-caching). Folio listens for the +`route:cache` Artisan command to ensure that Folio page definitions and route +names are properly cached for maximum performance. + diff --git a/output/12.x/fortify.md b/output/12.x/fortify.md new file mode 100644 index 0000000..9d34cdd --- /dev/null +++ b/output/12.x/fortify.md @@ -0,0 +1,1270 @@ +# Laravel Fortify + + * Introduction + * What is Fortify? + * When Should I Use Fortify? + * Installation + * Fortify Features + * Disabling Views + * Authentication + * Customizing User Authentication + * Customizing the Authentication Pipeline + * Customizing Redirects + * Two Factor Authentication + * Enabling Two Factor Authentication + * Authenticating With Two Factor Authentication + * Disabling Two Factor Authentication + * Registration + * Customizing Registration + * Password Reset + * Requesting a Password Reset Link + * Resetting the Password + * Customizing Password Resets + * Email Verification + * Protecting Routes + * Password Confirmation + +## Introduction + +[Laravel Fortify](https://github.com/laravel/fortify) is a frontend agnostic +authentication backend implementation for Laravel. Fortify registers the +routes and controllers needed to implement all of Laravel's authentication +features, including login, registration, password reset, email verification, +and more. After installing Fortify, you may run the `route:list` Artisan +command to see the routes that Fortify has registered. + +Since Fortify does not provide its own user interface, it is meant to be +paired with your own user interface which makes requests to the routes it +registers. We will discuss exactly how to make requests to these routes in the +remainder of this documentation. + +Remember, Fortify is a package that is meant to give you a head start +implementing Laravel's authentication features. **You are not required to use +it.** You are always free to manually interact with Laravel's authentication +services by following the documentation available in the +[authentication](/docs/12.x/authentication), [password +reset](/docs/12.x/passwords), and [email +verification](/docs/12.x/verification) documentation. + +### What is Fortify? + +As mentioned previously, Laravel Fortify is a frontend agnostic authentication +backend implementation for Laravel. Fortify registers the routes and +controllers needed to implement all of Laravel's authentication features, +including login, registration, password reset, email verification, and more. + +**You are not required to use Fortify in order to use Laravel's authentication +features.** You are always free to manually interact with Laravel's +authentication services by following the documentation available in the +[authentication](/docs/12.x/authentication), [password +reset](/docs/12.x/passwords), and [email +verification](/docs/12.x/verification) documentation. + +If you are new to Laravel, you may wish to explore [our application starter +kits](/docs/12.x/starter-kits) before attempting to use Laravel Fortify. Our +starter kits provide an authentication scaffolding for your application that +includes a user interface built with [Tailwind CSS](https://tailwindcss.com). +This allows you to study and get comfortable with Laravel's authentication +features before allowing Laravel Fortify to implement these features for you. + +Laravel Fortify essentially takes the routes and controllers of our +application starter kits and offers them as a package that does not include a +user interface. This allows you to still quickly scaffold the backend +implementation of your application's authentication layer without being tied +to any particular frontend opinions. + +### When Should I Use Fortify? + +You may be wondering when it is appropriate to use Laravel Fortify. First, if +you are using one of Laravel's [application starter kits](/docs/12.x/starter- +kits), you do not need to install Laravel Fortify since all of Laravel's +application starter kits already provide a full authentication implementation. + +If you are not using an application starter kit and your application needs +authentication features, you have two options: manually implement your +application's authentication features or use Laravel Fortify to provide the +backend implementation of these features. + +If you choose to install Fortify, your user interface will make requests to +Fortify's authentication routes that are detailed in this documentation in +order to authenticate and register users. + +If you choose to manually interact with Laravel's authentication services +instead of using Fortify, you may do so by following the documentation +available in the [authentication](/docs/12.x/authentication), [password +reset](/docs/12.x/passwords), and [email +verification](/docs/12.x/verification) documentation. + +#### Laravel Fortify and Laravel Sanctum + +Some developers become confused regarding the difference between [Laravel +Sanctum](/docs/12.x/sanctum) and Laravel Fortify. Because the two packages +solve two different but related problems, Laravel Fortify and Laravel Sanctum +are not mutually exclusive or competing packages. + +Laravel Sanctum is only concerned with managing API tokens and authenticating +existing users using session cookies or tokens. Sanctum does not provide any +routes that handle user registration, password reset, etc. + +If you are attempting to manually build the authentication layer for an +application that offers an API or serves as the backend for a single-page +application, it is entirely possible that you will utilize both Laravel +Fortify (for user registration, password reset, etc.) and Laravel Sanctum (API +token management, session authentication). + +## Installation + +To get started, install Fortify using the Composer package manager: + + + + 1composer require laravel/fortify + + + composer require laravel/fortify + +Next, publish Fortify's resources using the `fortify:install` Artisan command: + + + + 1php artisan fortify:install + + + php artisan fortify:install + +This command will publish Fortify's actions to your `app/Actions` directory, +which will be created if it does not exist. In addition, the +`FortifyServiceProvider`, configuration file, and all necessary database +migrations will be published. + +Next, you should migrate your database: + + + + 1php artisan migrate + + + php artisan migrate + +### Fortify Features + +The `fortify` configuration file contains a `features` configuration array. +This array defines which backend routes / features Fortify will expose by +default. We recommend that you only enable the following features, which are +the basic authentication features provided by most Laravel applications: + + + + 1'features' => [ + + 2 Features::registration(), + + 3 Features::resetPasswords(), + + 4 Features::emailVerification(), + + 5], + + + 'features' => [ + Features::registration(), + Features::resetPasswords(), + Features::emailVerification(), + ], + +### Disabling Views + +By default, Fortify defines routes that are intended to return views, such as +a login screen or registration screen. However, if you are building a +JavaScript driven single-page application, you may not need these routes. For +that reason, you may disable these routes entirely by setting the `views` +configuration value within your application's `config/fortify.php` +configuration file to `false`: + + + + 1'views' => false, + + + 'views' => false, + +#### Disabling Views and Password Reset + +If you choose to disable Fortify's views and you will be implementing password +reset features for your application, you should still define a route named +`password.reset` that is responsible for displaying your application's "reset +password" view. This is necessary because Laravel's +`Illuminate\Auth\Notifications\ResetPassword` notification will generate the +password reset URL via the `password.reset` named route. + +## Authentication + +To get started, we need to instruct Fortify how to return our "login" view. +Remember, Fortify is a headless authentication library. If you would like a +frontend implementation of Laravel's authentication features that are already +completed for you, you should use an [application starter +kit](/docs/12.x/starter-kits). + +All of the authentication view's rendering logic may be customized using the +appropriate methods available via the `Laravel\Fortify\Fortify` class. +Typically, you should call this method from the `boot` method of your +application's `App\Providers\FortifyServiceProvider` class. Fortify will take +care of defining the `/login` route that returns this view: + + + + 1use Laravel\Fortify\Fortify; + + 2  + + 3/** + + 4 * Bootstrap any application services. + + 5 */ + + 6public function boot(): void + + 7{ + + 8 Fortify::loginView(function () { + + 9 return view('auth.login'); + + 10 }); + + 11  + + 12 // ... + + 13} + + + use Laravel\Fortify\Fortify; + + /** + * Bootstrap any application services. + */ + public function boot(): void + { + Fortify::loginView(function () { + return view('auth.login'); + }); + + // ... + } + +Your login template should include a form that makes a POST request to +`/login`. The `/login` endpoint expects a string `email` / `username` and a +`password`. The name of the email / username field should match the `username` +value within the `config/fortify.php` configuration file. In addition, a +boolean `remember` field may be provided to indicate that the user would like +to use the "remember me" functionality provided by Laravel. + +If the login attempt is successful, Fortify will redirect you to the URI +configured via the `home` configuration option within your application's +`fortify` configuration file. If the login request was an XHR request, a 200 +HTTP response will be returned. + +If the request was not successful, the user will be redirected back to the +login screen and the validation errors will be available to you via the shared +`$errors` [Blade template variable](/docs/12.x/validation#quick-displaying- +the-validation-errors). Or, in the case of an XHR request, the validation +errors will be returned with the 422 HTTP response. + +### Customizing User Authentication + +Fortify will automatically retrieve and authenticate the user based on the +provided credentials and the authentication guard that is configured for your +application. However, you may sometimes wish to have full customization over +how login credentials are authenticated and users are retrieved. Thankfully, +Fortify allows you to easily accomplish this using the +`Fortify::authenticateUsing` method. + +This method accepts a closure which receives the incoming HTTP request. The +closure is responsible for validating the login credentials attached to the +request and returning the associated user instance. If the credentials are +invalid or no user can be found, `null` or `false` should be returned by the +closure. Typically, this method should be called from the `boot` method of +your `FortifyServiceProvider`: + + + + 1use App\Models\User; + + 2use Illuminate\Http\Request; + + 3use Illuminate\Support\Facades\Hash; + + 4use Laravel\Fortify\Fortify; + + 5  + + 6/** + + 7 * Bootstrap any application services. + + 8 */ + + 9public function boot(): void + + 10{ + + 11 Fortify::authenticateUsing(function (Request $request) { + + 12 $user = User::where('email', $request->email)->first(); + + 13  + + 14 if ($user && + + 15 Hash::check($request->password, $user->password)) { + + 16 return $user; + + 17 } + + 18 }); + + 19  + + 20 // ... + + 21} + + + use App\Models\User; + use Illuminate\Http\Request; + use Illuminate\Support\Facades\Hash; + use Laravel\Fortify\Fortify; + + /** + * Bootstrap any application services. + */ + public function boot(): void + { + Fortify::authenticateUsing(function (Request $request) { + $user = User::where('email', $request->email)->first(); + + if ($user && + Hash::check($request->password, $user->password)) { + return $user; + } + }); + + // ... + } + +#### Authentication Guard + +You may customize the authentication guard used by Fortify within your +application's `fortify` configuration file. However, you should ensure that +the configured guard is an implementation of +`Illuminate\Contracts\Auth\StatefulGuard`. If you are attempting to use +Laravel Fortify to authenticate an SPA, you should use Laravel's default `web` +guard in combination with [Laravel Sanctum](https://laravel.com/docs/sanctum). + +### Customizing the Authentication Pipeline + +Laravel Fortify authenticates login requests through a pipeline of invokable +classes. If you would like, you may define a custom pipeline of classes that +login requests should be piped through. Each class should have an `__invoke` +method which receives the incoming `Illuminate\Http\Request` instance and, +like [middleware](/docs/12.x/middleware), a `$next` variable that is invoked +in order to pass the request to the next class in the pipeline. + +To define your custom pipeline, you may use the `Fortify::authenticateThrough` +method. This method accepts a closure which should return the array of classes +to pipe the login request through. Typically, this method should be called +from the `boot` method of your `App\Providers\FortifyServiceProvider` class. + +The example below contains the default pipeline definition that you may use as +a starting point when making your own modifications: + + + + 1use Laravel\Fortify\Actions\AttemptToAuthenticate; + + 2use Laravel\Fortify\Actions\CanonicalizeUsername; + + 3use Laravel\Fortify\Actions\EnsureLoginIsNotThrottled; + + 4use Laravel\Fortify\Actions\PrepareAuthenticatedSession; + + 5use Laravel\Fortify\Actions\RedirectIfTwoFactorAuthenticatable; + + 6use Laravel\Fortify\Features; + + 7use Laravel\Fortify\Fortify; + + 8use Illuminate\Http\Request; + + 9  + + 10Fortify::authenticateThrough(function (Request $request) { + + 11 return array_filter([ + + 12 config('fortify.limiters.login') ? null : EnsureLoginIsNotThrottled::class, + + 13 config('fortify.lowercase_usernames') ? CanonicalizeUsername::class : null, + + 14 Features::enabled(Features::twoFactorAuthentication()) ? RedirectIfTwoFactorAuthenticatable::class : null, + + 15 AttemptToAuthenticate::class, + + 16 PrepareAuthenticatedSession::class, + + 17 ]); + + 18}); + + + use Laravel\Fortify\Actions\AttemptToAuthenticate; + use Laravel\Fortify\Actions\CanonicalizeUsername; + use Laravel\Fortify\Actions\EnsureLoginIsNotThrottled; + use Laravel\Fortify\Actions\PrepareAuthenticatedSession; + use Laravel\Fortify\Actions\RedirectIfTwoFactorAuthenticatable; + use Laravel\Fortify\Features; + use Laravel\Fortify\Fortify; + use Illuminate\Http\Request; + + Fortify::authenticateThrough(function (Request $request) { + return array_filter([ + config('fortify.limiters.login') ? null : EnsureLoginIsNotThrottled::class, + config('fortify.lowercase_usernames') ? CanonicalizeUsername::class : null, + Features::enabled(Features::twoFactorAuthentication()) ? RedirectIfTwoFactorAuthenticatable::class : null, + AttemptToAuthenticate::class, + PrepareAuthenticatedSession::class, + ]); + }); + +#### Authentication Throttling + +By default, Fortify will throttle authentication attempts using the +`EnsureLoginIsNotThrottled` middleware. This middleware throttles attempts +that are unique to a username and IP address combination. + +Some applications may require a different approach to throttling +authentication attempts, such as throttling by IP address alone. Therefore, +Fortify allows you to specify your own [rate limiter](/docs/12.x/routing#rate- +limiting) via the `fortify.limiters.login` configuration option. Of course, +this configuration option is located in your application's +`config/fortify.php` configuration file. + +Utilizing a mixture of throttling, [two factor +authentication](/docs/12.x/fortify#two-factor-authentication), and an external +web application firewall (WAF) will provide the most robust defense for your +legitimate application users. + +### Customizing Redirects + +If the login attempt is successful, Fortify will redirect you to the URI +configured via the `home` configuration option within your application's +`fortify` configuration file. If the login request was an XHR request, a 200 +HTTP response will be returned. After a user logs out of the application, the +user will be redirected to the `/` URI. + +If you need advanced customization of this behavior, you may bind +implementations of the `LoginResponse` and `LogoutResponse` contracts into the +Laravel [service container](/docs/12.x/container). Typically, this should be +done within the `register` method of your application's +`App\Providers\FortifyServiceProvider` class: + + + + 1use Laravel\Fortify\Contracts\LogoutResponse; + + 2  + + 3/** + + 4 * Register any application services. + + 5 */ + + 6public function register(): void + + 7{ + + 8 $this->app->instance(LogoutResponse::class, new class implements LogoutResponse { + + 9 public function toResponse($request) + + 10 { + + 11 return redirect('/'); + + 12 } + + 13 }); + + 14} + + + use Laravel\Fortify\Contracts\LogoutResponse; + + /** + * Register any application services. + */ + public function register(): void + { + $this->app->instance(LogoutResponse::class, new class implements LogoutResponse { + public function toResponse($request) + { + return redirect('/'); + } + }); + } + +## Two Factor Authentication + +When Fortify's two factor authentication feature is enabled, the user is +required to input a six digit numeric token during the authentication process. +This token is generated using a time-based one-time password (TOTP) that can +be retrieved from any TOTP compatible mobile authentication application such +as Google Authenticator. + +Before getting started, you should first ensure that your application's +`App\Models\User` model uses the `Laravel\Fortify\TwoFactorAuthenticatable` +trait: + + + + 1 By default, the `features` array of the `fortify` configuration file +> instructs Fortify's two factor authentication settings to require password +> confirmation before modification. Therefore, your application should +> implement Fortify's password confirmation feature before continuing. + +### Enabling Two Factor Authentication + +To begin enabling two factor authentication, your application should make a +POST request to the `/user/two-factor-authentication` endpoint defined by +Fortify. If the request is successful, the user will be redirected back to the +previous URL and the `status` session variable will be set to `two-factor- +authentication-enabled`. You may detect this `status` session variable within +your templates to display the appropriate success message. If the request was +an XHR request, `200` HTTP response will be returned. + +After choosing to enable two factor authentication, the user must still +"confirm" their two factor authentication configuration by providing a valid +two factor authentication code. So, your "success" message should instruct the +user that two factor authentication confirmation is still required: + + + + 1@if (session('status') == 'two-factor-authentication-enabled') + + 2
    + + 3 Please finish configuring two factor authentication below. + + 4
    + + 5@endif + + + @if (session('status') == 'two-factor-authentication-enabled') +
    + Please finish configuring two factor authentication below. +
    + @endif + +Next, you should display the two factor authentication QR code for the user to +scan into their authenticator application. If you are using Blade to render +your application's frontend, you may retrieve the QR code SVG using the +`twoFactorQrCodeSvg` method available on the user instance: + + + + 1$request->user()->twoFactorQrCodeSvg(); + + + $request->user()->twoFactorQrCodeSvg(); + +If you are building a JavaScript powered frontend, you may make an XHR GET +request to the `/user/two-factor-qr-code` endpoint to retrieve the user's two +factor authentication QR code. This endpoint will return a JSON object +containing an `svg` key. + +#### Confirming Two Factor Authentication + +In addition to displaying the user's two factor authentication QR code, you +should provide a text input where the user can supply a valid authentication +code to "confirm" their two factor authentication configuration. This code +should be provided to the Laravel application via a POST request to the +`/user/confirmed-two-factor-authentication` endpoint defined by Fortify. + +If the request is successful, the user will be redirected back to the previous +URL and the `status` session variable will be set to `two-factor- +authentication-confirmed`: + + + + 1@if (session('status') == 'two-factor-authentication-confirmed') + + 2
    + + 3 Two factor authentication confirmed and enabled successfully. + + 4
    + + 5@endif + + + @if (session('status') == 'two-factor-authentication-confirmed') +
    + Two factor authentication confirmed and enabled successfully. +
    + @endif + +If the request to the two factor authentication confirmation endpoint was made +via an XHR request, a `200` HTTP response will be returned. + +#### Displaying the Recovery Codes + +You should also display the user's two factor recovery codes. These recovery +codes allow the user to authenticate if they lose access to their mobile +device. If you are using Blade to render your application's frontend, you may +access the recovery codes via the authenticated user instance: + + + + 1(array) $request->user()->recoveryCodes() + + + (array) $request->user()->recoveryCodes() + +If you are building a JavaScript powered frontend, you may make an XHR GET +request to the `/user/two-factor-recovery-codes` endpoint. This endpoint will +return a JSON array containing the user's recovery codes. + +To regenerate the user's recovery codes, your application should make a POST +request to the `/user/two-factor-recovery-codes` endpoint. + +### Authenticating With Two Factor Authentication + +During the authentication process, Fortify will automatically redirect the +user to your application's two factor authentication challenge screen. +However, if your application is making an XHR login request, the JSON response +returned after a successful authentication attempt will contain a JSON object +that has a `two_factor` boolean property. You should inspect this value to +know whether you should redirect to your application's two factor +authentication challenge screen. + +To begin implementing two factor authentication functionality, we need to +instruct Fortify how to return our two factor authentication challenge view. +All of Fortify's authentication view rendering logic may be customized using +the appropriate methods available via the `Laravel\Fortify\Fortify` class. +Typically, you should call this method from the `boot` method of your +application's `App\Providers\FortifyServiceProvider` class: + + + + 1use Laravel\Fortify\Fortify; + + 2  + + 3/** + + 4 * Bootstrap any application services. + + 5 */ + + 6public function boot(): void + + 7{ + + 8 Fortify::twoFactorChallengeView(function () { + + 9 return view('auth.two-factor-challenge'); + + 10 }); + + 11  + + 12 // ... + + 13} + + + use Laravel\Fortify\Fortify; + + /** + * Bootstrap any application services. + */ + public function boot(): void + { + Fortify::twoFactorChallengeView(function () { + return view('auth.two-factor-challenge'); + }); + + // ... + } + +Fortify will take care of defining the `/two-factor-challenge` route that +returns this view. Your `two-factor-challenge` template should include a form +that makes a POST request to the `/two-factor-challenge` endpoint. The `/two- +factor-challenge` action expects a `code` field that contains a valid TOTP +token or a `recovery_code` field that contains one of the user's recovery +codes. + +If the login attempt is successful, Fortify will redirect the user to the URI +configured via the `home` configuration option within your application's +`fortify` configuration file. If the login request was an XHR request, a 204 +HTTP response will be returned. + +If the request was not successful, the user will be redirected back to the two +factor challenge screen and the validation errors will be available to you via +the shared `$errors` [Blade template variable](/docs/12.x/validation#quick- +displaying-the-validation-errors). Or, in the case of an XHR request, the +validation errors will be returned with a 422 HTTP response. + +### Disabling Two Factor Authentication + +To disable two factor authentication, your application should make a DELETE +request to the `/user/two-factor-authentication` endpoint. Remember, Fortify's +two factor authentication endpoints require password confirmation prior to +being called. + +## Registration + +To begin implementing our application's registration functionality, we need to +instruct Fortify how to return our "register" view. Remember, Fortify is a +headless authentication library. If you would like a frontend implementation +of Laravel's authentication features that are already completed for you, you +should use an [application starter kit](/docs/12.x/starter-kits). + +All of Fortify's view rendering logic may be customized using the appropriate +methods available via the `Laravel\Fortify\Fortify` class. Typically, you +should call this method from the `boot` method of your +`App\Providers\FortifyServiceProvider` class: + + + + 1use Laravel\Fortify\Fortify; + + 2  + + 3/** + + 4 * Bootstrap any application services. + + 5 */ + + 6public function boot(): void + + 7{ + + 8 Fortify::registerView(function () { + + 9 return view('auth.register'); + + 10 }); + + 11  + + 12 // ... + + 13} + + + use Laravel\Fortify\Fortify; + + /** + * Bootstrap any application services. + */ + public function boot(): void + { + Fortify::registerView(function () { + return view('auth.register'); + }); + + // ... + } + +Fortify will take care of defining the `/register` route that returns this +view. Your `register` template should include a form that makes a POST request +to the `/register` endpoint defined by Fortify. + +The `/register` endpoint expects a string `name`, string email address / +username, `password`, and `password_confirmation` fields. The name of the +email / username field should match the `username` configuration value defined +within your application's `fortify` configuration file. + +If the registration attempt is successful, Fortify will redirect the user to +the URI configured via the `home` configuration option within your +application's `fortify` configuration file. If the request was an XHR request, +a 201 HTTP response will be returned. + +If the request was not successful, the user will be redirected back to the +registration screen and the validation errors will be available to you via the +shared `$errors` [Blade template variable](/docs/12.x/validation#quick- +displaying-the-validation-errors). Or, in the case of an XHR request, the +validation errors will be returned with a 422 HTTP response. + +### Customizing Registration + +The user validation and creation process may be customized by modifying the +`App\Actions\Fortify\CreateNewUser` action that was generated when you +installed Laravel Fortify. + +## Password Reset + +### Requesting a Password Reset Link + +To begin implementing our application's password reset functionality, we need +to instruct Fortify how to return our "forgot password" view. Remember, +Fortify is a headless authentication library. If you would like a frontend +implementation of Laravel's authentication features that are already completed +for you, you should use an [application starter kit](/docs/12.x/starter-kits). + +All of Fortify's view rendering logic may be customized using the appropriate +methods available via the `Laravel\Fortify\Fortify` class. Typically, you +should call this method from the `boot` method of your application's +`App\Providers\FortifyServiceProvider` class: + + + + 1use Laravel\Fortify\Fortify; + + 2  + + 3/** + + 4 * Bootstrap any application services. + + 5 */ + + 6public function boot(): void + + 7{ + + 8 Fortify::requestPasswordResetLinkView(function () { + + 9 return view('auth.forgot-password'); + + 10 }); + + 11  + + 12 // ... + + 13} + + + use Laravel\Fortify\Fortify; + + /** + * Bootstrap any application services. + */ + public function boot(): void + { + Fortify::requestPasswordResetLinkView(function () { + return view('auth.forgot-password'); + }); + + // ... + } + +Fortify will take care of defining the `/forgot-password` endpoint that +returns this view. Your `forgot-password` template should include a form that +makes a POST request to the `/forgot-password` endpoint. + +The `/forgot-password` endpoint expects a string `email` field. The name of +this field / database column should match the `email` configuration value +within your application's `fortify` configuration file. + +#### Handling the Password Reset Link Request Response + +If the password reset link request was successful, Fortify will redirect the +user back to the `/forgot-password` endpoint and send an email to the user +with a secure link they can use to reset their password. If the request was an +XHR request, a 200 HTTP response will be returned. + +After being redirected back to the `/forgot-password` endpoint after a +successful request, the `status` session variable may be used to display the +status of the password reset link request attempt. + +The value of the `$status` session variable will match one of the translation +strings defined within your application's `passwords` [language +file](/docs/12.x/localization). If you would like to customize this value and +have not published Laravel's language files, you may do so via the +`lang:publish` Artisan command: + + + + 1@if (session('status')) + + 2
    + + 3 {{ session('status') }} + + 4
    + + 5@endif + + + @if (session('status')) +
    + {{ session('status') }} +
    + @endif + +If the request was not successful, the user will be redirected back to the +request password reset link screen and the validation errors will be available +to you via the shared `$errors` [Blade template +variable](/docs/12.x/validation#quick-displaying-the-validation-errors). Or, +in the case of an XHR request, the validation errors will be returned with a +422 HTTP response. + +### Resetting the Password + +To finish implementing our application's password reset functionality, we need +to instruct Fortify how to return our "reset password" view. + +All of Fortify's view rendering logic may be customized using the appropriate +methods available via the `Laravel\Fortify\Fortify` class. Typically, you +should call this method from the `boot` method of your application's +`App\Providers\FortifyServiceProvider` class: + + + + 1use Laravel\Fortify\Fortify; + + 2use Illuminate\Http\Request; + + 3  + + 4/** + + 5 * Bootstrap any application services. + + 6 */ + + 7public function boot(): void + + 8{ + + 9 Fortify::resetPasswordView(function (Request $request) { + + 10 return view('auth.reset-password', ['request' => $request]); + + 11 }); + + 12  + + 13 // ... + + 14} + + + use Laravel\Fortify\Fortify; + use Illuminate\Http\Request; + + /** + * Bootstrap any application services. + */ + public function boot(): void + { + Fortify::resetPasswordView(function (Request $request) { + return view('auth.reset-password', ['request' => $request]); + }); + + // ... + } + +Fortify will take care of defining the route to display this view. Your +`reset-password` template should include a form that makes a POST request to +`/reset-password`. + +The `/reset-password` endpoint expects a string `email` field, a `password` +field, a `password_confirmation` field, and a hidden field named `token` that +contains the value of `request()->route('token')`. The name of the "email" +field / database column should match the `email` configuration value defined +within your application's `fortify` configuration file. + +#### Handling the Password Reset Response + +If the password reset request was successful, Fortify will redirect back to +the `/login` route so that the user can log in with their new password. In +addition, a `status` session variable will be set so that you may display the +successful status of the reset on your login screen: + + + + 1@if (session('status')) + + 2
    + + 3 {{ session('status') }} + + 4
    + + 5@endif + + + @if (session('status')) +
    + {{ session('status') }} +
    + @endif + +If the request was an XHR request, a 200 HTTP response will be returned. + +If the request was not successful, the user will be redirected back to the +reset password screen and the validation errors will be available to you via +the shared `$errors` [Blade template variable](/docs/12.x/validation#quick- +displaying-the-validation-errors). Or, in the case of an XHR request, the +validation errors will be returned with a 422 HTTP response. + +### Customizing Password Resets + +The password reset process may be customized by modifying the +`App\Actions\ResetUserPassword` action that was generated when you installed +Laravel Fortify. + +## Email Verification + +After registration, you may wish for users to verify their email address +before they continue accessing your application. To get started, ensure the +`emailVerification` feature is enabled in your `fortify` configuration file's +`features` array. Next, you should ensure that your `App\Models\User` class +implements the `Illuminate\Contracts\Auth\MustVerifyEmail` interface. + +Once these two setup steps have been completed, newly registered users will +receive an email prompting them to verify their email address ownership. +However, we need to inform Fortify how to display the email verification +screen which informs the user that they need to go click the verification link +in the email. + +All of Fortify's view's rendering logic may be customized using the +appropriate methods available via the `Laravel\Fortify\Fortify` class. +Typically, you should call this method from the `boot` method of your +application's `App\Providers\FortifyServiceProvider` class: + + + + 1use Laravel\Fortify\Fortify; + + 2  + + 3/** + + 4 * Bootstrap any application services. + + 5 */ + + 6public function boot(): void + + 7{ + + 8 Fortify::verifyEmailView(function () { + + 9 return view('auth.verify-email'); + + 10 }); + + 11  + + 12 // ... + + 13} + + + use Laravel\Fortify\Fortify; + + /** + * Bootstrap any application services. + */ + public function boot(): void + { + Fortify::verifyEmailView(function () { + return view('auth.verify-email'); + }); + + // ... + } + +Fortify will take care of defining the route that displays this view when a +user is redirected to the `/email/verify` endpoint by Laravel's built-in +`verified` middleware. + +Your `verify-email` template should include an informational message +instructing the user to click the email verification link that was sent to +their email address. + +#### Resending Email Verification Links + +If you wish, you may add a button to your application's `verify-email` +template that triggers a POST request to the `/email/verification- +notification` endpoint. When this endpoint receives a request, a new +verification email link will be emailed to the user, allowing the user to get +a new verification link if the previous one was accidentally deleted or lost. + +If the request to resend the verification link email was successful, Fortify +will redirect the user back to the `/email/verify` endpoint with a `status` +session variable, allowing you to display an informational message to the user +informing them the operation was successful. If the request was an XHR +request, a 202 HTTP response will be returned: + + + + 1@if (session('status') == 'verification-link-sent') + + 2
    + + 3 A new email verification link has been emailed to you! + + 4
    + + 5@endif + + + @if (session('status') == 'verification-link-sent') +
    + A new email verification link has been emailed to you! +
    + @endif + +### Protecting Routes + +To specify that a route or group of routes requires that the user has verified +their email address, you should attach Laravel's built-in `verified` +middleware to the route. The `verified` middleware alias is automatically +registered by Laravel and serves as an alias for the +`Illuminate\Auth\Middleware\EnsureEmailIsVerified` middleware: + + + + 1Route::get('/dashboard', function () { + + 2 // ... + + 3})->middleware(['verified']); + + + Route::get('/dashboard', function () { + // ... + })->middleware(['verified']); + +## Password Confirmation + +While building your application, you may occasionally have actions that should +require the user to confirm their password before the action is performed. +Typically, these routes are protected by Laravel's built-in `password.confirm` +middleware. + +To begin implementing password confirmation functionality, we need to instruct +Fortify how to return our application's "password confirmation" view. +Remember, Fortify is a headless authentication library. If you would like a +frontend implementation of Laravel's authentication features that are already +completed for you, you should use an [application starter +kit](/docs/12.x/starter-kits). + +All of Fortify's view rendering logic may be customized using the appropriate +methods available via the `Laravel\Fortify\Fortify` class. Typically, you +should call this method from the `boot` method of your application's +`App\Providers\FortifyServiceProvider` class: + + + + 1use Laravel\Fortify\Fortify; + + 2  + + 3/** + + 4 * Bootstrap any application services. + + 5 */ + + 6public function boot(): void + + 7{ + + 8 Fortify::confirmPasswordView(function () { + + 9 return view('auth.confirm-password'); + + 10 }); + + 11  + + 12 // ... + + 13} + + + use Laravel\Fortify\Fortify; + + /** + * Bootstrap any application services. + */ + public function boot(): void + { + Fortify::confirmPasswordView(function () { + return view('auth.confirm-password'); + }); + + // ... + } + +Fortify will take care of defining the `/user/confirm-password` endpoint that +returns this view. Your `confirm-password` template should include a form that +makes a POST request to the `/user/confirm-password` endpoint. The +`/user/confirm-password` endpoint expects a `password` field that contains the +user's current password. + +If the password matches the user's current password, Fortify will redirect the +user to the route they were attempting to access. If the request was an XHR +request, a 201 HTTP response will be returned. + +If the request was not successful, the user will be redirected back to the +confirm password screen and the validation errors will be available to you via +the shared `$errors` Blade template variable. Or, in the case of an XHR +request, the validation errors will be returned with a 422 HTTP response. + diff --git a/output/12.x/frontend.md b/output/12.x/frontend.md new file mode 100644 index 0000000..4a3092e --- /dev/null +++ b/output/12.x/frontend.md @@ -0,0 +1,414 @@ +# Frontend + + * Introduction + * Using PHP + * PHP and Blade + * Livewire + * Starter Kits + * Using React or Vue + * Inertia + * Starter Kits + * Bundling Assets + +## Introduction + +Laravel is a backend framework that provides all of the features you need to +build modern web applications, such as [routing](/docs/12.x/routing), +[validation](/docs/12.x/validation), [caching](/docs/12.x/cache), +[queues](/docs/12.x/queues), [file storage](/docs/12.x/filesystem), and more. +However, we believe it's important to offer developers a beautiful full-stack +experience, including powerful approaches for building your application's +frontend. + +There are two primary ways to tackle frontend development when building an +application with Laravel, and which approach you choose is determined by +whether you would like to build your frontend by leveraging PHP or by using +JavaScript frameworks such as Vue and React. We'll discuss both of these +options below so that you can make an informed decision regarding the best +approach to frontend development for your application. + +## Using PHP + +### PHP and Blade + +In the past, most PHP applications rendered HTML to the browser using simple +HTML templates interspersed with PHP `echo` statements which render data that +was retrieved from a database during the request: + + + + 1
    + + 2 + + 3 Hello, name; ?>
    + + 4 + + 5
    + + +
    + + Hello, name; ?>
    + +
    + +In Laravel, this approach to rendering HTML can still be achieved using +[views](/docs/12.x/views) and [Blade](/docs/12.x/blade). Blade is an extremely +light-weight templating language that provides convenient, short syntax for +displaying data, iterating over data, and more: + + + + 1
    + + 2 @foreach ($users as $user) + + 3 Hello, {{ $user->name }}
    + + 4 @endforeach + + 5
    + + +
    + @foreach ($users as $user) + Hello, {{ $user->name }}
    + @endforeach +
    + +When building applications in this fashion, form submissions and other page +interactions typically receive an entirely new HTML document from the server +and the entire page is re-rendered by the browser. Even today, many +applications may be perfectly suited to having their frontends constructed in +this way using simple Blade templates. + +#### Growing Expectations + +However, as user expectations regarding web applications have matured, many +developers have found the need to build more dynamic frontends with +interactions that feel more polished. In light of this, some developers choose +to begin building their application's frontend using JavaScript frameworks +such as Vue and React. + +Others, preferring to stick with the backend language they are comfortable +with, have developed solutions that allow the construction of modern web +application UIs while still primarily utilizing their backend language of +choice. For example, in the [Rails](https://rubyonrails.org/) ecosystem, this +has spurred the creation of libraries such as +[Turbo](https://turbo.hotwired.dev/) [Hotwire](https://hotwired.dev/), and +[Stimulus](https://stimulus.hotwired.dev/). + +Within the Laravel ecosystem, the need to create modern, dynamic frontends by +primarily using PHP has led to the creation of [Laravel +Livewire](https://livewire.laravel.com) and +[Alpine.js](https://alpinejs.dev/). + +### Livewire + +[Laravel Livewire](https://livewire.laravel.com) is a framework for building +Laravel powered frontends that feel dynamic, modern, and alive just like +frontends built with modern JavaScript frameworks like Vue and React. + +When using Livewire, you will create Livewire "components" that render a +discrete portion of your UI and expose methods and data that can be invoked +and interacted with from your application's frontend. For example, a simple +"Counter" component might look like the following: + + + + 1count++; + + 14 } + + 15  + + 16 public function render() + + 17 { + + 18 return view('livewire.counter'); + + 19 } + + 20} + + + count++; + } + + public function render() + { + return view('livewire.counter'); + } + } + +And, the corresponding template for the counter would be written like so: + + + + 1
    + + 2 + + 3

    {{ $count }}

    + + 4
    + + +
    + +

    {{ $count }}

    +
    + +As you can see, Livewire enables you to write new HTML attributes such as +`wire:click` that connect your Laravel application's frontend and backend. In +addition, you can render your component's current state using simple Blade +expressions. + +For many, Livewire has revolutionized frontend development with Laravel, +allowing them to stay within the comfort of Laravel while constructing modern, +dynamic web applications. Typically, developers using Livewire will also +utilize [Alpine.js](https://alpinejs.dev/) to "sprinkle" JavaScript onto their +frontend only where it is needed, such as in order to render a dialog window. + +If you're new to Laravel, we recommend getting familiar with the basic usage +of [views](/docs/12.x/views) and [Blade](/docs/12.x/blade). Then, consult the +official [Laravel Livewire documentation](https://livewire.laravel.com/docs) +to learn how to take your application to the next level with interactive +Livewire components. + +### Starter Kits + +If you would like to build your frontend using PHP and Livewire, you can +leverage our [Livewire starter kit](/docs/12.x/starter-kits) to jump-start +your application's development. + +## Using React or Vue + +Although it's possible to build modern frontends using Laravel and Livewire, +many developers still prefer to leverage the power of a JavaScript framework +like React or Vue. This allows developers to take advantage of the rich +ecosystem of JavaScript packages and tools available via NPM. + +However, without additional tooling, pairing Laravel with React or Vue would +leave us needing to solve a variety of complicated problems such as client- +side routing, data hydration, and authentication. Client-side routing is often +simplified by using opinionated React / Vue frameworks such as +[Next](https://nextjs.org/) and [Nuxt](https://nuxt.com/); however, data +hydration and authentication remain complicated and cumbersome problems to +solve when pairing a backend framework like Laravel with these frontend +frameworks. + +In addition, developers are left maintaining two separate code repositories, +often needing to coordinate maintenance, releases, and deployments across both +repositories. While these problems are not insurmountable, we don't believe +it's a productive or enjoyable way to develop applications. + +### Inertia + +Thankfully, Laravel offers the best of both worlds. +[Inertia](https://inertiajs.com) bridges the gap between your Laravel +application and your modern React or Vue frontend, allowing you to build full- +fledged, modern frontends using React or Vue while leveraging Laravel routes +and controllers for routing, data hydration, and authentication — all within a +single code repository. With this approach, you can enjoy the full power of +both Laravel and React / Vue without crippling the capabilities of either +tool. + +After installing Inertia into your Laravel application, you will write routes +and controllers like normal. However, instead of returning a Blade template +from your controller, you will return an Inertia page: + + + + 1 User::findOrFail($id) + + 18 ]); + + 19 } + + 20} + + + User::findOrFail($id) + ]); + } + } + +An Inertia page corresponds to a React or Vue component, typically stored +within the `resources/js/pages` directory of your application. The data given +to the page via the `Inertia::render` method will be used to hydrate the +"props" of the page component: + + + + 1import Layout from '@/layouts/authenticated'; + + 2import { Head } from '@inertiajs/react'; + + 3  + + 4export default function Show({ user }) { + + 5 return ( + + 6 + + 7 + + 8

    Welcome

    + + 9

    Hello {user.name}, welcome to Inertia.

    + + 10
    + + 11 ) + + 12} + + + import Layout from '@/layouts/authenticated'; + import { Head } from '@inertiajs/react'; + + export default function Show({ user }) { + return ( + + +

    Welcome

    +

    Hello {user.name}, welcome to Inertia.

    +
    + ) + } + +As you can see, Inertia allows you to leverage the full power of React or Vue +when building your frontend, while providing a light-weight bridge between +your Laravel powered backend and your JavaScript powered frontend. + +#### Server-Side Rendering + +If you're concerned about diving into Inertia because your application +requires server-side rendering, don't worry. Inertia offers [server-side +rendering support](https://inertiajs.com/server-side-rendering). And, when +deploying your application via [Laravel Cloud](https://cloud.laravel.com) or +[Laravel Forge](https://forge.laravel.com), it's a breeze to ensure that +Inertia's server-side rendering process is always running. + +### Starter Kits + +If you would like to build your frontend using Inertia and Vue / React, you +can leverage our [React or Vue application starter kits](/docs/12.x/starter- +kits) to jump-start your application's development. Both of these starter kits +scaffold your application's backend and frontend authentication flow using +Inertia, Vue / React, [Tailwind](https://tailwindcss.com), and +[Vite](https://vitejs.dev) so that you can start building your next big idea. + +## Bundling Assets + +Regardless of whether you choose to develop your frontend using Blade and +Livewire or Vue / React and Inertia, you will likely need to bundle your +application's CSS into production-ready assets. Of course, if you choose to +build your application's frontend with Vue or React, you will also need to +bundle your components into browser ready JavaScript assets. + +By default, Laravel utilizes [Vite](https://vitejs.dev) to bundle your assets. +Vite provides lightning-fast build times and near instantaneous Hot Module +Replacement (HMR) during local development. In all new Laravel applications, +including those using our [starter kits](/docs/12.x/starter-kits), you will +find a `vite.config.js` file that loads our light-weight Laravel Vite plugin +that makes Vite a joy to use with Laravel applications. + +The fastest way to get started with Laravel and Vite is by beginning your +application's development using [our application starter +kits](/docs/12.x/starter-kits), which jump-starts your application by +providing frontend and backend authentication scaffolding. + +For more detailed documentation on utilizing Vite with Laravel, please see our +[dedicated documentation on bundling and compiling your +assets](/docs/12.x/vite). + diff --git a/output/12.x/hashing.md b/output/12.x/hashing.md new file mode 100644 index 0000000..115bd0e --- /dev/null +++ b/output/12.x/hashing.md @@ -0,0 +1,233 @@ +# Hashing + + * Introduction + * Configuration + * Basic Usage + * Hashing Passwords + * Verifying That a Password Matches a Hash + * Determining if a Password Needs to be Rehashed + * Hash Algorithm Verification + +## Introduction + +The Laravel `Hash` [facade](/docs/12.x/facades) provides secure Bcrypt and +Argon2 hashing for storing user passwords. If you are using one of the +[Laravel application starter kits](/docs/12.x/starter-kits), Bcrypt will be +used for registration and authentication by default. + +Bcrypt is a great choice for hashing passwords because its "work factor" is +adjustable, which means that the time it takes to generate a hash can be +increased as hardware power increases. When hashing passwords, slow is good. +The longer an algorithm takes to hash a password, the longer it takes +malicious users to generate "rainbow tables" of all possible string hash +values that may be used in brute force attacks against applications. + +## Configuration + +By default, Laravel uses the `bcrypt` hashing driver when hashing data. +However, several other hashing drivers are supported, including +[argon](https://en.wikipedia.org/wiki/Argon2) and +[argon2id](https://en.wikipedia.org/wiki/Argon2). + +You may specify your application's hashing driver using the `HASH_DRIVER` +environment variable. But, if you want to customize all of Laravel's hashing +driver options, you should publish the complete `hashing` configuration file +using the `config:publish` Artisan command: + + + + 1php artisan config:publish hashing + + + php artisan config:publish hashing + +## Basic Usage + +### Hashing Passwords + +You may hash a password by calling the `make` method on the `Hash` facade: + + + + 1user()->fill([ + + 19 'password' => Hash::make($request->newPassword) + + 20 ])->save(); + + 21  + + 22 return redirect('/profile'); + + 23 } + + 24} + + + 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: + + + + 1$hashed = Hash::make('password', [ + + 2 'rounds' => 12, + + 3]); + + + $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: + + + + 1$hashed = Hash::make('password', [ + + 2 'memory' => 1024, + + 3 'time' => 2, + + 4 'threads' => 2, + + 5]); + + + $hashed = Hash::make('password', [ + 'memory' => 1024, + 'time' => 2, + 'threads' => 2, + ]); + +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). + +### Verifying That a Password Matches a Hash + +The `check` method provided by the `Hash` facade allows you to verify that a +given plain-text string corresponds to a given hash: + + + + 1if (Hash::check('plain-text', $hashedPassword)) { + + 2 // The passwords match... + + 3} + + + 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: + + + + 1if (Hash::needsRehash($hashed)) { + + 2 $hashed = Hash::make('plain-text'); + + 3} + + + if (Hash::needsRehash($hashed)) { + $hashed = Hash::make('plain-text'); + } + +## Hash Algorithm Verification + +To prevent hash algorithm manipulation, Laravel's `Hash::check` method will +first verify the given hash was generated using the application's selected +hashing algorithm. If the algorithms are different, a `RuntimeException` +exception will be thrown. + +This is the expected behavior for most applications, where the hashing +algorithm is not expected to change and different algorithms can be an +indication of a malicious attack. However, if you need to support multiple +hashing algorithms within your application, such as when migrating from one +algorithm to another, you can disable hash algorithm verification by setting +the `HASH_VERIFY` environment variable to `false`: + + + + 1HASH_VERIFY=false + + + HASH_VERIFY=false + diff --git a/output/12.x/helpers.md b/output/12.x/helpers.md new file mode 100644 index 0000000..f5370d2 --- /dev/null +++ b/output/12.x/helpers.md @@ -0,0 +1,7273 @@ +# Helpers + + * Introduction + * Available Methods + * Other Utilities + * Benchmarking + * Dates + * Deferred Functions + * Lottery + * Pipeline + * Sleep + * Timebox + * URI + +## Introduction + +Laravel includes a variety of global "helper" PHP functions. Many of these +functions are used by the framework itself; however, you are free to use them +in your own applications if you find them convenient. + +## Available Methods + +### Arrays & Objects + +Arr::accessible Arr::add Arr::array Arr::boolean Arr::collapse Arr::crossJoin +Arr::divide Arr::dot Arr::every Arr::except Arr::exists Arr::first +Arr::flatten Arr::float Arr::forget Arr::from Arr::get Arr::has Arr::hasAll +Arr::hasAny Arr::integer Arr::isAssoc Arr::isList Arr::join Arr::keyBy +Arr::last Arr::map Arr::mapSpread Arr::mapWithKeys Arr::only Arr::partition +Arr::pluck Arr::prepend Arr::prependKeysWith Arr::pull Arr::push Arr::query +Arr::random Arr::reject Arr::select Arr::set Arr::shuffle Arr::sole Arr::some +Arr::sort Arr::sortDesc Arr::sortRecursive Arr::string Arr::take +Arr::toCssClasses Arr::toCssStyles Arr::undot Arr::where Arr::whereNotNull +Arr::wrap data_fill data_get data_set data_forget head last + +### Numbers + +Number::abbreviate Number::clamp Number::currency Number::defaultCurrency +Number::defaultLocale Number::fileSize Number::forHumans Number::format +Number::ordinal Number::pairs Number::parseInt Number::parseFloat +Number::percentage Number::spell Number::spellOrdinal Number::trim +Number::useLocale Number::withLocale Number::useCurrency Number::withCurrency + +### Paths + +app_path base_path config_path database_path lang_path public_path +resource_path storage_path + +### URLs + +action asset route secure_asset secure_url to_action to_route uri url + +### Miscellaneous + +abort abort_if abort_unless app auth back bcrypt blank broadcast broadcast_if +broadcast_unless cache class_uses_recursive collect config context cookie +csrf_field csrf_token decrypt dd dispatch dispatch_sync dump encrypt env event +fake filled info literal logger method_field now old once optional policy +redirect report report_if report_unless request rescue resolve response retry +session tap throw_if throw_unless today trait_uses_recursive transform +validator value view with when + +## Arrays & Objects + +#### `Arr::accessible()` + +The `Arr::accessible` method determines if the given value is array +accessible: + + + + 1use Illuminate\Support\Arr; + + 2use Illuminate\Support\Collection; + + 3  + + 4$isAccessible = Arr::accessible(['a' => 1, 'b' => 2]); + + 5  + + 6// true + + 7  + + 8$isAccessible = Arr::accessible(new Collection); + + 9  + + 10// true + + 11  + + 12$isAccessible = Arr::accessible('abc'); + + 13  + + 14// false + + 15  + + 16$isAccessible = Arr::accessible(new stdClass); + + 17  + + 18// false + + + use Illuminate\Support\Arr; + use Illuminate\Support\Collection; + + $isAccessible = Arr::accessible(['a' => 1, 'b' => 2]); + + // true + + $isAccessible = Arr::accessible(new Collection); + + // true + + $isAccessible = Arr::accessible('abc'); + + // false + + $isAccessible = Arr::accessible(new stdClass); + + // false + +#### `Arr::add()` + +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`: + + + + 1use Illuminate\Support\Arr; + + 2  + + 3$array = Arr::add(['name' => 'Desk'], 'price', 100); + + 4  + + 5// ['name' => 'Desk', 'price' => 100] + + 6  + + 7$array = Arr::add(['name' => 'Desk', 'price' => null], 'price', 100); + + 8  + + 9// ['name' => 'Desk', 'price' => 100] + + + use Illuminate\Support\Arr; + + $array = Arr::add(['name' => 'Desk'], 'price', 100); + + // ['name' => 'Desk', 'price' => 100] + + $array = Arr::add(['name' => 'Desk', 'price' => null], 'price', 100); + + // ['name' => 'Desk', 'price' => 100] + +#### `Arr::array()` + +The `Arr::array` method retrieves a value from a deeply nested array using +"dot" notation (just as Arr::get() does), but throws an +`InvalidArgumentException` if the requested value is not an `array`: + + + + 1use Illuminate\Support\Arr; + + 2  + + 3$array = ['name' => 'Joe', 'languages' => ['PHP', 'Ruby']]; + + 4  + + 5$value = Arr::array($array, 'languages'); + + 6  + + 7// ['PHP', 'Ruby'] + + 8  + + 9$value = Arr::array($array, 'name'); + + 10  + + 11// throws InvalidArgumentException + + + use Illuminate\Support\Arr; + + $array = ['name' => 'Joe', 'languages' => ['PHP', 'Ruby']]; + + $value = Arr::array($array, 'languages'); + + // ['PHP', 'Ruby'] + + $value = Arr::array($array, 'name'); + + // throws InvalidArgumentException + +#### `Arr::boolean()` + +The `Arr::boolean` method retrieves a value from a deeply nested array using +"dot" notation (just as Arr::get() does), but throws an +`InvalidArgumentException` if the requested value is not a `boolean`: + + + + 1use Illuminate\Support\Arr; + + 2  + + 3$array = ['name' => 'Joe', 'available' => true]; + + 4  + + 5$value = Arr::boolean($array, 'available'); + + 6  + + 7// true + + 8  + + 9$value = Arr::boolean($array, 'name'); + + 10  + + 11// throws InvalidArgumentException + + + use Illuminate\Support\Arr; + + $array = ['name' => 'Joe', 'available' => true]; + + $value = Arr::boolean($array, 'available'); + + // true + + $value = Arr::boolean($array, 'name'); + + // throws InvalidArgumentException + +#### `Arr::collapse()` + +The `Arr::collapse` method collapses an array of arrays or collections into a +single array: + + + + 1use Illuminate\Support\Arr; + + 2  + + 3$array = Arr::collapse([[1, 2, 3], [4, 5, 6], [7, 8, 9]]); + + 4  + + 5// [1, 2, 3, 4, 5, 6, 7, 8, 9] + + + use Illuminate\Support\Arr; + + $array = Arr::collapse([[1, 2, 3], [4, 5, 6], [7, 8, 9]]); + + // [1, 2, 3, 4, 5, 6, 7, 8, 9] + +#### `Arr::crossJoin()` + +The `Arr::crossJoin` method cross joins the given arrays, returning a +Cartesian product with all possible permutations: + + + + 1use Illuminate\Support\Arr; + + 2  + + 3$matrix = Arr::crossJoin([1, 2], ['a', 'b']); + + 4  + + 5/* + + 6 [ + + 7 [1, 'a'], + + 8 [1, 'b'], + + 9 [2, 'a'], + + 10 [2, 'b'], + + 11 ] + + 12*/ + + 13  + + 14$matrix = Arr::crossJoin([1, 2], ['a', 'b'], ['I', 'II']); + + 15  + + 16/* + + 17 [ + + 18 [1, 'a', 'I'], + + 19 [1, 'a', 'II'], + + 20 [1, 'b', 'I'], + + 21 [1, 'b', 'II'], + + 22 [2, 'a', 'I'], + + 23 [2, 'a', 'II'], + + 24 [2, 'b', 'I'], + + 25 [2, 'b', 'II'], + + 26 ] + + 27*/ + + + 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()` + +The `Arr::divide` method returns two arrays: one containing the keys and the +other containing the values of the given array: + + + + 1use Illuminate\Support\Arr; + + 2  + + 3[$keys, $values] = Arr::divide(['name' => 'Desk']); + + 4  + + 5// $keys: ['name'] + + 6  + + 7// $values: ['Desk'] + + + use Illuminate\Support\Arr; + + [$keys, $values] = Arr::divide(['name' => 'Desk']); + + // $keys: ['name'] + + // $values: ['Desk'] + +#### `Arr::dot()` + +The `Arr::dot` method flattens a multi-dimensional array into a single level +array that uses "dot" notation to indicate depth: + + + + 1use Illuminate\Support\Arr; + + 2  + + 3$array = ['products' => ['desk' => ['price' => 100]]]; + + 4  + + 5$flattened = Arr::dot($array); + + 6  + + 7// ['products.desk.price' => 100] + + + use Illuminate\Support\Arr; + + $array = ['products' => ['desk' => ['price' => 100]]]; + + $flattened = Arr::dot($array); + + // ['products.desk.price' => 100] + +#### `Arr::every()` + +The `Arr::every` method ensures that all values in the array pass a given +truth test: + + + + 1use Illuminate\Support\Arr; + + 2  + + 3$array = [1, 2, 3]; + + 4  + + 5Arr::every($array, fn ($i) => $i > 0); + + 6  + + 7// true + + 8  + + 9Arr::every($array, fn ($i) => $i > 2); + + 10  + + 11// false + + + use Illuminate\Support\Arr; + + $array = [1, 2, 3]; + + Arr::every($array, fn ($i) => $i > 0); + + // true + + Arr::every($array, fn ($i) => $i > 2); + + // false + +#### `Arr::except()` + +The `Arr::except` method removes the given key / value pairs from an array: + + + + 1use Illuminate\Support\Arr; + + 2  + + 3$array = ['name' => 'Desk', 'price' => 100]; + + 4  + + 5$filtered = Arr::except($array, ['price']); + + 6  + + 7// ['name' => 'Desk'] + + + use Illuminate\Support\Arr; + + $array = ['name' => 'Desk', 'price' => 100]; + + $filtered = Arr::except($array, ['price']); + + // ['name' => 'Desk'] + +#### `Arr::exists()` + +The `Arr::exists` method checks that the given key exists in the provided +array: + + + + 1use Illuminate\Support\Arr; + + 2  + + 3$array = ['name' => 'John Doe', 'age' => 17]; + + 4  + + 5$exists = Arr::exists($array, 'name'); + + 6  + + 7// true + + 8  + + 9$exists = Arr::exists($array, 'salary'); + + 10  + + 11// false + + + use Illuminate\Support\Arr; + + $array = ['name' => 'John Doe', 'age' => 17]; + + $exists = Arr::exists($array, 'name'); + + // true + + $exists = Arr::exists($array, 'salary'); + + // false + +#### `Arr::first()` + +The `Arr::first` method returns the first element of an array passing a given +truth test: + + + + 1use Illuminate\Support\Arr; + + 2  + + 3$array = [100, 200, 300]; + + 4  + + 5$first = Arr::first($array, function (int $value, int $key) { + + 6 return $value >= 150; + + 7}); + + 8  + + 9// 200 + + + use Illuminate\Support\Arr; + + $array = [100, 200, 300]; + + $first = Arr::first($array, function (int $value, int $key) { + return $value >= 150; + }); + + // 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: + + + + 1use Illuminate\Support\Arr; + + 2  + + 3$first = Arr::first($array, $callback, $default); + + + use Illuminate\Support\Arr; + + $first = Arr::first($array, $callback, $default); + +#### `Arr::flatten()` + +The `Arr::flatten` method flattens a multi-dimensional array into a single +level array: + + + + 1use Illuminate\Support\Arr; + + 2  + + 3$array = ['name' => 'Joe', 'languages' => ['PHP', 'Ruby']]; + + 4  + + 5$flattened = Arr::flatten($array); + + 6  + + 7// ['Joe', 'PHP', 'Ruby'] + + + use Illuminate\Support\Arr; + + $array = ['name' => 'Joe', 'languages' => ['PHP', 'Ruby']]; + + $flattened = Arr::flatten($array); + + // ['Joe', 'PHP', 'Ruby'] + +#### `Arr::float()` + +The `Arr::float` method retrieves a value from a deeply nested array using +"dot" notation (just as Arr::get() does), but throws an +`InvalidArgumentException` if the requested value is not a `float`: + + + + 1use Illuminate\Support\Arr; + + 2  + + 3$array = ['name' => 'Joe', 'balance' => 123.45]; + + 4  + + 5$value = Arr::float($array, 'balance'); + + 6  + + 7// 123.45 + + 8  + + 9$value = Arr::float($array, 'name'); + + 10  + + 11// throws InvalidArgumentException + + + use Illuminate\Support\Arr; + + $array = ['name' => 'Joe', 'balance' => 123.45]; + + $value = Arr::float($array, 'balance'); + + // 123.45 + + $value = Arr::float($array, 'name'); + + // throws InvalidArgumentException + +#### `Arr::forget()` + +The `Arr::forget` method removes a given key / value pairs from a deeply +nested array using "dot" notation: + + + + 1use Illuminate\Support\Arr; + + 2  + + 3$array = ['products' => ['desk' => ['price' => 100]]]; + + 4  + + 5Arr::forget($array, 'products.desk'); + + 6  + + 7// ['products' => []] + + + use Illuminate\Support\Arr; + + $array = ['products' => ['desk' => ['price' => 100]]]; + + Arr::forget($array, 'products.desk'); + + // ['products' => []] + +#### `Arr::from()` + +The `Arr::from` method converts various input types into a plain PHP array. It +supports a range of input types, including arrays, objects, and several common +Laravel interfaces, such as `Arrayable`, `Enumerable`, `Jsonable`, and +`JsonSerializable`. Additionally, it handles `Traversable` and `WeakMap` +instances: + + + + 1use Illuminate\Support\Arr; + + 2  + + 3Arr::from((object) ['foo' => 'bar']); // ['foo' => 'bar'] + + 4  + + 5class TestJsonableObject implements Jsonable + + 6{ + + 7 public function toJson($options = 0) + + 8 { + + 9 return json_encode(['foo' => 'bar']); + + 10 } + + 11} + + 12  + + 13Arr::from(new TestJsonableObject); // ['foo' => 'bar'] + + + use Illuminate\Support\Arr; + + Arr::from((object) ['foo' => 'bar']); // ['foo' => 'bar'] + + class TestJsonableObject implements Jsonable + { + public function toJson($options = 0) + { + return json_encode(['foo' => 'bar']); + } + } + + Arr::from(new TestJsonableObject); // ['foo' => 'bar'] + +#### `Arr::get()` + +The `Arr::get` method retrieves a value from a deeply nested array using "dot" +notation: + + + + 1use Illuminate\Support\Arr; + + 2  + + 3$array = ['products' => ['desk' => ['price' => 100]]]; + + 4  + + 5$price = Arr::get($array, 'products.desk.price'); + + 6  + + 7// 100 + + + use Illuminate\Support\Arr; + + $array = ['products' => ['desk' => ['price' => 100]]]; + + $price = Arr::get($array, 'products.desk.price'); + + // 100 + +The `Arr::get` method also accepts a default value, which will be returned if +the specified key is not present in the array: + + + + 1use Illuminate\Support\Arr; + + 2  + + 3$discount = Arr::get($array, 'products.desk.discount', 0); + + 4  + + 5// 0 + + + use Illuminate\Support\Arr; + + $discount = Arr::get($array, 'products.desk.discount', 0); + + // 0 + +#### `Arr::has()` + +The `Arr::has` method checks whether a given item or items exists in an array +using "dot" notation: + + + + 1use Illuminate\Support\Arr; + + 2  + + 3$array = ['product' => ['name' => 'Desk', 'price' => 100]]; + + 4  + + 5$contains = Arr::has($array, 'product.name'); + + 6  + + 7// true + + 8  + + 9$contains = Arr::has($array, ['product.price', 'product.discount']); + + 10  + + 11// false + + + use Illuminate\Support\Arr; + + $array = ['product' => ['name' => 'Desk', 'price' => 100]]; + + $contains = Arr::has($array, 'product.name'); + + // true + + $contains = Arr::has($array, ['product.price', 'product.discount']); + + // false + +#### `Arr::hasAll()` + +The `Arr::hasAll` method determines if all of the specified keys exist in the +given array using "dot" notation: + + + + 1use Illuminate\Support\Arr; + + 2  + + 3$array = ['name' => 'Taylor', 'language' => 'PHP']; + + 4  + + 5Arr::hasAll($array, ['name']); // true + + 6Arr::hasAll($array, ['name', 'language']); // true + + 7Arr::hasAll($array, ['name', 'IDE']); // false + + + use Illuminate\Support\Arr; + + $array = ['name' => 'Taylor', 'language' => 'PHP']; + + Arr::hasAll($array, ['name']); // true + Arr::hasAll($array, ['name', 'language']); // true + Arr::hasAll($array, ['name', 'IDE']); // false + +#### `Arr::hasAny()` + +The `Arr::hasAny` method checks whether any item in a given set exists in an +array using "dot" notation: + + + + 1use Illuminate\Support\Arr; + + 2  + + 3$array = ['product' => ['name' => 'Desk', 'price' => 100]]; + + 4  + + 5$contains = Arr::hasAny($array, 'product.name'); + + 6  + + 7// true + + 8  + + 9$contains = Arr::hasAny($array, ['product.name', 'product.discount']); + + 10  + + 11// true + + 12  + + 13$contains = Arr::hasAny($array, ['category', 'product.discount']); + + 14  + + 15// false + + + use Illuminate\Support\Arr; + + $array = ['product' => ['name' => 'Desk', 'price' => 100]]; + + $contains = Arr::hasAny($array, 'product.name'); + + // true + + $contains = Arr::hasAny($array, ['product.name', 'product.discount']); + + // true + + $contains = Arr::hasAny($array, ['category', 'product.discount']); + + // false + +#### `Arr::integer()` + +The `Arr::integer` method retrieves a value from a deeply nested array using +"dot" notation (just as Arr::get() does), but throws an +`InvalidArgumentException` if the requested value is not an `int`: + + + + 1use Illuminate\Support\Arr; + + 2  + + 3$array = ['name' => 'Joe', 'age' => 42]; + + 4  + + 5$value = Arr::integer($array, 'age'); + + 6  + + 7// 42 + + 8  + + 9$value = Arr::integer($array, 'name'); + + 10  + + 11// throws InvalidArgumentException + + + use Illuminate\Support\Arr; + + $array = ['name' => 'Joe', 'age' => 42]; + + $value = Arr::integer($array, 'age'); + + // 42 + + $value = Arr::integer($array, 'name'); + + // throws InvalidArgumentException + +#### `Arr::isAssoc()` + +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: + + + + 1use Illuminate\Support\Arr; + + 2  + + 3$isAssoc = Arr::isAssoc(['product' => ['name' => 'Desk', 'price' => 100]]); + + 4  + + 5// true + + 6  + + 7$isAssoc = Arr::isAssoc([1, 2, 3]); + + 8  + + 9// false + + + use Illuminate\Support\Arr; + + $isAssoc = Arr::isAssoc(['product' => ['name' => 'Desk', 'price' => 100]]); + + // true + + $isAssoc = Arr::isAssoc([1, 2, 3]); + + // false + +#### `Arr::isList()` + +The `Arr::isList` method returns `true` if the given array's keys are +sequential integers beginning from zero: + + + + 1use Illuminate\Support\Arr; + + 2  + + 3$isList = Arr::isList(['foo', 'bar', 'baz']); + + 4  + + 5// true + + 6  + + 7$isList = Arr::isList(['product' => ['name' => 'Desk', 'price' => 100]]); + + 8  + + 9// false + + + use Illuminate\Support\Arr; + + $isList = Arr::isList(['foo', 'bar', 'baz']); + + // true + + $isList = Arr::isList(['product' => ['name' => 'Desk', 'price' => 100]]); + + // false + +#### `Arr::join()` + +The `Arr::join` method joins array elements with a string. Using this method's +third argument, you may also specify the joining string for the final element +of the array: + + + + 1use Illuminate\Support\Arr; + + 2  + + 3$array = ['Tailwind', 'Alpine', 'Laravel', 'Livewire']; + + 4  + + 5$joined = Arr::join($array, ', '); + + 6  + + 7// Tailwind, Alpine, Laravel, Livewire + + 8  + + 9$joined = Arr::join($array, ', ', ', and '); + + 10  + + 11// Tailwind, Alpine, Laravel, and Livewire + + + use Illuminate\Support\Arr; + + $array = ['Tailwind', 'Alpine', 'Laravel', 'Livewire']; + + $joined = Arr::join($array, ', '); + + // Tailwind, Alpine, Laravel, Livewire + + $joined = Arr::join($array, ', ', ', and '); + + // Tailwind, Alpine, Laravel, and Livewire + +#### `Arr::keyBy()` + +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: + + + + 1use Illuminate\Support\Arr; + + 2  + + 3$array = [ + + 4 ['product_id' => 'prod-100', 'name' => 'Desk'], + + 5 ['product_id' => 'prod-200', 'name' => 'Chair'], + + 6]; + + 7  + + 8$keyed = Arr::keyBy($array, 'product_id'); + + 9  + + 10/* + + 11 [ + + 12 'prod-100' => ['product_id' => 'prod-100', 'name' => 'Desk'], + + 13 'prod-200' => ['product_id' => 'prod-200', 'name' => 'Chair'], + + 14 ] + + 15*/ + + + use Illuminate\Support\Arr; + + $array = [ + ['product_id' => 'prod-100', 'name' => 'Desk'], + ['product_id' => 'prod-200', 'name' => 'Chair'], + ]; + + $keyed = Arr::keyBy($array, 'product_id'); + + /* + [ + 'prod-100' => ['product_id' => 'prod-100', 'name' => 'Desk'], + 'prod-200' => ['product_id' => 'prod-200', 'name' => 'Chair'], + ] + */ + +#### `Arr::last()` + +The `Arr::last` method returns the last element of an array passing a given +truth test: + + + + 1use Illuminate\Support\Arr; + + 2  + + 3$array = [100, 200, 300, 110]; + + 4  + + 5$last = Arr::last($array, function (int $value, int $key) { + + 6 return $value >= 150; + + 7}); + + 8  + + 9// 300 + + + use Illuminate\Support\Arr; + + $array = [100, 200, 300, 110]; + + $last = Arr::last($array, function (int $value, int $key) { + return $value >= 150; + }); + + // 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: + + + + 1use Illuminate\Support\Arr; + + 2  + + 3$last = Arr::last($array, $callback, $default); + + + use Illuminate\Support\Arr; + + $last = Arr::last($array, $callback, $default); + +#### `Arr::map()` + +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: + + + + 1use Illuminate\Support\Arr; + + 2  + + 3$array = ['first' => 'james', 'last' => 'kirk']; + + 4  + + 5$mapped = Arr::map($array, function (string $value, string $key) { + + 6 return ucfirst($value); + + 7}); + + 8  + + 9// ['first' => 'James', 'last' => 'Kirk'] + + + use Illuminate\Support\Arr; + + $array = ['first' => 'james', 'last' => 'kirk']; + + $mapped = Arr::map($array, function (string $value, string $key) { + return ucfirst($value); + }); + + // ['first' => 'James', 'last' => 'Kirk'] + +#### `Arr::mapSpread()` + +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: + + + + 1use Illuminate\Support\Arr; + + 2  + + 3$array = [ + + 4 [0, 1], + + 5 [2, 3], + + 6 [4, 5], + + 7 [6, 7], + + 8 [8, 9], + + 9]; + + 10  + + 11$mapped = Arr::mapSpread($array, function (int $even, int $odd) { + + 12 return $even + $odd; + + 13}); + + 14  + + 15/* + + 16 [1, 5, 9, 13, 17] + + 17*/ + + + use Illuminate\Support\Arr; + + $array = [ + [0, 1], + [2, 3], + [4, 5], + [6, 7], + [8, 9], + ]; + + $mapped = Arr::mapSpread($array, function (int $even, int $odd) { + return $even + $odd; + }); + + /* + [1, 5, 9, 13, 17] + */ + +#### `Arr::mapWithKeys()` + +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: + + + + 1use Illuminate\Support\Arr; + + 2  + + 3$array = [ + + 4 [ + + 5 'name' => 'John', + + 6 'department' => 'Sales', + + 7 'email' => '[[email protected]](/cdn-cgi/l/email-protection)', + + 8 ], + + 9 [ + + 10 'name' => 'Jane', + + 11 'department' => 'Marketing', + + 12 'email' => '[[email protected]](/cdn-cgi/l/email-protection)', + + 13 ] + + 14]; + + 15  + + 16$mapped = Arr::mapWithKeys($array, function (array $item, int $key) { + + 17 return [$item['email'] => $item['name']]; + + 18}); + + 19  + + 20/* + + 21 [ + + 22 '[[email protected]](/cdn-cgi/l/email-protection)' => 'John', + + 23 '[[email protected]](/cdn-cgi/l/email-protection)' => 'Jane', + + 24 ] + + 25*/ + + + use Illuminate\Support\Arr; + + $array = [ + [ + 'name' => 'John', + 'department' => 'Sales', + 'email' => '[[email protected]](/cdn-cgi/l/email-protection)', + ], + [ + 'name' => 'Jane', + 'department' => 'Marketing', + 'email' => '[[email protected]](/cdn-cgi/l/email-protection)', + ] + ]; + + $mapped = Arr::mapWithKeys($array, function (array $item, int $key) { + return [$item['email'] => $item['name']]; + }); + + /* + [ + '[[email protected]](/cdn-cgi/l/email-protection)' => 'John', + '[[email protected]](/cdn-cgi/l/email-protection)' => 'Jane', + ] + */ + +#### `Arr::only()` + +The `Arr::only` method returns only the specified key / value pairs from the +given array: + + + + 1use Illuminate\Support\Arr; + + 2  + + 3$array = ['name' => 'Desk', 'price' => 100, 'orders' => 10]; + + 4  + + 5$slice = Arr::only($array, ['name', 'price']); + + 6  + + 7// ['name' => 'Desk', 'price' => 100] + + + use Illuminate\Support\Arr; + + $array = ['name' => 'Desk', 'price' => 100, 'orders' => 10]; + + $slice = Arr::only($array, ['name', 'price']); + + // ['name' => 'Desk', 'price' => 100] + +#### `Arr::partition()` + +The `Arr::partition` method may be combined with PHP array destructuring to +separate elements that pass a given truth test from those that do not: + + + + 1 ['id' => 1, 'name' => 'Taylor']], + + 5 ['developer' => ['id' => 2, 'name' => 'Abigail']], + + 6]; + + 7  + + 8$names = Arr::pluck($array, 'developer.name'); + + 9  + + 10// ['Taylor', 'Abigail'] + + + use Illuminate\Support\Arr; + + $array = [ + ['developer' => ['id' => 1, 'name' => 'Taylor']], + ['developer' => ['id' => 2, 'name' => 'Abigail']], + ]; + + $names = Arr::pluck($array, 'developer.name'); + + // ['Taylor', 'Abigail'] + +You may also specify how you wish the resulting list to be keyed: + + + + 1use Illuminate\Support\Arr; + + 2  + + 3$names = Arr::pluck($array, 'developer.name', 'developer.id'); + + 4  + + 5// [1 => 'Taylor', 2 => 'Abigail'] + + + use Illuminate\Support\Arr; + + $names = Arr::pluck($array, 'developer.name', 'developer.id'); + + // [1 => 'Taylor', 2 => 'Abigail'] + +#### `Arr::prepend()` + +The `Arr::prepend` method will push an item onto the beginning of an array: + + + + 1use Illuminate\Support\Arr; + + 2  + + 3$array = ['one', 'two', 'three', 'four']; + + 4  + + 5$array = Arr::prepend($array, 'zero'); + + 6  + + 7// ['zero', 'one', 'two', 'three', 'four'] + + + use Illuminate\Support\Arr; + + $array = ['one', 'two', 'three', 'four']; + + $array = Arr::prepend($array, 'zero'); + + // ['zero', 'one', 'two', 'three', 'four'] + +If needed, you may specify the key that should be used for the value: + + + + 1use Illuminate\Support\Arr; + + 2  + + 3$array = ['price' => 100]; + + 4  + + 5$array = Arr::prepend($array, 'Desk', 'name'); + + 6  + + 7// ['name' => 'Desk', 'price' => 100] + + + use Illuminate\Support\Arr; + + $array = ['price' => 100]; + + $array = Arr::prepend($array, 'Desk', 'name'); + + // ['name' => 'Desk', 'price' => 100] + +#### `Arr::prependKeysWith()` + +The `Arr::prependKeysWith` prepends all key names of an associative array with +the given prefix: + + + + 1use Illuminate\Support\Arr; + + 2  + + 3$array = [ + + 4 'name' => 'Desk', + + 5 'price' => 100, + + 6]; + + 7  + + 8$keyed = Arr::prependKeysWith($array, 'product.'); + + 9  + + 10/* + + 11 [ + + 12 'product.name' => 'Desk', + + 13 'product.price' => 100, + + 14 ] + + 15*/ + + + use Illuminate\Support\Arr; + + $array = [ + 'name' => 'Desk', + 'price' => 100, + ]; + + $keyed = Arr::prependKeysWith($array, 'product.'); + + /* + [ + 'product.name' => 'Desk', + 'product.price' => 100, + ] + */ + +#### `Arr::pull()` + +The `Arr::pull` method returns and removes a key / value pair from an array: + + + + 1use Illuminate\Support\Arr; + + 2  + + 3$array = ['name' => 'Desk', 'price' => 100]; + + 4  + + 5$name = Arr::pull($array, 'name'); + + 6  + + 7// $name: Desk + + 8  + + 9// $array: ['price' => 100] + + + use Illuminate\Support\Arr; + + $array = ['name' => 'Desk', 'price' => 100]; + + $name = Arr::pull($array, 'name'); + + // $name: Desk + + // $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: + + + + 1use Illuminate\Support\Arr; + + 2  + + 3$value = Arr::pull($array, $key, $default); + + + use Illuminate\Support\Arr; + + $value = Arr::pull($array, $key, $default); + +#### `Arr::push()` + +The `Arr::push` method pushes an item into an array using "dot" notation. If +an array does not exist at the given key, it will be created: + + + + 1use Illuminate\Support\Arr; + + 2  + + 3$array = []; + + 4  + + 5Arr::push($array, 'office.furniture', 'Desk'); + + 6  + + 7// $array: ['office' => ['furniture' => ['Desk']]] + + + use Illuminate\Support\Arr; + + $array = []; + + Arr::push($array, 'office.furniture', 'Desk'); + + // $array: ['office' => ['furniture' => ['Desk']]] + +#### `Arr::query()` + +The `Arr::query` method converts the array into a query string: + + + + 1use Illuminate\Support\Arr; + + 2  + + 3$array = [ + + 4 'name' => 'Taylor', + + 5 'order' => [ + + 6 'column' => 'created_at', + + 7 'direction' => 'desc' + + 8 ] + + 9]; + + 10  + + 11Arr::query($array); + + 12  + + 13// name=Taylor&order[column]=created_at&order[direction]=desc + + + use Illuminate\Support\Arr; + + $array = [ + 'name' => 'Taylor', + 'order' => [ + 'column' => 'created_at', + 'direction' => 'desc' + ] + ]; + + Arr::query($array); + + // name=Taylor&order[column]=created_at&order[direction]=desc + +#### `Arr::random()` + +The `Arr::random` method returns a random value from an array: + + + + 1use Illuminate\Support\Arr; + + 2  + + 3$array = [1, 2, 3, 4, 5]; + + 4  + + 5$random = Arr::random($array); + + 6  + + 7// 4 - (retrieved randomly) + + + use Illuminate\Support\Arr; + + $array = [1, 2, 3, 4, 5]; + + $random = Arr::random($array); + + // 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: + + + + 1use Illuminate\Support\Arr; + + 2  + + 3$items = Arr::random($array, 2); + + 4  + + 5// [2, 5] - (retrieved randomly) + + + use Illuminate\Support\Arr; + + $items = Arr::random($array, 2); + + // [2, 5] - (retrieved randomly) + +#### `Arr::reject()` + +The `Arr::reject` method removes items from an array using the given closure: + + + + 1use Illuminate\Support\Arr; + + 2  + + 3$array = [100, '200', 300, '400', 500]; + + 4  + + 5$filtered = Arr::reject($array, function (string|int $value, int $key) { + + 6 return is_string($value); + + 7}); + + 8  + + 9// [0 => 100, 2 => 300, 4 => 500] + + + use Illuminate\Support\Arr; + + $array = [100, '200', 300, '400', 500]; + + $filtered = Arr::reject($array, function (string|int $value, int $key) { + return is_string($value); + }); + + // [0 => 100, 2 => 300, 4 => 500] + +#### `Arr::select()` + +The `Arr::select` method selects an array of values from an array: + + + + 1use Illuminate\Support\Arr; + + 2  + + 3$array = [ + + 4 ['id' => 1, 'name' => 'Desk', 'price' => 200], + + 5 ['id' => 2, 'name' => 'Table', 'price' => 150], + + 6 ['id' => 3, 'name' => 'Chair', 'price' => 300], + + 7]; + + 8  + + 9Arr::select($array, ['name', 'price']); + + 10  + + 11// [['name' => 'Desk', 'price' => 200], ['name' => 'Table', 'price' => 150], ['name' => 'Chair', 'price' => 300]] + + + use Illuminate\Support\Arr; + + $array = [ + ['id' => 1, 'name' => 'Desk', 'price' => 200], + ['id' => 2, 'name' => 'Table', 'price' => 150], + ['id' => 3, 'name' => 'Chair', 'price' => 300], + ]; + + Arr::select($array, ['name', 'price']); + + // [['name' => 'Desk', 'price' => 200], ['name' => 'Table', 'price' => 150], ['name' => 'Chair', 'price' => 300]] + +#### `Arr::set()` + +The `Arr::set` method sets a value within a deeply nested array using "dot" +notation: + + + + 1use Illuminate\Support\Arr; + + 2  + + 3$array = ['products' => ['desk' => ['price' => 100]]]; + + 4  + + 5Arr::set($array, 'products.desk.price', 200); + + 6  + + 7// ['products' => ['desk' => ['price' => 200]]] + + + use Illuminate\Support\Arr; + + $array = ['products' => ['desk' => ['price' => 100]]]; + + Arr::set($array, 'products.desk.price', 200); + + // ['products' => ['desk' => ['price' => 200]]] + +#### `Arr::shuffle()` + +The `Arr::shuffle` method randomly shuffles the items in the array: + + + + 1use Illuminate\Support\Arr; + + 2  + + 3$array = Arr::shuffle([1, 2, 3, 4, 5]); + + 4  + + 5// [3, 2, 5, 1, 4] - (generated randomly) + + + use Illuminate\Support\Arr; + + $array = Arr::shuffle([1, 2, 3, 4, 5]); + + // [3, 2, 5, 1, 4] - (generated randomly) + +#### `Arr::sole()` + +The `Arr::sole` method retrieves a single value from an array using the given +closure. If more than one value within the array matches the given truth test, +an `Illuminate\Support\MultipleItemsFoundException` exception will be thrown. +If no values match the truth test, an +`Illuminate\Support\ItemNotFoundException` exception will be thrown: + + + + 1use Illuminate\Support\Arr; + + 2  + + 3$array = ['Desk', 'Table', 'Chair']; + + 4  + + 5$value = Arr::sole($array, fn (string $value) => $value === 'Desk'); + + 6  + + 7// 'Desk' + + + use Illuminate\Support\Arr; + + $array = ['Desk', 'Table', 'Chair']; + + $value = Arr::sole($array, fn (string $value) => $value === 'Desk'); + + // 'Desk' + +#### `Arr::some()` + +The `Arr::some` method ensures that at least one of the values in the array +passes a given truth test: + + + + 1use Illuminate\Support\Arr; + + 2  + + 3$array = [1, 2, 3]; + + 4  + + 5Arr::some($array, fn ($i) => $i > 2); + + 6  + + 7// true + + + use Illuminate\Support\Arr; + + $array = [1, 2, 3]; + + Arr::some($array, fn ($i) => $i > 2); + + // true + +#### `Arr::sort()` + +The `Arr::sort` method sorts an array by its values: + + + + 1use Illuminate\Support\Arr; + + 2  + + 3$array = ['Desk', 'Table', 'Chair']; + + 4  + + 5$sorted = Arr::sort($array); + + 6  + + 7// ['Chair', 'Desk', 'Table'] + + + use Illuminate\Support\Arr; + + $array = ['Desk', 'Table', 'Chair']; + + $sorted = Arr::sort($array); + + // ['Chair', 'Desk', 'Table'] + +You may also sort the array by the results of a given closure: + + + + 1use Illuminate\Support\Arr; + + 2  + + 3$array = [ + + 4 ['name' => 'Desk'], + + 5 ['name' => 'Table'], + + 6 ['name' => 'Chair'], + + 7]; + + 8  + + 9$sorted = array_values(Arr::sort($array, function (array $value) { + + 10 return $value['name']; + + 11})); + + 12  + + 13/* + + 14 [ + + 15 ['name' => 'Chair'], + + 16 ['name' => 'Desk'], + + 17 ['name' => 'Table'], + + 18 ] + + 19*/ + + + use Illuminate\Support\Arr; + + $array = [ + ['name' => 'Desk'], + ['name' => 'Table'], + ['name' => 'Chair'], + ]; + + $sorted = array_values(Arr::sort($array, function (array $value) { + return $value['name']; + })); + + /* + [ + ['name' => 'Chair'], + ['name' => 'Desk'], + ['name' => 'Table'], + ] + */ + +#### `Arr::sortDesc()` + +The `Arr::sortDesc` method sorts an array in descending order by its values: + + + + 1use Illuminate\Support\Arr; + + 2  + + 3$array = ['Desk', 'Table', 'Chair']; + + 4  + + 5$sorted = Arr::sortDesc($array); + + 6  + + 7// ['Table', 'Desk', 'Chair'] + + + use Illuminate\Support\Arr; + + $array = ['Desk', 'Table', 'Chair']; + + $sorted = Arr::sortDesc($array); + + // ['Table', 'Desk', 'Chair'] + +You may also sort the array by the results of a given closure: + + + + 1use Illuminate\Support\Arr; + + 2  + + 3$array = [ + + 4 ['name' => 'Desk'], + + 5 ['name' => 'Table'], + + 6 ['name' => 'Chair'], + + 7]; + + 8  + + 9$sorted = array_values(Arr::sortDesc($array, function (array $value) { + + 10 return $value['name']; + + 11})); + + 12  + + 13/* + + 14 [ + + 15 ['name' => 'Table'], + + 16 ['name' => 'Desk'], + + 17 ['name' => 'Chair'], + + 18 ] + + 19*/ + + + use Illuminate\Support\Arr; + + $array = [ + ['name' => 'Desk'], + ['name' => 'Table'], + ['name' => 'Chair'], + ]; + + $sorted = array_values(Arr::sortDesc($array, function (array $value) { + return $value['name']; + })); + + /* + [ + ['name' => 'Table'], + ['name' => 'Desk'], + ['name' => 'Chair'], + ] + */ + +#### `Arr::sortRecursive()` + +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: + + + + 1use Illuminate\Support\Arr; + + 2  + + 3$array = [ + + 4 ['Roman', 'Taylor', 'Li'], + + 5 ['PHP', 'Ruby', 'JavaScript'], + + 6 ['one' => 1, 'two' => 2, 'three' => 3], + + 7]; + + 8  + + 9$sorted = Arr::sortRecursive($array); + + 10  + + 11/* + + 12 [ + + 13 ['JavaScript', 'PHP', 'Ruby'], + + 14 ['one' => 1, 'three' => 3, 'two' => 2], + + 15 ['Li', 'Roman', 'Taylor'], + + 16 ] + + 17*/ + + + use Illuminate\Support\Arr; + + $array = [ + ['Roman', 'Taylor', 'Li'], + ['PHP', 'Ruby', 'JavaScript'], + ['one' => 1, 'two' => 2, 'three' => 3], + ]; + + $sorted = Arr::sortRecursive($array); + + /* + [ + ['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. + + + + 1$sorted = Arr::sortRecursiveDesc($array); + + + $sorted = Arr::sortRecursiveDesc($array); + +#### `Arr::string()` + +The `Arr::string` method retrieves a value from a deeply nested array using +"dot" notation (just as Arr::get() does), but throws an +`InvalidArgumentException` if the requested value is not a `string`: + + + + 1use Illuminate\Support\Arr; + + 2  + + 3$array = ['name' => 'Joe', 'languages' => ['PHP', 'Ruby']]; + + 4  + + 5$value = Arr::string($array, 'name'); + + 6  + + 7// Joe + + 8  + + 9$value = Arr::string($array, 'languages'); + + 10  + + 11// throws InvalidArgumentException + + + use Illuminate\Support\Arr; + + $array = ['name' => 'Joe', 'languages' => ['PHP', 'Ruby']]; + + $value = Arr::string($array, 'name'); + + // Joe + + $value = Arr::string($array, 'languages'); + + // throws InvalidArgumentException + +#### `Arr::take()` + +The `Arr::take` method returns a new array with the specified number of items: + + + + 1use Illuminate\Support\Arr; + + 2  + + 3$array = [0, 1, 2, 3, 4, 5]; + + 4  + + 5$chunk = Arr::take($array, 3); + + 6  + + 7// [0, 1, 2] + + + use Illuminate\Support\Arr; + + $array = [0, 1, 2, 3, 4, 5]; + + $chunk = Arr::take($array, 3); + + // [0, 1, 2] + +You may also pass a negative integer to take the specified number of items +from the end of the array: + + + + 1$array = [0, 1, 2, 3, 4, 5]; + + 2  + + 3$chunk = Arr::take($array, -2); + + 4  + + 5// [4, 5] + + + $array = [0, 1, 2, 3, 4, 5]; + + $chunk = Arr::take($array, -2); + + // [4, 5] + +#### `Arr::toCssClasses()` + +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: + + + + 1use Illuminate\Support\Arr; + + 2  + + 3$isActive = false; + + 4$hasError = true; + + 5  + + 6$array = ['p-4', 'font-bold' => $isActive, 'bg-red' => $hasError]; + + 7  + + 8$classes = Arr::toCssClasses($array); + + 9  + + 10/* + + 11 'p-4 bg-red' + + 12*/ + + + use Illuminate\Support\Arr; + + $isActive = false; + $hasError = true; + + $array = ['p-4', 'font-bold' => $isActive, 'bg-red' => $hasError]; + + $classes = Arr::toCssClasses($array); + + /* + 'p-4 bg-red' + */ + +#### `Arr::toCssStyles()` + +The `Arr::toCssStyles` conditionally compiles a CSS style 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: + + + + 1use Illuminate\Support\Arr; + + 2  + + 3$hasColor = true; + + 4  + + 5$array = ['background-color: blue', 'color: blue' => $hasColor]; + + 6  + + 7$classes = Arr::toCssStyles($array); + + 8  + + 9/* + + 10 'background-color: blue; color: blue;' + + 11*/ + + + use Illuminate\Support\Arr; + + $hasColor = true; + + $array = ['background-color: blue', 'color: blue' => $hasColor]; + + $classes = Arr::toCssStyles($array); + + /* + 'background-color: blue; color: blue;' + */ + +This method powers Laravel's functionality allowing [merging classes with a +Blade component's attribute bag](/docs/12.x/blade#conditionally-merge-classes) +as well as the `@class` [Blade directive](/docs/12.x/blade#conditional- +classes). + +#### `Arr::undot()` + +The `Arr::undot` method expands a single-dimensional array that uses "dot" +notation into a multi-dimensional array: + + + + 1use Illuminate\Support\Arr; + + 2  + + 3$array = [ + + 4 'user.name' => 'Kevin Malone', + + 5 'user.occupation' => 'Accountant', + + 6]; + + 7  + + 8$array = Arr::undot($array); + + 9  + + 10// ['user' => ['name' => 'Kevin Malone', 'occupation' => 'Accountant']] + + + use Illuminate\Support\Arr; + + $array = [ + 'user.name' => 'Kevin Malone', + 'user.occupation' => 'Accountant', + ]; + + $array = Arr::undot($array); + + // ['user' => ['name' => 'Kevin Malone', 'occupation' => 'Accountant']] + +#### `Arr::where()` + +The `Arr::where` method filters an array using the given closure: + + + + 1use Illuminate\Support\Arr; + + 2  + + 3$array = [100, '200', 300, '400', 500]; + + 4  + + 5$filtered = Arr::where($array, function (string|int $value, int $key) { + + 6 return is_string($value); + + 7}); + + 8  + + 9// [1 => '200', 3 => '400'] + + + use Illuminate\Support\Arr; + + $array = [100, '200', 300, '400', 500]; + + $filtered = Arr::where($array, function (string|int $value, int $key) { + return is_string($value); + }); + + // [1 => '200', 3 => '400'] + +#### `Arr::whereNotNull()` + +The `Arr::whereNotNull` method removes all `null` values from the given array: + + + + 1use Illuminate\Support\Arr; + + 2  + + 3$array = [0, null]; + + 4  + + 5$filtered = Arr::whereNotNull($array); + + 6  + + 7// [0 => 0] + + + use Illuminate\Support\Arr; + + $array = [0, null]; + + $filtered = Arr::whereNotNull($array); + + // [0 => 0] + +#### `Arr::wrap()` + +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: + + + + 1use Illuminate\Support\Arr; + + 2  + + 3$string = 'Laravel'; + + 4  + + 5$array = Arr::wrap($string); + + 6  + + 7// ['Laravel'] + + + use Illuminate\Support\Arr; + + $string = 'Laravel'; + + $array = Arr::wrap($string); + + // ['Laravel'] + +If the given value is `null`, an empty array will be returned: + + + + 1use Illuminate\Support\Arr; + + 2  + + 3$array = Arr::wrap(null); + + 4  + + 5// [] + + + use Illuminate\Support\Arr; + + $array = Arr::wrap(null); + + // [] + +#### `data_fill()` + +The `data_fill` function sets a missing value within a nested array or object +using "dot" notation: + + + + 1$data = ['products' => ['desk' => ['price' => 100]]]; + + 2  + + 3data_fill($data, 'products.desk.price', 200); + + 4  + + 5// ['products' => ['desk' => ['price' => 100]]] + + 6  + + 7data_fill($data, 'products.desk.discount', 10); + + 8  + + 9// ['products' => ['desk' => ['price' => 100, 'discount' => 10]]] + + + $data = ['products' => ['desk' => ['price' => 100]]]; + + data_fill($data, 'products.desk.price', 200); + + // ['products' => ['desk' => ['price' => 100]]] + + data_fill($data, 'products.desk.discount', 10); + + // ['products' => ['desk' => ['price' => 100, 'discount' => 10]]] + +This function also accepts asterisks as wildcards and will fill the target +accordingly: + + + + 1$data = [ + + 2 'products' => [ + + 3 ['name' => 'Desk 1', 'price' => 100], + + 4 ['name' => 'Desk 2'], + + 5 ], + + 6]; + + 7  + + 8data_fill($data, 'products.*.price', 200); + + 9  + + 10/* + + 11 [ + + 12 'products' => [ + + 13 ['name' => 'Desk 1', 'price' => 100], + + 14 ['name' => 'Desk 2', 'price' => 200], + + 15 ], + + 16 ] + + 17*/ + + + $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', 'price' => 200], + ], + ] + */ + +#### `data_get()` + +The `data_get` function retrieves a value from a nested array or object using +"dot" notation: + + + + 1$data = ['products' => ['desk' => ['price' => 100]]]; + + 2  + + 3$price = data_get($data, 'products.desk.price'); + + 4  + + 5// 100 + + + $data = ['products' => ['desk' => ['price' => 100]]]; + + $price = data_get($data, 'products.desk.price'); + + // 100 + +The `data_get` function also accepts a default value, which will be returned +if the specified key is not found: + + + + 1$discount = data_get($data, 'products.desk.discount', 0); + + 2  + + 3// 0 + + + $discount = data_get($data, 'products.desk.discount', 0); + + // 0 + +The function also accepts wildcards using asterisks, which may target any key +of the array or object: + + + + 1$data = [ + + 2 'product-one' => ['name' => 'Desk 1', 'price' => 100], + + 3 'product-two' => ['name' => 'Desk 2', 'price' => 150], + + 4]; + + 5  + + 6data_get($data, '*.name'); + + 7  + + 8// ['Desk 1', 'Desk 2']; + + + $data = [ + 'product-one' => ['name' => 'Desk 1', 'price' => 100], + 'product-two' => ['name' => 'Desk 2', 'price' => 150], + ]; + + data_get($data, '*.name'); + + // ['Desk 1', 'Desk 2']; + +The `{first}` and `{last}` placeholders may be used to retrieve the first or +last items in an array: + + + + 1$flight = [ + + 2 'segments' => [ + + 3 ['from' => 'LHR', 'departure' => '9:00', 'to' => 'IST', 'arrival' => '15:00'], + + 4 ['from' => 'IST', 'departure' => '16:00', 'to' => 'PKX', 'arrival' => '20:00'], + + 5 ], + + 6]; + + 7  + + 8data_get($flight, 'segments.{first}.arrival'); + + 9  + + 10// 15:00 + + + $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'); + + // 15:00 + +#### `data_set()` + +The `data_set` function sets a value within a nested array or object using +"dot" notation: + + + + 1$data = ['products' => ['desk' => ['price' => 100]]]; + + 2  + + 3data_set($data, 'products.desk.price', 200); + + 4  + + 5// ['products' => ['desk' => ['price' => 200]]] + + + $data = ['products' => ['desk' => ['price' => 100]]]; + + data_set($data, 'products.desk.price', 200); + + // ['products' => ['desk' => ['price' => 200]]] + +This function also accepts wildcards using asterisks and will set values on +the target accordingly: + + + + 1$data = [ + + 2 'products' => [ + + 3 ['name' => 'Desk 1', 'price' => 100], + + 4 ['name' => 'Desk 2', 'price' => 150], + + 5 ], + + 6]; + + 7  + + 8data_set($data, 'products.*.price', 200); + + 9  + + 10/* + + 11 [ + + 12 'products' => [ + + 13 ['name' => 'Desk 1', 'price' => 200], + + 14 ['name' => 'Desk 2', 'price' => 200], + + 15 ], + + 16 ] + + 17*/ + + + $data = [ + 'products' => [ + ['name' => 'Desk 1', 'price' => 100], + ['name' => 'Desk 2', 'price' => 150], + ], + ]; + + data_set($data, 'products.*.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: + + + + 1$data = ['products' => ['desk' => ['price' => 100]]]; + + 2  + + 3data_set($data, 'products.desk.price', 200, overwrite: false); + + 4  + + 5// ['products' => ['desk' => ['price' => 100]]] + + + $data = ['products' => ['desk' => ['price' => 100]]]; + + data_set($data, 'products.desk.price', 200, overwrite: false); + + // ['products' => ['desk' => ['price' => 100]]] + +#### `data_forget()` + +The `data_forget` function removes a value within a nested array or object +using "dot" notation: + + + + 1$data = ['products' => ['desk' => ['price' => 100]]]; + + 2  + + 3data_forget($data, 'products.desk.price'); + + 4  + + 5// ['products' => ['desk' => []]] + + + $data = ['products' => ['desk' => ['price' => 100]]]; + + data_forget($data, 'products.desk.price'); + + // ['products' => ['desk' => []]] + +This function also accepts wildcards using asterisks and will remove values on +the target accordingly: + + + + 1$data = [ + + 2 'products' => [ + + 3 ['name' => 'Desk 1', 'price' => 100], + + 4 ['name' => 'Desk 2', 'price' => 150], + + 5 ], + + 6]; + + 7  + + 8data_forget($data, 'products.*.price'); + + 9  + + 10/* + + 11 [ + + 12 'products' => [ + + 13 ['name' => 'Desk 1'], + + 14 ['name' => 'Desk 2'], + + 15 ], + + 16 ] + + 17*/ + + + $data = [ + 'products' => [ + ['name' => 'Desk 1', 'price' => 100], + ['name' => 'Desk 2', 'price' => 150], + ], + ]; + + data_forget($data, 'products.*.price'); + + /* + [ + 'products' => [ + ['name' => 'Desk 1'], + ['name' => 'Desk 2'], + ], + ] + */ + +#### `head()` + +The `head` function returns the first element in the given array. If the array +is empty, `false` will be returned: + + + + 1$array = [100, 200, 300]; + + 2  + + 3$first = head($array); + + 4  + + 5// 100 + + + $array = [100, 200, 300]; + + $first = head($array); + + // 100 + +#### `last()` + +The `last` function returns the last element in the given array. If the array +is empty, `false` will be returned: + + + + 1$array = [100, 200, 300]; + + 2  + + 3$last = last($array); + + 4  + + 5// 300 + + + $array = [100, 200, 300]; + + $last = last($array); + + // 300 + +## Numbers + +#### `Number::abbreviate()` + +The `Number::abbreviate` method returns the human-readable format of the +provided numerical value, with an abbreviation for the units: + + + + 1use Illuminate\Support\Number; + + 2  + + 3$number = Number::abbreviate(1000); + + 4  + + 5// 1K + + 6  + + 7$number = Number::abbreviate(489939); + + 8  + + 9// 490K + + 10  + + 11$number = Number::abbreviate(1230000, precision: 2); + + 12  + + 13// 1.23M + + + use Illuminate\Support\Number; + + $number = Number::abbreviate(1000); + + // 1K + + $number = Number::abbreviate(489939); + + // 490K + + $number = Number::abbreviate(1230000, precision: 2); + + // 1.23M + +#### `Number::clamp()` + +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: + + + + 1use Illuminate\Support\Number; + + 2  + + 3$number = Number::clamp(105, min: 10, max: 100); + + 4  + + 5// 100 + + 6  + + 7$number = Number::clamp(5, min: 10, max: 100); + + 8  + + 9// 10 + + 10  + + 11$number = Number::clamp(10, min: 10, max: 100); + + 12  + + 13// 10 + + 14  + + 15$number = Number::clamp(20, min: 10, max: 100); + + 16  + + 17// 20 + + + use Illuminate\Support\Number; + + $number = Number::clamp(105, min: 10, max: 100); + + // 100 + + $number = Number::clamp(5, min: 10, max: 100); + + // 10 + + $number = Number::clamp(10, min: 10, max: 100); + + // 10 + + $number = Number::clamp(20, min: 10, max: 100); + + // 20 + +#### `Number::currency()` + +The `Number::currency` method returns the currency representation of the given +value as a string: + + + + 1use Illuminate\Support\Number; + + 2  + + 3$currency = Number::currency(1000); + + 4  + + 5// $1,000.00 + + 6  + + 7$currency = Number::currency(1000, in: 'EUR'); + + 8  + + 9// €1,000.00 + + 10  + + 11$currency = Number::currency(1000, in: 'EUR', locale: 'de'); + + 12  + + 13// 1.000,00 € + + 14  + + 15$currency = Number::currency(1000, in: 'EUR', locale: 'de', precision: 0); + + 16  + + 17// 1.000 € + + + use Illuminate\Support\Number; + + $currency = Number::currency(1000); + + // $1,000.00 + + $currency = Number::currency(1000, in: 'EUR'); + + // €1,000.00 + + $currency = Number::currency(1000, in: 'EUR', locale: 'de'); + + // 1.000,00 € + + $currency = Number::currency(1000, in: 'EUR', locale: 'de', precision: 0); + + // 1.000 € + +#### `Number::defaultCurrency()` + +The `Number::defaultCurrency` method returns the default currency being used +by the `Number` class: + + + + 1use Illuminate\Support\Number; + + 2  + + 3$currency = Number::defaultCurrency(); + + 4  + + 5// USD + + + use Illuminate\Support\Number; + + $currency = Number::defaultCurrency(); + + // USD + +#### `Number::defaultLocale()` + +The `Number::defaultLocale` method returns the default locale being used by +the `Number` class: + + + + 1use Illuminate\Support\Number; + + 2  + + 3$locale = Number::defaultLocale(); + + 4  + + 5// en + + + use Illuminate\Support\Number; + + $locale = Number::defaultLocale(); + + // en + +#### `Number::fileSize()` + +The `Number::fileSize` method returns the file size representation of the +given byte value as a string: + + + + 1use Illuminate\Support\Number; + + 2  + + 3$size = Number::fileSize(1024); + + 4  + + 5// 1 KB + + 6  + + 7$size = Number::fileSize(1024 * 1024); + + 8  + + 9// 1 MB + + 10  + + 11$size = Number::fileSize(1024, precision: 2); + + 12  + + 13// 1.00 KB + + + use Illuminate\Support\Number; + + $size = Number::fileSize(1024); + + // 1 KB + + $size = Number::fileSize(1024 * 1024); + + // 1 MB + + $size = Number::fileSize(1024, precision: 2); + + // 1.00 KB + +#### `Number::forHumans()` + +The `Number::forHumans` method returns the human-readable format of the +provided numerical value: + + + + 1use Illuminate\Support\Number; + + 2  + + 3$number = Number::forHumans(1000); + + 4  + + 5// 1 thousand + + 6  + + 7$number = Number::forHumans(489939); + + 8  + + 9// 490 thousand + + 10  + + 11$number = Number::forHumans(1230000, precision: 2); + + 12  + + 13// 1.23 million + + + use Illuminate\Support\Number; + + $number = Number::forHumans(1000); + + // 1 thousand + + $number = Number::forHumans(489939); + + // 490 thousand + + $number = Number::forHumans(1230000, precision: 2); + + // 1.23 million + +#### `Number::format()` + +The `Number::format` method formats the given number into a locale specific +string: + + + + 1use Illuminate\Support\Number; + + 2  + + 3$number = Number::format(100000); + + 4  + + 5// 100,000 + + 6  + + 7$number = Number::format(100000, precision: 2); + + 8  + + 9// 100,000.00 + + 10  + + 11$number = Number::format(100000.123, maxPrecision: 2); + + 12  + + 13// 100,000.12 + + 14  + + 15$number = Number::format(100000, locale: 'de'); + + 16  + + 17// 100.000 + + + use Illuminate\Support\Number; + + $number = Number::format(100000); + + // 100,000 + + $number = Number::format(100000, precision: 2); + + // 100,000.00 + + $number = Number::format(100000.123, maxPrecision: 2); + + // 100,000.12 + + $number = Number::format(100000, locale: 'de'); + + // 100.000 + +#### `Number::ordinal()` + +The `Number::ordinal` method returns a number's ordinal representation: + + + + 1use Illuminate\Support\Number; + + 2  + + 3$number = Number::ordinal(1); + + 4  + + 5// 1st + + 6  + + 7$number = Number::ordinal(2); + + 8  + + 9// 2nd + + 10  + + 11$number = Number::ordinal(21); + + 12  + + 13// 21st + + + use Illuminate\Support\Number; + + $number = Number::ordinal(1); + + // 1st + + $number = Number::ordinal(2); + + // 2nd + + $number = Number::ordinal(21); + + // 21st + +#### `Number::pairs()` + +The `Number::pairs` method generates an array of number pairs (sub-ranges) +based on a specified range and step value. This method can be useful for +dividing a larger range of numbers into smaller, manageable sub-ranges for +things like pagination or batching tasks. The `pairs` method returns an array +of arrays, where each inner array represents a pair (sub-range) of numbers: + + + + 1use Illuminate\Support\Number; + + 2  + + 3$result = Number::pairs(25, 10); + + 4  + + 5// [[0, 9], [10, 19], [20, 25]] + + 6  + + 7$result = Number::pairs(25, 10, offset: 0); + + 8  + + 9// [[0, 10], [10, 20], [20, 25]] + + + use Illuminate\Support\Number; + + $result = Number::pairs(25, 10); + + // [[0, 9], [10, 19], [20, 25]] + + $result = Number::pairs(25, 10, offset: 0); + + // [[0, 10], [10, 20], [20, 25]] + +#### `Number::parseInt()` + +The `Number::parseInt` method parse a string into an integer according to the +specified locale: + + + + 1use Illuminate\Support\Number; + + 2  + + 3$result = Number::parseInt('10.123'); + + 4  + + 5// (int) 10 + + 6  + + 7$result = Number::parseInt('10,123', locale: 'fr'); + + 8  + + 9// (int) 10 + + + use Illuminate\Support\Number; + + $result = Number::parseInt('10.123'); + + // (int) 10 + + $result = Number::parseInt('10,123', locale: 'fr'); + + // (int) 10 + +#### `Number::parseFloat()` + +The `Number::parseFloat` method parse a string into a float according to the +specified locale: + + + + 1use Illuminate\Support\Number; + + 2  + + 3$result = Number::parseFloat('10'); + + 4  + + 5// (float) 10.0 + + 6  + + 7$result = Number::parseFloat('10', locale: 'fr'); + + 8  + + 9// (float) 10.0 + + + use Illuminate\Support\Number; + + $result = Number::parseFloat('10'); + + // (float) 10.0 + + $result = Number::parseFloat('10', locale: 'fr'); + + // (float) 10.0 + +#### `Number::percentage()` + +The `Number::percentage` method returns the percentage representation of the +given value as a string: + + + + 1use Illuminate\Support\Number; + + 2  + + 3$percentage = Number::percentage(10); + + 4  + + 5// 10% + + 6  + + 7$percentage = Number::percentage(10, precision: 2); + + 8  + + 9// 10.00% + + 10  + + 11$percentage = Number::percentage(10.123, maxPrecision: 2); + + 12  + + 13// 10.12% + + 14  + + 15$percentage = Number::percentage(10, precision: 2, locale: 'de'); + + 16  + + 17// 10,00% + + + use Illuminate\Support\Number; + + $percentage = Number::percentage(10); + + // 10% + + $percentage = Number::percentage(10, precision: 2); + + // 10.00% + + $percentage = Number::percentage(10.123, maxPrecision: 2); + + // 10.12% + + $percentage = Number::percentage(10, precision: 2, locale: 'de'); + + // 10,00% + +#### `Number::spell()` + +The `Number::spell` method transforms the given number into a string of words: + + + + 1use Illuminate\Support\Number; + + 2  + + 3$number = Number::spell(102); + + 4  + + 5// one hundred and two + + 6  + + 7$number = Number::spell(88, locale: 'fr'); + + 8  + + 9// quatre-vingt-huit + + + use Illuminate\Support\Number; + + $number = Number::spell(102); + + // one hundred and two + + $number = Number::spell(88, locale: 'fr'); + + // quatre-vingt-huit + +The `after` argument allows you to specify a value after which all numbers +should be spelled out: + + + + 1$number = Number::spell(10, after: 10); + + 2  + + 3// 10 + + 4  + + 5$number = Number::spell(11, after: 10); + + 6  + + 7// eleven + + + $number = Number::spell(10, after: 10); + + // 10 + + $number = Number::spell(11, after: 10); + + // eleven + +The `until` argument allows you to specify a value before which all numbers +should be spelled out: + + + + 1$number = Number::spell(5, until: 10); + + 2  + + 3// five + + 4  + + 5$number = Number::spell(10, until: 10); + + 6  + + 7// 10 + + + $number = Number::spell(5, until: 10); + + // five + + $number = Number::spell(10, until: 10); + + // 10 + +#### `Number::spellOrdinal()` + +The `Number::spellOrdinal` method returns the number's ordinal representation +as a string of words: + + + + 1use Illuminate\Support\Number; + + 2  + + 3$number = Number::spellOrdinal(1); + + 4  + + 5// first + + 6  + + 7$number = Number::spellOrdinal(2); + + 8  + + 9// second + + 10  + + 11$number = Number::spellOrdinal(21); + + 12  + + 13// twenty-first + + + use Illuminate\Support\Number; + + $number = Number::spellOrdinal(1); + + // first + + $number = Number::spellOrdinal(2); + + // second + + $number = Number::spellOrdinal(21); + + // twenty-first + +#### `Number::trim()` + +The `Number::trim` method removes any trailing zero digits after the decimal +point of the given number: + + + + 1use Illuminate\Support\Number; + + 2  + + 3$number = Number::trim(12.0); + + 4  + + 5// 12 + + 6  + + 7$number = Number::trim(12.30); + + 8  + + 9// 12.3 + + + use Illuminate\Support\Number; + + $number = Number::trim(12.0); + + // 12 + + $number = Number::trim(12.30); + + // 12.3 + +#### `Number::useLocale()` + +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: + + + + 1use Illuminate\Support\Number; + + 2  + + 3/** + + 4 * Bootstrap any application services. + + 5 */ + + 6public function boot(): void + + 7{ + + 8 Number::useLocale('de'); + + 9} + + + use Illuminate\Support\Number; + + /** + * Bootstrap any application services. + */ + public function boot(): void + { + Number::useLocale('de'); + } + +#### `Number::withLocale()` + +The `Number::withLocale` method executes the given closure using the specified +locale and then restores the original locale after the callback has executed: + + + + 1use Illuminate\Support\Number; + + 2  + + 3$number = Number::withLocale('de', function () { + + 4 return Number::format(1500); + + 5}); + + + use Illuminate\Support\Number; + + $number = Number::withLocale('de', function () { + return Number::format(1500); + }); + +#### `Number::useCurrency()` + +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: + + + + 1use Illuminate\Support\Number; + + 2  + + 3/** + + 4 * Bootstrap any application services. + + 5 */ + + 6public function boot(): void + + 7{ + + 8 Number::useCurrency('GBP'); + + 9} + + + use Illuminate\Support\Number; + + /** + * Bootstrap any application services. + */ + public function boot(): void + { + Number::useCurrency('GBP'); + } + +#### `Number::withCurrency()` + +The `Number::withCurrency` method executes the given closure using the +specified currency and then restores the original currency after the callback +has executed: + + + + 1use Illuminate\Support\Number; + + 2  + + 3$number = Number::withCurrency('GBP', function () { + + 4 // ... + + 5}); + + + use Illuminate\Support\Number; + + $number = Number::withCurrency('GBP', function () { + // ... + }); + +## Paths + +#### `app_path()` + +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: + + + + 1$path = app_path(); + + 2  + + 3$path = app_path('Http/Controllers/Controller.php'); + + + $path = app_path(); + + $path = app_path('Http/Controllers/Controller.php'); + +#### `base_path()` + +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: + + + + 1$path = base_path(); + + 2  + + 3$path = base_path('vendor/bin'); + + + $path = base_path(); + + $path = base_path('vendor/bin'); + +#### `config_path()` + +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: + + + + 1$path = config_path(); + + 2  + + 3$path = config_path('app.php'); + + + $path = config_path(); + + $path = config_path('app.php'); + +#### `database_path()` + +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: + + + + 1$path = database_path(); + + 2  + + 3$path = database_path('factories/UserFactory.php'); + + + $path = database_path(); + + $path = database_path('factories/UserFactory.php'); + +#### `lang_path()` + +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: + + + + 1$path = lang_path(); + + 2  + + 3$path = lang_path('en/messages.php'); + + + $path = lang_path(); + + $path = lang_path('en/messages.php'); + +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. + +#### `public_path()` + +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: + + + + 1$path = public_path(); + + 2  + + 3$path = public_path('css/app.css'); + + + $path = public_path(); + + $path = public_path('css/app.css'); + +#### `resource_path()` + +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: + + + + 1$path = resource_path(); + + 2  + + 3$path = resource_path('sass/app.scss'); + + + $path = resource_path(); + + $path = resource_path('sass/app.scss'); + +#### `storage_path()` + +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: + + + + 1$path = storage_path(); + + 2  + + 3$path = storage_path('app/file.txt'); + + + $path = storage_path(); + + $path = storage_path('app/file.txt'); + +## URLs + +#### `action()` + +The `action` function generates a URL for the given controller action: + + + + 1use App\Http\Controllers\HomeController; + + 2  + + 3$url = action([HomeController::class, 'index']); + + + use App\Http\Controllers\HomeController; + + $url = action([HomeController::class, 'index']); + +If the method accepts route parameters, you may pass them as the second +argument to the method: + + + + 1$url = action([UserController::class, 'profile'], ['id' => 1]); + + + $url = action([UserController::class, 'profile'], ['id' => 1]); + +#### `asset()` + +The `asset` function generates a URL for an asset using the current scheme of +the request (HTTP or HTTPS): + + + + 1$url = asset('img/photo.jpg'); + + + $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: + + + + 1// ASSET_URL=http://example.com/assets + + 2  + + 3$url = asset('img/photo.jpg'); // http://example.com/assets/img/photo.jpg + + + // ASSET_URL=http://example.com/assets + + $url = asset('img/photo.jpg'); // http://example.com/assets/img/photo.jpg + +#### `route()` + +The `route` function generates a URL for a given [named +route](/docs/12.x/routing#named-routes): + + + + 1$url = route('route.name'); + + + $url = route('route.name'); + +If the route accepts parameters, you may pass them as the second argument to +the function: + + + + 1$url = route('route.name', ['id' => 1]); + + + $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: + + + + 1$url = route('route.name', ['id' => 1], false); + + + $url = route('route.name', ['id' => 1], false); + +#### `secure_asset()` + +The `secure_asset` function generates a URL for an asset using HTTPS: + + + + 1$url = secure_asset('img/photo.jpg'); + + + $url = secure_asset('img/photo.jpg'); + +#### `secure_url()` + +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: + + + + 1$url = secure_url('user/profile'); + + 2  + + 3$url = secure_url('user/profile', [1]); + + + $url = secure_url('user/profile'); + + $url = secure_url('user/profile', [1]); + +#### `to_action()` + +The `to_action` function generates a [redirect HTTP +response](/docs/12.x/responses#redirects) for a given controller action: + + + + 1use App\Http\Controllers\UserController; + + 2  + + 3return to_action([UserController::class, 'show'], ['user' => 1]); + + + use App\Http\Controllers\UserController; + + return to_action([UserController::class, '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_action` method: + + + + 1return to_action( + + 2 [UserController::class, 'show'], + + 3 ['user' => 1], + + 4 302, + + 5 ['X-Framework' => 'Laravel'] + + 6); + + + return to_action( + [UserController::class, 'show'], + ['user' => 1], + 302, + ['X-Framework' => 'Laravel'] + ); + +#### `to_route()` + +The `to_route` function generates a [redirect HTTP +response](/docs/12.x/responses#redirects) for a given [named +route](/docs/12.x/routing#named-routes): + + + + 1return to_route('users.show', ['user' => 1]); + + + 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: + + + + 1return to_route('users.show', ['user' => 1], 302, ['X-Framework' => 'Laravel']); + + + return to_route('users.show', ['user' => 1], 302, ['X-Framework' => 'Laravel']); + +#### `uri()` + +The `uri` function generates a fluent URI instance for the given URI: + + + + 1$uri = uri('https://example.com') + + 2 ->withPath('/users') + + 3 ->withQuery(['page' => 1]); + + + $uri = uri('https://example.com') + ->withPath('/users') + ->withQuery(['page' => 1]); + +If the `uri` function is given an array containing a callable controller and +method pair, the function will create a `Uri` instance for the controller +method's route path: + + + + 1use App\Http\Controllers\UserController; + + 2  + + 3$uri = uri([UserController::class, 'show'], ['user' => $user]); + + + use App\Http\Controllers\UserController; + + $uri = uri([UserController::class, 'show'], ['user' => $user]); + +If the controller is invokable, you may simply provide the controller class +name: + + + + 1use App\Http\Controllers\UserIndexController; + + 2  + + 3$uri = uri(UserIndexController::class); + + + use App\Http\Controllers\UserIndexController; + + $uri = uri(UserIndexController::class); + +If the value given to the `uri` function matches the name of a [named +route](/docs/12.x/routing#named-routes), a `Uri` instance will be generated +for that route's path: + + + + 1$uri = uri('users.show', ['user' => $user]); + + + $uri = uri('users.show', ['user' => $user]); + +#### `url()` + +The `url` function generates a fully qualified URL to the given path: + + + + 1$url = url('user/profile'); + + 2  + + 3$url = url('user/profile', [1]); + + + $url = url('user/profile'); + + $url = url('user/profile', [1]); + +If no path is provided, an `Illuminate\Routing\UrlGenerator` instance is +returned: + + + + 1$current = url()->current(); + + 2  + + 3$full = url()->full(); + + 4  + + 5$previous = url()->previous(); + + + $current = url()->current(); + + $full = url()->full(); + + $previous = url()->previous(); + +For more information on working with the `url` function, consult the [URL +generation documentation](/docs/12.x/urls#generating-urls). + +## Miscellaneous + +#### `abort()` + +The `abort` function throws [an HTTP exception](/docs/12.x/errors#http- +exceptions) which will be rendered by the [exception +handler](/docs/12.x/errors#handling-exceptions): + + + + 1abort(403); + + + abort(403); + +You may also provide the exception's message and custom HTTP response headers +that should be sent to the browser: + + + + 1abort(403, 'Unauthorized.', $headers); + + + abort(403, 'Unauthorized.', $headers); + +#### `abort_if()` + +The `abort_if` function throws an HTTP exception if a given boolean expression +evaluates to `true`: + + + + 1abort_if(! Auth::user()->isAdmin(), 403); + + + 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. + +#### `abort_unless()` + +The `abort_unless` function throws an HTTP exception if a given boolean +expression evaluates to `false`: + + + + 1abort_unless(Auth::user()->isAdmin(), 403); + + + 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. + +#### `app()` + +The `app` function returns the [service container](/docs/12.x/container) +instance: + + + + 1$container = app(); + + + $container = app(); + +You may pass a class or interface name to resolve it from the container: + + + + 1$api = app('HelpSpot\API'); + + + $api = app('HelpSpot\API'); + +#### `auth()` + +The `auth` function returns an [authenticator](/docs/12.x/authentication) +instance. You may use it as an alternative to the `Auth` facade: + + + + 1$user = auth()->user(); + + + $user = auth()->user(); + +If needed, you may specify which guard instance you would like to access: + + + + 1$user = auth('admin')->user(); + + + $user = auth('admin')->user(); + +#### `back()` + +The `back` function generates a [redirect HTTP +response](/docs/12.x/responses#redirects) to the user's previous location: + + + + 1return back($status = 302, $headers = [], $fallback = '/'); + + 2  + + 3return back(); + + + return back($status = 302, $headers = [], $fallback = '/'); + + return back(); + +#### `bcrypt()` + +The `bcrypt` function [hashes](/docs/12.x/hashing) the given value using +Bcrypt. You may use this function as an alternative to the `Hash` facade: + + + + 1$password = bcrypt('my-secret-password'); + + + $password = bcrypt('my-secret-password'); + +#### `blank()` + +The `blank` function determines whether the given value is "blank": + + + + 1blank(''); + + 2blank(' '); + + 3blank(null); + + 4blank(collect()); + + 5  + + 6// true + + 7  + + 8blank(0); + + 9blank(true); + + 10blank(false); + + 11  + + 12// false + + + blank(''); + blank(' '); + blank(null); + blank(collect()); + + // true + + blank(0); + blank(true); + blank(false); + + // false + +For the inverse of `blank`, see the filled function. + +#### `broadcast()` + +The `broadcast` function [broadcasts](/docs/12.x/broadcasting) the given +[event](/docs/12.x/events) to its listeners: + + + + 1broadcast(new UserRegistered($user)); + + 2  + + 3broadcast(new UserRegistered($user))->toOthers(); + + + broadcast(new UserRegistered($user)); + + broadcast(new UserRegistered($user))->toOthers(); + +#### `broadcast_if()` + +The `broadcast_if` function [broadcasts](/docs/12.x/broadcasting) the given +[event](/docs/12.x/events) to its listeners if a given boolean expression +evaluates to `true`: + + + + 1broadcast_if($user->isActive(), new UserRegistered($user)); + + 2  + + 3broadcast_if($user->isActive(), new UserRegistered($user))->toOthers(); + + + broadcast_if($user->isActive(), new UserRegistered($user)); + + broadcast_if($user->isActive(), new UserRegistered($user))->toOthers(); + +#### `broadcast_unless()` + +The `broadcast_unless` function [broadcasts](/docs/12.x/broadcasting) the +given [event](/docs/12.x/events) to its listeners if a given boolean +expression evaluates to `false`: + + + + 1broadcast_unless($user->isBanned(), new UserRegistered($user)); + + 2  + + 3broadcast_unless($user->isBanned(), new UserRegistered($user))->toOthers(); + + + broadcast_unless($user->isBanned(), new UserRegistered($user)); + + broadcast_unless($user->isBanned(), new UserRegistered($user))->toOthers(); + +#### `cache()` + +The `cache` function may be used to get values from the +[cache](/docs/12.x/cache). If the given key does not exist in the cache, an +optional default value will be returned: + + + + 1$value = cache('key'); + + 2  + + 3$value = cache('key', 'default'); + + + $value = cache('key'); + + $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: + + + + 1cache(['key' => 'value'], 300); + + 2  + + 3cache(['key' => 'value'], now()->addSeconds(10)); + + + cache(['key' => 'value'], 300); + + cache(['key' => 'value'], now()->addSeconds(10)); + +#### `class_uses_recursive()` + +The `class_uses_recursive` function returns all traits used by a class, +including traits used by all of its parent classes: + + + + 1$traits = class_uses_recursive(App\Models\User::class); + + + $traits = class_uses_recursive(App\Models\User::class); + +#### `collect()` + +The `collect` function creates a [collection](/docs/12.x/collections) instance +from the given value: + + + + 1$collection = collect(['Taylor', 'Abigail']); + + + $collection = collect(['Taylor', 'Abigail']); + +#### `config()` + +The `config` function gets the value of a +[configuration](/docs/12.x/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. You may also provide a default value that will +be returned if the configuration option does not exist: + + + + 1$value = config('app.timezone'); + + 2  + + 3$value = config('app.timezone', $default); + + + $value = config('app.timezone'); + + $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: + + + + 1config(['app.debug' => true]); + + + config(['app.debug' => true]); + +#### `context()` + +The `context` function gets the value from the current +[context](/docs/12.x/context). You may also provide a default value that will +be returned if the context key does not exist: + + + + 1$value = context('trace_id'); + + 2  + + 3$value = context('trace_id', $default); + + + $value = context('trace_id'); + + $value = context('trace_id', $default); + +You may set context values by passing an array of key / value pairs: + + + + 1use Illuminate\Support\Str; + + 2  + + 3context(['trace_id' => Str::uuid()->toString()]); + + + use Illuminate\Support\Str; + + context(['trace_id' => Str::uuid()->toString()]); + +#### `cookie()` + +The `cookie` function creates a new [cookie](/docs/12.x/requests#cookies) +instance: + + + + 1$cookie = cookie('name', 'value', $minutes); + + + $cookie = cookie('name', 'value', $minutes); + +#### `csrf_field()` + +The `csrf_field` function generates an HTML `hidden` input field containing +the value of the CSRF token. For example, using [Blade +syntax](/docs/12.x/blade): + + + + 1{{ csrf_field() }} + + + {{ csrf_field() }} + +#### `csrf_token()` + +The `csrf_token` function retrieves the value of the current CSRF token: + + + + 1$token = csrf_token(); + + + $token = csrf_token(); + +#### `decrypt()` + +The `decrypt` function [decrypts](/docs/12.x/encryption) the given value. You +may use this function as an alternative to the `Crypt` facade: + + + + 1$password = decrypt($value); + + + $password = decrypt($value); + +For the inverse of `decrypt`, see the encrypt function. + +#### `dd()` + +The `dd` function dumps the given variables and ends the execution of the +script: + + + + 1dd($value); + + 2  + + 3dd($value1, $value2, $value3, ...); + + + dd($value); + + dd($value1, $value2, $value3, ...); + +If you do not want to halt the execution of your script, use the dump function +instead. + +#### `dispatch()` + +The `dispatch` function pushes the given [job](/docs/12.x/queues#creating- +jobs) onto the Laravel [job queue](/docs/12.x/queues): + + + + 1dispatch(new App\Jobs\SendEmails); + + + dispatch(new App\Jobs\SendEmails); + +#### `dispatch_sync()` + +The `dispatch_sync` function pushes the given job to the +[sync](/docs/12.x/queues#synchronous-dispatching) queue so that it is +processed immediately: + + + + 1dispatch_sync(new App\Jobs\SendEmails); + + + dispatch_sync(new App\Jobs\SendEmails); + +#### `dump()` + +The `dump` function dumps the given variables: + + + + 1dump($value); + + 2  + + 3dump($value1, $value2, $value3, ...); + + + dump($value); + + dump($value1, $value2, $value3, ...); + +If you want to stop executing the script after dumping the variables, use the +dd function instead. + +#### `encrypt()` + +The `encrypt` function [encrypts](/docs/12.x/encryption) the given value. You +may use this function as an alternative to the `Crypt` facade: + + + + 1$secret = encrypt('my-secret-value'); + + + $secret = encrypt('my-secret-value'); + +For the inverse of `encrypt`, see the decrypt function. + +#### `env()` + +The `env` function retrieves the value of an [environment +variable](/docs/12.x/configuration#environment-configuration) or returns a +default value: + + + + 1$env = env('APP_ENV'); + + 2  + + 3$env = env('APP_ENV', 'production'); + + + $env = env('APP_ENV'); + + $env = env('APP_ENV', 'production'); + +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 external +environment variables such as server-level or system-level environment +variables or `null`. + +#### `event()` + +The `event` function dispatches the given [event](/docs/12.x/events) to its +listeners: + + + + 1event(new UserRegistered($user)); + + + event(new UserRegistered($user)); + +#### `fake()` + +The `fake` function resolves a [Faker](https://github.com/FakerPHP/Faker) +singleton from the container, which can be useful when creating fake data in +model factories, database seeding, tests, and prototyping views: + + + + 1@for ($i = 0; $i < 10; $i++) + + 2
    + + 3
    Name
    + + 4
    {{ fake()->name() }}
    + + 5  + + 6
    Email
    + + 7
    {{ fake()->unique()->safeEmail() }}
    + + 8
    + + 9@endfor + + + @for ($i = 0; $i < 10; $i++) +
    +
    Name
    +
    {{ fake()->name() }}
    + +
    Email
    +
    {{ fake()->unique()->safeEmail() }}
    +
    + @endfor + +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: + + + + 1fake('nl_NL')->name() + + + fake('nl_NL')->name() + +#### `filled()` + +The `filled` function determines whether the given value is not "blank": + + + + 1filled(0); + + 2filled(true); + + 3filled(false); + + 4  + + 5// true + + 6  + + 7filled(''); + + 8filled(' '); + + 9filled(null); + + 10filled(collect()); + + 11  + + 12// false + + + filled(0); + filled(true); + filled(false); + + // true + + filled(''); + filled(' '); + filled(null); + filled(collect()); + + // false + +For the inverse of `filled`, see the blank function. + +#### `info()` + +The `info` function will write information to your application's +[log](/docs/12.x/logging): + + + + 1info('Some helpful information!'); + + + info('Some helpful information!'); + +An array of contextual data may also be passed to the function: + + + + 1info('User login attempt failed.', ['id' => $user->id]); + + + info('User login attempt failed.', ['id' => $user->id]); + +#### `literal()` + +The `literal` function creates a new +[stdClass](https://www.php.net/manual/en/class.stdclass.php) instance with the +given named arguments as properties: + + + + 1$obj = literal( + + 2 name: 'Joe', + + 3 languages: ['PHP', 'Ruby'], + + 4); + + 5  + + 6$obj->name; // 'Joe' + + 7$obj->languages; // ['PHP', 'Ruby'] + + + $obj = literal( + name: 'Joe', + languages: ['PHP', 'Ruby'], + ); + + $obj->name; // 'Joe' + $obj->languages; // ['PHP', 'Ruby'] + +#### `logger()` + +The `logger` function can be used to write a `debug` level message to the +[log](/docs/12.x/logging): + + + + 1logger('Debug message'); + + + logger('Debug message'); + +An array of contextual data may also be passed to the function: + + + + 1logger('User has logged in.', ['id' => $user->id]); + + + logger('User has logged in.', ['id' => $user->id]); + +A [logger](/docs/12.x/logging) instance will be returned if no value is passed +to the function: + + + + 1logger()->error('You are not allowed here.'); + + + logger()->error('You are not allowed here.'); + +#### `method_field()` + +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/12.x/blade): + + + + 1
    + + 2 {{ method_field('DELETE') }} + + 3
    + + +
    + {{ method_field('DELETE') }} +
    + +#### `now()` + +The `now` function creates a new `Illuminate\Support\Carbon` instance for the +current time: + + + + 1$now = now(); + + + $now = now(); + +#### `old()` + +The `old` function [retrieves](/docs/12.x/requests#retrieving-input) an [old +input](/docs/12.x/requests#old-input) value flashed into the session: + + + + 1$value = old('value'); + + 2  + + 3$value = old('value', 'default'); + + + $value = old('value'); + + $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": + + + + 1{{ old('name', $user->name) }} + + 2  + + 3// Is equivalent to... + + 4  + + 5{{ old('name', $user) }} + + + {{ old('name', $user->name) }} + + // Is equivalent to... + + {{ old('name', $user) }} + +#### `once()` + +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: + + + + 1function random(): int + + 2{ + + 3 return once(function () { + + 4 return random_int(1, 1000); + + 5 }); + + 6} + + 7  + + 8random(); // 123 + + 9random(); // 123 (cached result) + + 10random(); // 123 (cached result) + + + function random(): int + { + return once(function () { + return random_int(1, 1000); + }); + } + + 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: + + + + 1 [1, 2, 3]); + + 8 } + + 9} + + 10  + + 11$service = new NumberService; + + 12  + + 13$service->all(); + + 14$service->all(); // (cached result) + + 15  + + 16$secondService = new NumberService; + + 17  + + 18$secondService->all(); + + 19$secondService->all(); // (cached result) + + + [1, 2, 3]); + } + } + + $service = new NumberService; + + $service->all(); + $service->all(); // (cached result) + + $secondService = new NumberService; + + $secondService->all(); + $secondService->all(); // (cached result) + +#### `optional()` + +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: + + + + 1return optional($user->address)->street; + + 2  + + 3{!! old('name', optional($user)->name) !!} + + + return optional($user->address)->street; + + {!! 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: + + + + 1return optional(User::find($id), function (User $user) { + + 2 return $user->name; + + 3}); + + + return optional(User::find($id), function (User $user) { + return $user->name; + }); + +#### `policy()` + +The `policy` method retrieves a [policy](/docs/12.x/authorization#creating- +policies) instance for a given class: + + + + 1$policy = policy(App\Models\User::class); + + + $policy = policy(App\Models\User::class); + +#### `redirect()` + +The `redirect` function returns a [redirect HTTP +response](/docs/12.x/responses#redirects), or returns the redirector instance +if called with no arguments: + + + + 1return redirect($to = null, $status = 302, $headers = [], $secure = null); + + 2  + + 3return redirect('/home'); + + 4  + + 5return redirect()->route('route.name'); + + + return redirect($to = null, $status = 302, $headers = [], $secure = null); + + return redirect('/home'); + + return redirect()->route('route.name'); + +#### `report()` + +The `report` function will report an exception using your [exception +handler](/docs/12.x/errors#handling-exceptions): + + + + 1report($e); + + + 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: + + + + 1report('Something went wrong.'); + + + report('Something went wrong.'); + +#### `report_if()` + +The `report_if` function will report an exception using your [exception +handler](/docs/12.x/errors#handling-exceptions) if a given boolean expression +evaluates to `true`: + + + + 1report_if($shouldReport, $e); + + 2  + + 3report_if($shouldReport, 'Something went wrong.'); + + + report_if($shouldReport, $e); + + report_if($shouldReport, 'Something went wrong.'); + +#### `report_unless()` + +The `report_unless` function will report an exception using your [exception +handler](/docs/12.x/errors#handling-exceptions) if a given boolean expression +evaluates to `false`: + + + + 1report_unless($reportingDisabled, $e); + + 2  + + 3report_unless($reportingDisabled, 'Something went wrong.'); + + + report_unless($reportingDisabled, $e); + + report_unless($reportingDisabled, 'Something went wrong.'); + +#### `request()` + +The `request` function returns the current [request](/docs/12.x/requests) +instance or obtains an input field's value from the current request: + + + + 1$request = request(); + + 2  + + 3$value = request('key', $default); + + + $request = request(); + + $value = request('key', $default); + +#### `rescue()` + +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/12.x/errors#handling-exceptions); however, +the request will continue processing: + + + + 1return rescue(function () { + + 2 return $this->method(); + + 3}); + + + 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: + + + + 1return rescue(function () { + + 2 return $this->method(); + + 3}, false); + + 4  + + 5return rescue(function () { + + 6 return $this->method(); + + 7}, function () { + + 8 return $this->failure(); + + 9}); + + + 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: + + + + 1return rescue(function () { + + 2 return $this->method(); + + 3}, report: function (Throwable $throwable) { + + 4 return $throwable instanceof InvalidArgumentException; + + 5}); + + + return rescue(function () { + return $this->method(); + }, report: function (Throwable $throwable) { + return $throwable instanceof InvalidArgumentException; + }); + +#### `resolve()` + +The `resolve` function resolves a given class or interface name to an instance +using the [service container](/docs/12.x/container): + + + + 1$api = resolve('HelpSpot\API'); + + + $api = resolve('HelpSpot\API'); + +#### `response()` + +The `response` function creates a [response](/docs/12.x/responses) instance or +obtains an instance of the response factory: + + + + 1return response('Hello World', 200, $headers); + + 2  + + 3return response()->json(['foo' => 'bar'], 200, $headers); + + + return response('Hello World', 200, $headers); + + return response()->json(['foo' => 'bar'], 200, $headers); + +#### `retry()` + +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: + + + + 1return retry(5, function () { + + 2 // Attempt 5 times while resting 100ms between attempts... + + 3}, 100); + + + 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: + + + + 1use Exception; + + 2  + + 3return retry(5, function () { + + 4 // ... + + 5}, function (int $attempt, Exception $exception) { + + 6 return $attempt * 100; + + 7}); + + + use Exception; + + 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: + + + + 1return retry([100, 200], function () { + + 2 // Sleep for 100ms on first retry, 200ms on second retry... + + 3}); + + + 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: + + + + 1use App\Exceptions\TemporaryException; + + 2use Exception; + + 3  + + 4return retry(5, function () { + + 5 // ... + + 6}, 100, function (Exception $exception) { + + 7 return $exception instanceof TemporaryException; + + 8}); + + + use App\Exceptions\TemporaryException; + use Exception; + + return retry(5, function () { + // ... + }, 100, function (Exception $exception) { + return $exception instanceof TemporaryException; + }); + +#### `session()` + +The `session` function may be used to get or set [session](/docs/12.x/session) +values: + + + + 1$value = session('key'); + + + $value = session('key'); + +You may set values by passing an array of key / value pairs to the function: + + + + 1session(['chairs' => 7, 'instruments' => 3]); + + + session(['chairs' => 7, 'instruments' => 3]); + +The session store will be returned if no value is passed to the function: + + + + 1$value = session()->get('key'); + + 2  + + 3session()->put('key', $value); + + + $value = session()->get('key'); + + session()->put('key', $value); + +#### `tap()` + +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: + + + + 1$user = tap(User::first(), function (User $user) { + + 2 $user->name = 'Taylor'; + + 3  + + 4 $user->save(); + + 5}); + + + $user = tap(User::first(), function (User $user) { + $user->name = 'Taylor'; + + $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: + + + + 1$user = tap($user)->update([ + + 2 'name' => $name, + + 3 'email' => $email, + + 4]); + + + $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: + + + + 1return $user->tap(function (User $user) { + + 2 // ... + + 3}); + + + return $user->tap(function (User $user) { + // ... + }); + +#### `throw_if()` + +The `throw_if` function throws the given exception if a given boolean +expression evaluates to `true`: + + + + 1throw_if(! Auth::user()->isAdmin(), AuthorizationException::class); + + 2  + + 3throw_if( + + 4 ! Auth::user()->isAdmin(), + + 5 AuthorizationException::class, + + 6 'You are not allowed to access this page.' + + 7); + + + throw_if(! Auth::user()->isAdmin(), AuthorizationException::class); + + throw_if( + ! Auth::user()->isAdmin(), + AuthorizationException::class, + 'You are not allowed to access this page.' + ); + +#### `throw_unless()` + +The `throw_unless` function throws the given exception if a given boolean +expression evaluates to `false`: + + + + 1throw_unless(Auth::user()->isAdmin(), AuthorizationException::class); + + 2  + + 3throw_unless( + + 4 Auth::user()->isAdmin(), + + 5 AuthorizationException::class, + + 6 'You are not allowed to access this page.' + + 7); + + + throw_unless(Auth::user()->isAdmin(), AuthorizationException::class); + + throw_unless( + Auth::user()->isAdmin(), + AuthorizationException::class, + 'You are not allowed to access this page.' + ); + +#### `today()` + +The `today` function creates a new `Illuminate\Support\Carbon` instance for +the current date: + + + + 1$today = today(); + + + $today = today(); + +#### `trait_uses_recursive()` + +The `trait_uses_recursive` function returns all traits used by a trait: + + + + 1$traits = trait_uses_recursive(\Illuminate\Notifications\Notifiable::class); + + + $traits = trait_uses_recursive(\Illuminate\Notifications\Notifiable::class); + +#### `transform()` + +The `transform` function executes a closure on a given value if the value is +not blank and then returns the return value of the closure: + + + + 1$callback = function (int $value) { + + 2 return $value * 2; + + 3}; + + 4  + + 5$result = transform(5, $callback); + + 6  + + 7// 10 + + + $callback = function (int $value) { + return $value * 2; + }; + + $result = transform(5, $callback); + + // 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: + + + + 1$result = transform(null, $callback, 'The value is blank'); + + 2  + + 3// The value is blank + + + $result = transform(null, $callback, 'The value is blank'); + + // The value is blank + +#### `validator()` + +The `validator` function creates a new [validator](/docs/12.x/validation) +instance with the given arguments. You may use it as an alternative to the +`Validator` facade: + + + + 1$validator = validator($data, $rules, $messages); + + + $validator = validator($data, $rules, $messages); + +#### `value()` + +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: + + + + 1$result = value(true); + + 2  + + 3// true + + 4  + + 5$result = value(function () { + + 6 return false; + + 7}); + + 8  + + 9// false + + + $result = value(true); + + // true + + $result = value(function () { + return 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: + + + + 1$result = value(function (string $name) { + + 2 return $name; + + 3}, 'Taylor'); + + 4  + + 5// 'Taylor' + + + $result = value(function (string $name) { + return $name; + }, 'Taylor'); + + // 'Taylor' + +#### `view()` + +The `view` function retrieves a [view](/docs/12.x/views) instance: + + + + 1return view('auth.login'); + + + return view('auth.login'); + +#### `with()` + +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: + + + + 1$callback = function (mixed $value) { + + 2 return is_numeric($value) ? $value * 2 : 0; + + 3}; + + 4  + + 5$result = with(5, $callback); + + 6  + + 7// 10 + + 8  + + 9$result = with(null, $callback); + + 10  + + 11// 0 + + 12  + + 13$result = with(5, null); + + 14  + + 15// 5 + + + $callback = function (mixed $value) { + return is_numeric($value) ? $value * 2 : 0; + }; + + $result = with(5, $callback); + + // 10 + + $result = with(null, $callback); + + // 0 + + $result = with(5, null); + + // 5 + +#### `when()` + +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: + + + + 1$value = when(true, 'Hello World'); + + 2  + + 3$value = when(true, fn () => 'Hello World'); + + + $value = when(true, 'Hello World'); + + $value = when(true, fn () => 'Hello World'); + +The `when` function is primarily useful for conditionally rendering HTML +attributes: + + + + 1
    + + 2 ... + + 3
    + + +
    + ... +
    + +## Other Utilities + +### Benchmarking + +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: + + + + 1 User::find(1)); // 0.1 ms + + 7  + + 8Benchmark::dd([ + + 9 'Scenario 1' => fn () => User::count(), // 0.5 ms + + 10 'Scenario 2' => fn () => User::all()->count(), // 20.0 ms + + 11]); + + + 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 + ]); + +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 number of milliseconds it took to execute the callback across all +iterations: + + + + 1Benchmark::dd(fn () => User::count(), iterations: 10); // 0.5 ms + + + 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 number of +milliseconds it took to execute the callback: + + + + 1[$count, $duration] = Benchmark::value(fn () => User::count()); + + + [$count, $duration] = Benchmark::value(fn () => User::count()); + +### Dates + +Laravel includes [Carbon](https://carbon.nesbot.com/docs/), a powerful date +and time manipulation library. To create a new `Carbon` instance, you may +invoke the `now` function. This function is globally available within your +Laravel application: + + + + 1$now = now(); + + + $now = now(); + +Or, you may create a new `Carbon` instance using the +`Illuminate\Support\Carbon` class: + + + + 1use Illuminate\Support\Carbon; + + 2  + + 3$now = Carbon::now(); + + + use Illuminate\Support\Carbon; + + $now = Carbon::now(); + +For a thorough discussion of Carbon and its features, please consult the +[official Carbon documentation](https://carbon.nesbot.com/docs/). + +### Deferred Functions + +While Laravel's [queued jobs](/docs/12.x/queues) allow you to queue tasks for +background processing, sometimes you may have simple tasks you would like to +defer without configuring or maintaining a long-running queue worker. + +Deferred functions allow you to defer the execution of a closure until after +the HTTP response has been sent to the user, keeping your application feeling +fast and responsive. To defer the execution of a closure, simply pass the +closure to the `Illuminate\Support\defer` function: + + + + 1use App\Services\Metrics; + + 2use Illuminate\Http\Request; + + 3use Illuminate\Support\Facades\Route; + + 4use function Illuminate\Support\defer; + + 5  + + 6Route::post('/orders', function (Request $request) { + + 7 // Create order... + + 8  + + 9 defer(fn () => Metrics::reportOrder($order)); + + 10  + + 11 return $order; + + 12}); + + + use App\Services\Metrics; + use Illuminate\Http\Request; + use Illuminate\Support\Facades\Route; + use function Illuminate\Support\defer; + + Route::post('/orders', function (Request $request) { + // Create order... + + defer(fn () => Metrics::reportOrder($order)); + + return $order; + }); + +By default, deferred functions will only be executed if the HTTP response, +Artisan command, or queued job from which `Illuminate\Support\defer` is +invoked completes successfully. This means that deferred functions will not be +executed if a request results in a `4xx` or `5xx` HTTP response. If you would +like a deferred function to always execute, you may chain the `always` method +onto your deferred function: + + + + 1defer(fn () => Metrics::reportOrder($order))->always(); + + + defer(fn () => Metrics::reportOrder($order))->always(); + +If you have the [Swoole PHP +extension](https://www.php.net/manual/en/book.swoole.php) installed, Laravel's +`defer` function may conflict with Swoole's own global `defer` function, +leading to web server errors. Make sure you call Laravel's `defer` helper by +explicitly namespacing it: `use function Illuminate\Support\defer;` + +#### Cancelling Deferred Functions + +If you need to cancel a deferred function before it is executed, you can use +the `forget` method to cancel the function by its name. To name a deferred +function, provide a second argument to the `Illuminate\Support\defer` +function: + + + + 1defer(fn () => Metrics::report(), 'reportMetrics'); + + 2  + + 3defer()->forget('reportMetrics'); + + + defer(fn () => Metrics::report(), 'reportMetrics'); + + defer()->forget('reportMetrics'); + +#### Disabling Deferred Functions in Tests + +When writing tests, it may be useful to disable deferred functions. You may +call `withoutDefer` in your test to instruct Laravel to invoke all deferred +functions immediately: + +Pest PHPUnit + + + + 1test('without defer', function () { + + 2 $this->withoutDefer(); + + 3  + + 4 // ... + + 5}); + + + test('without defer', function () { + $this->withoutDefer(); + + // ... + }); + + + 1use Tests\TestCase; + + 2  + + 3class ExampleTest extends TestCase + + 4{ + + 5 public function test_without_defer(): void + + 6 { + + 7 $this->withoutDefer(); + + 8  + + 9 // ... + + 10 } + + 11} + + + use Tests\TestCase; + + class ExampleTest extends TestCase + { + public function test_without_defer(): void + { + $this->withoutDefer(); + + // ... + } + } + +If you would like to disable deferred functions for all tests within a test +case, you may call the `withoutDefer` method from the `setUp` method on your +base `TestCase` class: + + + + 1withoutDefer(); + + 14 } + + 15} + + + withoutDefer(); + } + } + +### Lottery + +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: + + + + 1use Illuminate\Support\Lottery; + + 2  + + 3Lottery::odds(1, 20) + + 4 ->winner(fn () => $user->won()) + + 5 ->loser(fn () => $user->lost()) + + 6 ->choose(); + + + use Illuminate\Support\Lottery; + + 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: + + + + 1use Carbon\CarbonInterval; + + 2use Illuminate\Support\Facades\DB; + + 3use Illuminate\Support\Lottery; + + 4  + + 5DB::whenQueryingForLongerThan( + + 6 CarbonInterval::seconds(2), + + 7 Lottery::odds(1, 100)->winner(fn () => report('Querying > 2 seconds.')), + + 8); + + + 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: + + + + 1// Lottery will always win... + + 2Lottery::alwaysWin(); + + 3  + + 4// Lottery will always lose... + + 5Lottery::alwaysLose(); + + 6  + + 7// Lottery will win then lose, and finally return to normal behavior... + + 8Lottery::fix([true, false]); + + 9  + + 10// Lottery will return to normal behavior... + + 11Lottery::determineResultsNormally(); + + + // Lottery will always win... + Lottery::alwaysWin(); + + // Lottery will always lose... + Lottery::alwaysLose(); + + // Lottery will win then lose, and finally return to normal behavior... + Lottery::fix([true, false]); + + // Lottery will return to normal behavior... + Lottery::determineResultsNormally(); + +### Pipeline + +Laravel's `Pipeline` facade provides a convenient way to "pipe" a given input +through a series of invokable classes, closures, or callables, giving each +class the opportunity to inspect or modify the input and invoke the next +callable in the pipeline: + + + + 1use Closure; + + 2use App\Models\User; + + 3use Illuminate\Support\Facades\Pipeline; + + 4  + + 5$user = Pipeline::send($user) + + 6 ->through([ + + 7 function (User $user, Closure $next) { + + 8 // ... + + 9  + + 10 return $next($user); + + 11 }, + + 12 function (User $user, Closure $next) { + + 13 // ... + + 14  + + 15 return $next($user); + + 16 }, + + 17 ]) + + 18 ->then(fn (User $user) => $user); + + + use Closure; + use App\Models\User; + use Illuminate\Support\Facades\Pipeline; + + $user = Pipeline::send($user) + ->through([ + function (User $user, Closure $next) { + // ... + + return $next($user); + }, + function (User $user, Closure $next) { + // ... + + return $next($user); + }, + ]) + ->then(fn (User $user) => $user); + +As you can see, each invokable class or closure in the pipeline is provided +the input and a `$next` closure. Invoking the `$next` closure will invoke the +next callable in the pipeline. As you may have noticed, this is very similar +to [middleware](/docs/12.x/middleware). + +When the last callable in the pipeline invokes the `$next` closure, the +callable provided to the `then` method will be invoked. Typically, this +callable will simply return the given input. For convenience, if you simply +want to return the input after it has been processed, you may use the +`thenReturn` method. + +Of course, as discussed previously, you are not limited to providing closures +to your pipeline. You may also provide invokable classes. If a class name is +provided, the class will be instantiated via Laravel's [service +container](/docs/12.x/container), allowing dependencies to be injected into +the invokable class: + + + + 1$user = Pipeline::send($user) + + 2 ->through([ + + 3 GenerateProfilePhoto::class, + + 4 ActivateSubscription::class, + + 5 SendWelcomeEmail::class, + + 6 ]) + + 7 ->thenReturn(); + + + $user = Pipeline::send($user) + ->through([ + GenerateProfilePhoto::class, + ActivateSubscription::class, + SendWelcomeEmail::class, + ]) + ->thenReturn(); + +The `withinTransaction` method may be invoked on the pipeline to automatically +wrap all steps of the pipeline within a single database transaction: + + + + 1$user = Pipeline::send($user) + + 2 ->withinTransaction() + + 3 ->through([ + + 4 ProcessOrder::class, + + 5 TransferFunds::class, + + 6 UpdateInventory::class, + + 7 ]) + + 8 ->thenReturn(); + + + $user = Pipeline::send($user) + ->withinTransaction() + ->through([ + ProcessOrder::class, + TransferFunds::class, + UpdateInventory::class, + ]) + ->thenReturn(); + +### Sleep + +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: + + + + 1use Illuminate\Support\Sleep; + + 2  + + 3$waiting = true; + + 4  + + 5while ($waiting) { + + 6 Sleep::for(1)->second(); + + 7  + + 8 $waiting = /* ... */; + + 9} + + + use Illuminate\Support\Sleep; + + $waiting = true; + + while ($waiting) { + Sleep::for(1)->second(); + + $waiting = /* ... */; + } + +The `Sleep` class offers a variety of methods that allow you to work with +different units of time: + + + + 1// Return a value after sleeping... + + 2$result = Sleep::for(1)->second()->then(fn () => 1 + 1); + + 3  + + 4// Sleep while a given value is true... + + 5Sleep::for(1)->second()->while(fn () => shouldKeepSleeping()); + + 6  + + 7// Pause execution for 90 seconds... + + 8Sleep::for(1.5)->minutes(); + + 9  + + 10// Pause execution for 2 seconds... + + 11Sleep::for(2)->seconds(); + + 12  + + 13// Pause execution for 500 milliseconds... + + 14Sleep::for(500)->milliseconds(); + + 15  + + 16// Pause execution for 5,000 microseconds... + + 17Sleep::for(5000)->microseconds(); + + 18  + + 19// Pause execution until a given time... + + 20Sleep::until(now()->addMinute()); + + 21  + + 22// Alias of PHP's native "sleep" function... + + 23Sleep::sleep(2); + + 24  + + 25// Alias of PHP's native "usleep" function... + + 26Sleep::usleep(5000); + + + // 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()); + + // Pause execution for 90 seconds... + Sleep::for(1.5)->minutes(); + + // Pause execution for 2 seconds... + Sleep::for(2)->seconds(); + + // Pause execution for 500 milliseconds... + Sleep::for(500)->milliseconds(); + + // Pause execution for 5,000 microseconds... + Sleep::for(5000)->microseconds(); + + // 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 "usleep" function... + Sleep::usleep(5000); + +To easily combine units of time, you may use the `and` method: + + + + 1Sleep::for(1)->second()->and(10)->milliseconds(); + + + 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: + + + + 1$waiting = /* ... */; + + 2  + + 3$seconds = 1; + + 4  + + 5while ($waiting) { + + 6 Sleep::for($seconds++)->seconds(); + + 7  + + 8 $waiting = /* ... */; + + 9} + + + $waiting = /* ... */; + + $seconds = 1; + + while ($waiting) { + Sleep::for($seconds++)->seconds(); + + $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: + +Pest PHPUnit + + + + 1it('waits until ready', function () { + + 2 Sleep::fake(); + + 3  + + 4 // ... + + 5}); + + + it('waits until ready', function () { + Sleep::fake(); + + // ... + }); + + + 1public function test_it_waits_until_ready() + + 2{ + + 3 Sleep::fake(); + + 4  + + 5 // ... + + 6} + + + public function test_it_waits_until_ready() + { + Sleep::fake(); + + // ... + } + +When faking the `Sleep` class, the actual execution pause is by-passed, +leading to a substantially faster test. + +Once the `Sleep` class has been faked, it is possible to make assertions +against the expected "sleeps" that should have occurred. To illustrate this, +let's imagine we are testing code that pauses execution three times, with each +pause increasing by a single second. Using the `assertSequence` method, we can +assert that our code "slept" for the proper amount of time while keeping our +test fast: + +Pest PHPUnit + + + + 1it('checks if ready three times', function () { + + 2 Sleep::fake(); + + 3  + + 4 // ... + + 5  + + 6 Sleep::assertSequence([ + + 7 Sleep::for(1)->second(), + + 8 Sleep::for(2)->seconds(), + + 9 Sleep::for(3)->seconds(), + + 10 ]); + + 11} + + + it('checks if ready three times', function () { + Sleep::fake(); + + // ... + + Sleep::assertSequence([ + Sleep::for(1)->second(), + Sleep::for(2)->seconds(), + Sleep::for(3)->seconds(), + ]); + } + + + 1public function test_it_checks_if_ready_three_times() + + 2{ + + 3 Sleep::fake(); + + 4  + + 5 // ... + + 6  + + 7 Sleep::assertSequence([ + + 8 Sleep::for(1)->second(), + + 9 Sleep::for(2)->seconds(), + + 10 Sleep::for(3)->seconds(), + + 11 ]); + + 12} + + + public function test_it_checks_if_ready_three_times() + { + Sleep::fake(); + + // ... + + Sleep::assertSequence([ + Sleep::for(1)->second(), + Sleep::for(2)->seconds(), + Sleep::for(3)->seconds(), + ]); + } + +Of course, the `Sleep` class offers a variety of other assertions you may use +when testing: + + + + 1use Carbon\CarbonInterval as Duration; + + 2use Illuminate\Support\Sleep; + + 3  + + 4// Assert that sleep was called 3 times... + + 5Sleep::assertSleptTimes(3); + + 6  + + 7// Assert against the duration of sleep... + + 8Sleep::assertSlept(function (Duration $duration): bool { + + 9 return /* ... */; + + 10}, times: 1); + + 11  + + 12// Assert that the Sleep class was never invoked... + + 13Sleep::assertNeverSlept(); + + 14  + + 15// Assert that, even if Sleep was called, no execution paused occurred... + + 16Sleep::assertInsomniac(); + + + use Carbon\CarbonInterval as Duration; + use Illuminate\Support\Sleep; + + // 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 that the Sleep class was never invoked... + Sleep::assertNeverSlept(); + + // 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. +To achieve this, you may provide a callback to the `whenFakingSleep` method. +In the following example, we use Laravel's [time manipulation +helpers](/docs/12.x/mocking#interacting-with-time) to instantly progress time +by the duration of each sleep: + + + + 1use Carbon\CarbonInterval as Duration; + + 2  + + 3$this->freezeTime(); + + 4  + + 5Sleep::fake(); + + 6  + + 7Sleep::whenFakingSleep(function (Duration $duration) { + + 8 // Progress time when faking sleep... + + 9 $this->travel($duration->totalMilliseconds)->milliseconds(); + + 10}); + + + use Carbon\CarbonInterval as Duration; + + $this->freezeTime(); + + Sleep::fake(); + + Sleep::whenFakingSleep(function (Duration $duration) { + // Progress time when faking sleep... + $this->travel($duration->totalMilliseconds)->milliseconds(); + }); + +As progressing time is a common requirement, the `fake` method accepts a +`syncWithCarbon` argument to keep Carbon in sync when sleeping within a test: + + + + 1Sleep::fake(syncWithCarbon: true); + + 2  + + 3$start = now(); + + 4  + + 5Sleep::for(1)->second(); + + 6  + + 7$start->diffForHumans(); // 1 second ago + + + Sleep::fake(syncWithCarbon: true); + + $start = now(); + + Sleep::for(1)->second(); + + $start->diffForHumans(); // 1 second ago + +Laravel uses the `Sleep` class internally whenever it is pausing execution. +For example, the retry helper uses the `Sleep` class when sleeping, allowing +for improved testability when using that helper. + +### Timebox + +Laravel's `Timebox` class ensures that the given callback always takes a fixed +amount of time to execute, even if its actual execution completes sooner. This +is particularly useful for cryptographic operations and user authentication +checks, where attackers might exploit variations in execution time to infer +sensitive information. + +If the execution exceeds the fixed duration, `Timebox` has no effect. It is up +to the developer to choose a sufficiently long time as the fixed duration to +account for worst-case scenarios. + +The call method accepts a closure and a time limit in microseconds, and then +executes the closure and waits until the time limit is reached: + + + + 1use Illuminate\Support\Timebox; + + 2  + + 3(new Timebox)->call(function ($timebox) { + + 4 // ... + + 5}, microseconds: 10000); + + + use Illuminate\Support\Timebox; + + (new Timebox)->call(function ($timebox) { + // ... + }, microseconds: 10000); + +If an exception is thrown within the closure, this class will respect the +defined delay and re-throw the exception after the delay. + +### URI + +Laravel's `Uri` class provides a convenient and fluent interface for creating +and manipulating URIs. This class wraps the functionality provided by the +underlying League URI package and integrates seamlessly with Laravel's routing +system. + +You can create a `Uri` instance easily using static methods: + + + + 1use App\Http\Controllers\UserController; + + 2use App\Http\Controllers\InvokableController; + + 3use Illuminate\Support\Uri; + + 4  + + 5// Generate a URI instance from the given string... + + 6$uri = Uri::of('https://example.com/path'); + + 7  + + 8// Generate URI instances to paths, named routes, or controller actions... + + 9$uri = Uri::to('/dashboard'); + + 10$uri = Uri::route('users.show', ['user' => 1]); + + 11$uri = Uri::signedRoute('users.show', ['user' => 1]); + + 12$uri = Uri::temporarySignedRoute('user.index', now()->addMinutes(5)); + + 13$uri = Uri::action([UserController::class, 'index']); + + 14$uri = Uri::action(InvokableController::class); + + 15  + + 16// Generate a URI instance from the current request URL... + + 17$uri = $request->uri(); + + + use App\Http\Controllers\UserController; + use App\Http\Controllers\InvokableController; + use Illuminate\Support\Uri; + + // Generate a URI instance from the given string... + $uri = Uri::of('https://example.com/path'); + + // Generate URI instances to paths, named routes, or controller actions... + $uri = Uri::to('/dashboard'); + $uri = Uri::route('users.show', ['user' => 1]); + $uri = Uri::signedRoute('users.show', ['user' => 1]); + $uri = Uri::temporarySignedRoute('user.index', now()->addMinutes(5)); + $uri = Uri::action([UserController::class, 'index']); + $uri = Uri::action(InvokableController::class); + + // Generate a URI instance from the current request URL... + $uri = $request->uri(); + +Once you have a URI instance, you can fluently modify it: + + + + 1$uri = Uri::of('https://example.com') + + 2 ->withScheme('http') + + 3 ->withHost('test.com') + + 4 ->withPort(8000) + + 5 ->withPath('/users') + + 6 ->withQuery(['page' => 2]) + + 7 ->withFragment('section-1'); + + + $uri = Uri::of('https://example.com') + ->withScheme('http') + ->withHost('test.com') + ->withPort(8000) + ->withPath('/users') + ->withQuery(['page' => 2]) + ->withFragment('section-1'); + +#### Inspecting URIs + +The `Uri` class also allows you to easily inspect the various components of +the underlying URI: + + + + 1$scheme = $uri->scheme(); + + 2$host = $uri->host(); + + 3$port = $uri->port(); + + 4$path = $uri->path(); + + 5$segments = $uri->pathSegments(); + + 6$query = $uri->query(); + + 7$fragment = $uri->fragment(); + + + $scheme = $uri->scheme(); + $host = $uri->host(); + $port = $uri->port(); + $path = $uri->path(); + $segments = $uri->pathSegments(); + $query = $uri->query(); + $fragment = $uri->fragment(); + +#### Manipulating Query Strings + +The `Uri` class offers several methods that may be used to manipulate a URI's +query string. The `withQuery` method may be used to merge additional query +string parameters into the existing query string: + + + + 1$uri = $uri->withQuery(['sort' => 'name']); + + + $uri = $uri->withQuery(['sort' => 'name']); + +The `withQueryIfMissing` method may be used to merge additional query string +parameters into the existing query string if the given keys do not already +exist in the query string: + + + + 1$uri = $uri->withQueryIfMissing(['page' => 1]); + + + $uri = $uri->withQueryIfMissing(['page' => 1]); + +The `replaceQuery` method may be used to complete replace the existing query +string with a new one: + + + + 1$uri = $uri->replaceQuery(['page' => 1]); + + + $uri = $uri->replaceQuery(['page' => 1]); + +The `pushOntoQuery` method may be used to push additional parameters onto a +query string parameter that has an array value: + + + + 1$uri = $uri->pushOntoQuery('filter', ['active', 'pending']); + + + $uri = $uri->pushOntoQuery('filter', ['active', 'pending']); + +The `withoutQuery` method may be used to remove parameters from the query +string: + + + + 1$uri = $uri->withoutQuery(['page']); + + + $uri = $uri->withoutQuery(['page']); + +#### Generating Responses From URIs + +The `redirect` method may be used to generate a `RedirectResponse` instance to +the given URI: + + + + 1$uri = Uri::of('https://example.com'); + + 2  + + 3return $uri->redirect(); + + + $uri = Uri::of('https://example.com'); + + return $uri->redirect(); + +Or, you may simply return the `Uri` instance from a route or controller +action, which will automatically generate a redirect response to the returned +URI: + + + + 1use Illuminate\Support\Facades\Route; + + 2use Illuminate\Support\Uri; + + 3  + + 4Route::get('/redirect', function () { + + 5 return Uri::to('/index') + + 6 ->withQuery(['sort' => 'name']); + + 7}); + + + use Illuminate\Support\Facades\Route; + use Illuminate\Support\Uri; + + Route::get('/redirect', function () { + return Uri::to('/index') + ->withQuery(['sort' => 'name']); + }); + diff --git a/output/12.x/homestead.md b/output/12.x/homestead.md new file mode 100644 index 0000000..1117104 --- /dev/null +++ b/output/12.x/homestead.md @@ -0,0 +1,1471 @@ +# Laravel Homestead + + * Introduction + * Installation and Setup + * First Steps + * Configuring Homestead + * Configuring Nginx Sites + * Configuring Services + * Launching the Vagrant Box + * Per Project Installation + * Installing Optional Features + * Aliases + * Updating Homestead + * Daily Usage + * Connecting via SSH + * Adding Additional Sites + * Environment Variables + * Ports + * PHP Versions + * Connecting to Databases + * Database Backups + * Configuring Cron Schedules + * Configuring Mailpit + * Configuring Minio + * Laravel Dusk + * Sharing Your Environment + * Debugging and Profiling + * Debugging Web Requests With Xdebug + * Debugging CLI Applications + * Profiling Applications With Blackfire + * Network Interfaces + * Extending Homestead + * Provider Specific Settings + * VirtualBox + +## Introduction + +Laravel Homestead is a legacy package that is no longer actively maintained. +[Laravel Sail](/docs/12.x/sail) may be used as a modern alternative. + +Laravel strives to make the entire PHP development experience delightful, +including your local development environment. [Laravel +Homestead](https://github.com/laravel/homestead) is an official, pre-packaged +Vagrant box that provides you a wonderful development environment without +requiring you to install PHP, a web server, or any other server software on +your local machine. + +[Vagrant](https://www.vagrantup.com) provides a simple, elegant way to manage +and provision Virtual Machines. Vagrant boxes are completely disposable. If +something goes wrong, you can destroy and re-create the box in minutes! + +Homestead runs on any Windows, macOS, or Linux system and includes Nginx, PHP, +MySQL, PostgreSQL, Redis, Memcached, Node, and all of the other software you +need to develop amazing Laravel applications. + +If you are using Windows, you may need to enable hardware virtualization +(VT-x). It can usually be enabled via your BIOS. If you are using Hyper-V on a +UEFI system you may additionally need to disable Hyper-V in order to access +VT-x. + +### Included Software + + * Ubuntu 22.04 + * Git + * PHP 8.3 + * PHP 8.2 + * PHP 8.1 + * PHP 8.0 + * PHP 7.4 + * PHP 7.3 + * PHP 7.2 + * PHP 7.1 + * PHP 7.0 + * PHP 5.6 + * Nginx + * MySQL 8.0 + * lmm + * Sqlite3 + * PostgreSQL 15 + * Composer + * Docker + * Node (With Yarn, Bower, Grunt, and Gulp) + * Redis + * Memcached + * Beanstalkd + * Mailpit + * avahi + * ngrok + * Xdebug + * XHProf / Tideways / XHGui + * wp-cli + +### Optional Software + + * Apache + * Blackfire + * Cassandra + * Chronograf + * CouchDB + * Crystal & Lucky Framework + * Elasticsearch + * EventStoreDB + * Flyway + * Gearman + * Go + * Grafana + * InfluxDB + * Logstash + * MariaDB + * Meilisearch + * MinIO + * MongoDB + * Neo4j + * Oh My Zsh + * Open Resty + * PM2 + * Python + * R + * RabbitMQ + * Rust + * RVM (Ruby Version Manager) + * Solr + * TimescaleDB + * Trader (PHP extension) + * Webdriver & Laravel Dusk Utilities + +## Installation and Setup + +### First Steps + +Before launching your Homestead environment, you must install +[Vagrant](https://developer.hashicorp.com/vagrant/downloads) as well as one of +the following supported providers: + + * [VirtualBox 6.1.x](https://www.virtualbox.org/wiki/Download_Old_Builds_6_1) + * [Parallels](https://www.parallels.com/products/desktop/) + +All of these software packages provide easy-to-use visual installers for all +popular operating systems. + +To use the Parallels provider, you will need to install [Parallels Vagrant +plug-in](https://github.com/Parallels/vagrant-parallels). It is free of +charge. + +#### Installing Homestead + +You may install Homestead by cloning the Homestead repository onto your host +machine. Consider cloning the repository into a `Homestead` folder within your +"home" directory, as the Homestead virtual machine will serve as the host to +all of your Laravel applications. Throughout this documentation, we will refer +to this directory as your "Homestead directory": + + + + 1git clone https://github.com/laravel/homestead.git ~/Homestead + + + git clone https://github.com/laravel/homestead.git ~/Homestead + +After cloning the Laravel Homestead repository, you should checkout the +`release` branch. This branch always contains the latest stable release of +Homestead: + + + + 1cd ~/Homestead + + 2  + + 3git checkout release + + + cd ~/Homestead + + git checkout release + +Next, execute the `bash init.sh` command from the Homestead directory to +create the `Homestead.yaml` configuration file. The `Homestead.yaml` file is +where you will configure all of the settings for your Homestead installation. +This file will be placed in the Homestead directory: + + + + 1# macOS / Linux... + + 2bash init.sh + + 3  + + 4# Windows... + + 5init.bat + + + # macOS / Linux... + bash init.sh + + # Windows... + init.bat + +### Configuring Homestead + +#### Setting Your Provider + +The `provider` key in your `Homestead.yaml` file indicates which Vagrant +provider should be used: `virtualbox` or `parallels`: + + + + 1provider: virtualbox + + + provider: virtualbox + +If you are using Apple Silicon the Parallels provider is required. + +#### Configuring Shared Folders + +The `folders` property of the `Homestead.yaml` file lists all of the folders +you wish to share with your Homestead environment. As files within these +folders are changed, they will be kept in sync between your local machine and +the Homestead virtual environment. You may configure as many shared folders as +necessary: + + + + 1folders: + + 2 - map: ~/code/project1 + + 3 to: /home/vagrant/project1 + + + folders: + - map: ~/code/project1 + to: /home/vagrant/project1 + +Windows users should not use the `~/` path syntax and instead should use the +full path to their project, such as `C:\Users\user\Code\project1`. + +You should always map individual applications to their own folder mapping +instead of mapping a single large directory that contains all of your +applications. When you map a folder, the virtual machine must keep track of +all disk IO for _every_ file in the folder. You may experience reduced +performance if you have a large number of files in a folder: + + + + 1folders: + + 2 - map: ~/code/project1 + + 3 to: /home/vagrant/project1 + + 4 - map: ~/code/project2 + + 5 to: /home/vagrant/project2 + + + folders: + - map: ~/code/project1 + to: /home/vagrant/project1 + - map: ~/code/project2 + to: /home/vagrant/project2 + +You should never mount `.` (the current directory) when using Homestead. This +causes Vagrant to not map the current folder to `/vagrant` and will break +optional features and cause unexpected results while provisioning. + +To enable [NFS](https://developer.hashicorp.com/vagrant/docs/synced- +folders/nfs), you may add a `type` option to your folder mapping: + + + + 1folders: + + 2 - map: ~/code/project1 + + 3 to: /home/vagrant/project1 + + 4 type: "nfs" + + + folders: + - map: ~/code/project1 + to: /home/vagrant/project1 + type: "nfs" + +When using NFS on Windows, you should consider installing the [vagrant- +winnfsd](https://github.com/winnfsd/vagrant-winnfsd) plug-in. This plug-in +will maintain the correct user / group permissions for files and directories +within the Homestead virtual machine. + +You may also pass any options supported by Vagrant's [Synced +Folders](https://developer.hashicorp.com/vagrant/docs/synced- +folders/basic_usage) by listing them under the `options` key: + + + + 1folders: + + 2 - map: ~/code/project1 + + 3 to: /home/vagrant/project1 + + 4 type: "rsync" + + 5 options: + + 6 rsync__args: ["--verbose", "--archive", "--delete", "-zz"] + + 7 rsync__exclude: ["node_modules"] + + + folders: + - map: ~/code/project1 + to: /home/vagrant/project1 + type: "rsync" + options: + rsync__args: ["--verbose", "--archive", "--delete", "-zz"] + rsync__exclude: ["node_modules"] + +### Configuring Nginx Sites + +Not familiar with Nginx? No problem. Your `Homestead.yaml` file's `sites` +property allows you to easily map a "domain" to a folder on your Homestead +environment. A sample site configuration is included in the `Homestead.yaml` +file. Again, you may add as many sites to your Homestead environment as +necessary. Homestead can serve as a convenient, virtualized environment for +every Laravel application you are working on: + + + + 1sites: + + 2 - map: homestead.test + + 3 to: /home/vagrant/project1/public + + + sites: + - map: homestead.test + to: /home/vagrant/project1/public + +If you change the `sites` property after provisioning the Homestead virtual +machine, you should execute the `vagrant reload --provision` command in your +terminal to update the Nginx configuration on the virtual machine. + +Homestead scripts are built to be as idempotent as possible. However, if you +are experiencing issues while provisioning you should destroy and rebuild the +machine by executing the `vagrant destroy && vagrant up` command. + +#### Hostname Resolution + +Homestead publishes hostnames using `mDNS` for automatic host resolution. If +you set `hostname: homestead` in your `Homestead.yaml` file, the host will be +available at `homestead.local`. macOS, iOS, and Linux desktop distributions +include `mDNS` support by default. If you are using Windows, you must install +[Bonjour Print Services for +Windows](https://support.apple.com/kb/DL999?viewlocale=en_US&locale=en_US). + +Using automatic hostnames works best for per project installations 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: + + + + 1192.168.56.56 homestead.test + + + 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: + + + + 1http://homestead.test + + + http://homestead.test + +### Configuring Services + +Homestead starts several services by default; however, you may customize which +services are enabled or disabled during provisioning. For example, you may +enable PostgreSQL and disable MySQL by modifying the `services` option within +your `Homestead.yaml` file: + + + + 1services: + + 2 - enabled: + + 3 - "postgresql" + + 4 - disabled: + + 5 - "mysql" + + + services: + - enabled: + - "postgresql" + - disabled: + - "mysql" + +The specified services will be started or stopped based on their order in the +`enabled` and `disabled` directives. + +### Launching the Vagrant Box + +Once you have edited the `Homestead.yaml` to your liking, run the `vagrant up` +command from your Homestead directory. Vagrant will boot the virtual machine +and automatically configure your shared folders and Nginx sites. + +To destroy the machine, you may use the `vagrant destroy` command. + +### Per Project Installation + +Instead of installing Homestead globally and sharing the same Homestead +virtual machine across all of your projects, you may instead configure a +Homestead instance for each project you manage. Installing Homestead per +project may be beneficial if you wish to ship a `Vagrantfile` with your +project, allowing others working on the project to `vagrant up` immediately +after cloning the project's repository. + +You may install Homestead into your project using the Composer package +manager: + + + + 1composer require laravel/homestead --dev + + + composer require laravel/homestead --dev + +Once Homestead has been installed, invoke Homestead's `make` command to +generate the `Vagrantfile` and `Homestead.yaml` file for your project. These +files will be placed in the root of your project. The `make` command will +automatically configure the `sites` and `folders` directives in the +`Homestead.yaml` file: + + + + 1# macOS / Linux... + + 2php vendor/bin/homestead make + + 3  + + 4# Windows... + + 5vendor\\bin\\homestead make + + + # macOS / Linux... + php vendor/bin/homestead make + + # Windows... + vendor\\bin\\homestead make + +Next, run the `vagrant up` command in your terminal and access your project at +`http://homestead.test` in your browser. Remember, you will still need to add +an `/etc/hosts` file entry for `homestead.test` or the domain of your choice +if you are not using automatic hostname resolution. + +### Installing Optional Features + +Optional software is installed using the `features` option within your +`Homestead.yaml` file. Most features can be enabled or disabled with a boolean +value, while some features allow multiple configuration options: + + + + 1features: + + 2 - blackfire: + + 3 server_id: "server_id" + + 4 server_token: "server_value" + + 5 client_id: "client_id" + + 6 client_token: "client_value" + + 7 - cassandra: true + + 8 - chronograf: true + + 9 - couchdb: true + + 10 - crystal: true + + 11 - dragonflydb: true + + 12 - elasticsearch: + + 13 version: 7.9.0 + + 14 - eventstore: true + + 15 version: 21.2.0 + + 16 - flyway: true + + 17 - gearman: true + + 18 - golang: true + + 19 - grafana: true + + 20 - influxdb: true + + 21 - logstash: true + + 22 - mariadb: true + + 23 - meilisearch: true + + 24 - minio: true + + 25 - mongodb: true + + 26 - neo4j: true + + 27 - ohmyzsh: true + + 28 - openresty: true + + 29 - pm2: true + + 30 - python: true + + 31 - r-base: true + + 32 - rabbitmq: true + + 33 - rustc: true + + 34 - rvm: true + + 35 - solr: true + + 36 - timescaledb: true + + 37 - trader: true + + 38 - webdriver: true + + + features: + - blackfire: + server_id: "server_id" + server_token: "server_value" + client_id: "client_id" + client_token: "client_value" + - cassandra: true + - chronograf: true + - couchdb: true + - crystal: true + - dragonflydb: true + - elasticsearch: + version: 7.9.0 + - eventstore: true + version: 21.2.0 + - flyway: true + - gearman: true + - golang: true + - grafana: true + - influxdb: true + - logstash: true + - mariadb: true + - meilisearch: true + - minio: true + - mongodb: true + - neo4j: true + - ohmyzsh: true + - openresty: true + - pm2: true + - python: true + - r-base: true + - rabbitmq: true + - rustc: true + - rvm: true + - solr: true + - timescaledb: true + - trader: true + - webdriver: true + +#### Elasticsearch + +You may specify a supported version of Elasticsearch, which must be an exact +version number (major.minor.patch). The default installation will create a +cluster named 'homestead'. You should never give Elasticsearch more than half +of the operating system's memory, so make sure your Homestead virtual machine +has at least twice the Elasticsearch allocation. + +Check out the [Elasticsearch +documentation](https://www.elastic.co/guide/en/elasticsearch/reference/current) +to learn how to customize your configuration. + +#### MariaDB + +Enabling MariaDB will remove MySQL and install MariaDB. MariaDB typically +serves as a drop-in replacement for MySQL, so you should still use the `mysql` +database driver in your application's database configuration. + +#### MongoDB + +The default MongoDB installation will set the database username to `homestead` +and the corresponding password to `secret`. + +#### Neo4j + +The default Neo4j installation will set the database username to `homestead` +and the corresponding password to `secret`. To access the Neo4j browser, visit +`http://homestead.test:7474` via your web browser. The ports `7687` (Bolt), +`7474` (HTTP), and `7473` (HTTPS) are ready to serve requests from the Neo4j +client. + +### Aliases + +You may add Bash aliases to your Homestead virtual machine by modifying the +`aliases` file within your Homestead directory: + + + + 1alias c='clear' + + 2alias ..='cd ..' + + + alias c='clear' + alias ..='cd ..' + +After you have updated the `aliases` file, you should re-provision the +Homestead virtual machine using the `vagrant reload --provision` command. This +will ensure that your new aliases are available on the machine. + +## Updating Homestead + +Before you begin updating Homestead you should ensure you have removed your +current virtual machine by running the following command in your Homestead +directory: + + + + 1vagrant destroy + + + vagrant destroy + +Next, you need to update the Homestead source code. If you cloned the +repository, you can execute the following commands at the location you +originally cloned the repository: + + + + 1git fetch + + 2  + + 3git pull origin release + + + git fetch + + git pull origin release + +These commands pull the latest Homestead code from the GitHub repository, +fetch the latest tags, and then check out the latest tagged release. You can +find the latest stable release version on Homestead's [GitHub releases +page](https://github.com/laravel/homestead/releases). + +If you have installed Homestead via your project's `composer.json` file, you +should ensure your `composer.json` file contains `"laravel/homestead": "^12"` +and update your dependencies: + + + + 1composer update + + + composer update + +Next, you should update the Vagrant box using the `vagrant box update` +command: + + + + 1vagrant box update + + + vagrant box update + +After updating the Vagrant box, you should run the `bash init.sh` command from +the Homestead directory in order to update Homestead's additional +configuration files. You will be asked whether you wish to overwrite your +existing `Homestead.yaml`, `after.sh`, and `aliases` files: + + + + 1# macOS / Linux... + + 2bash init.sh + + 3  + + 4# Windows... + + 5init.bat + + + # macOS / Linux... + bash init.sh + + # Windows... + init.bat + +Finally, you will need to regenerate your Homestead virtual machine to utilize +the latest Vagrant installation: + + + + 1vagrant up + + + vagrant up + +## Daily Usage + +### Connecting via SSH + +You can SSH into your virtual machine by executing the `vagrant ssh` terminal +command from your Homestead directory. + +### Adding Additional Sites + +Once your Homestead environment is provisioned and running, you may want to +add additional Nginx sites for your other Laravel projects. You can run as +many Laravel projects as you wish on a single Homestead environment. To add an +additional site, add the site to your `Homestead.yaml` file. + + + + 1sites: + + 2 - map: homestead.test + + 3 to: /home/vagrant/project1/public + + 4 - map: another.test + + 5 to: /home/vagrant/project2/public + + + sites: + - map: homestead.test + to: /home/vagrant/project1/public + - map: another.test + to: /home/vagrant/project2/public + +You should ensure that you have configured a folder mapping for the project's +directory before adding the site. + +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`: + + + + 1192.168.56.56 homestead.test + + 2192.168.56.56 another.test + + + 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. + +#### Site Types + +Homestead supports several "types" of sites which allow you to easily run +projects that are not based on Laravel. For example, we may easily add a +Statamic application to Homestead using the `statamic` site type: + + + + 1sites: + + 2 - map: statamic.test + + 3 to: /home/vagrant/my-symfony-project/web + + 4 type: "statamic" + + + sites: + - map: statamic.test + to: /home/vagrant/my-symfony-project/web + type: "statamic" + +The available site types are: `apache`, `apache-proxy`, `apigility`, +`expressive`, `laravel` (the default), `proxy` (for nginx), `silverstripe`, +`statamic`, `symfony2`, `symfony4`, and `zf`. + +#### Site Parameters + +You may add additional Nginx `fastcgi_param` values to your site via the +`params` site directive: + + + + 1sites: + + 2 - map: homestead.test + + 3 to: /home/vagrant/project1/public + + 4 params: + + 5 - key: FOO + + 6 value: BAR + + + sites: + - map: homestead.test + to: /home/vagrant/project1/public + params: + - key: FOO + value: BAR + +### Environment Variables + +You can define global environment variables by adding them to your +`Homestead.yaml` file: + + + + 1variables: + + 2 - key: APP_ENV + + 3 value: local + + 4 - key: FOO + + 5 value: bar + + + variables: + - key: APP_ENV + value: local + - key: FOO + value: bar + +After updating the `Homestead.yaml` file, be sure to re-provision the machine +by executing the `vagrant reload --provision` command. This will update the +PHP-FPM configuration for all of the installed PHP versions and also update +the environment for the `vagrant` user. + +### Ports + +By default, the following ports are forwarded to your Homestead environment: + + * **HTTP:** 8000 → Forwards To 80 + * **HTTPS:** 44300 → Forwards To 443 + +#### Forwarding Additional Ports + +If you wish, you may forward additional ports to the Vagrant box by defining a +`ports` configuration entry within your `Homestead.yaml` file. After updating +the `Homestead.yaml` file, be sure to re-provision the machine by executing +the `vagrant reload --provision` command: + + + + 1ports: + + 2 - send: 50000 + + 3 to: 5000 + + 4 - send: 7777 + + 5 to: 777 + + 6 protocol: udp + + + ports: + - send: 50000 + to: 5000 + - send: 7777 + to: 777 + protocol: udp + +Below is a list of additional Homestead service ports that you may wish to map +from your host machine to your Vagrant box: + + * **SSH:** 2222 → To 22 + * **ngrok UI:** 4040 → To 4040 + * **MySQL:** 33060 → To 3306 + * **PostgreSQL:** 54320 → To 5432 + * **MongoDB:** 27017 → To 27017 + * **Mailpit:** 8025 → To 8025 + * **Minio:** 9600 → To 9600 + +### PHP Versions + +Homestead supports running multiple versions of PHP on the same virtual +machine. You may specify which version of PHP to use for a given site within +your `Homestead.yaml` file. The available PHP versions are: "5.6", "7.0", +"7.1", "7.2", "7.3", "7.4", "8.0", "8.1", "8.2", and "8.3", (the default): + + + + 1sites: + + 2 - map: homestead.test + + 3 to: /home/vagrant/project1/public + + 4 php: "7.1" + + + sites: + - map: homestead.test + to: /home/vagrant/project1/public + php: "7.1" + +Within your Homestead virtual machine, you may use any of the supported PHP +versions via the CLI: + + + + 1php5.6 artisan list + + 2php7.0 artisan list + + 3php7.1 artisan list + + 4php7.2 artisan list + + 5php7.3 artisan list + + 6php7.4 artisan list + + 7php8.0 artisan list + + 8php8.1 artisan list + + 9php8.2 artisan list + + 10php8.3 artisan list + + + php5.6 artisan list + php7.0 artisan list + php7.1 artisan list + php7.2 artisan list + php7.3 artisan list + php7.4 artisan list + php8.0 artisan list + php8.1 artisan list + php8.2 artisan list + php8.3 artisan list + +You may change the default version of PHP used by the CLI by issuing the +following commands from within your Homestead virtual machine: + + + + 1php56 + + 2php70 + + 3php71 + + 4php72 + + 5php73 + + 6php74 + + 7php80 + + 8php81 + + 9php82 + + 10php83 + + + php56 + php70 + php71 + php72 + php73 + php74 + php80 + php81 + php82 + php83 + +### Connecting to Databases + +A `homestead` database is configured for both MySQL and PostgreSQL out of the +box. To connect to your MySQL or PostgreSQL database from your host machine's +database client, you should connect to `127.0.0.1` on port `33060` (MySQL) or +`54320` (PostgreSQL). The username and password for both databases is +`homestead` / `secret`. + +You should only use these non-standard ports when connecting to the databases +from your host machine. You will use the default 3306 and 5432 ports in your +Laravel application's `database` configuration file since Laravel is running +_within_ the virtual machine. + +### Database Backups + +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: + + + + 1backup: true + + + 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 method. + +### Configuring Cron Schedules + +Laravel provides a convenient way to [schedule cron +jobs](/docs/12.x/scheduling) by scheduling a single `schedule:run` Artisan +command to run every minute. The `schedule:run` command will examine the job +schedule defined in your `routes/console.php` file to determine which +scheduled tasks to run. + +If you would like the `schedule:run` command to be run for a Homestead site, +you may set the `schedule` option to `true` when defining the site: + + + + 1sites: + + 2 - map: homestead.test + + 3 to: /home/vagrant/project1/public + + 4 schedule: true + + + sites: + - map: homestead.test + to: /home/vagrant/project1/public + schedule: true + +The cron job for the site will be defined in the `/etc/cron.d` directory of +the Homestead virtual machine. + +### Configuring Mailpit + +[Mailpit](https://github.com/axllent/mailpit) allows you to intercept your +outgoing email and examine it without actually sending the mail to its +recipients. To get started, update your application's `.env` file to use the +following mail settings: + + + + 1MAIL_MAILER=smtp + + 2MAIL_HOST=localhost + + 3MAIL_PORT=1025 + + 4MAIL_USERNAME=null + + 5MAIL_PASSWORD=null + + 6MAIL_ENCRYPTION=null + + + MAIL_MAILER=smtp + MAIL_HOST=localhost + MAIL_PORT=1025 + MAIL_USERNAME=null + MAIL_PASSWORD=null + MAIL_ENCRYPTION=null + +Once Mailpit has been configured, you may access the Mailpit dashboard at +`http://localhost:8025`. + +### Configuring Minio + +[Minio](https://github.com/minio/minio) is an open source object storage +server with an Amazon S3 compatible API. To install Minio, update your +`Homestead.yaml` file with the following configuration option in the features +section: + + + + 1minio: true + + + minio: true + +By default, Minio is available on port 9600. You may access the Minio control +panel by visiting `http://localhost:9600`. The default access key is +`homestead`, while the default secret key is `secretkey`. When accessing +Minio, you should always use region `us-east-1`. + +In order to use Minio, ensure your `.env` file has the following options: + + + + 1AWS_USE_PATH_STYLE_ENDPOINT=true + + 2AWS_ENDPOINT=http://localhost:9600 + + 3AWS_ACCESS_KEY_ID=homestead + + 4AWS_SECRET_ACCESS_KEY=secretkey + + 5AWS_DEFAULT_REGION=us-east-1 + + + AWS_USE_PATH_STYLE_ENDPOINT=true + AWS_ENDPOINT=http://localhost:9600 + AWS_ACCESS_KEY_ID=homestead + AWS_SECRET_ACCESS_KEY=secretkey + AWS_DEFAULT_REGION=us-east-1 + +To provision Minio powered "S3" buckets, add a `buckets` directive to your +`Homestead.yaml` file. After defining your buckets, you should execute the +`vagrant reload --provision` command in your terminal: + + + + 1buckets: + + 2 - name: your-bucket + + 3 policy: public + + 4 - name: your-private-bucket + + 5 policy: none + + + buckets: + - name: your-bucket + policy: public + - name: your-private-bucket + policy: none + +Supported `policy` values include: `none`, `download`, `upload`, and `public`. + +### Laravel Dusk + +In order to run [Laravel Dusk](/docs/12.x/dusk) tests within Homestead, you +should enable the webdriver feature in your Homestead configuration: + + + + 1features: + + 2 - webdriver: true + + + features: + - webdriver: true + +After enabling the `webdriver` feature, you should execute the `vagrant reload +--provision` command in your terminal. + +### Sharing Your Environment + +Sometimes you may wish to share what you're currently working on with +coworkers or a client. Vagrant has built-in support for this via the `vagrant +share` command; however, this will not work if you have multiple sites +configured in your `Homestead.yaml` file. + +To solve this problem, Homestead includes its own `share` command. To get +started, SSH into your Homestead virtual machine via `vagrant ssh` and execute +the `share homestead.test` command. This command will share the +`homestead.test` site from your `Homestead.yaml` configuration file. You may +substitute any of your other configured sites for `homestead.test`: + + + + 1share homestead.test + + + share homestead.test + +After running the command, you will see an Ngrok screen appear which contains +the activity log and the publicly accessible URLs for the shared site. If you +would like to specify a custom region, subdomain, or other Ngrok runtime +option, you may add them to your `share` command: + + + + 1share homestead.test -region=eu -subdomain=laravel + + + share homestead.test -region=eu -subdomain=laravel + +If you need to share content over HTTPS rather than HTTP, using the `sshare` +command instead of `share` will enable you to do so. + +Remember, Vagrant is inherently insecure and you are exposing your virtual +machine to the Internet when running the `share` command. + +## Debugging and Profiling + +### Debugging Web Requests With Xdebug + +Homestead includes support for step debugging using +[Xdebug](https://xdebug.org). For example, you can access a page in your +browser and PHP will connect to your IDE to allow inspection and modification +of the running code. + +By default, Xdebug is already running and ready to accept connections. If you +need to enable Xdebug on the CLI, execute the `sudo phpenmod xdebug` command +within your Homestead virtual machine. Next, follow your IDE's instructions to +enable debugging. Finally, configure your browser to trigger Xdebug with an +extension or [bookmarklet](https://www.jetbrains.com/phpstorm/marklets/). + +Xdebug causes PHP to run significantly slower. To disable Xdebug, run `sudo +phpdismod xdebug` within your Homestead virtual machine and restart the FPM +service. + +#### Autostarting Xdebug + +When debugging functional tests that make requests to the web server, it is +easier to autostart debugging rather than modifying tests to pass through a +custom header or cookie to trigger debugging. To force Xdebug to start +automatically, modify the `/etc/php/7.x/fpm/conf.d/20-xdebug.ini` file inside +your Homestead virtual machine and add the following configuration: + + + + 1; If Homestead.yaml contains a different subnet for the IP address, this address may be different... + + 2xdebug.client_host = 192.168.10.1 + + 3xdebug.mode = debug + + 4xdebug.start_with_request = yes + + + ; If Homestead.yaml contains a different subnet for the IP address, this address may be different... + xdebug.client_host = 192.168.10.1 + xdebug.mode = debug + xdebug.start_with_request = yes + +### Debugging CLI Applications + +To debug a PHP CLI application, use the `xphp` shell alias inside your +Homestead virtual machine: + + + + 1xphp /path/to/script + + + xphp /path/to/script + +### Profiling Applications With Blackfire + +[Blackfire](https://blackfire.io/docs/introduction) is a service for profiling +web requests and CLI applications. It offers an interactive user interface +which displays profile data in call-graphs and timelines. It is built for use +in development, staging, and production, with no overhead for end users. In +addition, Blackfire provides performance, quality, and security checks on code +and `php.ini` configuration settings. + +The [Blackfire Player](https://blackfire.io/docs/player/index) is an open- +source Web Crawling, Web Testing, and Web Scraping application which can work +jointly with Blackfire in order to script profiling scenarios. + +To enable Blackfire, use the "features" setting in your Homestead +configuration file: + + + + 1features: + + 2 - blackfire: + + 3 server_id: "server_id" + + 4 server_token: "server_value" + + 5 client_id: "client_id" + + 6 client_token: "client_value" + + + features: + - blackfire: + server_id: "server_id" + server_token: "server_value" + client_id: "client_id" + client_token: "client_value" + +Blackfire server credentials and client credentials [require a Blackfire +account](https://blackfire.io/signup). Blackfire offers various options to +profile an application, including a CLI tool and browser extension. Please +[review the Blackfire documentation for more +details](https://blackfire.io/docs/php/integrations/laravel/index). + +## Network Interfaces + +The `networks` property of the `Homestead.yaml` file configures network +interfaces for your Homestead virtual machine. You may configure as many +interfaces as necessary: + + + + 1networks: + + 2 - type: "private_network" + + 3 ip: "192.168.10.20" + + + networks: + - type: "private_network" + ip: "192.168.10.20" + +To enable a +[bridged](https://developer.hashicorp.com/vagrant/docs/networking/public_network) +interface, configure a `bridge` setting for the network and change the network +type to `public_network`: + + + + 1networks: + + 2 - type: "public_network" + + 3 ip: "192.168.10.20" + + 4 bridge: "en1: Wi-Fi (AirPort)" + + + networks: + - type: "public_network" + ip: "192.168.10.20" + bridge: "en1: Wi-Fi (AirPort)" + +To enable +[DHCP](https://developer.hashicorp.com/vagrant/docs/networking/public_network#dhcp), +just remove the `ip` option from your configuration: + + + + 1networks: + + 2 - type: "public_network" + + 3 bridge: "en1: Wi-Fi (AirPort)" + + + networks: + - type: "public_network" + bridge: "en1: Wi-Fi (AirPort)" + +To update what device the network is using, you may add a `dev` option to the +network's configuration. The default `dev` value is `eth0`: + + + + 1networks: + + 2 - type: "public_network" + + 3 ip: "192.168.10.20" + + 4 bridge: "en1: Wi-Fi (AirPort)" + + 5 dev: "enp2s0" + + + networks: + - type: "public_network" + ip: "192.168.10.20" + bridge: "en1: Wi-Fi (AirPort)" + dev: "enp2s0" + +## Extending Homestead + +You may extend Homestead using the `after.sh` script in the root of your +Homestead directory. Within this file, you may add any shell commands that are +necessary to properly configure and customize your virtual machine. + +When customizing Homestead, Ubuntu may ask you if you would like to keep a +package's original configuration or overwrite it with a new configuration +file. To avoid this, you should use the following command when installing +packages in order to avoid overwriting any configuration previously written by +Homestead: + + + + 1sudo apt-get -y \ + + 2 -o Dpkg::Options::="--force-confdef" \ + + 3 -o Dpkg::Options::="--force-confold" \ + + 4 install package-name + + + sudo apt-get -y \ + -o Dpkg::Options::="--force-confdef" \ + -o Dpkg::Options::="--force-confold" \ + install package-name + +### User Customizations + +When using Homestead with your team, you may want to tweak Homestead to better +fit your personal development style. To accomplish this, you may create a +`user-customizations.sh` file in the root of your Homestead directory (the +same directory containing your `Homestead.yaml` file). Within this file, you +may make any customization you would like; however, the `user- +customizations.sh` should not be version controlled. + +## Provider Specific Settings + +### VirtualBox + +#### `natdnshostresolver` + +By default, Homestead configures the `natdnshostresolver` setting to `on`. +This allows Homestead to use your host operating system's DNS settings. If you +would like to override this behavior, add the following configuration options +to your `Homestead.yaml` file: + + + + 1provider: virtualbox + + 2natdnshostresolver: 'off' + + + provider: virtualbox + natdnshostresolver: 'off' + diff --git a/output/12.x/horizon.md b/output/12.x/horizon.md new file mode 100644 index 0000000..1689e6c --- /dev/null +++ b/output/12.x/horizon.md @@ -0,0 +1,1424 @@ +# Laravel Horizon + + * Introduction + * Installation + * Configuration + * Dashboard Authorization + * Max Job Attempts + * Job Timeout + * Job Backoff + * Silenced Jobs + * Balancing Strategies + * Auto Balancing + * Simple Balancing + * No Balancing + * Upgrading Horizon + * Running Horizon + * Deploying Horizon + * Tags + * Notifications + * Metrics + * Deleting Failed Jobs + * Clearing Jobs From Queues + +## Introduction + +Before digging into Laravel Horizon, you should familiarize yourself with +Laravel's base [queue services](/docs/12.x/queues). Horizon augments Laravel's +queue with additional features that may be confusing if you are not already +familiar with the basic queue features offered by Laravel. + +[Laravel Horizon](https://github.com/laravel/horizon) provides a beautiful +dashboard and code-driven configuration for your Laravel powered [Redis +queues](/docs/12.x/queues). Horizon allows you to easily monitor key metrics +of your queue system such as job throughput, runtime, and job failures. + +When using Horizon, all of your queue worker configuration is stored in a +single, simple configuration file. By defining your application's worker +configuration in a version controlled file, you may easily scale or modify +your application's queue workers when deploying your application. + +![](https://laravel.com/img/docs/horizon-example.png) + +## Installation + +Laravel Horizon requires that you use [Redis](https://redis.io) to power your +queue. Therefore, you should ensure that your queue connection is set to +`redis` in your application's `config/queue.php` configuration file. + +You may install Horizon into your project using the Composer package manager: + + + + 1composer require laravel/horizon + + + composer require laravel/horizon + +After installing Horizon, publish its assets using the `horizon:install` +Artisan command: + + + + 1php artisan horizon:install + + + php artisan horizon:install + +### Configuration + +After publishing Horizon's assets, its primary configuration file will be +located at `config/horizon.php`. This configuration file allows you to +configure the queue worker options for your application. Each configuration +option includes a description of its purpose, so be sure to thoroughly explore +this file. + +Horizon uses a Redis connection named `horizon` internally. This Redis +connection name is reserved and should not be assigned to another Redis +connection in the `database.php` configuration file or as the value of the +`use` option in the `horizon.php` configuration file. + +#### Environments + +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: + + + + 1'environments' => [ + + 2 'production' => [ + + 3 'supervisor-1' => [ + + 4 'maxProcesses' => 10, + + 5 'balanceMaxShift' => 1, + + 6 'balanceCooldown' => 3, + + 7 ], + + 8 ], + + 9  + + 10 'local' => [ + + 11 'supervisor-1' => [ + + 12 'maxProcesses' => 3, + + 13 ], + + 14 ], + + 15], + + + 'environments' => [ + 'production' => [ + 'supervisor-1' => [ + 'maxProcesses' => 10, + 'balanceMaxShift' => 1, + 'balanceCooldown' => 3, + ], + ], + + 'local' => [ + 'supervisor-1' => [ + 'maxProcesses' => 3, + ], + ], + ], + +You may also define a wildcard environment (`*`) which will be used when no +other matching environment is found: + + + + 1'environments' => [ + + 2 // ... + + 3  + + 4 '*' => [ + + 5 'supervisor-1' => [ + + 6 'maxProcesses' => 3, + + 7 ], + + 8 ], + + 9], + + + 'environments' => [ + // ... + + '*' => [ + '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/12.x/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. + +You should ensure that the `environments` portion of your `horizon` +configuration file contains an entry for each +[environment](/docs/12.x/configuration#environment-configuration) on which you +plan to run Horizon. + +#### Supervisors + +As you can see in Horizon's default configuration file, each environment can +contain one or more "supervisors". By default, the configuration file defines +this supervisor as `supervisor-1`; however, you are free to name your +supervisors whatever you want. Each supervisor is essentially responsible for +"supervising" a group of worker processes and takes care of balancing worker +processes across queues. + +You may add additional supervisors to a given environment if you would like to +define a new group of worker processes that should run in that environment. +You may choose to do this if you would like to define a different balancing +strategy or worker process count for a given queue used by your application. + +#### Maintenance Mode + +While your application is in [maintenance +mode](/docs/12.x/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: + + + + 1'environments' => [ + + 2 'production' => [ + + 3 'supervisor-1' => [ + + 4 // ... + + 5 'force' => true, + + 6 ], + + 7 ], + + 8], + + + 'environments' => [ + 'production' => [ + 'supervisor-1' => [ + // ... + 'force' => true, + ], + ], + ], + +#### Default Values + +Within Horizon's default configuration file, you will notice a `defaults` +configuration option. This configuration option specifies the default values +for your application's supervisors. The supervisor's default configuration +values will be merged into the supervisor's configuration for each +environment, allowing you to avoid unnecessary repetition when defining your +supervisors. + +### Dashboard Authorization + +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/12.x/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: + + + + 1/** + + 2 * Register the Horizon gate. + + 3 * + + 4 * This gate determines who can access Horizon in non-local environments. + + 5 */ + + 6protected function gate(): void + + 7{ + + 8 Gate::define('viewHorizon', function (User $user) { + + 9 return in_array($user->email, [ + + 10 '[[email protected]](/cdn-cgi/l/email-protection)', + + 11 ]); + + 12 }); + + 13} + + + /** + * 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, [ + '[[email protected]](/cdn-cgi/l/email-protection)', + ]); + }); + } + +#### Alternative Authentication Strategies + +Remember that Laravel automatically injects the authenticated user into the +gate closure. If your application is providing Horizon security via another +method, such as IP restrictions, then your Horizon users may not need to +"login". Therefore, you will need to change `function (User $user)` closure +signature above to `function (User $user = null)` in order to force Laravel to +not require authentication. + +### Max Job Attempts + +Before refining these options, make sure you are familiar with Laravel's +default [queue services](/docs/12.x/queues#max-job-attempts-and-timeout) and +the concept of 'attempts'. + +You can define the maximum number of attempts a job can consume within a +supervisor's configuration: + + + + 1'environments' => [ + + 2 'production' => [ + + 3 'supervisor-1' => [ + + 4 // ... + + 5 'tries' => 10, + + 6 ], + + 7 ], + + 8], + + + 'environments' => [ + 'production' => [ + 'supervisor-1' => [ + // ... + 'tries' => 10, + ], + ], + ], + +This option is similar to the `--tries` option when using the Artisan command +to process queues. + +Adjusting the `tries` option is essential when using middlewares such as +`WithoutOverlapping` or `RateLimited` because they consume attempts. To handle +this, adjust the `tries` configuration value either at the supervisor level or +by defining the `$tries` property on the job class. + +If you don't set the `tries` option, Horizon defaults to a single attempt, +unless the job class defines `$tries`, which takes precedence over the Horizon +configuration. + +Setting `tries` or `$tries` to 0 allows unlimited attempts, which is ideal +when the number of attempts is uncertain. To prevent endless failures, you can +limit the number of exceptions allowed by setting the `$maxExceptions` +property on the job class. + +### Job Timeout + +Similarly, you can set a `timeout` value at the supervisor level, which +specifies how many seconds a worker process can run a job before it's +forcefully terminated. Once terminated, the job will either be retried or +marked as failed, depending on your queue configuration: + + + + 1'environments' => [ + + 2 'production' => [ + + 3 'supervisor-1' => [ + + 4 // ...¨ + + 5 'timeout' => 60, + + 6 ], + + 7 ], + + 8], + + + 'environments' => [ + 'production' => [ + 'supervisor-1' => [ + // ...¨ + 'timeout' => 60, + ], + ], + ], + +The `timeout` value should always be at least a few seconds shorter than the +`retry_after` value defined in your `config/queue.php` configuration file. +Otherwise, your jobs may be processed twice. + +### Job Backoff + +You can define the `backoff` value at the supervisor level to specify how long +Horizon should wait before retrying a job that encounters an unhandled +exception: + + + + 1'environments' => [ + + 2 'production' => [ + + 3 'supervisor-1' => [ + + 4 // ... + + 5 'backoff' => 10, + + 6 ], + + 7 ], + + 8], + + + 'environments' => [ + 'production' => [ + 'supervisor-1' => [ + // ... + 'backoff' => 10, + ], + ], + ], + +You may also configure "exponential" backoffs by using an array for the +`backoff` value. 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: + + + + 1'environments' => [ + + 2 'production' => [ + + 3 'supervisor-1' => [ + + 4 // ... + + 5 'backoff' => [1, 5, 10], + + 6 ], + + 7 ], + + 8], + + + 'environments' => [ + 'production' => [ + 'supervisor-1' => [ + // ... + 'backoff' => [1, 5, 10], + ], + ], + ], + +### Silenced Jobs + +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: + + + + 1'silenced' => [ + + 2 App\Jobs\ProcessPodcast::class, + + 3], + + + '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: + + + + 1use Laravel\Horizon\Contracts\Silenced; + + 2  + + 3class ProcessPodcast implements ShouldQueue, Silenced + + 4{ + + 5 use Queueable; + + 6  + + 7 // ... + + 8} + + + use Laravel\Horizon\Contracts\Silenced; + + class ProcessPodcast implements ShouldQueue, Silenced + { + use Queueable; + + // ... + } + +## Balancing Strategies + +Each supervisor can process one or more queues but unlike Laravel's default +queue system, Horizon allows you to choose from three worker balancing +strategies: `auto`, `simple`, and `false`. + +### Auto Balancing + +The `auto` strategy, which is the default strategy, adjusts the number of +worker processes per queue based on the current workload of the queue. For +example, if your `notifications` queue has 1,000 pending jobs while your +`default` queue is empty, Horizon will allocate more workers to your +`notifications` queue until the queue is empty. + +When using the `auto` strategy, you may also configure the `minProcesses` and +`maxProcesses` configuration options: + + * `minProcesses` defines the minimum number of worker processes per queue. This value must be greater than or equal to 1. + * `maxProcesses` defines the maximum total number of worker processes Horizon may scale up to across all queues. This value should typically be greater than the number of queues multiplied by the `minProcesses` value. To prevent the supervisor from spawning any processes, you may set this value to 0. + +For example, you may configure Horizon to maintain at least one process per +queue and scale up to a total of 10 worker processes: + + + + 1'environments' => [ + + 2 'production' => [ + + 3 'supervisor-1' => [ + + 4 'connection' => 'redis', + + 5 'queue' => ['default', 'notifications'], + + 6 'balance' => 'auto', + + 7 'autoScalingStrategy' => 'time', + + 8 'minProcesses' => 1, + + 9 'maxProcesses' => 10, + + 10 'balanceMaxShift' => 1, + + 11 'balanceCooldown' => 3, + + 12 ], + + 13 ], + + 14], + + + 'environments' => [ + 'production' => [ + 'supervisor-1' => [ + 'connection' => 'redis', + 'queue' => ['default', 'notifications'], + 'balance' => 'auto', + 'autoScalingStrategy' => 'time', + 'minProcesses' => 1, + 'maxProcesses' => 10, + 'balanceMaxShift' => 1, + 'balanceCooldown' => 3, + ], + ], + ], + +The `autoScalingStrategy` configuration option determines how Horizon will +assign more worker processes to queues. You can choose between two strategies: + + * The `time` strategy will assign workers based on the total estimated amount of time it will take to clear the queue. + * The `size` strategy will assign workers based on the total number of jobs on the queue. + +The `balanceMaxShift` and `balanceCooldown` configuration values determine how +quickly Horizon will scale to meet worker demand. In the example above, a +maximum of one new process will be created or destroyed every three seconds. +You are free to tweak these values as necessary based on your application's +needs. + +#### Queue Priorities and Auto Balancing + +When using the `auto` balancing strategy, Horizon does not enforce strict +priority between queues. The order of queues in a supervisor's configuration +does not affect how worker processes are assigned. Instead, Horizon relies on +the selected `autoScalingStrategy` to dynamically allocate worker processes +based on queue load. + +For example, in the following configuration, the high queue is not prioritized +over the default queue, despite appearing first in the list: + + + + 1'environments' => [ + + 2 'production' => [ + + 3 'supervisor-1' => [ + + 4 // ... + + 5 'queue' => ['high', 'default'], + + 6 'minProcesses' => 1, + + 7 'maxProcesses' => 10, + + 8 ], + + 9 ], + + 10], + + + 'environments' => [ + 'production' => [ + 'supervisor-1' => [ + // ... + 'queue' => ['high', 'default'], + 'minProcesses' => 1, + 'maxProcesses' => 10, + ], + ], + ], + +If you need to enforce a relative priority between queues, you may define +multiple supervisors and explicitly allocate processing resources: + + + + 1'environments' => [ + + 2 'production' => [ + + 3 'supervisor-1' => [ + + 4 // ... + + 5 'queue' => ['default'], + + 6 'minProcesses' => 1, + + 7 'maxProcesses' => 10, + + 8 ], + + 9 'supervisor-2' => [ + + 10 // ... + + 11 'queue' => ['images'], + + 12 'minProcesses' => 1, + + 13 'maxProcesses' => 1, + + 14 ], + + 15 ], + + 16], + + + 'environments' => [ + 'production' => [ + 'supervisor-1' => [ + // ... + 'queue' => ['default'], + 'minProcesses' => 1, + 'maxProcesses' => 10, + ], + 'supervisor-2' => [ + // ... + 'queue' => ['images'], + 'minProcesses' => 1, + 'maxProcesses' => 1, + ], + ], + ], + +In this example, the default `queue` can scale up to 10 processes, while the +`images` queue is limited to one process. This configuration ensures that your +queues can scale independently. + +When dispatching resource-intensive jobs, it's sometimes best to assign them +to a dedicated queue with a limited `maxProcesses` value. Otherwise, these +jobs could consume excessive CPU resources and overload your system. + +### Simple Balancing + +The `simple` strategy distributes worker processes evenly across the specified +queues. With this strategy, Horizon does not automatically scale the number of +worker processes. Rather, it uses a fixed number of processes: + + + + 1'environments' => [ + + 2 'production' => [ + + 3 'supervisor-1' => [ + + 4 // ... + + 5 'queue' => ['default', 'notifications'], + + 6 'balance' => 'simple', + + 7 'processes' => 10, + + 8 ], + + 9 ], + + 10], + + + 'environments' => [ + 'production' => [ + 'supervisor-1' => [ + // ... + 'queue' => ['default', 'notifications'], + 'balance' => 'simple', + 'processes' => 10, + ], + ], + ], + +In the example above, Horizon will assign 5 processes to each queue, splitting +the total of 10 evenly. + +If you'd like to control the number of worker processes assigned to each queue +individually, you can define multiple supervisors: + + + + 1'environments' => [ + + 2 'production' => [ + + 3 'supervisor-1' => [ + + 4 // ... + + 5 'queue' => ['default'], + + 6 'balance' => 'simple', + + 7 'processes' => 10, + + 8 ], + + 9 'supervisor-notifications' => [ + + 10 // ... + + 11 'queue' => ['notifications'], + + 12 'balance' => 'simple', + + 13 'processes' => 2, + + 14 ], + + 15 ], + + 16], + + + 'environments' => [ + 'production' => [ + 'supervisor-1' => [ + // ... + 'queue' => ['default'], + 'balance' => 'simple', + 'processes' => 10, + ], + 'supervisor-notifications' => [ + // ... + 'queue' => ['notifications'], + 'balance' => 'simple', + 'processes' => 2, + ], + ], + ], + +With this configuration, Horizon will assign 10 processes to the `default` +queue and 2 processes to the `notifications` queue. + +### No Balancing + +When the `balance` option is set to `false`, Horizon processes queues strictly +in the order they're listed, similar to Laravel's default queue system. +However, it will still scale the number of worker processes if jobs begin to +accumulate: + + + + 1'environments' => [ + + 2 'production' => [ + + 3 'supervisor-1' => [ + + 4 // ... + + 5 'queue' => ['default', 'notifications'], + + 6 'balance' => false, + + 7 'minProcesses' => 1, + + 8 'maxProcesses' => 10, + + 9 ], + + 10 ], + + 11], + + + 'environments' => [ + 'production' => [ + 'supervisor-1' => [ + // ... + 'queue' => ['default', 'notifications'], + 'balance' => false, + 'minProcesses' => 1, + 'maxProcesses' => 10, + ], + ], + ], + +In the example above, jobs in the `default` queue are always prioritized over +jobs in the `notifications` queue. For instance, if there are 1,000 jobs in +`default` and only 10 in `notifications`, Horizon will fully process all +`default` jobs before handling any from `notifications`. + +You can control Horizon's ability to scale worker processes using the +`minProcesses` and `maxProcesses` options: + + * `minProcesses` defines the minimum number of worker processes in total. This value must be greater than or equal to 1. + * `maxProcesses` defines the maximum total number of worker processes Horizon may scale up to. + +## Upgrading Horizon + +When upgrading to a new major version of Horizon, it's important that you +carefully review [the upgrade +guide](https://github.com/laravel/horizon/blob/master/UPGRADE.md). + +## Running Horizon + +Once you have configured your supervisors and workers in your application's +`config/horizon.php` configuration file, you may start Horizon using the +`horizon` Artisan command. This single command will start all of the +configured worker processes for the current environment: + + + + 1php artisan horizon + + + php artisan horizon + +You may pause the Horizon process and instruct it to continue processing jobs +using the `horizon:pause` and `horizon:continue` Artisan commands: + + + + 1php artisan horizon:pause + + 2  + + 3php artisan horizon:continue + + + php artisan horizon:pause + + php artisan horizon:continue + +You may also pause and continue specific Horizon supervisors using the +`horizon:pause-supervisor` and `horizon:continue-supervisor` Artisan commands: + + + + 1php artisan horizon:pause-supervisor supervisor-1 + + 2  + + 3php artisan horizon:continue-supervisor supervisor-1 + + + php artisan horizon:pause-supervisor supervisor-1 + + php artisan horizon:continue-supervisor supervisor-1 + +You may check the current status of the Horizon process using the +`horizon:status` Artisan command: + + + + 1php artisan horizon:status + + + php artisan horizon:status + +You may check the current status of a specific Horizon supervisor using the +`horizon:supervisor-status` Artisan command: + + + + 1php artisan horizon:supervisor-status supervisor-1 + + + php artisan horizon:supervisor-status supervisor-1 + +You may gracefully terminate the Horizon process using the `horizon:terminate` +Artisan command. Any jobs that are currently being processed will be completed +and then Horizon will stop executing: + + + + 1php artisan horizon:terminate + + + php artisan horizon:terminate + +### Deploying Horizon + +When you're ready to deploy Horizon to your application's actual server, you +should configure a process monitor to monitor the `php artisan horizon` +command and restart it if it exits unexpectedly. Don't worry, we'll discuss +how to install a process monitor below. + +During your application's deployment process, you should instruct the Horizon +process to terminate so that it will be restarted by your process monitor and +receive your code changes: + + + + 1php artisan horizon:terminate + + + php artisan horizon:terminate + +#### Installing Supervisor + +Supervisor is a process monitor for the Linux operating system and will +automatically restart your `horizon` process if it stops executing. To install +Supervisor on Ubuntu, you may use the following command. If you are not using +Ubuntu, you can likely install Supervisor using your operating system's +package manager: + + + + 1sudo apt-get install supervisor + + + sudo apt-get install supervisor + +If configuring Supervisor yourself sounds overwhelming, consider using +[Laravel Cloud](https://cloud.laravel.com), which can manage background +processes for your Laravel applications. + +#### Supervisor Configuration + +Supervisor configuration files are typically stored within your server's +`/etc/supervisor/conf.d` directory. Within this directory, you may create any +number of configuration files that instruct supervisor how your processes +should be monitored. For example, let's create a `horizon.conf` file that +starts and monitors a `horizon` process: + + + + 1[program:horizon] + + 2process_name=%(program_name)s + + 3command=php /home/forge/example.com/artisan horizon + + 4autostart=true + + 5autorestart=true + + 6user=forge + + 7redirect_stderr=true + + 8stdout_logfile=/home/forge/example.com/horizon.log + + 9stopwaitsecs=3600 + + + [program:horizon] + process_name=%(program_name)s + command=php /home/forge/example.com/artisan horizon + autostart=true + autorestart=true + user=forge + redirect_stderr=true + stdout_logfile=/home/forge/example.com/horizon.log + stopwaitsecs=3600 + +When defining your Supervisor configuration, you should ensure that the value +of `stopwaitsecs` is greater than the number of seconds consumed by your +longest running job. Otherwise, Supervisor may kill the job before it is +finished processing. + +While the examples above are valid for Ubuntu based servers, the location and +file extension expected of Supervisor configuration files may vary between +other server operating systems. Please consult your server's documentation for +more information. + +#### Starting Supervisor + +Once the configuration file has been created, you may update the Supervisor +configuration and start the monitored processes using the following commands: + + + + 1sudo supervisorctl reread + + 2  + + 3sudo supervisorctl update + + 4  + + 5sudo supervisorctl start horizon + + + sudo supervisorctl reread + + sudo supervisorctl update + + sudo supervisorctl start horizon + +For more information on running Supervisor, consult the [Supervisor +documentation](http://supervisord.org/index.html). + +## Tags + +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: + + + + 1 + + 7 */ + + 8 public function tags(): array + + 9 { + + 10 return ['render', 'video:'.$this->video->id]; + + 11 } + + 12} + + + class RenderVideo implements ShouldQueue + { + /** + * Get the tags that should be assigned to the job. + * + * @return array + */ + public function tags(): array + { + 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: + + + + 1class SendRenderNotifications implements ShouldQueue + + 2{ + + 3 /** + + 4 * Get the tags that should be assigned to the listener. + + 5 * + + 6 * @return array + + 7 */ + + 8 public function tags(VideoRendered $event): array + + 9 { + + 10 return ['video:'.$event->video->id]; + + 11 } + + 12} + + + class SendRenderNotifications implements ShouldQueue + { + /** + * Get the tags that should be assigned to the listener. + * + * @return array + */ + public function tags(VideoRendered $event): array + { + return ['video:'.$event->video->id]; + } + } + +## Notifications + +When configuring Horizon to send Slack or SMS notifications, you should review +the [prerequisites for the relevant notification +channel](/docs/12.x/notifications). + +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`: + + + + 1/** + + 2 * Bootstrap any application services. + + 3 */ + + 4public function boot(): void + + 5{ + + 6 parent::boot(); + + 7  + + 8 Horizon::routeSmsNotificationsTo('15556667777'); + + 9 Horizon::routeMailNotificationsTo('[[email protected]](/cdn-cgi/l/email-protection)'); + + 10 Horizon::routeSlackNotificationsTo('slack-webhook-url', '#channel'); + + 11} + + + /** + * Bootstrap any application services. + */ + public function boot(): void + { + parent::boot(); + + Horizon::routeSmsNotificationsTo('15556667777'); + Horizon::routeMailNotificationsTo('[[email protected]](/cdn-cgi/l/email-protection)'); + 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: + + + + 1'waits' => [ + + 2 'redis:critical' => 30, + + 3 'redis:default' => 60, + + 4 'redis:batch' => 120, + + 5], + + + '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: + + + + 1use Illuminate\Support\Facades\Schedule; + + 2  + + 3Schedule::command('horizon:snapshot')->everyFiveMinutes(); + + + use Illuminate\Support\Facades\Schedule; + + Schedule::command('horizon:snapshot')->everyFiveMinutes(); + +If you would like to delete all metric data, you can invoke the +`horizon:clear-metrics` Artisan command: + + + + 1php artisan horizon:clear-metrics + + + php artisan horizon:clear-metrics + +## Deleting Failed Jobs + +If you would like to delete a failed job, you may use the `horizon:forget` +command. The `horizon:forget` command accepts the ID or UUID of the failed job +as its only argument: + + + + 1php artisan horizon:forget 5 + + + php artisan horizon:forget 5 + +If you would like to delete all failed jobs, you may provide the `--all` +option to the `horizon:forget` command: + + + + 1php artisan horizon:forget --all + + + php artisan horizon:forget --all + +## Clearing Jobs From Queues + +If you would like to delete all jobs from your application's default queue, +you may do so using the `horizon:clear` Artisan command: + + + + 1php artisan horizon:clear + + + php artisan horizon:clear + +You may provide the `queue` option to delete jobs from a specific queue: + + + + 1php artisan horizon:clear --queue=emails + + + php artisan horizon:clear --queue=emails + diff --git a/output/12.x/http-client.md b/output/12.x/http-client.md new file mode 100644 index 0000000..4df9d2a --- /dev/null +++ b/output/12.x/http-client.md @@ -0,0 +1,1896 @@ +# HTTP Client + + * Introduction + * Making Requests + * Request Data + * Headers + * Authentication + * Timeout + * Retries + * Error Handling + * Guzzle Middleware + * Guzzle Options + * Concurrent Requests + * Macros + * Testing + * Faking Responses + * Inspecting Requests + * Preventing Stray Requests + * Events + +## Introduction + +Laravel provides an expressive, minimal API around the [Guzzle HTTP +client](http://docs.guzzlephp.org/en/stable/), allowing you to quickly make +outgoing HTTP requests to communicate with other web applications. Laravel's +wrapper around Guzzle is focused on its most common use cases and a wonderful +developer experience. + +## Making Requests + +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: + + + + 1use Illuminate\Support\Facades\Http; + + 2  + + 3$response = Http::get('http://example.com'); + + + use Illuminate\Support\Facades\Http; + + $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: + + + + 1$response->body() : string; + + 2$response->json($key = null, $default = null) : mixed; + + 3$response->object() : object; + + 4$response->collect($key = null) : Illuminate\Support\Collection; + + 5$response->resource() : resource; + + 6$response->status() : int; + + 7$response->successful() : bool; + + 8$response->redirect(): bool; + + 9$response->failed() : bool; + + 10$response->clientError() : bool; + + 11$response->header($header) : string; + + 12$response->headers() : array; + + + $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: + + + + 1return Http::get('http://example.com/users/1')['name']; + + + 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 specific status code: + + + + 1$response->ok() : bool; // 200 OK + + 2$response->created() : bool; // 201 Created + + 3$response->accepted() : bool; // 202 Accepted + + 4$response->noContent() : bool; // 204 No Content + + 5$response->movedPermanently() : bool; // 301 Moved Permanently + + 6$response->found() : bool; // 302 Found + + 7$response->badRequest() : bool; // 400 Bad Request + + 8$response->unauthorized() : bool; // 401 Unauthorized + + 9$response->paymentRequired() : bool; // 402 Payment Required + + 10$response->forbidden() : bool; // 403 Forbidden + + 11$response->notFound() : bool; // 404 Not Found + + 12$response->requestTimeout() : bool; // 408 Request Timeout + + 13$response->conflict() : bool; // 409 Conflict + + 14$response->unprocessableEntity() : bool; // 422 Unprocessable Entity + + 15$response->tooManyRequests() : bool; // 429 Too Many Requests + + 16$response->serverError() : bool; // 500 Internal Server Error + + + $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 + +The HTTP client also allows you to construct request URLs using the [URI +template specification](https://www.rfc-editor.org/rfc/rfc6570). To define the +URL parameters that can be expanded by your URI template, you may use the +`withUrlParameters` method: + + + + 1Http::withUrlParameters([ + + 2 'endpoint' => 'https://laravel.com', + + 3 'page' => 'docs', + + 4 'version' => '12.x', + + 5 'topic' => 'validation', + + 6])->get('{+endpoint}/{page}/{version}/{topic}'); + + + Http::withUrlParameters([ + 'endpoint' => 'https://laravel.com', + 'page' => 'docs', + 'version' => '12.x', + 'topic' => 'validation', + ])->get('{+endpoint}/{page}/{version}/{topic}'); + +#### Dumping Requests + +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: + + + + 1return Http::dd()->get('http://example.com'); + + + 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: + + + + 1use Illuminate\Support\Facades\Http; + + 2  + + 3$response = Http::post('http://example.com/users', [ + + 4 'name' => 'Steve', + + 5 'role' => 'Network Administrator', + + 6]); + + + use Illuminate\Support\Facades\Http; + + $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: + + + + 1$response = Http::get('http://example.com/users', [ + + 2 'name' => 'Taylor', + + 3 'page' => 1, + + 4]); + + + $response = Http::get('http://example.com/users', [ + 'name' => 'Taylor', + 'page' => 1, + ]); + +Alternatively, the `withQueryParameters` method may be used: + + + + 1Http::retry(3, 100)->withQueryParameters([ + + 2 'name' => 'Taylor', + + 3 'page' => 1, + + 4])->get('http://example.com/users'); + + + 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: + + + + 1$response = Http::asForm()->post('http://example.com/users', [ + + 2 'name' => 'Sara', + + 3 'role' => 'Privacy Consultant', + + 4]); + + + $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: + + + + 1$response = Http::withBody( + + 2 base64_encode($photo), 'image/jpeg' + + 3)->post('http://example.com/photo'); + + + $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: + + + + 1$response = Http::attach( + + 2 'attachment', file_get_contents('photo.jpg'), 'photo.jpg', ['Content-Type' => 'image/jpeg'] + + 3)->post('http://example.com/attachments'); + + + $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: + + + + 1$photo = fopen('photo.jpg', 'r'); + + 2  + + 3$response = Http::attach( + + 4 'attachment', $photo, 'photo.jpg' + + 5)->post('http://example.com/attachments'); + + + $photo = fopen('photo.jpg', 'r'); + + $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: + + + + 1$response = Http::withHeaders([ + + 2 'X-First' => 'foo', + + 3 'X-Second' => 'bar' + + 4])->post('http://example.com/users', [ + + 5 'name' => 'Taylor', + + 6]); + + + $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: + + + + 1$response = Http::accept('application/json')->get('http://example.com/users'); + + + $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: + + + + 1$response = Http::acceptJson()->get('http://example.com/users'); + + + $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: + + + + 1$response = Http::withHeaders([ + + 2 'X-Original' => 'foo', + + 3])->replaceHeaders([ + + 4 'X-Replacement' => 'bar', + + 5])->post('http://example.com/users', [ + + 6 'name' => 'Taylor', + + 7]); + + + $response = Http::withHeaders([ + 'X-Original' => 'foo', + ])->replaceHeaders([ + 'X-Replacement' => 'bar', + ])->post('http://example.com/users', [ + 'name' => 'Taylor', + ]); + +### Authentication + +You may specify basic and digest authentication credentials using the +`withBasicAuth` and `withDigestAuth` methods, respectively: + + + + 1// Basic authentication... + + 2$response = Http::withBasicAuth('[[email protected]](/cdn-cgi/l/email-protection)', 'secret')->post(/* ... */); + + 3  + + 4// Digest authentication... + + 5$response = Http::withDigestAuth('[[email protected]](/cdn-cgi/l/email-protection)', 'secret')->post(/* ... */); + + + // Basic authentication... + $response = Http::withBasicAuth('[[email protected]](/cdn-cgi/l/email-protection)', 'secret')->post(/* ... */); + + // Digest authentication... + $response = Http::withDigestAuth('[[email protected]](/cdn-cgi/l/email-protection)', '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: + + + + 1$response = Http::withToken('token')->post(/* ... */); + + + $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: + + + + 1$response = Http::timeout(3)->get(/* ... */); + + + $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. The default is 10 seconds: + + + + 1$response = Http::connectTimeout(3)->get(/* ... */); + + + $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: + + + + 1$response = Http::retry(3, 100)->post(/* ... */); + + + $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: + + + + 1use Exception; + + 2  + + 3$response = Http::retry(3, function (int $attempt, Exception $exception) { + + 4 return $attempt * 100; + + 5})->post(/* ... */); + + + use Exception; + + $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: + + + + 1$response = Http::retry([100, 200])->post(/* ... */); + + + $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`: + + + + 1use Exception; + + 2use Illuminate\Http\Client\PendingRequest; + + 3  + + 4$response = Http::retry(3, 100, function (Exception $exception, PendingRequest $request) { + + 5 return $exception instanceof ConnectionException; + + 6})->post(/* ... */); + + + use Exception; + use Illuminate\Http\Client\PendingRequest; + + $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: + + + + 1use Exception; + + 2use Illuminate\Http\Client\PendingRequest; + + 3use Illuminate\Http\Client\RequestException; + + 4  + + 5$response = Http::withToken($this->getToken())->retry(2, 0, function (Exception $exception, PendingRequest $request) { + + 6 if (! $exception instanceof RequestException || $exception->response->status() !== 401) { + + 7 return false; + + 8 } + + 9  + + 10 $request->withToken($this->getNewToken()); + + 11  + + 12 return true; + + 13})->post(/* ... */); + + + 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; + } + + $request->withToken($this->getNewToken()); + + 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: + + + + 1$response = Http::retry(3, 100, throw: false)->post(/* ... */); + + + $response = Http::retry(3, 100, throw: false)->post(/* ... */); + +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`. + +### Error Handling + +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: + + + + 1// Determine if the status code is >= 200 and < 300... + + 2$response->successful(); + + 3  + + 4// Determine if the status code is >= 400... + + 5$response->failed(); + + 6  + + 7// Determine if the response has a 400 level status code... + + 8$response->clientError(); + + 9  + + 10// Determine if the response has a 500 level status code... + + 11$response->serverError(); + + 12  + + 13// Immediately execute the given callback if there was a client or server error... + + 14$response->onError(callable $callback); + + + // Determine if the status code is >= 200 and < 300... + $response->successful(); + + // 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 500 level status code... + $response->serverError(); + + // 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: + + + + 1use Illuminate\Http\Client\Response; + + 2  + + 3$response = Http::post(/* ... */); + + 4  + + 5// Throw an exception if a client or server error occurred... + + 6$response->throw(); + + 7  + + 8// Throw an exception if an error occurred and the given condition is true... + + 9$response->throwIf($condition); + + 10  + + 11// Throw an exception if an error occurred and the given closure resolves to true... + + 12$response->throwIf(fn (Response $response) => true); + + 13  + + 14// Throw an exception if an error occurred and the given condition is false... + + 15$response->throwUnless($condition); + + 16  + + 17// Throw an exception if an error occurred and the given closure resolves to false... + + 18$response->throwUnless(fn (Response $response) => false); + + 19  + + 20// Throw an exception if the response has a specific status code... + + 21$response->throwIfStatus(403); + + 22  + + 23// Throw an exception unless the response has a specific status code... + + 24$response->throwUnlessStatus(200); + + 25  + + 26return $response['user']['id']; + + + use Illuminate\Http\Client\Response; + + $response = Http::post(/* ... */); + + // 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 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 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 unless the response has a specific status code... + $response->throwUnlessStatus(200); + + 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: + + + + 1return Http::post(/* ... */)->throw()->json(); + + + 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: + + + + 1use Illuminate\Http\Client\Response; + + 2use Illuminate\Http\Client\RequestException; + + 3  + + 4return Http::post(/* ... */)->throw(function (Response $response, RequestException $e) { + + 5 // ... + + 6})->json(); + + + use Illuminate\Http\Client\Response; + use Illuminate\Http\Client\RequestException; + + 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: + + + + 1use Illuminate\Foundation\Configuration\Exceptions; + + 2  + + 3->withExceptions(function (Exceptions $exceptions) { + + 4 // Truncate request exception messages to 240 characters... + + 5 $exceptions->truncateRequestExceptionsAt(240); + + 6  + + 7 // Disable request exception message truncation... + + 8 $exceptions->dontTruncateRequestExceptions(); + + 9}) + + + use Illuminate\Foundation\Configuration\Exceptions; + + ->withExceptions(function (Exceptions $exceptions) { + // Truncate request exception messages to 240 characters... + $exceptions->truncateRequestExceptionsAt(240); + + // Disable request exception message truncation... + $exceptions->dontTruncateRequestExceptions(); + }) + +Alternatively, you may customize the exception truncation behavior per request +using the `truncateExceptionsAt` method: + + + + 1return Http::truncateExceptionsAt(240)->post(/* ... */); + + + return Http::truncateExceptionsAt(240)->post(/* ... */); + +### 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: + + + + 1use Illuminate\Support\Facades\Http; + + 2use Psr\Http\Message\RequestInterface; + + 3  + + 4$response = Http::withRequestMiddleware( + + 5 function (RequestInterface $request) { + + 6 return $request->withHeader('X-Example', 'Value'); + + 7 } + + 8)->get('http://example.com'); + + + 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'); + +Likewise, you can inspect the incoming HTTP response by registering a +middleware via the `withResponseMiddleware` method: + + + + 1use Illuminate\Support\Facades\Http; + + 2use Psr\Http\Message\ResponseInterface; + + 3  + + 4$response = Http::withResponseMiddleware( + + 5 function (ResponseInterface $response) { + + 6 $header = $response->getHeader('X-Example'); + + 7  + + 8 // ... + + 9  + + 10 return $response; + + 11 } + + 12)->get('http://example.com'); + + + use Illuminate\Support\Facades\Http; + use Psr\Http\Message\ResponseInterface; + + $response = Http::withResponseMiddleware( + function (ResponseInterface $response) { + $header = $response->getHeader('X-Example'); + + // ... + + return $response; + } + )->get('http://example.com'); + +#### Global Middleware + +Sometimes, you may want to register a middleware that applies to every +outgoing request and incoming response. To accomplish this, you may use the +`globalRequestMiddleware` and `globalResponseMiddleware` methods. Typically, +these methods should be invoked in the `boot` method of your application's +`AppServiceProvider`: + + + + 1use Illuminate\Support\Facades\Http; + + 2  + + 3Http::globalRequestMiddleware(fn ($request) => $request->withHeader( + + 4 'User-Agent', 'Example Application/1.0' + + 5)); + + 6  + + 7Http::globalResponseMiddleware(fn ($response) => $response->withHeader( + + 8 'X-Finished-At', now()->toDateTimeString() + + 9)); + + + use Illuminate\Support\Facades\Http; + + Http::globalRequestMiddleware(fn ($request) => $request->withHeader( + 'User-Agent', 'Example Application/1.0' + )); + + Http::globalResponseMiddleware(fn ($response) => $response->withHeader( + 'X-Finished-At', now()->toDateTimeString() + )); + +### Guzzle Options + +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: + + + + 1$response = Http::withOptions([ + + 2 'debug' => true, + + 3])->get('http://example.com/users'); + + + $response = Http::withOptions([ + 'debug' => true, + ])->get('http://example.com/users'); + +#### Global Options + +To configure default options for every outgoing request, you may utilize the +`globalOptions` method. Typically, this method should be invoked from the +`boot` method of your application's `AppServiceProvider`: + + + + 1use Illuminate\Support\Facades\Http; + + 2  + + 3/** + + 4 * Bootstrap any application services. + + 5 */ + + 6public function boot(): void + + 7{ + + 8 Http::globalOptions([ + + 9 'allow_redirects' => false, + + 10 ]); + + 11} + + + use Illuminate\Support\Facades\Http; + + /** + * Bootstrap any application services. + */ + public function boot(): void + { + Http::globalOptions([ + 'allow_redirects' => false, + ]); + } + +## Concurrent Requests + +Sometimes, you may wish to make multiple HTTP requests concurrently. In other +words, you want several requests to be dispatched at the same time instead of +issuing the requests sequentially. This can lead to substantial performance +improvements when interacting with slow HTTP APIs. + +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: + + + + 1use Illuminate\Http\Client\Pool; + + 2use Illuminate\Support\Facades\Http; + + 3  + + 4$responses = Http::pool(fn (Pool $pool) => [ + + 5 $pool->get('http://localhost/first'), + + 6 $pool->get('http://localhost/second'), + + 7 $pool->get('http://localhost/third'), + + 8]); + + 9  + + 10return $responses[0]->ok() && + + 11 $responses[1]->ok() && + + 12 $responses[2]->ok(); + + + 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'), + ]); + + 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: + + + + 1use Illuminate\Http\Client\Pool; + + 2use Illuminate\Support\Facades\Http; + + 3  + + 4$responses = Http::pool(fn (Pool $pool) => [ + + 5 $pool->as('first')->get('http://localhost/first'), + + 6 $pool->as('second')->get('http://localhost/second'), + + 7 $pool->as('third')->get('http://localhost/third'), + + 8]); + + 9  + + 10return $responses['first']->ok(); + + + 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'), + ]); + + return $responses['first']->ok(); + +#### Customizing Concurrent Requests + +The `pool` method cannot be chained with other HTTP client methods such as the +`withHeaders` or `middleware` methods. If you want to apply custom headers or +middleware to pooled requests, you should configure those options on each +request in the pool: + + + + 1use Illuminate\Http\Client\Pool; + + 2use Illuminate\Support\Facades\Http; + + 3  + + 4$headers = [ + + 5 'X-Example' => 'example', + + 6]; + + 7  + + 8$responses = Http::pool(fn (Pool $pool) => [ + + 9 $pool->withHeaders($headers)->get('http://laravel.test/test'), + + 10 $pool->withHeaders($headers)->get('http://laravel.test/test'), + + 11 $pool->withHeaders($headers)->get('http://laravel.test/test'), + + 12]); + + + use Illuminate\Http\Client\Pool; + use Illuminate\Support\Facades\Http; + + $headers = [ + 'X-Example' => 'example', + ]; + + $responses = Http::pool(fn (Pool $pool) => [ + $pool->withHeaders($headers)->get('http://laravel.test/test'), + $pool->withHeaders($headers)->get('http://laravel.test/test'), + $pool->withHeaders($headers)->get('http://laravel.test/test'), + ]); + +## Macros + +The Laravel HTTP client allows you to define "macros", which can serve as a +fluent, expressive mechanism to configure common request paths and headers +when interacting with services throughout your application. To get started, +you may define the macro within the `boot` method of your application's +`App\Providers\AppServiceProvider` class: + + + + 1use Illuminate\Support\Facades\Http; + + 2  + + 3/** + + 4 * Bootstrap any application services. + + 5 */ + + 6public function boot(): void + + 7{ + + 8 Http::macro('github', function () { + + 9 return Http::withHeaders([ + + 10 'X-Example' => 'example', + + 11 ])->baseUrl('https://github.com'); + + 12 }); + + 13} + + + use Illuminate\Support\Facades\Http; + + /** + * Bootstrap any application services. + */ + public function boot(): void + { + Http::macro('github', function () { + return Http::withHeaders([ + 'X-Example' => 'example', + ])->baseUrl('https://github.com'); + }); + } + +Once your macro has been configured, you may invoke it from anywhere in your +application to create a pending request with the specified configuration: + + + + 1$response = Http::github()->get('/'); + + + $response = Http::github()->get('/'); + +## Testing + +Many Laravel services provide functionality to help you easily and +expressively write tests, and Laravel's HTTP client is no exception. The +`Http` facade's `fake` method allows you to instruct the HTTP client to return +stubbed / dummy responses when requests are made. + +### Faking Responses + +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: + + + + 1use Illuminate\Support\Facades\Http; + + 2  + + 3Http::fake(); + + 4  + + 5$response = Http::post(/* ... */); + + + use Illuminate\Support\Facades\Http; + + Http::fake(); + + $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. You may use +the `Http` facade's `response` method to construct stub / fake responses for +these endpoints: + + + + 1Http::fake([ + + 2 // Stub a JSON response for GitHub endpoints... + + 3 'github.com/*' => Http::response(['foo' => 'bar'], 200, $headers), + + 4  + + 5 // Stub a string response for Google endpoints... + + 6 'google.com/*' => Http::response('Hello World', 200, $headers), + + 7]); + + + 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), + ]); + +Any requests made to URLs that have not been faked will actually be executed. +If you would like to specify a fallback URL pattern that will stub all +unmatched URLs, you may use a single `*` character: + + + + 1Http::fake([ + + 2 // Stub a JSON response for GitHub endpoints... + + 3 'github.com/*' => Http::response(['foo' => 'bar'], 200, ['Headers']), + + 4  + + 5 // Stub a string response for all other endpoints... + + 6 '*' => Http::response('Hello World', 200, ['Headers']), + + 7]); + + + 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']), + ]); + +For convenience, simple string, JSON, and empty responses may be generated by +providing a string, array, or integer as the response: + + + + 1Http::fake([ + + 2 'google.com/*' => 'Hello World', + + 3 'github.com/*' => ['foo' => 'bar'], + + 4 'chatgpt.com/*' => 200, + + 5]); + + + Http::fake([ + 'google.com/*' => 'Hello World', + 'github.com/*' => ['foo' => 'bar'], + 'chatgpt.com/*' => 200, + ]); + +#### Faking 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: + + + + 1Http::fake([ + + 2 'github.com/*' => Http::failedConnection(), + + 3]); + + + Http::fake([ + 'github.com/*' => Http::failedConnection(), + ]); + +To test your application's behavior if a +`Illuminate\Http\Client\RequestException` is thrown, you may use the +`failedRequest` method: + + + + 1Http::fake([ + + 2 'github.com/*' => Http::failedRequest(['code' => 'not_found'], 404), + + 3]); + + + Http::fake([ + 'github.com/*' => Http::failedRequest(['code' => 'not_found'], 404), + ]); + +#### 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: + + + + 1Http::fake([ + + 2 // Stub a series of responses for GitHub endpoints... + + 3 'github.com/*' => Http::sequence() + + 4 ->push('Hello World', 200) + + 5 ->push(['foo' => 'bar'], 200) + + 6 ->pushStatus(404), + + 7]); + + + 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: + + + + 1Http::fake([ + + 2 // Stub a series of responses for GitHub endpoints... + + 3 'github.com/*' => Http::sequence() + + 4 ->push('Hello World', 200) + + 5 ->push(['foo' => 'bar'], 200) + + 6 ->whenEmpty(Http::response()), + + 7]); + + + 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: + + + + 1Http::fakeSequence() + + 2 ->push('Hello World', 200) + + 3 ->whenEmpty(Http::response()); + + + 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: + + + + 1use Illuminate\Http\Client\Request; + + 2  + + 3Http::fake(function (Request $request) { + + 4 return Http::response('Hello World', 200); + + 5}); + + + use Illuminate\Http\Client\Request; + + Http::fake(function (Request $request) { + return Http::response('Hello World', 200); + }); + +### Inspecting Requests + +When faking responses, you may occasionally wish to inspect the requests the +client receives in order to make sure your application is sending the correct +data or headers. You may accomplish this by calling the `Http::assertSent` +method after calling `Http::fake`. + +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: + + + + 1use Illuminate\Http\Client\Request; + + 2use Illuminate\Support\Facades\Http; + + 3  + + 4Http::fake(); + + 5  + + 6Http::withHeaders([ + + 7 'X-First' => 'foo', + + 8])->post('http://example.com/users', [ + + 9 'name' => 'Taylor', + + 10 'role' => 'Developer', + + 11]); + + 12  + + 13Http::assertSent(function (Request $request) { + + 14 return $request->hasHeader('X-First', 'foo') && + + 15 $request->url() == 'http://example.com/users' && + + 16 $request['name'] == 'Taylor' && + + 17 $request['role'] == 'Developer'; + + 18}); + + + use Illuminate\Http\Client\Request; + use Illuminate\Support\Facades\Http; + + Http::fake(); + + 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'; + }); + +If needed, you may assert that a specific request was not sent using the +`assertNotSent` method: + + + + 1use Illuminate\Http\Client\Request; + + 2use Illuminate\Support\Facades\Http; + + 3  + + 4Http::fake(); + + 5  + + 6Http::post('http://example.com/users', [ + + 7 'name' => 'Taylor', + + 8 'role' => 'Developer', + + 9]); + + 10  + + 11Http::assertNotSent(function (Request $request) { + + 12 return $request->url() === 'http://example.com/posts'; + + 13}); + + + use Illuminate\Http\Client\Request; + use Illuminate\Support\Facades\Http; + + Http::fake(); + + Http::post('http://example.com/users', [ + 'name' => 'Taylor', + 'role' => 'Developer', + ]); + + 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: + + + + 1Http::fake(); + + 2  + + 3Http::assertSentCount(5); + + + Http::fake(); + + Http::assertSentCount(5); + +Or, you may use the `assertNothingSent` method to assert that no requests were +sent during the test: + + + + 1Http::fake(); + + 2  + + 3Http::assertNothingSent(); + + + Http::fake(); + + Http::assertNothingSent(); + +#### Recording Requests / Responses + +You may use the `recorded` method to gather all requests and their +corresponding responses. The `recorded` method returns a collection of arrays +that contains instances of `Illuminate\Http\Client\Request` and +`Illuminate\Http\Client\Response`: + + + + 1Http::fake([ + + 2 'https://laravel.com' => Http::response(status: 500), + + 3 'https://nova.laravel.com/' => Http::response(), + + 4]); + + 5  + + 6Http::get('https://laravel.com'); + + 7Http::get('https://nova.laravel.com/'); + + 8  + + 9$recorded = Http::recorded(); + + 10  + + 11[$request, $response] = $recorded[0]; + + + Http::fake([ + 'https://laravel.com' => Http::response(status: 500), + 'https://nova.laravel.com/' => Http::response(), + ]); + + Http::get('https://laravel.com'); + Http::get('https://nova.laravel.com/'); + + $recorded = Http::recorded(); + + [$request, $response] = $recorded[0]; + +Additionally, the `recorded` method accepts a closure which will receive an +instance of `Illuminate\Http\Client\Request` and +`Illuminate\Http\Client\Response` and may be used to filter request / response +pairs based on your expectations: + + + + 1use Illuminate\Http\Client\Request; + + 2use Illuminate\Http\Client\Response; + + 3  + + 4Http::fake([ + + 5 'https://laravel.com' => Http::response(status: 500), + + 6 'https://nova.laravel.com/' => Http::response(), + + 7]); + + 8  + + 9Http::get('https://laravel.com'); + + 10Http::get('https://nova.laravel.com/'); + + 11  + + 12$recorded = Http::recorded(function (Request $request, Response $response) { + + 13 return $request->url() !== 'https://laravel.com' && + + 14 $response->successful(); + + 15}); + + + use Illuminate\Http\Client\Request; + use Illuminate\Http\Client\Response; + + Http::fake([ + 'https://laravel.com' => Http::response(status: 500), + 'https://nova.laravel.com/' => Http::response(), + ]); + + Http::get('https://laravel.com'); + Http::get('https://nova.laravel.com/'); + + $recorded = Http::recorded(function (Request $request, Response $response) { + return $request->url() !== 'https://laravel.com' && + $response->successful(); + }); + +### 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: + + + + 1use Illuminate\Support\Facades\Http; + + 2  + + 3Http::preventStrayRequests(); + + 4  + + 5Http::fake([ + + 6 'github.com/*' => Http::response('ok'), + + 7]); + + 8  + + 9// An "ok" response is returned... + + 10Http::get('https://github.com/laravel/framework'); + + 11  + + 12// An exception is thrown... + + 13Http::get('https://laravel.com'); + + + use Illuminate\Support\Facades\Http; + + Http::preventStrayRequests(); + + Http::fake([ + 'github.com/*' => Http::response('ok'), + ]); + + // An "ok" response is returned... + Http::get('https://github.com/laravel/framework'); + + // An exception is thrown... + Http::get('https://laravel.com'); + +Sometimes, you may wish to prevent most stray requests while still allowing +specific requests to execute. To accomplish this, you may pass an array of URL +patterns to the `allowStrayRequests` method. Any request matching one of the +given patterns will be allowed, while all other requests will continue to +throw an exception: + + + + 1use Illuminate\Support\Facades\Http; + + 2  + + 3Http::preventStrayRequests(); + + 4  + + 5Http::allowStrayRequests([ + + 6 'http://127.0.0.1:5000/*', + + 7]); + + 8  + + 9// This request is executed... + + 10Http::get('http://127.0.0.1:5000/generate'); + + 11  + + 12// An exception is thrown... + + 13Http::get('https://laravel.com'); + + + use Illuminate\Support\Facades\Http; + + Http::preventStrayRequests(); + + Http::allowStrayRequests([ + 'http://127.0.0.1:5000/*', + ]); + + // This request is executed... + Http::get('http://127.0.0.1:5000/generate'); + + // An exception is thrown... + Http::get('https://laravel.com'); + +## Events + +Laravel fires three events during the process of sending HTTP requests. The +`RequestSending` event is fired prior to a request being sent, while the +`ResponseReceived` event is fired after a response is received for a given +request. The `ConnectionFailed` event is fired if no response is received for +a given request. + +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/12.x/events) for these events within your +application: + + + + 1use Illuminate\Http\Client\Events\RequestSending; + + 2  + + 3class LogRequest + + 4{ + + 5 /** + + 6 * Handle the event. + + 7 */ + + 8 public function handle(RequestSending $event): void + + 9 { + + 10 // $event->request ... + + 11 } + + 12} + + + use Illuminate\Http\Client\Events\RequestSending; + + class LogRequest + { + /** + * Handle the event. + */ + public function handle(RequestSending $event): void + { + // $event->request ... + } + } + diff --git a/output/12.x/http-tests.md b/output/12.x/http-tests.md new file mode 100644 index 0000000..dfedb4a --- /dev/null +++ b/output/12.x/http-tests.md @@ -0,0 +1,3774 @@ +# HTTP Tests + + * Introduction + * Making Requests + * Customizing Request Headers + * Cookies + * Session / Authentication + * Debugging Responses + * Exception Handling + * Testing JSON APIs + * Fluent JSON Testing + * Testing File Uploads + * Testing Views + * Rendering Blade and Components + * Available Assertions + * Response Assertions + * Authentication Assertions + * Validation Assertions + +## Introduction + +Laravel provides a very fluent API for making HTTP requests to your +application and examining the responses. For example, take a look at the +feature test defined below: + +Pest PHPUnit + + + + 1get('/'); + + 5  + + 6 $response->assertStatus(200); + + 7}); + + + get('/'); + + $response->assertStatus(200); + }); + + + 1get('/'); + + 15  + + 16 $response->assertStatus(200); + + 17 } + + 18} + + + get('/'); + + $response->assertStatus(200); + } + } + +The `get` method makes a `GET` request into the application, while the +`assertStatus` method asserts that the returned response should have the given +HTTP status code. In addition to this simple assertion, Laravel also contains +a variety of assertions for inspecting the response headers, content, JSON +structure, and more. + +## Making Requests + +To make a request to your application, you may invoke the `get`, `post`, +`put`, `patch`, or `delete` methods within your test. These methods do not +actually issue a "real" HTTP request to your application. Instead, the entire +network request is simulated internally. + +Instead of returning an `Illuminate\Http\Response` instance, test request +methods return an instance of `Illuminate\Testing\TestResponse`, which +provides a variety of helpful assertions that allow you to inspect your +application's responses: + +Pest PHPUnit + + + + 1get('/'); + + 5  + + 6 $response->assertStatus(200); + + 7}); + + + get('/'); + + $response->assertStatus(200); + }); + + + 1get('/'); + + 15  + + 16 $response->assertStatus(200); + + 17 } + + 18} + + + get('/'); + + $response->assertStatus(200); + } + } + +In general, each of your tests should only make one request to your +application. Unexpected behavior may occur if multiple requests are executed +within a single test method. + +For convenience, the CSRF middleware is automatically disabled when running +tests. + +### Customizing Request Headers + +You may use the `withHeaders` method to customize the request's headers before +it is sent to the application. This method allows you to add any custom +headers you would like to the request: + +Pest PHPUnit + + + + 1withHeaders([ + + 5 'X-Header' => 'Value', + + 6 ])->post('/user', ['name' => 'Sally']); + + 7  + + 8 $response->assertStatus(201); + + 9}); + + + withHeaders([ + 'X-Header' => 'Value', + ])->post('/user', ['name' => 'Sally']); + + $response->assertStatus(201); + }); + + + 1withHeaders([ + + 15 'X-Header' => 'Value', + + 16 ])->post('/user', ['name' => 'Sally']); + + 17  + + 18 $response->assertStatus(201); + + 19 } + + 20} + + + withHeaders([ + 'X-Header' => 'Value', + ])->post('/user', ['name' => 'Sally']); + + $response->assertStatus(201); + } + } + +### Cookies + +You may use the `withCookie` or `withCookies` methods to set cookie values +before making a request. The `withCookie` method accepts a cookie name and +value as its two arguments, while the `withCookies` method accepts an array of +name / value pairs: + +Pest PHPUnit + + + + 1withCookie('color', 'blue')->get('/'); + + 5  + + 6 $response = $this->withCookies([ + + 7 'color' => 'blue', + + 8 'name' => 'Taylor', + + 9 ])->get('/'); + + 10  + + 11 // + + 12}); + + + withCookie('color', 'blue')->get('/'); + + $response = $this->withCookies([ + 'color' => 'blue', + 'name' => 'Taylor', + ])->get('/'); + + // + }); + + + 1withCookie('color', 'blue')->get('/'); + + 12  + + 13 $response = $this->withCookies([ + + 14 'color' => 'blue', + + 15 'name' => 'Taylor', + + 16 ])->get('/'); + + 17  + + 18 // + + 19 } + + 20} + + + withCookie('color', 'blue')->get('/'); + + $response = $this->withCookies([ + 'color' => 'blue', + 'name' => 'Taylor', + ])->get('/'); + + // + } + } + +### Session / Authentication + +Laravel provides several helpers for interacting with the session during HTTP +testing. First, you may set the session data to a given array using the +`withSession` method. This is useful for loading the session with data before +issuing a request to your application: + +Pest PHPUnit + + + + 1withSession(['banned' => false])->get('/'); + + 5  + + 6 // + + 7}); + + + withSession(['banned' => false])->get('/'); + + // + }); + + + 1withSession(['banned' => false])->get('/'); + + 12  + + 13 // + + 14 } + + 15} + + + withSession(['banned' => false])->get('/'); + + // + } + } + +Laravel's session is typically used to maintain state for the currently +authenticated user. Therefore, the `actingAs` helper method provides a simple +way to authenticate a given user as the current user. For example, we may use +a [model factory](/docs/12.x/eloquent-factories) to generate and authenticate +a user: + +Pest PHPUnit + + + + 1create(); + + 7  + + 8 $response = $this->actingAs($user) + + 9 ->withSession(['banned' => false]) + + 10 ->get('/'); + + 11  + + 12 // + + 13}); + + + create(); + + $response = $this->actingAs($user) + ->withSession(['banned' => false]) + ->get('/'); + + // + }); + + + 1create(); + + 13  + + 14 $response = $this->actingAs($user) + + 15 ->withSession(['banned' => false]) + + 16 ->get('/'); + + 17  + + 18 // + + 19 } + + 20} + + + create(); + + $response = $this->actingAs($user) + ->withSession(['banned' => false]) + ->get('/'); + + // + } + } + +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: + + + + 1$this->actingAs($user, 'web'); + + + $this->actingAs($user, 'web'); + +If you would like to ensure the request is unauthenticated, you may use the +`actingAsGuest` method: + + + + 1$this->actingAsGuest(); + + + $this->actingAsGuest(); + +### Debugging Responses + +After making a test request to your application, the `dump`, `dumpHeaders`, +and `dumpSession` methods may be used to examine and debug the response +contents: + +Pest PHPUnit + + + + 1get('/'); + + 5  + + 6 $response->dump(); + + 7 $response->dumpHeaders(); + + 8 $response->dumpSession(); + + 9}); + + + get('/'); + + $response->dump(); + $response->dumpHeaders(); + $response->dumpSession(); + }); + + + 1get('/'); + + 15  + + 16 $response->dump(); + + 17 $response->dumpHeaders(); + + 18 $response->dumpSession(); + + 19 } + + 20} + + + get('/'); + + $response->dump(); + $response->dumpHeaders(); + $response->dumpSession(); + } + } + +Alternatively, you may use the `dd`, `ddHeaders`, `ddBody`, `ddJson`, and +`ddSession` methods to dump information about the response and then stop +execution: + +Pest PHPUnit + + + + 1get('/'); + + 5  + + 6 $response->dd(); + + 7 $response->ddHeaders(); + + 8 $response->ddBody(); + + 9 $response->ddJson(); + + 10 $response->ddSession(); + + 11}); + + + get('/'); + + $response->dd(); + $response->ddHeaders(); + $response->ddBody(); + $response->ddJson(); + $response->ddSession(); + }); + + + 1get('/'); + + 15  + + 16 $response->dd(); + + 17 $response->ddHeaders(); + + 18 $response->ddBody(); + + 19 $response->ddJson(); + + 20 $response->ddSession(); + + 21 } + + 22} + + + get('/'); + + $response->dd(); + $response->ddHeaders(); + $response->ddBody(); + $response->ddJson(); + $response->ddSession(); + } + } + +### Exception Handling + +Sometimes you may need to test that your application is throwing a specific +exception. To accomplish this, you may "fake" the exception handler via the +`Exceptions` facade. Once the exception handler has been faked, you may +utilize the `assertReported` and `assertNotReported` methods to make +assertions against exceptions that were thrown during the request: + +Pest PHPUnit + + + + 1get('/order/1'); + + 10  + + 11 // Assert an exception was thrown... + + 12 Exceptions::assertReported(InvalidOrderException::class); + + 13  + + 14 // Assert against the exception... + + 15 Exceptions::assertReported(function (InvalidOrderException $e) { + + 16 return $e->getMessage() === 'The order was invalid.'; + + 17 }); + + 18}); + + + get('/order/1'); + + // Assert an exception was thrown... + Exceptions::assertReported(InvalidOrderException::class); + + // Assert against the exception... + Exceptions::assertReported(function (InvalidOrderException $e) { + return $e->getMessage() === 'The order was invalid.'; + }); + }); + + + 1get('/'); + + 19  + + 20 // Assert an exception was thrown... + + 21 Exceptions::assertReported(InvalidOrderException::class); + + 22  + + 23 // Assert against the exception... + + 24 Exceptions::assertReported(function (InvalidOrderException $e) { + + 25 return $e->getMessage() === 'The order was invalid.'; + + 26 }); + + 27 } + + 28} + + + get('/'); + + // Assert an exception was thrown... + Exceptions::assertReported(InvalidOrderException::class); + + // Assert against the exception... + Exceptions::assertReported(function (InvalidOrderException $e) { + return $e->getMessage() === 'The order was invalid.'; + }); + } + } + +The `assertNotReported` and `assertNothingReported` methods may be used to +assert that a given exception was not thrown during the request or that no +exceptions were thrown: + + + + 1Exceptions::assertNotReported(InvalidOrderException::class); + + 2  + + 3Exceptions::assertNothingReported(); + + + Exceptions::assertNotReported(InvalidOrderException::class); + + Exceptions::assertNothingReported(); + +You may totally disable exception handling for a given request by invoking the +`withoutExceptionHandling` method before making your request: + + + + 1$response = $this->withoutExceptionHandling()->get('/'); + + + $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: + + + + 1$response = $this->withoutDeprecationHandling()->get('/'); + + + $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: + + + + 1$this->assertThrows( + + 2 fn () => (new ProcessOrder)->execute(), + + 3 OrderInvalid::class + + 4); + + + $this->assertThrows( + fn () => (new ProcessOrder)->execute(), + OrderInvalid::class + ); + +If you would like to inspect and make assertions against the exception that is +thrown, you may provide a closure as the second argument to the `assertThrows` +method: + + + + 1$this->assertThrows( + + 2 fn () => (new ProcessOrder)->execute(), + + 3 fn (OrderInvalid $e) => $e->orderId() === 123; + + 4); + + + $this->assertThrows( + fn () => (new ProcessOrder)->execute(), + fn (OrderInvalid $e) => $e->orderId() === 123; + ); + +The `assertDoesntThrow` method may be used to assert that the code within a +given closure does not throw any exceptions: + + + + 1$this->assertDoesntThrow(fn () => (new ProcessOrder)->execute()); + + + $this->assertDoesntThrow(fn () => (new ProcessOrder)->execute()); + +## Testing JSON APIs + +Laravel also provides several helpers for testing JSON APIs and their +responses. For example, the `json`, `getJson`, `postJson`, `putJson`, +`patchJson`, `deleteJson`, and `optionsJson` methods may be used to issue JSON +requests with various HTTP verbs. You may also easily pass data and headers to +these methods. To get started, let's write a test to make a `POST` request to +`/api/user` and assert that the expected JSON data was returned: + +Pest PHPUnit + + + + 1postJson('/api/user', ['name' => 'Sally']); + + 5  + + 6 $response + + 7 ->assertStatus(201) + + 8 ->assertJson([ + + 9 'created' => true, + + 10 ]); + + 11}); + + + postJson('/api/user', ['name' => 'Sally']); + + $response + ->assertStatus(201) + ->assertJson([ + 'created' => true, + ]); + }); + + + 1postJson('/api/user', ['name' => 'Sally']); + + 15  + + 16 $response + + 17 ->assertStatus(201) + + 18 ->assertJson([ + + 19 'created' => true, + + 20 ]); + + 21 } + + 22} + + + postJson('/api/user', ['name' => 'Sally']); + + $response + ->assertStatus(201) + ->assertJson([ + 'created' => true, + ]); + } + } + +In addition, JSON response data may be accessed as array variables on the +response, making it convenient for you to inspect the individual values +returned within a JSON response: + +Pest PHPUnit + + + + 1expect($response['created'])->toBeTrue(); + + + expect($response['created'])->toBeTrue(); + + + 1$this->assertTrue($response['created']); + + + $this->assertTrue($response['created']); + +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. + +#### Asserting Exact JSON Matches + +As previously mentioned, the `assertJson` method may be used to assert that a +fragment of JSON exists within the JSON response. If you would like to verify +that a given array **exactly matches** the JSON returned by your application, +you should use the `assertExactJson` method: + +Pest PHPUnit + + + + 1postJson('/user', ['name' => 'Sally']); + + 5  + + 6 $response + + 7 ->assertStatus(201) + + 8 ->assertExactJson([ + + 9 'created' => true, + + 10 ]); + + 11}); + + + postJson('/user', ['name' => 'Sally']); + + $response + ->assertStatus(201) + ->assertExactJson([ + 'created' => true, + ]); + }); + + + 1postJson('/user', ['name' => 'Sally']); + + 15  + + 16 $response + + 17 ->assertStatus(201) + + 18 ->assertExactJson([ + + 19 'created' => true, + + 20 ]); + + 21 } + + 22} + + + postJson('/user', ['name' => 'Sally']); + + $response + ->assertStatus(201) + ->assertExactJson([ + 'created' => true, + ]); + } + } + +#### Asserting on JSON Paths + +If you would like to verify that the JSON response contains the given data at +a specified path, you should use the `assertJsonPath` method: + +Pest PHPUnit + + + + 1postJson('/user', ['name' => 'Sally']); + + 5  + + 6 $response + + 7 ->assertStatus(201) + + 8 ->assertJsonPath('team.owner.name', 'Darian'); + + 9}); + + + postJson('/user', ['name' => 'Sally']); + + $response + ->assertStatus(201) + ->assertJsonPath('team.owner.name', 'Darian'); + }); + + + 1postJson('/user', ['name' => 'Sally']); + + 15  + + 16 $response + + 17 ->assertStatus(201) + + 18 ->assertJsonPath('team.owner.name', 'Darian'); + + 19 } + + 20} + + + postJson('/user', ['name' => 'Sally']); + + $response + ->assertStatus(201) + ->assertJsonPath('team.owner.name', 'Darian'); + } + } + +The `assertJsonPath` method also accepts a closure, which may be used to +dynamically determine if the assertion should pass: + + + + 1$response->assertJsonPath('team.owner.name', fn (string $name) => strlen($name) >= 3); + + + $response->assertJsonPath('team.owner.name', fn (string $name) => strlen($name) >= 3); + +### Fluent JSON Testing + +Laravel also offers a beautiful way to fluently test your application's JSON +responses. To get started, pass a closure to the `assertJson` method. This +closure will be invoked with an instance of +`Illuminate\Testing\Fluent\AssertableJson` which can be used to make +assertions against the JSON that was returned by your application. The `where` +method may be used to make assertions against a particular attribute of the +JSON, while the `missing` method may be used to assert that a particular +attribute is missing from the JSON: + +Pest PHPUnit + + + + 1use Illuminate\Testing\Fluent\AssertableJson; + + 2  + + 3test('fluent json', function () { + + 4 $response = $this->getJson('/users/1'); + + 5  + + 6 $response + + 7 ->assertJson(fn (AssertableJson $json) => + + 8 $json->where('id', 1) + + 9 ->where('name', 'Victoria Faith') + + 10 ->where('email', fn (string $email) => str($email)->is('[[email protected]](/cdn-cgi/l/email-protection)')) + + 11 ->whereNot('status', 'pending') + + 12 ->missing('password') + + 13 ->etc() + + 14 ); + + 15}); + + + use Illuminate\Testing\Fluent\AssertableJson; + + test('fluent json', function () { + $response = $this->getJson('/users/1'); + + $response + ->assertJson(fn (AssertableJson $json) => + $json->where('id', 1) + ->where('name', 'Victoria Faith') + ->where('email', fn (string $email) => str($email)->is('[[email protected]](/cdn-cgi/l/email-protection)')) + ->whereNot('status', 'pending') + ->missing('password') + ->etc() + ); + }); + + + 1use Illuminate\Testing\Fluent\AssertableJson; + + 2  + + 3/** + + 4 * A basic functional test example. + + 5 */ + + 6public function test_fluent_json(): void + + 7{ + + 8 $response = $this->getJson('/users/1'); + + 9  + + 10 $response + + 11 ->assertJson(fn (AssertableJson $json) => + + 12 $json->where('id', 1) + + 13 ->where('name', 'Victoria Faith') + + 14 ->where('email', fn (string $email) => str($email)->is('[[email protected]](/cdn-cgi/l/email-protection)')) + + 15 ->whereNot('status', 'pending') + + 16 ->missing('password') + + 17 ->etc() + + 18 ); + + 19} + + + use Illuminate\Testing\Fluent\AssertableJson; + + /** + * A basic functional test example. + */ + public function test_fluent_json(): void + { + $response = $this->getJson('/users/1'); + + $response + ->assertJson(fn (AssertableJson $json) => + $json->where('id', 1) + ->where('name', 'Victoria Faith') + ->where('email', fn (string $email) => str($email)->is('[[email protected]](/cdn-cgi/l/email-protection)')) + ->whereNot('status', 'pending') + ->missing('password') + ->etc() + ); + } + +#### Understanding the `etc` Method + +In the example above, you may have noticed we invoked the `etc` method at the +end of our assertion chain. This method informs Laravel that there may be +other attributes present on the JSON object. If the `etc` method is not used, +the test will fail if other attributes that you did not make assertions +against exist on the JSON object. + +The intention behind this behavior is to protect you from unintentionally +exposing sensitive information in your JSON responses by forcing you to either +explicitly make an assertion against the attribute or explicitly allow +additional attributes via the `etc` method. + +However, you should be aware that not including the `etc` method in your +assertion chain does not ensure that additional attributes are not being added +to arrays that are nested within your JSON object. The `etc` method only +ensures that no additional attributes exist at the nesting level in which the +`etc` method is invoked. + +#### Asserting Attribute Presence / Absence + +To assert that an attribute is present or absent, you may use the `has` and +`missing` methods: + + + + 1$response->assertJson(fn (AssertableJson $json) => + + 2 $json->has('data') + + 3 ->missing('message') + + 4); + + + $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: + + + + 1$response->assertJson(fn (AssertableJson $json) => + + 2 $json->hasAll(['status', 'data']) + + 3 ->missingAll(['message', 'code']) + + 4); + + + $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: + + + + 1$response->assertJson(fn (AssertableJson $json) => + + 2 $json->has('status') + + 3 ->hasAny('data', 'message', 'code') + + 4); + + + $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: + + + + 1Route::get('/users', function () { + + 2 return User::all(); + + 3}); + + + 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: + + + + 1$response + + 2 ->assertJson(fn (AssertableJson $json) => + + 3 $json->has(3) + + 4 ->first(fn (AssertableJson $json) => + + 5 $json->where('id', 1) + + 6 ->where('name', 'Victoria Faith') + + 7 ->where('email', fn (string $email) => str($email)->is('[[email protected]](/cdn-cgi/l/email-protection)')) + + 8 ->missing('password') + + 9 ->etc() + + 10 ) + + 11 ); + + + $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('[[email protected]](/cdn-cgi/l/email-protection)')) + ->missing('password') + ->etc() + ) + ); + +#### Scoping JSON Collection Assertions + +Sometimes, your application's routes will return JSON collections that are +assigned named keys: + + + + 1Route::get('/users', function () { + + 2 return [ + + 3 'meta' => [...], + + 4 'users' => User::all(), + + 5 ]; + + 6}) + + + 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: + + + + 1$response + + 2 ->assertJson(fn (AssertableJson $json) => + + 3 $json->has('meta') + + 4 ->has('users', 3) + + 5 ->has('users.0', fn (AssertableJson $json) => + + 6 $json->where('id', 1) + + 7 ->where('name', 'Victoria Faith') + + 8 ->where('email', fn (string $email) => str($email)->is('[[email protected]](/cdn-cgi/l/email-protection)')) + + 9 ->missing('password') + + 10 ->etc() + + 11 ) + + 12 ); + + + $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('[[email protected]](/cdn-cgi/l/email-protection)')) + ->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: + + + + 1$response + + 2 ->assertJson(fn (AssertableJson $json) => + + 3 $json->has('meta') + + 4 ->has('users', 3, fn (AssertableJson $json) => + + 5 $json->where('id', 1) + + 6 ->where('name', 'Victoria Faith') + + 7 ->where('email', fn (string $email) => str($email)->is('[[email protected]](/cdn-cgi/l/email-protection)')) + + 8 ->missing('password') + + 9 ->etc() + + 10 ) + + 11 ); + + + $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('[[email protected]](/cdn-cgi/l/email-protection)')) + ->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: + + + + 1$response->assertJson(fn (AssertableJson $json) => + + 2 $json->whereType('id', 'integer') + + 3 ->whereAllType([ + + 4 'users.0.name' => 'string', + + 5 'meta' => 'array' + + 6 ]) + + 7); + + + $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: + + + + 1$response->assertJson(fn (AssertableJson $json) => + + 2 $json->whereType('name', 'string|null') + + 3 ->whereType('id', ['string', 'integer']) + + 4); + + + $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`. + +## Testing File Uploads + +The `Illuminate\Http\UploadedFile` class provides a `fake` method which may be +used to generate dummy files or images for testing. This, combined with the +`Storage` facade's `fake` method, greatly simplifies the testing of file +uploads. For example, you may combine these two features to easily test an +avatar upload form: + +Pest PHPUnit + + + + 1image('avatar.jpg'); + + 10  + + 11 $response = $this->post('/avatar', [ + + 12 'avatar' => $file, + + 13 ]); + + 14  + + 15 Storage::disk('avatars')->assertExists($file->hashName()); + + 16}); + + + image('avatar.jpg'); + + $response = $this->post('/avatar', [ + 'avatar' => $file, + ]); + + Storage::disk('avatars')->assertExists($file->hashName()); + }); + + + 1image('avatar.jpg'); + + 16  + + 17 $response = $this->post('/avatar', [ + + 18 'avatar' => $file, + + 19 ]); + + 20  + + 21 Storage::disk('avatars')->assertExists($file->hashName()); + + 22 } + + 23} + + + image('avatar.jpg'); + + $response = $this->post('/avatar', [ + 'avatar' => $file, + ]); + + Storage::disk('avatars')->assertExists($file->hashName()); + } + } + +If you would like to assert that a given file does not exist, you may use the +`assertMissing` method provided by the `Storage` facade: + + + + 1Storage::fake('avatars'); + + 2  + + 3// ... + + 4  + + 5Storage::disk('avatars')->assertMissing('missing.jpg'); + + + Storage::fake('avatars'); + + // ... + + 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: + + + + 1UploadedFile::fake()->image('avatar.jpg', $width, $height)->size(100); + + + 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: + + + + 1UploadedFile::fake()->create('document.pdf', $sizeInKilobytes); + + + 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: + + + + 1UploadedFile::fake()->create( + + 2 'document.pdf', $sizeInKilobytes, 'application/pdf' + + 3); + + + UploadedFile::fake()->create( + 'document.pdf', $sizeInKilobytes, 'application/pdf' + ); + +## Testing Views + +Laravel also allows you to render a view without making a simulated HTTP +request to the application. To accomplish this, you may call the `view` method +within your test. The `view` method accepts the view name and an optional +array of data. The method returns an instance of +`Illuminate\Testing\TestView`, which offers several methods to conveniently +make assertions about the view's contents: + +Pest PHPUnit + + + + 1view('welcome', ['name' => 'Taylor']); + + 5  + + 6 $view->assertSee('Taylor'); + + 7}); + + + view('welcome', ['name' => 'Taylor']); + + $view->assertSee('Taylor'); + }); + + + 1view('welcome', ['name' => 'Taylor']); + + 12  + + 13 $view->assertSee('Taylor'); + + 14 } + + 15} + + + view('welcome', ['name' => 'Taylor']); + + $view->assertSee('Taylor'); + } + } + +The `TestView` class provides the following assertion methods: `assertSee`, +`assertSeeInOrder`, `assertSeeText`, `assertSeeTextInOrder`, `assertDontSee`, +and `assertDontSeeText`. + +If needed, you may get the raw, rendered view contents by casting the +`TestView` instance to a string: + + + + 1$contents = (string) $this->view('welcome'); + + + $contents = (string) $this->view('welcome'); + +#### Sharing Errors + +Some views may depend on errors shared in the [global error bag provided by +Laravel](/docs/12.x/validation#quick-displaying-the-validation-errors). To +hydrate the error bag with error messages, you may use the `withViewErrors` +method: + + + + 1$view = $this->withViewErrors([ + + 2 'name' => ['Please provide a valid name.'] + + 3])->view('form'); + + 4  + + 5$view->assertSee('Please provide a valid name.'); + + + $view = $this->withViewErrors([ + 'name' => ['Please provide a valid name.'] + ])->view('form'); + + $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/12.x/blade) string. Like the `view` method, the `blade` method +returns an instance of `Illuminate\Testing\TestView`: + + + + 1$view = $this->blade( + + 2 '', + + 3 ['name' => 'Taylor'] + + 4); + + 5  + + 6$view->assertSee('Taylor'); + + + $view = $this->blade( + '', + ['name' => 'Taylor'] + ); + + $view->assertSee('Taylor'); + +You may use the `component` method to evaluate and render a [Blade +component](/docs/12.x/blade#components). The `component` method returns an +instance of `Illuminate\Testing\TestComponent`: + + + + 1$view = $this->component(Profile::class, ['name' => 'Taylor']); + + 2  + + 3$view->assertSee('Taylor'); + + + $view = $this->component(Profile::class, ['name' => 'Taylor']); + + $view->assertSee('Taylor'); + +## Available Assertions + +### Response Assertions + +Laravel's `Illuminate\Testing\TestResponse` class provides a variety of custom +assertion methods that you may utilize when testing your application. These +assertions may be accessed on the response that is returned by the `json`, +`get`, `post`, `put`, and `delete` test methods: + +assertAccepted assertBadRequest assertClientError assertConflict assertCookie +assertCookieExpired assertCookieNotExpired assertCookieMissing assertCreated +assertDontSee assertDontSeeText assertDownload assertExactJson +assertExactJsonStructure assertForbidden assertFound assertGone assertHeader +assertHeaderMissing assertInternalServerError assertJson assertJsonCount +assertJsonFragment assertJsonIsArray assertJsonIsObject assertJsonMissing +assertJsonMissingExact assertJsonMissingValidationErrors assertJsonPath +assertJsonMissingPath assertJsonStructure assertJsonValidationErrors +assertJsonValidationErrorFor assertLocation assertMethodNotAllowed +assertMovedPermanently assertContent assertNoContent assertStreamed +assertStreamedContent assertNotFound assertOk assertPaymentRequired +assertPlainCookie assertRedirect assertRedirectBack +assertRedirectBackWithErrors assertRedirectBackWithoutErrors +assertRedirectContains assertRedirectToRoute assertRedirectToSignedRoute +assertRequestTimeout assertSee assertSeeInOrder assertSeeText +assertSeeTextInOrder assertServerError assertServiceUnavailable +assertSessionHas assertSessionHasInput assertSessionHasAll +assertSessionHasErrors assertSessionHasErrorsIn assertSessionHasNoErrors +assertSessionDoesntHaveErrors assertSessionMissing assertStatus +assertSuccessful assertTooManyRequests assertUnauthorized assertUnprocessable +assertUnsupportedMediaType assertValid assertInvalid assertViewHas +assertViewHasAll assertViewIs assertViewMissing + +#### assertAccepted + +Assert that the response has an accepted (202) HTTP status code: + + + + 1$response->assertAccepted(); + + + $response->assertAccepted(); + +#### assertBadRequest + +Assert that the response has a bad request (400) HTTP status code: + + + + 1$response->assertBadRequest(); + + + $response->assertBadRequest(); + +#### assertClientError + +Assert that the response has a client error (>= 400, < 500) HTTP status code: + + + + 1$response->assertClientError(); + + + $response->assertClientError(); + +#### assertConflict + +Assert that the response has a conflict (409) HTTP status code: + + + + 1$response->assertConflict(); + + + $response->assertConflict(); + +#### assertCookie + +Assert that the response contains the given cookie: + + + + 1$response->assertCookie($cookieName, $value = null); + + + $response->assertCookie($cookieName, $value = null); + +#### assertCookieExpired + +Assert that the response contains the given cookie and it is expired: + + + + 1$response->assertCookieExpired($cookieName); + + + $response->assertCookieExpired($cookieName); + +#### assertCookieNotExpired + +Assert that the response contains the given cookie and it is not expired: + + + + 1$response->assertCookieNotExpired($cookieName); + + + $response->assertCookieNotExpired($cookieName); + +#### assertCookieMissing + +Assert that the response does not contain the given cookie: + + + + 1$response->assertCookieMissing($cookieName); + + + $response->assertCookieMissing($cookieName); + +#### assertCreated + +Assert that the response has a 201 HTTP status code: + + + + 1$response->assertCreated(); + + + $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`: + + + + 1$response->assertDontSee($value, $escape = true); + + + $response->assertDontSee($value, $escape = 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: + + + + 1$response->assertDontSeeText($value, $escape = true); + + + $response->assertDontSeeText($value, $escape = 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: + + + + 1$response->assertDownload(); + + + $response->assertDownload(); + +If you wish, you may assert that the downloadable file was assigned a given +file name: + + + + 1$response->assertDownload('image.jpg'); + + + $response->assertDownload('image.jpg'); + +#### assertExactJson + +Assert that the response contains an exact match of the given JSON data: + + + + 1$response->assertExactJson(array $data); + + + $response->assertExactJson(array $data); + +#### assertExactJsonStructure + +Assert that the response contains an exact match of the given JSON structure: + + + + 1$response->assertExactJsonStructure(array $data); + + + $response->assertExactJsonStructure(array $data); + +This method is a more strict variant of assertJsonStructure. In contrast with +`assertJsonStructure`, this method will fail if the response contains any keys +that aren't explicitly included in the expected JSON structure. + +#### assertForbidden + +Assert that the response has a forbidden (403) HTTP status code: + + + + 1$response->assertForbidden(); + + + $response->assertForbidden(); + +#### assertFound + +Assert that the response has a found (302) HTTP status code: + + + + 1$response->assertFound(); + + + $response->assertFound(); + +#### assertGone + +Assert that the response has a gone (410) HTTP status code: + + + + 1$response->assertGone(); + + + $response->assertGone(); + +#### assertHeader + +Assert that the given header and value is present on the response: + + + + 1$response->assertHeader($headerName, $value = null); + + + $response->assertHeader($headerName, $value = null); + +#### assertHeaderMissing + +Assert that the given header is not present on the response: + + + + 1$response->assertHeaderMissing($headerName); + + + $response->assertHeaderMissing($headerName); + +#### assertInternalServerError + +Assert that the response has an "Internal Server Error" (500) HTTP status +code: + + + + 1$response->assertInternalServerError(); + + + $response->assertInternalServerError(); + +#### assertJson + +Assert that the response contains the given JSON data: + + + + 1$response->assertJson(array $data, $strict = false); + + + $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. + +#### assertJsonCount + +Assert that the response JSON has an array with the expected number of items +at the given key: + + + + 1$response->assertJsonCount($count, $key = null); + + + $response->assertJsonCount($count, $key = null); + +#### assertJsonFragment + +Assert that the response contains the given JSON data anywhere in the +response: + + + + 1Route::get('/users', function () { + + 2 return [ + + 3 'users' => [ + + 4 [ + + 5 'name' => 'Taylor Otwell', + + 6 ], + + 7 ], + + 8 ]; + + 9}); + + 10  + + 11$response->assertJsonFragment(['name' => 'Taylor Otwell']); + + + Route::get('/users', function () { + return [ + 'users' => [ + [ + 'name' => 'Taylor Otwell', + ], + ], + ]; + }); + + $response->assertJsonFragment(['name' => 'Taylor Otwell']); + +#### assertJsonIsArray + +Assert that the response JSON is an array: + + + + 1$response->assertJsonIsArray(); + + + $response->assertJsonIsArray(); + +#### assertJsonIsObject + +Assert that the response JSON is an object: + + + + 1$response->assertJsonIsObject(); + + + $response->assertJsonIsObject(); + +#### assertJsonMissing + +Assert that the response does not contain the given JSON data: + + + + 1$response->assertJsonMissing(array $data); + + + $response->assertJsonMissing(array $data); + +#### assertJsonMissingExact + +Assert that the response does not contain the exact JSON data: + + + + 1$response->assertJsonMissingExact(array $data); + + + $response->assertJsonMissingExact(array $data); + +#### assertJsonMissingValidationErrors + +Assert that the response has no JSON validation errors for the given keys: + + + + 1$response->assertJsonMissingValidationErrors($keys); + + + $response->assertJsonMissingValidationErrors($keys); + +The more generic assertValid 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. + +#### assertJsonPath + +Assert that the response contains the given data at the specified path: + + + + 1$response->assertJsonPath($path, $expectedValue); + + + $response->assertJsonPath($path, $expectedValue); + +For example, if the following JSON response is returned by your application: + + + + 1{ + + 2 "user": { + + 3 "name": "Steve Schoger" + + 4 } + + 5} + + + { + "user": { + "name": "Steve Schoger" + } + } + +You may assert that the `name` property of the `user` object matches a given +value like so: + + + + 1$response->assertJsonPath('user.name', 'Steve Schoger'); + + + $response->assertJsonPath('user.name', 'Steve Schoger'); + +#### assertJsonMissingPath + +Assert that the response does not contain the given path: + + + + 1$response->assertJsonMissingPath($path); + + + $response->assertJsonMissingPath($path); + +For example, if the following JSON response is returned by your application: + + + + 1{ + + 2 "user": { + + 3 "name": "Steve Schoger" + + 4 } + + 5} + + + { + "user": { + "name": "Steve Schoger" + } + } + +You may assert that it does not contain the `email` property of the `user` +object: + + + + 1$response->assertJsonMissingPath('user.email'); + + + $response->assertJsonMissingPath('user.email'); + +#### assertJsonStructure + +Assert that the response has a given JSON structure: + + + + 1$response->assertJsonStructure(array $structure); + + + $response->assertJsonStructure(array $structure); + +For example, if the JSON response returned by your application contains the +following data: + + + + 1{ + + 2 "user": { + + 3 "name": "Steve Schoger" + + 4 } + + 5} + + + { + "user": { + "name": "Steve Schoger" + } + } + +You may assert that the JSON structure matches your expectations like so: + + + + 1$response->assertJsonStructure([ + + 2 'user' => [ + + 3 'name', + + 4 ] + + 5]); + + + $response->assertJsonStructure([ + 'user' => [ + 'name', + ] + ]); + +Sometimes, JSON responses returned by your application may contain arrays of +objects: + + + + 1{ + + 2 "user": [ + + 3 { + + 4 "name": "Steve Schoger", + + 5 "age": 55, + + 6 "location": "Earth" + + 7 }, + + 8 { + + 9 "name": "Mary Schoger", + + 10 "age": 60, + + 11 "location": "Earth" + + 12 } + + 13 ] + + 14} + + + { + "user": [ + { + "name": "Steve Schoger", + "age": 55, + "location": "Earth" + }, + { + "name": "Mary Schoger", + "age": 60, + "location": "Earth" + } + ] + } + +In this situation, you may use the `*` character to assert against the +structure of all of the objects in the array: + + + + 1$response->assertJsonStructure([ + + 2 'user' => [ + + 3 '*' => [ + + 4 'name', + + 5 'age', + + 6 'location' + + 7 ] + + 8 ] + + 9]); + + + $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: + + + + 1$response->assertJsonValidationErrors(array $data, $responseKey = 'errors'); + + + $response->assertJsonValidationErrors(array $data, $responseKey = 'errors'); + +The more generic assertInvalid method may be used to assert that a response +has validation errors returned as JSON **or** that errors were flashed to +session storage. + +#### assertJsonValidationErrorFor + +Assert the response has any JSON validation errors for the given key: + + + + 1$response->assertJsonValidationErrorFor(string $key, $responseKey = 'errors'); + + + $response->assertJsonValidationErrorFor(string $key, $responseKey = 'errors'); + +#### assertMethodNotAllowed + +Assert that the response has a method not allowed (405) HTTP status code: + + + + 1$response->assertMethodNotAllowed(); + + + $response->assertMethodNotAllowed(); + +#### assertMovedPermanently + +Assert that the response has a moved permanently (301) HTTP status code: + + + + 1$response->assertMovedPermanently(); + + + $response->assertMovedPermanently(); + +#### assertLocation + +Assert that the response has the given URI value in the `Location` header: + + + + 1$response->assertLocation($uri); + + + $response->assertLocation($uri); + +#### assertContent + +Assert that the given string matches the response content: + + + + 1$response->assertContent($value); + + + $response->assertContent($value); + +#### assertNoContent + +Assert that the response has the given HTTP status code and no content: + + + + 1$response->assertNoContent($status = 204); + + + $response->assertNoContent($status = 204); + +#### assertStreamed + +Assert that the response was a streamed response: + + + + 1$response->assertStreamed(); + + + $response->assertStreamed(); + +#### assertStreamedContent + +Assert that the given string matches the streamed response content: + + + + 1$response->assertStreamedContent($value); + + + $response->assertStreamedContent($value); + +#### assertNotFound + +Assert that the response has a not found (404) HTTP status code: + + + + 1$response->assertNotFound(); + + + $response->assertNotFound(); + +#### assertOk + +Assert that the response has a 200 HTTP status code: + + + + 1$response->assertOk(); + + + $response->assertOk(); + +#### assertPaymentRequired + +Assert that the response has a payment required (402) HTTP status code: + + + + 1$response->assertPaymentRequired(); + + + $response->assertPaymentRequired(); + +#### assertPlainCookie + +Assert that the response contains the given unencrypted cookie: + + + + 1$response->assertPlainCookie($cookieName, $value = null); + + + $response->assertPlainCookie($cookieName, $value = null); + +#### assertRedirect + +Assert that the response is a redirect to the given URI: + + + + 1$response->assertRedirect($uri = null); + + + $response->assertRedirect($uri = null); + +#### assertRedirectBack + +Assert whether the response is redirecting back to the previous page: + + + + 1$response->assertRedirectBack(); + + + $response->assertRedirectBack(); + +#### assertRedirectBackWithErrors + +Assert whether the response is redirecting back to the previous page and the +session has the given errors: + + + + 1$response->assertRedirectBackWithErrors( + + 2 array $keys = [], $format = null, $errorBag = 'default' + + 3); + + + $response->assertRedirectBackWithErrors( + array $keys = [], $format = null, $errorBag = 'default' + ); + +#### assertRedirectBackWithoutErrors + +Assert whether the response is redirecting back to the previous page and the +session does not contain any error messages: + + + + 1$response->assertRedirectBackWithoutErrors(); + + + $response->assertRedirectBackWithoutErrors(); + +#### assertRedirectContains + +Assert whether the response is redirecting to a URI that contains the given +string: + + + + 1$response->assertRedirectContains($string); + + + $response->assertRedirectContains($string); + +#### assertRedirectToRoute + +Assert that the response is a redirect to the given [named +route](/docs/12.x/routing#named-routes): + + + + 1$response->assertRedirectToRoute($name, $parameters = []); + + + $response->assertRedirectToRoute($name, $parameters = []); + +#### assertRedirectToSignedRoute + +Assert that the response is a redirect to the given [signed +route](/docs/12.x/urls#signed-urls): + + + + 1$response->assertRedirectToSignedRoute($name = null, $parameters = []); + + + $response->assertRedirectToSignedRoute($name = null, $parameters = []); + +#### assertRequestTimeout + +Assert that the response has a request timeout (408) HTTP status code: + + + + 1$response->assertRequestTimeout(); + + + $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`: + + + + 1$response->assertSee($value, $escape = true); + + + $response->assertSee($value, $escape = 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`: + + + + 1$response->assertSeeInOrder(array $values, $escape = true); + + + $response->assertSeeInOrder(array $values, $escape = 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: + + + + 1$response->assertSeeText($value, $escape = true); + + + $response->assertSeeText($value, $escape = 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: + + + + 1$response->assertSeeTextInOrder(array $values, $escape = true); + + + $response->assertSeeTextInOrder(array $values, $escape = true); + +#### assertServerError + +Assert that the response has a server error (>= 500 , < 600) HTTP status code: + + + + 1$response->assertServerError(); + + + $response->assertServerError(); + +#### assertServiceUnavailable + +Assert that the response has a "Service Unavailable" (503) HTTP status code: + + + + 1$response->assertServiceUnavailable(); + + + $response->assertServiceUnavailable(); + +#### assertSessionHas + +Assert that the session contains the given piece of data: + + + + 1$response->assertSessionHas($key, $value = null); + + + $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`: + + + + 1$response->assertSessionHas($key, function (User $value) { + + 2 return $value->name === 'Taylor Otwell'; + + 3}); + + + $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/12.x/responses#redirecting-with-flashed-session-data): + + + + 1$response->assertSessionHasInput($key, $value = null); + + + $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`: + + + + 1use Illuminate\Support\Facades\Crypt; + + 2  + + 3$response->assertSessionHasInput($key, function (string $value) { + + 4 return Crypt::decryptString($value) === 'secret'; + + 5}); + + + use Illuminate\Support\Facades\Crypt; + + $response->assertSessionHasInput($key, function (string $value) { + return Crypt::decryptString($value) === 'secret'; + }); + +#### assertSessionHasAll + +Assert that the session contains a given array of key / value pairs: + + + + 1$response->assertSessionHasAll(array $data); + + + $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: + + + + 1$response->assertSessionHasAll([ + + 2 'name' => 'Taylor Otwell', + + 3 'status' => 'active', + + 4]); + + + $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: + + + + 1$response->assertSessionHasErrors( + + 2 array $keys = [], $format = null, $errorBag = 'default' + + 3); + + + $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: + + + + 1$response->assertSessionHasErrors(['name', 'email']); + + + $response->assertSessionHasErrors(['name', 'email']); + +Or, you may assert that a given field has a particular validation error +message: + + + + 1$response->assertSessionHasErrors([ + + 2 'name' => 'The given name was invalid.' + + 3]); + + + $response->assertSessionHasErrors([ + 'name' => 'The given name was invalid.' + ]); + +The more generic assertInvalid method may be used to assert that a response +has validation errors returned as JSON **or** that errors were flashed to +session storage. + +#### assertSessionHasErrorsIn + +Assert that the session contains an error for the given `$keys` within a +specific [error bag](/docs/12.x/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: + + + + 1$response->assertSessionHasErrorsIn($errorBag, $keys = [], $format = null); + + + $response->assertSessionHasErrorsIn($errorBag, $keys = [], $format = null); + +#### assertSessionHasNoErrors + +Assert that the session has no validation errors: + + + + 1$response->assertSessionHasNoErrors(); + + + $response->assertSessionHasNoErrors(); + +#### assertSessionDoesntHaveErrors + +Assert that the session has no validation errors for the given keys: + + + + 1$response->assertSessionDoesntHaveErrors($keys = [], $format = null, $errorBag = 'default'); + + + $response->assertSessionDoesntHaveErrors($keys = [], $format = null, $errorBag = 'default'); + +The more generic assertValid 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. + +#### assertSessionMissing + +Assert that the session does not contain the given key: + + + + 1$response->assertSessionMissing($key); + + + $response->assertSessionMissing($key); + +#### assertStatus + +Assert that the response has a given HTTP status code: + + + + 1$response->assertStatus($code); + + + $response->assertStatus($code); + +#### assertSuccessful + +Assert that the response has a successful (>= 200 and < 300) HTTP status code: + + + + 1$response->assertSuccessful(); + + + $response->assertSuccessful(); + +#### assertTooManyRequests + +Assert that the response has a too many requests (429) HTTP status code: + + + + 1$response->assertTooManyRequests(); + + + $response->assertTooManyRequests(); + +#### assertUnauthorized + +Assert that the response has an unauthorized (401) HTTP status code: + + + + 1$response->assertUnauthorized(); + + + $response->assertUnauthorized(); + +#### assertUnprocessable + +Assert that the response has an unprocessable entity (422) HTTP status code: + + + + 1$response->assertUnprocessable(); + + + $response->assertUnprocessable(); + +#### assertUnsupportedMediaType + +Assert that the response has an unsupported media type (415) HTTP status code: + + + + 1$response->assertUnsupportedMediaType(); + + + $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: + + + + 1// Assert that no validation errors are present... + + 2$response->assertValid(); + + 3  + + 4// Assert that the given keys do not have validation errors... + + 5$response->assertValid(['name', 'email']); + + + // Assert that no validation errors are present... + $response->assertValid(); + + // 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: + + + + 1$response->assertInvalid(['name', 'email']); + + + $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: + + + + 1$response->assertInvalid([ + + 2 'name' => 'The name field is required.', + + 3 'email' => 'valid email address', + + 4]); + + + $response->assertInvalid([ + 'name' => 'The name field is required.', + 'email' => 'valid email address', + ]); + +If you would like to assert that the given fields are the only fields with +validation errors, you may use the `assertOnlyInvalid` method: + + + + 1$response->assertOnlyInvalid(['name', 'email']); + + + $response->assertOnlyInvalid(['name', 'email']); + +#### assertViewHas + +Assert that the response view contains a given piece of data: + + + + 1$response->assertViewHas($key, $value = null); + + + $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: + + + + 1$response->assertViewHas('user', function (User $user) { + + 2 return $user->name === 'Taylor'; + + 3}); + + + $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: + +Pest PHPUnit + + + + 1expect($response['name'])->toBe('Taylor'); + + + expect($response['name'])->toBe('Taylor'); + + + 1$this->assertEquals('Taylor', $response['name']); + + + $this->assertEquals('Taylor', $response['name']); + +#### assertViewHasAll + +Assert that the response view has a given list of data: + + + + 1$response->assertViewHasAll(array $data); + + + $response->assertViewHasAll(array $data); + +This method may be used to assert that the view simply contains data matching +the given keys: + + + + 1$response->assertViewHasAll([ + + 2 'name', + + 3 'email', + + 4]); + + + $response->assertViewHasAll([ + 'name', + 'email', + ]); + +Or, you may assert that the view data is present and has specific values: + + + + 1$response->assertViewHasAll([ + + 2 'name' => 'Taylor Otwell', + + 3 'email' => '[[email protected]](/cdn-cgi/l/email-protection),', + + 4]); + + + $response->assertViewHasAll([ + 'name' => 'Taylor Otwell', + 'email' => '[[email protected]](/cdn-cgi/l/email-protection),', + ]); + +#### assertViewIs + +Assert that the given view was returned by the route: + + + + 1$response->assertViewIs($value); + + + $response->assertViewIs($value); + +#### assertViewMissing + +Assert that the given data key was not made available to the view returned in +the application's response: + + + + 1$response->assertViewMissing($key); + + + $response->assertViewMissing($key); + +### Authentication Assertions + +Laravel also provides a variety of authentication related assertions that you +may utilize within your application's feature tests. Note that these methods +are invoked on the test class itself and not the +`Illuminate\Testing\TestResponse` instance returned by methods such as `get` +and `post`. + +#### assertAuthenticated + +Assert that a user is authenticated: + + + + 1$this->assertAuthenticated($guard = null); + + + $this->assertAuthenticated($guard = null); + +#### assertGuest + +Assert that a user is not authenticated: + + + + 1$this->assertGuest($guard = null); + + + $this->assertGuest($guard = null); + +#### assertAuthenticatedAs + +Assert that a specific user is authenticated: + + + + 1$this->assertAuthenticatedAs($user, $guard = null); + + + $this->assertAuthenticatedAs($user, $guard = null); + +## Validation Assertions + +Laravel provides two primary validation related assertions that you may use to +ensure the data provided in your request was either valid or invalid. + +#### 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: + + + + 1// Assert that no validation errors are present... + + 2$response->assertValid(); + + 3  + + 4// Assert that the given keys do not have validation errors... + + 5$response->assertValid(['name', 'email']); + + + // Assert that no validation errors are present... + $response->assertValid(); + + // 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: + + + + 1$response->assertInvalid(['name', 'email']); + + + $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: + + + + 1$response->assertInvalid([ + + 2 'name' => 'The name field is required.', + + 3 'email' => 'valid email address', + + 4]); + + + $response->assertInvalid([ + 'name' => 'The name field is required.', + 'email' => 'valid email address', + ]); + diff --git a/output/12.x/installation.md b/output/12.x/installation.md new file mode 100644 index 0000000..5a9650f --- /dev/null +++ b/output/12.x/installation.md @@ -0,0 +1,483 @@ +# Installation + + * Meet Laravel + * Why Laravel? + * Creating a Laravel Application + * Installing PHP and the Laravel Installer + * Creating an Application + * Initial Configuration + * Environment Based Configuration + * Databases and Migrations + * Directory Configuration + * Installation Using Herd + * Herd on macOS + * Herd on Windows + * IDE Support + * Laravel and AI + * Installing Laravel Boost + * Next Steps + * Laravel the Full Stack Framework + * Laravel the API Backend + +## Meet Laravel + +Laravel is a web application framework with expressive, elegant syntax. A web +framework provides a structure and starting point for creating your +application, allowing you to focus on creating something amazing while we +sweat the details. + +Laravel strives to provide an amazing developer experience while providing +powerful features such as thorough dependency injection, an expressive +database abstraction layer, queues and scheduled jobs, unit and integration +testing, and more. + +Whether you are new to PHP web frameworks or have years of experience, Laravel +is a framework that can grow with you. We'll help you take your first steps as +a web developer or give you a boost as you take your expertise to the next +level. We can't wait to see what you build. + +### Why Laravel? + +There are a variety of tools and frameworks available to you when building a +web application. However, we believe Laravel is the best choice for building +modern, full-stack web applications. + +#### A Progressive Framework + +We like to call Laravel a "progressive" framework. By that, we mean that +Laravel grows with you. If you're just taking your first steps into web +development, Laravel's vast library of documentation, guides, and [video +tutorials](https://laracasts.com) will help you learn the ropes without +becoming overwhelmed. + +If you're a senior developer, Laravel gives you robust tools for [dependency +injection](/docs/12.x/container), [unit testing](/docs/12.x/testing), +[queues](/docs/12.x/queues), [real-time events](/docs/12.x/broadcasting), and +more. Laravel is fine-tuned for building professional web applications and +ready to handle enterprise work loads. + +#### A Scalable Framework + +Laravel is incredibly scalable. Thanks to the scaling-friendly nature of PHP +and Laravel's built-in support for fast, distributed cache systems like Redis, +horizontal scaling with Laravel is a breeze. In fact, Laravel applications +have been easily scaled to handle hundreds of millions of requests per month. + +Need extreme scaling? Platforms like [Laravel +Cloud](https://cloud.laravel.com) allow you to run your Laravel application at +nearly limitless scale. + +#### A Community Framework + +Laravel combines the best packages in the PHP ecosystem to offer the most +robust and developer friendly framework available. In addition, thousands of +talented developers from around the world have [contributed to the +framework](https://github.com/laravel/framework). Who knows, maybe you'll even +become a Laravel contributor. + +## Creating a Laravel Application + +### Installing PHP and the Laravel Installer + +Before creating your first Laravel application, make sure that your local +machine has [PHP](https://php.net), [Composer](https://getcomposer.org), and +[the Laravel installer](https://github.com/laravel/installer) installed. In +addition, you should install either [Node and NPM](https://nodejs.org) or +[Bun](https://bun.sh/) so that you can compile your application's frontend +assets. + +If you don't have PHP and Composer installed on your local machine, the +following commands will install PHP, Composer, and the Laravel installer on +macOS, Windows, or Linux: + +macOS Windows PowerShell Linux + + + + 1/bin/bash -c "$(curl -fsSL https://php.new/install/mac/8.4)" + + + /bin/bash -c "$(curl -fsSL https://php.new/install/mac/8.4)" + + + 1# Run as administrator... + + 2Set-ExecutionPolicy Bypass -Scope Process -Force; [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072; iex ((New-Object System.Net.WebClient).DownloadString('https://php.new/install/windows/8.4')) + + + # Run as administrator... + Set-ExecutionPolicy Bypass -Scope Process -Force; [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072; iex ((New-Object System.Net.WebClient).DownloadString('https://php.new/install/windows/8.4')) + + + 1/bin/bash -c "$(curl -fsSL https://php.new/install/linux/8.4)" + + + /bin/bash -c "$(curl -fsSL https://php.new/install/linux/8.4)" + +After running one of the commands above, you should restart your terminal +session. To update PHP, Composer, and the Laravel installer after installing +them via `php.new`, you can re-run the command in your terminal. + +If you already have PHP and Composer installed, you may install the Laravel +installer via Composer: + + + + 1composer global require laravel/installer + + + composer global require laravel/installer + +For a fully-featured, graphical PHP installation and management experience, +check out Laravel Herd. + +### Creating an Application + +After you have installed PHP, Composer, and the Laravel installer, you're +ready to create a new Laravel application. The Laravel installer will prompt +you to select your preferred testing framework, database, and starter kit: + + + + 1laravel new example-app + + + laravel new example-app + +Once the application has been created, you can start Laravel's local +development server, queue worker, and Vite development server using the `dev` +Composer script: + + + + 1cd example-app + + 2npm install && npm run build + + 3composer run dev + + + cd example-app + npm install && npm run build + composer run dev + +Once you have started the development server, your application will be +accessible in your web browser at . Next, you're ready +to start taking your next steps into the Laravel ecosystem. Of course, you may +also want to configure a database. + +If you would like a head start when developing your Laravel application, +consider using one of our [starter kits](/docs/12.x/starter-kits). Laravel's +starter kits provide backend and frontend authentication scaffolding for your +new Laravel application. + +## Initial Configuration + +All of the configuration files for the Laravel framework are stored in the +`config` directory. Each option is documented, so feel free to look through +the files and get familiar with the options available to you. + +Laravel needs almost no additional configuration out of the box. You are free +to get started developing! However, you may wish to review the +`config/app.php` file and its documentation. It contains several options such +as `url` and `locale` that you may wish to change according to your +application. + +### Environment Based Configuration + +Since many of Laravel's configuration option values may vary depending on +whether your application is running on your local machine or on a production +web server, many important configuration values are defined using the `.env` +file that exists at the root of your application. + +Your `.env` file should not be committed to your application's source control, +since each developer / server using your application could require a different +environment configuration. Furthermore, this would be a security risk in the +event an intruder gains access to your source control repository, since any +sensitive credentials would be exposed. + +For more information about the `.env` file and environment based +configuration, check out the full [configuration +documentation](/docs/12.x/configuration#environment-configuration). + +### Databases and Migrations + +Now that you have created your Laravel application, you probably want to store +some data in a database. By default, your application's `.env` configuration +file specifies that Laravel will be interacting with an SQLite database. + +During the creation of the application, Laravel created a +`database/database.sqlite` file for you, and ran the necessary migrations to +create the application's database tables. + +If you prefer to use another database driver such as MySQL or PostgreSQL, you +can update your `.env` configuration file to use the appropriate database. For +example, if you wish to use MySQL, update your `.env` configuration file's +`DB_*` variables like so: + + + + 1DB_CONNECTION=mysql + + 2DB_HOST=127.0.0.1 + + 3DB_PORT=3306 + + 4DB_DATABASE=laravel + + 5DB_USERNAME=root + + 6DB_PASSWORD= + + + DB_CONNECTION=mysql + DB_HOST=127.0.0.1 + DB_PORT=3306 + DB_DATABASE=laravel + DB_USERNAME=root + DB_PASSWORD= + +If you choose to use a database other than SQLite, you will need to create the +database and run your application's [database +migrations](/docs/12.x/migrations): + + + + 1php artisan migrate + + + php artisan migrate + +If you are developing on macOS or Windows and need to install MySQL, +PostgreSQL, or Redis locally, consider using [Herd +Pro](https://herd.laravel.com/#plans) or [DBngin](https://dbngin.com/). + +### Directory Configuration + +Laravel should always be served out of the root of the "web directory" +configured for your web server. You should not attempt to serve a Laravel +application out of a subdirectory of the "web directory". Attempting to do so +could expose sensitive files present within your application. + +## Installation Using Herd + +[Laravel Herd](https://herd.laravel.com) is a blazing fast, native Laravel and +PHP development environment for macOS and Windows. Herd includes everything +you need to get started with Laravel development, including PHP and Nginx. + +Once you install Herd, you're ready to start developing with Laravel. Herd +includes command line tools for `php`, `composer`, `laravel`, `expose`, +`node`, `npm`, and `nvm`. + +[Herd Pro](https://herd.laravel.com/#plans) augments Herd with additional +powerful features, such as the ability to create and manage local MySQL, +Postgres, and Redis databases, as well as local mail viewing and log +monitoring. + +### Herd on macOS + +If you develop on macOS, you can download the Herd installer from the [Herd +website](https://herd.laravel.com). The installer automatically downloads the +latest version of PHP and configures your Mac to always run +[Nginx](https://www.nginx.com/) in the background. + +Herd for macOS uses [dnsmasq](https://en.wikipedia.org/wiki/Dnsmasq) to +support "parked" directories. Any Laravel application in a parked directory +will automatically be served by Herd. By default, Herd creates a parked +directory at `~/Herd` and you can access any Laravel application in this +directory on the `.test` domain using its directory name. + +After installing Herd, the fastest way to create a new Laravel application is +using the Laravel CLI, which is bundled with Herd: + + + + 1cd ~/Herd + + 2laravel new my-app + + 3cd my-app + + 4herd open + + + cd ~/Herd + laravel new my-app + cd my-app + herd open + +Of course, you can always manage your parked directories and other PHP +settings via Herd's UI, which can be opened from the Herd menu in your system +tray. + +You can learn more about Herd by checking out the [Herd +documentation](https://herd.laravel.com/docs). + +### Herd on Windows + +You can download the Windows installer for Herd on the [Herd +website](https://herd.laravel.com/windows). After the installation finishes, +you can start Herd to complete the onboarding process and access the Herd UI +for the first time. + +The Herd UI is accessible by left-clicking on Herd's system tray icon. A +right-click opens the quick menu with access to all tools that you need on a +daily basis. + +During installation, Herd creates a "parked" directory in your home directory +at `%USERPROFILE%\Herd`. Any Laravel application in a parked directory will +automatically be served by Herd, and you can access any Laravel application in +this directory on the `.test` domain using its directory name. + +After installing Herd, the fastest way to create a new Laravel application is +using the Laravel CLI, which is bundled with Herd. To get started, open +Powershell and run the following commands: + + + + 1cd ~\Herd + + 2laravel new my-app + + 3cd my-app + + 4herd open + + + cd ~\Herd + laravel new my-app + cd my-app + herd open + +You can learn more about Herd by checking out the [Herd documentation for +Windows](https://herd.laravel.com/docs/windows). + +## IDE Support + +You are free to use any code editor you wish when developing Laravel +applications. If you're looking for lightweight and extensible editors, [VS +Code](https://code.visualstudio.com) or [Cursor](https://cursor.com) combined +with the official [Laravel VS Code +Extension](https://marketplace.visualstudio.com/items?itemName=laravel.vscode- +laravel) offers excellent Laravel support with features like syntax +highlighting, snippets, artisan command integration, and smart autocompletion +for Eloquent models, routes, middleware, assets, config, and Inertia.js. + +[PhpStorm](https://www.jetbrains.com/phpstorm/laravel/) by JetBrains combined +with the [Laravel Idea plugin](https://laravel-idea.com/) provides extensive +support for Laravel and its ecosystem including Laravel Pint, Larastan, and +Pest. The framework support covers Blade templates, smart autocompletion for +Eloquent models, routes, views, translations, and components, along with +powerful code generation and navigation across Laravel projects. + +For those seeking a cloud-based development experience, [Firebase +Studio](https://firebase.studio/) provides instant access to building with +Laravel directly in your browser. With zero setup required, Firebase Studio +makes it easy to start building Laravel applications from any device. + +## Laravel and AI + +[Laravel Boost](https://github.com/laravel/boost) is a powerful tool that +bridges the gap between AI coding agents and Laravel applications. Boost +provides AI agents with Laravel-specific context, tools, and guidelines so +they can generate more accurate, version-specific code that follows Laravel +conventions. + +When you install Boost in your Laravel application, AI agents gain access to +over 15 specialized tools including the ability to know which packages you are +using, query your database, search the Laravel documentation, read browser +logs, generate tests, and execute code via Tinker. + +In addition, Boost gives AI agents access to over 17,000 pieces of vectorized +Laravel ecosystem documentation, specific to your installed package versions. +This means agents can provide guidance targeted to the exact versions your +project uses. + +Boost also includes Laravel-maintained AI guidelines that help agents to +follow framework conventions, write appropriate tests, and avoid common +pitfalls when generating Laravel code. + +### Installing Laravel Boost + +Boost can be installed in Laravel 10, 11, and 12 applications running PHP 8.1 +or higher. To get started, install Boost as a development dependency: + + + + 1composer require laravel/boost --dev + + + composer require laravel/boost --dev + +Once installed, run the interactive installer: + + + + 1php artisan boost:install + + + php artisan boost:install + +The installer will auto-detect your IDE and AI agents, allowing you to opt +into the features that make sense for your project. Boost respects existing +project conventions and doesn't force opinionated style rules by default. + +To learn more about Boost, check out the [Laravel Boost repository on +GitHub](https://github.com/laravel/boost). + +## Next Steps + +Now that you have created your Laravel application, you may be wondering what +to learn next. First, we strongly recommend becoming familiar with how Laravel +works by reading the following documentation: + + * [Request Lifecycle](/docs/12.x/lifecycle) + * [Configuration](/docs/12.x/configuration) + * [Directory Structure](/docs/12.x/structure) + * [Frontend](/docs/12.x/frontend) + * [Service Container](/docs/12.x/container) + * [Facades](/docs/12.x/facades) + +How you want to use Laravel will also dictate the next steps on your journey. +There are a variety of ways to use Laravel, and we'll explore two primary use +cases for the framework below. + +### Laravel the Full Stack Framework + +Laravel may serve as a full stack framework. By "full stack" framework we mean +that you are going to use Laravel to route requests to your application and +render your frontend via [Blade templates](/docs/12.x/blade) or a single-page +application hybrid technology like [Inertia](https://inertiajs.com). This is +the most common way to use the Laravel framework, and, in our opinion, the +most productive way to use Laravel. + +If this is how you plan to use Laravel, you may want to check out our +documentation on [frontend development](/docs/12.x/frontend), +[routing](/docs/12.x/routing), [views](/docs/12.x/views), or the [Eloquent +ORM](/docs/12.x/eloquent). In addition, you might be interested in learning +about community packages like [Livewire](https://livewire.laravel.com) and +[Inertia](https://inertiajs.com). These packages allow you to use Laravel as a +full-stack framework while enjoying many of the UI benefits provided by +single-page JavaScript applications. + +If you are using Laravel as a full stack framework, we also strongly encourage +you to learn how to compile your application's CSS and JavaScript using +[Vite](/docs/12.x/vite). + +If you want to get a head start building your application, check out one of +our official [application starter kits](/docs/12.x/starter-kits). + +### Laravel the API Backend + +Laravel may also serve as an API backend to a JavaScript single-page +application or mobile application. For example, you might use Laravel as an +API backend for your [Next.js](https://nextjs.org) application. In this +context, you may use Laravel to provide [authentication](/docs/12.x/sanctum) +and data storage / retrieval for your application, while also taking advantage +of Laravel's powerful services such as queues, emails, notifications, and +more. + +If this is how you plan to use Laravel, you may want to check out our +documentation on [routing](/docs/12.x/routing), [Laravel +Sanctum](/docs/12.x/sanctum), and the [Eloquent ORM](/docs/12.x/eloquent). + diff --git a/output/12.x/lifecycle.md b/output/12.x/lifecycle.md new file mode 100644 index 0000000..e1d8052 --- /dev/null +++ b/output/12.x/lifecycle.md @@ -0,0 +1,145 @@ +# Request Lifecycle + + * Introduction + * Lifecycle Overview + * First Steps + * HTTP / Console Kernels + * Service Providers + * Routing + * Finishing Up + * Focus on Service Providers + +## Introduction + +When using any tool in the "real world", you feel more confident if you +understand how that tool works. Application development is no different. When +you understand how your development tools function, you feel more comfortable +and confident using them. + +The goal of this document is to give you a good, high-level overview of how +the Laravel framework works. By getting to know the overall framework better, +everything feels less "magical" and you will be more confident building your +applications. If you don't understand all of the terms right away, don't lose +heart! Just try to get a basic grasp of what is going on, and your knowledge +will grow as you explore other sections of the documentation. + +## Lifecycle Overview + +### First Steps + +The entry point for all requests to a Laravel application is the +`public/index.php` file. All requests are directed to this file by your web +server (Apache / Nginx) configuration. The `index.php` file doesn't contain +much code. Rather, it is a starting point for loading the rest of the +framework. + +The `index.php` file loads the Composer generated autoloader definition, and +then retrieves an instance of the Laravel application from +`bootstrap/app.php`. The first action taken by Laravel itself is to create an +instance of the application / [service container](/docs/12.x/container). + +### HTTP / Console Kernels + +Next, the incoming request is sent to either the HTTP kernel or the console +kernel, using the `handleRequest` or `handleCommand` methods of the +application instance, depending on the type of request entering the +application. These two kernels serve as the central location through which all +requests flow. For now, let's just focus on the HTTP kernel, which is an +instance of `Illuminate\Foundation\Http\Kernel`. + +The HTTP kernel defines an array of `bootstrappers` that will be run before +the request is executed. These bootstrappers configure error handling, +configure logging, [detect the application +environment](/docs/12.x/configuration#environment-configuration), and perform +other tasks that need to be done before the request is actually handled. +Typically, these classes handle internal Laravel configuration that you do not +need to worry about. + +The HTTP kernel is also responsible for passing the request through the +application's middleware stack. These middleware handle reading and writing +the [HTTP session](/docs/12.x/session), determining if the application is in +maintenance mode, [verifying the CSRF token](/docs/12.x/csrf), and more. We'll +talk more about these soon. + +The method signature for the HTTP kernel's `handle` method is quite simple: it +receives a `Request` and returns a `Response`. Think of the kernel as being a +big black box that represents your entire application. Feed it HTTP requests +and it will return HTTP responses. + +### Service Providers + +One of the most important kernel bootstrapping actions is loading the [service +providers](/docs/12.x/providers) for your application. Service providers are +responsible for bootstrapping all of the framework's various components, such +as the database, queue, validation, and routing components. + +Laravel will iterate through this list of providers and instantiate each of +them. After instantiating the providers, the `register` method will be called +on all of the providers. Then, once all of the providers have been registered, +the `boot` method will be called on each provider. This is so service +providers may depend on every container binding being registered and available +by the time their `boot` method is executed. + +Essentially every major feature offered by Laravel is bootstrapped and +configured by a service provider. Since they bootstrap and configure so many +features offered by the framework, service providers are the most important +aspect of the entire Laravel bootstrap process. + +While the framework internally uses dozens of service providers, you also have +the option to create your own. You can find a list of the user-defined or +third-party service providers that your application is using in the +`bootstrap/providers.php` file. + +### Routing + +Once the application has been bootstrapped and all service providers have been +registered, the `Request` will be handed off to the router for dispatching. +The router will dispatch the request to a route or controller, as well as run +any route specific middleware. + +Middleware provide a convenient mechanism for filtering or examining HTTP +requests entering your application. For example, Laravel includes a middleware +that verifies if the user of your application is authenticated. If the user is +not authenticated, the middleware will redirect the user to the login screen. +However, if the user is authenticated, the middleware will allow the request +to proceed further into the application. Some middleware are assigned to all +routes within the application, like `PreventRequestsDuringMaintenance`, while +some are only assigned to specific routes or route groups. You can learn more +about middleware by reading the complete [middleware +documentation](/docs/12.x/middleware). + +If the request passes through all of the matched route's assigned middleware, +the route or controller method will be executed and the response returned by +the route or controller method will be sent back through the route's chain of +middleware. + +### Finishing Up + +Once the route or controller method returns a response, the response will +travel back outward through the route's middleware, giving the application a +chance to modify or examine the outgoing response. + +Finally, once the response travels back through the middleware, the HTTP +kernel's `handle` method returns the response object to the `handleRequest` of +the application instance, and this method calls the `send` method on the +returned response. The `send` method sends the response content to the user's +web browser. We've now completed our journey through the entire Laravel +request lifecycle! + +## Focus on Service Providers + +Service providers are truly the key to bootstrapping a Laravel application. +The application instance is created, the service providers are registered, and +the request is handed to the bootstrapped application. It's really that +simple! + +Having a firm grasp of how a Laravel application is built and bootstrapped via +service providers is very valuable. Your application's user-defined service +providers are stored in the `app/Providers` directory. + +By default, the `AppServiceProvider` is fairly empty. This provider is a great +place to add your application's own bootstrapping and service container +bindings. For large applications, you may wish to create several service +providers, each with more granular bootstrapping for specific services used by +your application. + diff --git a/output/12.x/localization.md b/output/12.x/localization.md new file mode 100644 index 0000000..eb8169b --- /dev/null +++ b/output/12.x/localization.md @@ -0,0 +1,540 @@ +# Localization + + * Introduction + * Publishing the Language Files + * Configuring the Locale + * Pluralization Language + * Defining Translation Strings + * Using Short Keys + * Using Translation Strings as Keys + * Retrieving Translation Strings + * Replacing Parameters in Translation Strings + * Pluralization + * Overriding Package Language Files + +## Introduction + +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. + +Laravel's localization features provide a convenient way to retrieve strings +in various languages, allowing you to easily support multiple languages within +your application. + +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: + + + + 1/lang + + 2 /en + + 3 messages.php + + 4 /es + + 5 messages.php + + + /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: + + + + 1/lang + + 2 en.json + + 3 es.json + + + /lang + en.json + es.json + +We'll discuss each approach to managing translation strings within this +documentation. + +### Publishing the Language Files + +By default, the Laravel application skeleton does not include the `lang` +directory. If you would like to customize Laravel's language files or create +your own, you should scaffold the `lang` directory via the `lang:publish` +Artisan command. The `lang:publish` command will create the `lang` directory +in your application and publish the default set of language files used by +Laravel: + + + + 1php artisan lang:publish + + + php artisan lang:publish + +### Configuring the Locale + +The default language for your application is stored in the `config/app.php` +configuration file's `locale` configuration option, which is typically set +using the `APP_LOCALE` environment variable. You are free to modify this value +to suit the needs of your application. + +You may also configure a "fallback language", which will be used when the +default language does not contain a given translation string. Like the default +language, the fallback language is also configured in the `config/app.php` +configuration file, and its value is typically set using the +`APP_FALLBACK_LOCALE` environment variable. + +You may modify the default language for a single HTTP request at runtime using +the `setLocale` method provided by the `App` facade: + + + + 1use Illuminate\Support\Facades\App; + + 2  + + 3Route::get('/greeting/{locale}', function (string $locale) { + + 4 if (! in_array($locale, ['en', 'es', 'fr'])) { + + 5 abort(400); + + 6 } + + 7  + + 8 App::setLocale($locale); + + 9  + + 10 // ... + + 11}); + + + use Illuminate\Support\Facades\App; + + Route::get('/greeting/{locale}', function (string $locale) { + if (! in_array($locale, ['en', 'es', 'fr'])) { + abort(400); + } + + 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: + + + + 1use Illuminate\Support\Facades\App; + + 2  + + 3$locale = App::currentLocale(); + + 4  + + 5if (App::isLocale('en')) { + + 6 // ... + + 7} + + + use Illuminate\Support\Facades\App; + + $locale = App::currentLocale(); + + 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`: + + + + 1use Illuminate\Support\Pluralizer; + + 2  + + 3/** + + 4 * Bootstrap any application services. + + 5 */ + + 6public function boot(): void + + 7{ + + 8 Pluralizer::useLanguage('spanish'); + + 9  + + 10 // ... + + 11} + + + use Illuminate\Support\Pluralizer; + + /** + * Bootstrap any application services. + */ + public function boot(): void + { + Pluralizer::useLanguage('spanish'); + + // ... + } + +If you customize the pluralizer's language, you should explicitly define your +Eloquent model's [table names](/docs/12.x/eloquent#table-names). + +## Defining Translation Strings + +### Using Short Keys + +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: + + + + 1/lang + + 2 /en + + 3 messages.php + + 4 /es + + 5 messages.php + + + /lang + /en + messages.php + /es + messages.php + +All language files return an array of keyed strings. For example: + + + + 1 'Welcome to our application!', + + 7]; + + + 'Welcome to our application!', + ]; + +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". + +### Using Translation Strings as Keys + +For applications with a large number of translatable strings, defining every +string with a "short key" can become confusing when referencing the keys in +your views and it is cumbersome to continually invent keys for every +translation string supported by your application. + +For this reason, Laravel also provides support for defining translation +strings using the "default" translation of the string as the key. Language +files that use translation strings as keys are stored as JSON files in the +`lang` directory. For example, if your application has a Spanish translation, +you should create a `lang/es.json` file: + + + + 1{ + + 2 "I love programming.": "Me encanta programar." + + 3} + + + { + "I love programming.": "Me encanta programar." + } + +#### Key / File Conflicts + +You should not define translation string keys that conflict with other +translation filenames. For example, translating `__('Action')` for the "NL" +locale while a `nl/action.php` file exists but a `nl.json` file does not exist +will result in the translator returning the entire contents of +`nl/action.php`. + +## Retrieving Translation Strings + +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: + + + + 1echo __('messages.welcome'); + + + 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, +you should pass the default translation of your string to the `__` function; + + + + 1echo __('I love programming.'); + + + 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/12.x/blade), you may use +the `{{ }}` echo syntax to display the translation string: + + + + 1{{ __('messages.welcome') }} + + + {{ __('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: + + + + 1'welcome' => 'Welcome, :name', + + + '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: + + + + 1echo __('messages.welcome', ['name' => 'dayle']); + + + 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: + + + + 1'welcome' => 'Welcome, :NAME', // Welcome, DAYLE + + 2'goodbye' => 'Goodbye, :Name', // Goodbye, Dayle + + + 'welcome' => 'Welcome, :NAME', // Welcome, DAYLE + 'goodbye' => 'Goodbye, :Name', // Goodbye, Dayle + +#### Object Replacement Formatting + +If you attempt to provide an object as a translation placeholder, the object's +`__toString` method will be invoked. The +[__toString](https://www.php.net/manual/en/language.oop5.magic.php#object.tostring) +method is one of PHP's built-in "magic methods". However, sometimes you may +not have control over the `__toString` method of a given class, such as when +the class that you are interacting with belongs to a third-party library. + +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: + + + + 1use Illuminate\Support\Facades\Lang; + + 2use Money\Money; + + 3  + + 4/** + + 5 * Bootstrap any application services. + + 6 */ + + 7public function boot(): void + + 8{ + + 9 Lang::stringable(function (Money $money) { + + 10 return $money->formatTo('en_GB'); + + 11 }); + + 12} + + + 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: + + + + 1'apples' => 'There is one apple|There are many apples', + + + 'apples' => 'There is one apple|There are many apples', + +Of course, pluralization is also supported when using translation strings as +keys: + + + + 1{ + + 2 "There is one apple|There are many apples": "Hay una manzana|Hay muchas manzanas" + + 3} + + + { + "There is one apple|There are many apples": "Hay una manzana|Hay muchas manzanas" + } + +You may even create more complex pluralization rules which specify translation +strings for multiple ranges of values: + + + + 1'apples' => '{0} There are none|[1,19] There are some|[20,*] There are many', + + + '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: + + + + 1echo trans_choice('messages.apples', 10); + + + 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: + + + + 1'minutes_ago' => '{1} :value minute ago|[2,*] :value minutes ago', + + 2  + + 3echo trans_choice('time.minutes_ago', 5, ['value' => 5]); + + + 'minutes_ago' => '{1} :value minute ago|[2,*] :value minutes ago', + + 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: + + + + 1'apples' => '{0} There are none|{1} There is one|[2,*] There are :count', + + + 'apples' => '{0} There are none|{1} There is one|[2,*] There are :count', + +## Overriding Package Language Files + +Some packages may ship with their own language files. Instead of changing the +package's core files to tweak these lines, you may override them by placing +files in the `lang/vendor/{package}/{locale}` directory. + +So, for example, if you need to override the English translation strings in +`messages.php` for a package named `skyrim/hearthfire`, you should place a +language file at: `lang/vendor/hearthfire/en/messages.php`. Within this file, +you should only define the translation strings you wish to override. Any +translation strings you don't override will still be loaded from the package's +original language files. + diff --git a/output/12.x/logging.md b/output/12.x/logging.md new file mode 100644 index 0000000..2935f29 --- /dev/null +++ b/output/12.x/logging.md @@ -0,0 +1,1224 @@ +# Logging + + * Introduction + * Configuration + * Available Channel Drivers + * Channel Prerequisites + * Logging Deprecation Warnings + * Building Log Stacks + * Writing Log Messages + * Contextual Information + * Writing to Specific Channels + * Monolog Channel Customization + * Customizing Monolog for Channels + * Creating Monolog Handler Channels + * Creating Custom Channels via Factories + * Tailing Log Messages Using Pail + * Installation + * Usage + * Filtering Logs + +## Introduction + +To help you learn more about what's happening within your application, Laravel +provides robust logging services that allow you to log messages to files, the +system error log, and even to Slack to notify your entire team. + +Laravel logging is based on "channels". Each channel represents a specific way +of writing log information. For example, the `single` channel writes log files +to a single log file, while the `slack` channel sends log messages to Slack. +Log messages may be written to multiple channels based on their severity. + +Under the hood, Laravel utilizes the +[Monolog](https://github.com/Seldaek/monolog) library, which provides support +for a variety of powerful log handlers. Laravel makes it a cinch to configure +these handlers, allowing you to mix and match them to customize your +application's log handling. + +## Configuration + +All of the configuration options that control your application's logging +behavior are housed in the `config/logging.php` configuration file. This file +allows you to configure your application's log channels, so be sure to review +each of the available channels and their options. We'll review a few common +options below. + +By default, Laravel will use the `stack` channel when logging messages. The +`stack` channel is used to aggregate multiple log channels into a single +channel. For more information on building stacks, check out the documentation +below. + +### Available Channel Drivers + +Each log channel is powered by a "driver". The driver determines how and where +the log message is actually recorded. The following log channel drivers are +available in every Laravel application. An entry for most of these drivers is +already present in your application's `config/logging.php` configuration file, +so be sure to review this file to become familiar with its contents: + +Name | Description +---|--- +`custom` | A driver that calls a specified factory to create a channel. +`daily` | A `RotatingFileHandler` based Monolog driver which rotates daily. +`errorlog` | An `ErrorLogHandler` based Monolog driver. +`monolog` | A Monolog factory driver that may use any supported Monolog handler. +`papertrail` | A `SyslogUdpHandler` based Monolog driver. +`single` | A single file or path based logger channel (`StreamHandler`). +`slack` | A `SlackWebhookHandler` based Monolog driver. +`stack` | A wrapper to facilitate creating "multi-channel" channels. +`syslog` | A `SyslogHandler` based Monolog driver. + +Check out the documentation on advanced channel customization to learn more +about the `monolog` and `custom` drivers. + +#### Configuring the Channel Name + +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: + + + + 1'stack' => [ + + 2 'driver' => 'stack', + + 3 'name' => 'channel-name', + + 4 'channels' => ['single', 'slack'], + + 5], + + + 'stack' => [ + 'driver' => 'stack', + 'name' => 'channel-name', + 'channels' => ['single', 'slack'], + ], + +### Channel Prerequisites + +#### Configuring the Single and Daily Channels + +The `single` and `daily` channels have three optional configuration options: +`bubble`, `permission`, and `locking`. + +Name | Description | Default +---|---|--- +`bubble` | Indicates if messages should bubble up to other channels after being handled. | `true` +`locking` | Attempt to lock the log file before writing to it. | `false` +`permission` | The log file's permissions. | `0644` + +Additionally, the retention policy for the `daily` channel can be configured +via the `LOG_DAILY_DAYS` environment variable or by setting the `days` +configuration option. + +Name | Description | Default +---|---|--- +`days` | The number of days that daily log files should be retained. | `14` + +#### Configuring the Papertrail Channel + +The `papertrail` channel requires `host` and `port` configuration options. +These may be defined via the `PAPERTRAIL_URL` and `PAPERTRAIL_PORT` +environment variables. You can obtain these values from +[Papertrail](https://help.papertrailapp.com/kb/configuration/configuring- +centralized-logging-from-php-apps/#send-events-from-php-app). + +#### Configuring the Slack Channel + +The `slack` channel requires a `url` configuration option. This value may be +defined via the `LOG_SLACK_WEBHOOK_URL` environment variable. This URL should +match a URL for an [incoming webhook](https://slack.com/apps/A0F7XDUAZ- +incoming-webhooks) that you have configured for your Slack team. + +By default, Slack will only receive logs at the `critical` level and above; +however, you can adjust this using the `LOG_LEVEL` environment variable or by +modifying the `level` configuration option within your Slack log channel's +configuration array. + +### Logging Deprecation Warnings + +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: + + + + 1'deprecations' => [ + + 2 'channel' => env('LOG_DEPRECATIONS_CHANNEL', 'null'), + + 3 'trace' => env('LOG_DEPRECATIONS_TRACE', false), + + 4], + + 5  + + 6'channels' => [ + + 7 // ... + + 8] + + + 'deprecations' => [ + 'channel' => env('LOG_DEPRECATIONS_CHANNEL', 'null'), + 'trace' => env('LOG_DEPRECATIONS_TRACE', false), + ], + + '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: + + + + 1'channels' => [ + + 2 'deprecations' => [ + + 3 'driver' => 'single', + + 4 'path' => storage_path('logs/php-deprecation-warnings.log'), + + 5 ], + + 6], + + + 'channels' => [ + 'deprecations' => [ + 'driver' => 'single', + 'path' => storage_path('logs/php-deprecation-warnings.log'), + ], + ], + +## Building Log Stacks + +As mentioned previously, the `stack` driver allows you to combine multiple +channels into a single log channel for convenience. To illustrate how to use +log stacks, let's take a look at an example configuration that you might see +in a production application: + + + + 1'channels' => [ + + 2 'stack' => [ + + 3 'driver' => 'stack', + + 4 'channels' => ['syslog', 'slack'], + + 5 'ignore_exceptions' => false, + + 6 ], + + 7  + + 8 'syslog' => [ + + 9 'driver' => 'syslog', + + 10 'level' => env('LOG_LEVEL', 'debug'), + + 11 'facility' => env('LOG_SYSLOG_FACILITY', LOG_USER), + + 12 'replace_placeholders' => true, + + 13 ], + + 14  + + 15 'slack' => [ + + 16 'driver' => 'slack', + + 17 'url' => env('LOG_SLACK_WEBHOOK_URL'), + + 18 'username' => env('LOG_SLACK_USERNAME', 'Laravel Log'), + + 19 'emoji' => env('LOG_SLACK_EMOJI', ':boom:'), + + 20 'level' => env('LOG_LEVEL', 'critical'), + + 21 'replace_placeholders' => true, + + 22 ], + + 23], + + + 'channels' => [ + 'stack' => [ + 'driver' => 'stack', + 'channels' => ['syslog', 'slack'], + 'ignore_exceptions' => false, + ], + + 'syslog' => [ + 'driver' => 'syslog', + 'level' => env('LOG_LEVEL', 'debug'), + 'facility' => env('LOG_SYSLOG_FACILITY', LOG_USER), + 'replace_placeholders' => true, + ], + + 'slack' => [ + 'driver' => 'slack', + 'url' => env('LOG_SLACK_WEBHOOK_URL'), + 'username' => env('LOG_SLACK_USERNAME', 'Laravel Log'), + 'emoji' => env('LOG_SLACK_EMOJI', ':boom:'), + 'level' => env('LOG_LEVEL', 'critical'), + 'replace_placeholders' => true, + ], + ], + +Let's dissect this configuration. First, notice our `stack` channel aggregates +two other channels via its `channels` option: `syslog` and `slack`. So, when +logging messages, both of these channels will have the opportunity to log the +message. However, as we will see below, whether these channels actually log +the message may be determined by the message's severity / "level". + +#### Log Levels + +Take note of the `level` configuration option present on the `syslog` and +`slack` channel configurations in the example above. This option determines +the minimum "level" a message must be in order to be logged by the channel. +Monolog, which powers Laravel's logging services, offers all of the log levels +defined in the [RFC 5424 specification](https://tools.ietf.org/html/rfc5424). +In descending order of severity, these log levels are: **emergency** , +**alert** , **critical** , **error** , **warning** , **notice** , **info** , +and **debug**. + +So, imagine we log a message using the `debug` method: + + + + 1Log::debug('An informational message.'); + + + 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: + + + + 1Log::emergency('The system is down!'); + + + Log::emergency('The system is down!'); + +## Writing Log Messages + +You may write information to the logs using the `Log` +[facade](/docs/12.x/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** +: + + + + 1use Illuminate\Support\Facades\Log; + + 2  + + 3Log::emergency($message); + + 4Log::alert($message); + + 5Log::critical($message); + + 6Log::error($message); + + 7Log::warning($message); + + 8Log::notice($message); + + 9Log::info($message); + + 10Log::debug($message); + + + 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: + + + + 1 $id]); + + 17  + + 18 return view('user.profile', [ + + 19 'user' => User::findOrFail($id) + + 20 ]); + + 21 } + + 22} + + + $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: + + + + 1use Illuminate\Support\Facades\Log; + + 2  + + 3Log::info('User {id} failed to login.', ['id' => $user->id]); + + + use Illuminate\Support\Facades\Log; + + 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: + + + + 1 $requestId + + 24 ]); + + 25  + + 26 $response = $next($request); + + 27  + + 28 $response->headers->set('Request-Id', $requestId); + + 29  + + 30 return $response; + + 31 } + + 32} + + + $requestId + ]); + + $response = $next($request); + + $response->headers->set('Request-Id', $requestId); + + 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: + + + + 1 $requestId + + 24 ]); + + 25  + + 26 // ... + + 27 } + + 28} + + + $requestId + ]); + + // ... + } + } + +If you need to share log context while processing queued jobs, you may utilize +[job middleware](/docs/12.x/queues#job-middleware). + +### Writing to Specific 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: + + + + 1use Illuminate\Support\Facades\Log; + + 2  + + 3Log::channel('slack')->info('Something happened!'); + + + use Illuminate\Support\Facades\Log; + + 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: + + + + 1Log::stack(['single', 'slack'])->info('Something happened!'); + + + 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: + + + + 1use Illuminate\Support\Facades\Log; + + 2  + + 3Log::build([ + + 4 'driver' => 'single', + + 5 'path' => storage_path('logs/custom.log'), + + 6])->info('Something happened!'); + + + use Illuminate\Support\Facades\Log; + + 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: + + + + 1use Illuminate\Support\Facades\Log; + + 2  + + 3$channel = Log::build([ + + 4 'driver' => 'single', + + 5 'path' => storage_path('logs/custom.log'), + + 6]); + + 7  + + 8Log::stack(['slack', $channel])->info('Something happened!'); + + + use Illuminate\Support\Facades\Log; + + $channel = Log::build([ + 'driver' => 'single', + 'path' => storage_path('logs/custom.log'), + ]); + + Log::stack(['slack', $channel])->info('Something happened!'); + +## Monolog Channel Customization + +### Customizing Monolog for Channels + +Sometimes you may need complete control over how Monolog is configured for an +existing channel. For example, you may want to configure a custom Monolog +`FormatterInterface` implementation for Laravel's built-in `single` channel. + +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: + + + + 1'single' => [ + + 2 'driver' => 'single', + + 3 'tap' => [App\Logging\CustomizeFormatter::class], + + 4 'path' => storage_path('logs/laravel.log'), + + 5 'level' => env('LOG_LEVEL', 'debug'), + + 6 'replace_placeholders' => true, + + 7], + + + '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: + + + + 1getHandlers() as $handler) { + + 16 $handler->setFormatter(new LineFormatter( + + 17 '[%datetime%] %channel%.%level_name%: %message% %context% %extra%' + + 18 )); + + 19 } + + 20 } + + 21} + + + getHandlers() as $handler) { + $handler->setFormatter(new LineFormatter( + '[%datetime%] %channel%.%level_name%: %message% %context% %extra%' + )); + } + } + } + +All of your "tap" classes are resolved by the [service +container](/docs/12.x/container), so any constructor dependencies they require +will automatically be injected. + +### Creating Monolog Handler Channels + +Monolog has a variety of [available +handlers](https://github.com/Seldaek/monolog/tree/main/src/Monolog/Handler) +and Laravel does not include a built-in channel for each one. In some cases, +you may wish to create a custom channel that is merely an instance of a +specific Monolog handler that does not have a corresponding Laravel log +driver. These channels can be easily created using the `monolog` driver. + +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 `handler_with` +configuration option: + + + + 1'logentries' => [ + + 2 'driver' => 'monolog', + + 3 'handler' => Monolog\Handler\SyslogUdpHandler::class, + + 4 'handler_with' => [ + + 5 'host' => 'my.logentries.internal.datahubhost.company.com', + + 6 'port' => '10000', + + 7 ], + + 8], + + + 'logentries' => [ + 'driver' => 'monolog', + 'handler' => Monolog\Handler\SyslogUdpHandler::class, + 'handler_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: + + + + 1'browser' => [ + + 2 'driver' => 'monolog', + + 3 'handler' => Monolog\Handler\BrowserConsoleHandler::class, + + 4 'formatter' => Monolog\Formatter\HtmlFormatter::class, + + 5 'formatter_with' => [ + + 6 'dateFormat' => 'Y-m-d', + + 7 ], + + 8], + + + '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`: + + + + 1'newrelic' => [ + + 2 'driver' => 'monolog', + + 3 'handler' => Monolog\Handler\NewRelicHandler::class, + + 4 'formatter' => 'default', + + 5], + + + 'newrelic' => [ + 'driver' => 'monolog', + 'handler' => Monolog\Handler\NewRelicHandler::class, + 'formatter' => 'default', + ], + +#### Monolog Processors + +Monolog can also process messages before logging them. You can create your own +processors or use the [existing processors offered by +Monolog](https://github.com/Seldaek/monolog/tree/main/src/Monolog/Processor). + +If you would like to customize the processors for a `monolog` driver, add a +`processors` configuration value to your channel's configuration: + + + + 1'memory' => [ + + 2 'driver' => 'monolog', + + 3 'handler' => Monolog\Handler\StreamHandler::class, + + 4 'handler_with' => [ + + 5 'stream' => 'php://stderr', + + 6 ], + + 7 'processors' => [ + + 8 // Simple syntax... + + 9 Monolog\Processor\MemoryUsageProcessor::class, + + 10  + + 11 // With options... + + 12 [ + + 13 'processor' => Monolog\Processor\PsrLogMessageProcessor::class, + + 14 'with' => ['removeUsedContextFields' => true], + + 15 ], + + 16 ], + + 17], + + + 'memory' => [ + 'driver' => 'monolog', + 'handler' => Monolog\Handler\StreamHandler::class, + 'handler_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: + + + + 1'channels' => [ + + 2 'example-custom-channel' => [ + + 3 'driver' => 'custom', + + 4 'via' => App\Logging\CreateCustomLogger::class, + + 5 ], + + 6], + + + '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: + + + + 1 env('MAIL_MAILER', 'mailgun'), + + + 'default' => env('MAIL_MAILER', 'mailgun'), + +Second, add the following configuration array to your array of `mailers`: + + + + 1'mailgun' => [ + + 2 'transport' => 'mailgun', + + 3 // 'client' => [ + + 4 // 'timeout' => 5, + + 5 // ], + + 6], + + + 'mailgun' => [ + 'transport' => 'mailgun', + // 'client' => [ + // 'timeout' => 5, + // ], + ], + +After configuring your application's default mailer, add the following options +to your `config/services.php` configuration file: + + + + 1'mailgun' => [ + + 2 'domain' => env('MAILGUN_DOMAIN'), + + 3 'secret' => env('MAILGUN_SECRET'), + + 4 'endpoint' => env('MAILGUN_ENDPOINT', 'api.mailgun.net'), + + 5 'scheme' => 'https', + + 6], + + + '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/docs/mailgun/api-reference/#mailgun- +regions), you may define your region's endpoint in the `services` +configuration file: + + + + 1'mailgun' => [ + + 2 'domain' => env('MAILGUN_DOMAIN'), + + 3 'secret' => env('MAILGUN_SECRET'), + + 4 'endpoint' => env('MAILGUN_ENDPOINT', 'api.eu.mailgun.net'), + + 5 'scheme' => 'https', + + 6], + + + 'mailgun' => [ + 'domain' => env('MAILGUN_DOMAIN'), + 'secret' => env('MAILGUN_SECRET'), + 'endpoint' => env('MAILGUN_ENDPOINT', 'api.eu.mailgun.net'), + 'scheme' => 'https', + ], + +#### Postmark Driver + +To use the [Postmark](https://postmarkapp.com/) driver, install Symfony's +Postmark Mailer transport via Composer: + + + + 1composer require symfony/postmark-mailer symfony/http-client + + + 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: + + + + 1'postmark' => [ + + 2 'token' => env('POSTMARK_TOKEN'), + + 3], + + + '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: + + + + 1'postmark' => [ + + 2 'transport' => 'postmark', + + 3 'message_stream_id' => env('POSTMARK_MESSAGE_STREAM_ID'), + + 4 // 'client' => [ + + 5 // 'timeout' => 5, + + 6 // ], + + 7], + + + '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. + +#### Resend Driver + +To use the [Resend](https://resend.com/) driver, install Resend's PHP SDK via +Composer: + + + + 1composer require resend/resend-php + + + 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: + + + + 1'resend' => [ + + 2 'key' => env('RESEND_KEY'), + + 3], + + + 'resend' => [ + 'key' => env('RESEND_KEY'), + ], + +#### SES Driver + +To use the Amazon SES driver you must first install the Amazon AWS SDK for +PHP. You may install this library via the Composer package manager: + + + + 1composer require aws/aws-sdk-php + + + 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: + + + + 1'ses' => [ + + 2 'key' => env('AWS_ACCESS_KEY_ID'), + + 3 'secret' => env('AWS_SECRET_ACCESS_KEY'), + + 4 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'), + + 5], + + + '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: + + + + 1'ses' => [ + + 2 'key' => env('AWS_ACCESS_KEY_ID'), + + 3 'secret' => env('AWS_SECRET_ACCESS_KEY'), + + 4 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'), + + 5 'token' => env('AWS_SESSION_TOKEN'), + + 6], + + + '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 method of a mail message: + + + + 1/** + + 2 * Get the message headers. + + 3 */ + + 4public function headers(): Headers + + 5{ + + 6 return new Headers( + + 7 text: [ + + 8 'X-Ses-List-Management-Options' => 'contactListName=MyContactList;topicName=MyTopic', + + 9 ], + + 10 ); + + 11} + + + /** + * Get the message headers. + */ + public function headers(): Headers + { + return new Headers( + text: [ + 'X-Ses-List-Management-Options' => 'contactListName=MyContactList;topicName=MyTopic', + ], + ); + } + +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: + + + + 1'ses' => [ + + 2 'key' => env('AWS_ACCESS_KEY_ID'), + + 3 'secret' => env('AWS_SECRET_ACCESS_KEY'), + + 4 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'), + + 5 'options' => [ + + 6 'ConfigurationSetName' => 'MyConfigurationSet', + + 7 'EmailTags' => [ + + 8 ['Name' => 'foo', 'Value' => 'bar'], + + 9 ], + + 10 ], + + 11], + + + '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 + +[MailerSend](https://www.mailersend.com/), a transactional email and SMS +service, maintains their own API based mail driver for Laravel. The package +containing the driver may be installed via the Composer package manager: + + + + 1composer require mailersend/laravel-driver + + + composer require mailersend/laravel-driver + +Once the package is installed, add the `MAILERSEND_API_KEY` environment +variable to your application's `.env` file. In addition, the `MAIL_MAILER` +environment variable should be defined as `mailersend`: + + + + 1MAIL_MAILER=mailersend + + 2MAIL_FROM_ADDRESS[[email protected]](/cdn-cgi/l/email-protection) + + 3MAIL_FROM_NAME="App Name" + + 4  + + 5MAILERSEND_API_KEY=your-api-key + + + MAIL_MAILER=mailersend + [[email protected]](/cdn-cgi/l/email-protection) + MAIL_FROM_NAME="App Name" + + MAILERSEND_API_KEY=your-api-key + +Finally, add MailerSend to the `mailers` array in your application's +`config/mail.php` configuration file: + + + + 1'mailersend' => [ + + 2 'transport' => 'mailersend', + + 3], + + + 'mailersend' => [ + 'transport' => 'mailersend', + ], + +To learn more about MailerSend, including how to use hosted templates, consult +the [MailerSend driver +documentation](https://github.com/mailersend/mailersend-laravel-driver#usage). + +### Failover Configuration + +Sometimes, an external service you have configured to send your application's +mail may be down. In these cases, it can be useful to define one or more +backup mail delivery configurations that will be used in case your primary +delivery driver is down. + +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: + + + + 1'mailers' => [ + + 2 'failover' => [ + + 3 'transport' => 'failover', + + 4 'mailers' => [ + + 5 'postmark', + + 6 'mailgun', + + 7 'sendmail', + + 8 ], + + 9 'retry_after' => 60, + + 10 ], + + 11  + + 12 // ... + + 13], + + + 'mailers' => [ + 'failover' => [ + 'transport' => 'failover', + 'mailers' => [ + 'postmark', + 'mailgun', + 'sendmail', + ], + 'retry_after' => 60, + ], + + // ... + ], + +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: + + + + 1'default' => env('MAIL_MAILER', 'failover'), + + + '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: + + + + 1'mailers' => [ + + 2 'roundrobin' => [ + + 3 'transport' => 'roundrobin', + + 4 'mailers' => [ + + 5 'ses', + + 6 'postmark', + + 7 ], + + 8 'retry_after' => 60, + + 9 ], + + 10  + + 11 // ... + + 12], + + + 'mailers' => [ + 'roundrobin' => [ + 'transport' => 'roundrobin', + 'mailers' => [ + 'ses', + 'postmark', + ], + 'retry_after' => 60, + ], + + // ... + ], + +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: + + + + 1'default' => env('MAIL_MAILER', 'roundrobin'), + + + '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\))_. + +## Generating Mailables + +When building Laravel applications, each type of email sent by your +application is represented as a "mailable" class. These classes are stored in +the `app/Mail` directory. Don't worry if you don't see this directory in your +application, since it will be generated for you when you create your first +mailable class using the `make:mail` Artisan command: + + + + 1php artisan make:mail OrderShipped + + + php artisan make:mail OrderShipped + +## Writing Mailables + +Once you have generated a mailable class, open it up so we can explore its +contents. Mailable class configuration is done in several methods, including +the `envelope`, `content`, and `attachments` methods. + +The `envelope` method returns an `Illuminate\Mail\Mailables\Envelope` object +that defines the subject and, sometimes, the recipients of the message. The +`content` method returns an `Illuminate\Mail\Mailables\Content` object that +defines the [Blade template](/docs/12.x/blade) that will be used to generate +the message content. + +### Configuring the Sender + +#### Using the Envelope + +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: + + + + 1use Illuminate\Mail\Mailables\Address; + + 2use Illuminate\Mail\Mailables\Envelope; + + 3  + + 4/** + + 5 * Get the message envelope. + + 6 */ + + 7public function envelope(): Envelope + + 8{ + + 9 return new Envelope( + + 10 from: new Address('[[email protected]](/cdn-cgi/l/email-protection)', 'Jeffrey Way'), + + 11 subject: 'Order Shipped', + + 12 ); + + 13} + + + use Illuminate\Mail\Mailables\Address; + use Illuminate\Mail\Mailables\Envelope; + + /** + * Get the message envelope. + */ + public function envelope(): Envelope + { + return new Envelope( + from: new Address('[[email protected]](/cdn-cgi/l/email-protection)', 'Jeffrey Way'), + subject: 'Order Shipped', + ); + } + +If you would like, you may also specify a `replyTo` address: + + + + 1return new Envelope( + + 2 from: new Address('[[email protected]](/cdn-cgi/l/email-protection)', 'Jeffrey Way'), + + 3 replyTo: [ + + 4 new Address('[[email protected]](/cdn-cgi/l/email-protection)', 'Taylor Otwell'), + + 5 ], + + 6 subject: 'Order Shipped', + + 7); + + + return new Envelope( + from: new Address('[[email protected]](/cdn-cgi/l/email-protection)', 'Jeffrey Way'), + replyTo: [ + new Address('[[email protected]](/cdn-cgi/l/email-protection)', '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: + + + + 1'from' => [ + + 2 'address' => env('MAIL_FROM_ADDRESS', '[[email protected]](/cdn-cgi/l/email-protection)'), + + 3 'name' => env('MAIL_FROM_NAME', 'Example'), + + 4], + + + 'from' => [ + 'address' => env('MAIL_FROM_ADDRESS', '[[email protected]](/cdn-cgi/l/email-protection)'), + 'name' => env('MAIL_FROM_NAME', 'Example'), + ], + +In addition, you may define a global "reply_to" address within your +`config/mail.php` configuration file: + + + + 1'reply_to' => [ + + 2 'address' => '[[email protected]](/cdn-cgi/l/email-protection)', + + 3 'name' => 'App Name', + + 4], + + + 'reply_to' => [ + 'address' => '[[email protected]](/cdn-cgi/l/email-protection)', + '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/12.x/blade) to render its +contents, you have the full power and convenience of the Blade templating +engine when building your email's HTML: + + + + 1/** + + 2 * Get the message content definition. + + 3 */ + + 4public function content(): Content + + 5{ + + 6 return new Content( + + 7 view: 'mail.orders.shipped', + + 8 ); + + 9} + + + /** + * Get the message content definition. + */ + public function content(): Content + { + return new Content( + view: 'mail.orders.shipped', + ); + } + +You may wish to create a `resources/views/mail` directory to house all of your +email templates; however, you are free to place them wherever you wish within +your `resources/views` directory. + +#### Plain Text Emails + +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: + + + + 1/** + + 2 * Get the message content definition. + + 3 */ + + 4public function content(): Content + + 5{ + + 6 return new Content( + + 7 view: 'mail.orders.shipped', + + 8 text: 'mail.orders.shipped-text' + + 9 ); + + 10} + + + /** + * 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: + + + + 1return new Content( + + 2 html: 'mail.orders.shipped', + + 3 text: 'mail.orders.shipped-text' + + 4); + + + return new Content( + html: 'mail.orders.shipped', + text: 'mail.orders.shipped-text' + ); + +### View Data + +#### Via Public Properties + +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: + + + + 1 + + 2 Price: {{ $order->price }} + + 3 + + +
    + 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: + + + + 1 $this->order->name, + + 31 'orderPrice' => $this->order->price, + + 32 ], + + 33 ); + + 34 } + + 35} + + + $this->order->name, + 'orderPrice' => $this->order->price, + ], + ); + } + } + +Once the data has been passed via the `with` parameter, it will automatically +be available in your view, so you may access it like you would access any +other data in your Blade templates: + + + + 1
    + + 2 Price: {{ $orderPrice }} + + 3
    + + +
    + 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: + + + + 1use Illuminate\Mail\Mailables\Attachment; + + 2  + + 3/** + + 4 * Get the attachments for the message. + + 5 * + + 6 * @return array + + 7 */ + + 8public function attachments(): array + + 9{ + + 10 return [ + + 11 Attachment::fromPath('/path/to/file'), + + 12 ]; + + 13} + + + use Illuminate\Mail\Mailables\Attachment; + + /** + * 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: + + + + 1/** + + 2 * Get the attachments for the message. + + 3 * + + 4 * @return array + + 5 */ + + 6public function attachments(): array + + 7{ + + 8 return [ + + 9 Attachment::fromPath('/path/to/file') + + 10 ->as('name.pdf') + + 11 ->withMime('application/pdf'), + + 12 ]; + + 13} + + + /** + * 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/12.x/filesystem), you may attach it to the email using the +`fromStorage` attachment method: + + + + 1/** + + 2 * Get the attachments for the message. + + 3 * + + 4 * @return array + + 5 */ + + 6public function attachments(): array + + 7{ + + 8 return [ + + 9 Attachment::fromStorage('/path/to/file'), + + 10 ]; + + 11} + + + /** + * 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: + + + + 1/** + + 2 * Get the attachments for the message. + + 3 * + + 4 * @return array + + 5 */ + + 6public function attachments(): array + + 7{ + + 8 return [ + + 9 Attachment::fromStorage('/path/to/file') + + 10 ->as('name.pdf') + + 11 ->withMime('application/pdf'), + + 12 ]; + + 13} + + + /** + * 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: + + + + 1/** + + 2 * Get the attachments for the message. + + 3 * + + 4 * @return array + + 5 */ + + 6public function attachments(): array + + 7{ + + 8 return [ + + 9 Attachment::fromStorageDisk('s3', '/path/to/file') + + 10 ->as('name.pdf') + + 11 ->withMime('application/pdf'), + + 12 ]; + + 13} + + + /** + * 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: + + + + 1/** + + 2 * Get the attachments for the message. + + 3 * + + 4 * @return array + + 5 */ + + 6public function attachments(): array + + 7{ + + 8 return [ + + 9 Attachment::fromData(fn () => $this->pdf, 'Report.pdf') + + 10 ->withMime('application/pdf'), + + 11 ]; + + 12} + + + /** + * 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 + +Embedding inline images into your emails is typically cumbersome; however, +Laravel provides a convenient way to attach images to your emails. To embed an +inline image, use the `embed` method on the `$message` variable within your +email template. Laravel automatically makes the `$message` variable available +to all of your email templates, so you don't need to worry about passing it in +manually: + + + + 1 + + 2 Here is an image: + + 3  + + 4 + + 5 + + + + Here is an image: + + + + +The `$message` variable is not available in plain-text message templates since +plain-text messages do not utilize inline attachments. + +#### Embedding Raw Data Attachments + +If you already have a raw image data string you wish to embed into an email +template, you may call the `embedData` method on the `$message` variable. When +calling the `embedData` method, you will need to provide a filename that +should be assigned to the embedded image: + + + + 1 + + 2 Here is an image from raw data: + + 3  + + 4 + + 5 + + + + Here is an image from raw data: + + + + +### Attachable Objects + +While attaching files to messages via simple string paths is often sufficient, +in many cases the attachable entities within your application are represented +by classes. For example, if your application is attaching a photo to a +message, your application may also have a `Photo` model that represents that +photo. When that is the case, wouldn't it be convenient to simply pass the +`Photo` model to the `attach` method? Attachable objects allow you to do just +that. + +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: + + + + 1 + + 5 */ + + 6public function attachments(): array + + 7{ + + 8 return [$this->photo]; + + 9} + + + /** + * 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/12.x/filesystem): + + + + 1// Create an attachment from a file on your default disk... + + 2return Attachment::fromStorage($this->path); + + 3  + + 4// Create an attachment from a file on a specific disk... + + 5return Attachment::fromStorageDisk('backblaze', $this->path); + + + // 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); + +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: + + + + 1return Attachment::fromData(fn () => $this->content, 'Photo Name'); + + + 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: + + + + 1return Attachment::fromPath('/path/to/file') + + 2 ->as('Photo Name') + + 3 ->withMime('image/jpeg'); + + + return Attachment::fromPath('/path/to/file') + ->as('Photo Name') + ->withMime('image/jpeg'); + +### Headers + +Sometimes you may need to attach additional headers to the outgoing message. +For instance, you may need to set a custom `Message-Id` or other arbitrary +text headers. + +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: + + + + 1use Illuminate\Mail\Mailables\Headers; + + 2  + + 3/** + + 4 * Get the message headers. + + 5 */ + + 6public function headers(): Headers + + 7{ + + 8 return new Headers( + + 9 messageId: '[[email protected]](/cdn-cgi/l/email-protection)', + + 10 references: ['[[email protected]](/cdn-cgi/l/email-protection)'], + + 11 text: [ + + 12 'X-Custom-Header' => 'Custom Value', + + 13 ], + + 14 ); + + 15} + + + use Illuminate\Mail\Mailables\Headers; + + /** + * Get the message headers. + */ + public function headers(): Headers + { + return new Headers( + messageId: '[[email protected]](/cdn-cgi/l/email-protection)', + references: ['[[email protected]](/cdn-cgi/l/email-protection)'], + 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: + + + + 1use Illuminate\Mail\Mailables\Envelope; + + 2  + + 3/** + + 4 * Get the message envelope. + + 5 * + + 6 * @return \Illuminate\Mail\Mailables\Envelope + + 7 */ + + 8public function envelope(): Envelope + + 9{ + + 10 return new Envelope( + + 11 subject: 'Order Shipped', + + 12 tags: ['shipment'], + + 13 metadata: [ + + 14 'order_id' => $this->order->id, + + 15 ], + + 16 ); + + 17} + + + 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, + ], + ); + } + +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/#tags) and +[metadata](https://documentation.mailgun.com/docs/mailgun/user-manual/sending- +messages/#attaching-metadata-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). + +If your application is using Amazon SES to send emails, you should use the +`metadata` method to attach [SES +"tags"](https://docs.aws.amazon.com/ses/latest/APIReference/API_MessageTag.html) +to the message. + +### Customizing the Symfony Message + +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: + + + + 1use Illuminate\Mail\Mailables\Envelope; + + 2use Symfony\Component\Mime\Email; + + 3  + + 4/** + + 5 * Get the message envelope. + + 6 */ + + 7public function envelope(): Envelope + + 8{ + + 9 return new Envelope( + + 10 subject: 'Order Shipped', + + 11 using: [ + + 12 function (Email $message) { + + 13 // ... + + 14 }, + + 15 ] + + 16 ); + + 17} + + + 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) { + // ... + }, + ] + ); + } + +## Markdown Mailables + +Markdown mailable messages allow you to take advantage of the pre-built +templates and components of [mail +notifications](/docs/12.x/notifications#mail-notifications) in your mailables. +Since the messages are written in Markdown, Laravel is able to render +beautiful, responsive HTML templates for the messages while also automatically +generating a plain-text counterpart. + +### Generating Markdown Mailables + +To generate a mailable with a corresponding Markdown template, you may use the +`--markdown` option of the `make:mail` Artisan command: + + + + 1php artisan make:mail OrderShipped --markdown=mail.orders.shipped + + + 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: + + + + 1use Illuminate\Mail\Mailables\Content; + + 2  + + 3/** + + 4 * Get the message content definition. + + 5 */ + + 6public function content(): Content + + 7{ + + 8 return new Content( + + 9 markdown: 'mail.orders.shipped', + + 10 with: [ + + 11 'url' => $this->orderUrl, + + 12 ], + + 13 ); + + 14} + + + 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, + ], + ); + } + +### Writing Markdown Messages + +Markdown mailables use a combination of Blade components and Markdown syntax +which allow you to easily construct mail messages while leveraging Laravel's +pre-built email UI components: + + + + 1 + + 2# Order Shipped + + 3  + + 4Your order has been shipped! + + 5  + + 6 + + 7View Order + + 8 + + 9  + + 10Thanks,
    + + 11{{ config('app.name') }} + + 12
    + + + + # Order Shipped + + Your order has been shipped! + + + View Order + + + Thanks,
    + {{ config('app.name') }} +
    + +Do not use excess indentation when writing Markdown emails. Per Markdown +standards, Markdown parsers will render indented content as code blocks. + +#### Button Component + +The button component renders a centered button link. The component accepts two +arguments, a `url` and an optional `color`. Supported colors are `primary`, +`success`, and `error`. You may add as many button components to a message as +you wish: + + + + 1 + + 2View Order + + 3 + + + + View Order + + +#### Panel Component + +The panel component renders the given block of text in a panel that has a +slightly different background color than the rest of the message. This allows +you to draw attention to a given block of text: + + + + 1 + + 2This is the panel content. + + 3 + + + + This is the panel content. + + +#### Table Component + +The table component allows you to transform a Markdown table into an HTML +table. The component accepts the Markdown table as its content. Table column +alignment is supported using the default Markdown table alignment syntax: + + + + 1 + + 2| Laravel | Table | Example | + + 3| ------------- | :-----------: | ------------: | + + 4| Col 2 is | Centered | $10 | + + 5| Col 3 is | Right-Aligned | $20 | + + 6 + + + + | Laravel | Table | Example | + | ------------- | :-----------: | ------------: | + | Col 2 is | Centered | $10 | + | Col 3 is | Right-Aligned | $20 | + + +### Customizing the Components + +You may export all of the Markdown mail components to your own application for +customization. To export the components, use the `vendor:publish` Artisan +command to publish the `laravel-mail` asset tag: + + + + 1php artisan vendor:publish --tag=laravel-mail + + + php artisan vendor:publish --tag=laravel-mail + +This command will publish the Markdown mail components to the +`resources/views/vendor/mail` directory. The `mail` directory will contain an +`html` and a `text` directory, each containing their respective +representations of every available component. You are free to customize these +components however you like. + +#### Customizing the CSS + +After exporting the components, the `resources/views/vendor/mail/html/themes` +directory will contain a `default.css` file. You may customize the CSS in this +file and your styles will automatically be converted to inline CSS styles +within the HTML representations of your Markdown mail messages. + +If you would like to build an entirely new theme for Laravel's Markdown +components, you may place a CSS file within the `html/themes` directory. After +naming and saving your CSS file, update the `theme` option of your +application's `config/mail.php` configuration file to match the name of your +new theme. + +To customize the theme for an individual mailable, you may set the `$theme` +property of the mailable class to the name of the theme that should be used +when sending that mailable. + +## Sending Mail + +To send a message, use the `to` method on the `Mail` +[facade](/docs/12.x/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: + + + + 1order_id); + + 19  + + 20 // Ship the order... + + 21  + + 22 Mail::to($request->user())->send(new OrderShipped($order)); + + 23  + + 24 return redirect('/orders'); + + 25 } + + 26} + + + order_id); + + // Ship the order... + + Mail::to($request->user())->send(new OrderShipped($order)); + + 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: + + + + 1Mail::to($request->user()) + + 2 ->cc($moreUsers) + + 3 ->bcc($evenMoreUsers) + + 4 ->send(new OrderShipped($order)); + + + 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: + + + + 1foreach (['[[email protected]](/cdn-cgi/l/email-protection)', '[[email protected]](/cdn-cgi/l/email-protection)'] as $recipient) { + + 2 Mail::to($recipient)->send(new OrderShipped($order)); + + 3} + + + foreach (['[[email protected]](/cdn-cgi/l/email-protection)', '[[email protected]](/cdn-cgi/l/email-protection)'] 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: + + + + 1Mail::mailer('postmark') + + 2 ->to($request->user()) + + 3 ->send(new OrderShipped($order)); + + + Mail::mailer('postmark') + ->to($request->user()) + ->send(new OrderShipped($order)); + +### Queueing Mail + +#### Queueing a Mail Message + +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/12.x/queues). To queue a mail message, use the `queue` method on +the `Mail` facade after specifying the message's recipients: + + + + 1Mail::to($request->user()) + + 2 ->cc($moreUsers) + + 3 ->bcc($evenMoreUsers) + + 4 ->queue(new OrderShipped($order)); + + + 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/12.x/queues) before using this feature. + +#### Delayed Message Queueing + +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: + + + + 1Mail::to($request->user()) + + 2 ->cc($moreUsers) + + 3 ->bcc($evenMoreUsers) + + 4 ->later(now()->addMinutes(10), new OrderShipped($order)); + + + 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: + + + + 1$message = (new OrderShipped($order)) + + 2 ->onConnection('sqs') + + 3 ->onQueue('emails'); + + 4  + + 5Mail::to($request->user()) + + 6 ->cc($moreUsers) + + 7 ->bcc($evenMoreUsers) + + 8 ->queue($message); + + + $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: + + + + 1use Illuminate\Contracts\Queue\ShouldQueue; + + 2  + + 3class OrderShipped extends Mailable implements ShouldQueue + + 4{ + + 5 // ... + + 6} + + + use Illuminate\Contracts\Queue\ShouldQueue; + + class OrderShipped extends Mailable implements ShouldQueue + { + // ... + } + +#### Queued Mailables and Database Transactions + +When queued mailables are dispatched within database transactions, they may be +processed by the queue before the database transaction has committed. When +this happens, any updates you have made to models or database records during +the database transaction may not yet be reflected in the database. In +addition, any models or database records created within the transaction may +not exist in the database. If your mailable depends on these models, +unexpected errors can occur when the job that sends the queued mailable is +processed. + +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: + + + + 1Mail::to($request->user())->send( + + 2 (new OrderShipped($order))->afterCommit() + + 3); + + + Mail::to($request->user())->send( + (new OrderShipped($order))->afterCommit() + ); + +Alternatively, you may call the `afterCommit` method from your mailable's +constructor: + + + + 1afterCommit(); + + 20 } + + 21} + + + afterCommit(); + } + } + +To learn more about working around these issues, please review the +documentation regarding [queued jobs and database +transactions](/docs/12.x/queues#jobs-and-database-transactions). + +#### Queued Email Failures + +When a queued email fails, the `failed` method on the queued mailable class +will be invoked if it has been defined. The `Throwable` instance that caused +the queued email to fail will be passed to the `failed` method: + + + + 1render(); + + + use App\Mail\InvoicePaid; + use App\Models\Invoice; + + $invoice = Invoice::find(1); + + 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: + + + + 1Route::get('/mailable', function () { + + 2 $invoice = App\Models\Invoice::find(1); + + 3  + + 4 return new App\Mail\InvoicePaid($invoice); + + 5}); + + + Route::get('/mailable', function () { + $invoice = App\Models\Invoice::find(1); + + return new App\Mail\InvoicePaid($invoice); + }); + +## Localizing Mailables + +Laravel allows you to send mailables in a locale other than the request's +current locale, and will even remember this locale if the mail is queued. + +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: + + + + 1Mail::to($request->user())->locale('es')->send( + + 2 new OrderShipped($order) + + 3); + + + 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: + + + + 1use Illuminate\Contracts\Translation\HasLocalePreference; + + 2  + + 3class User extends Model implements HasLocalePreference + + 4{ + + 5 /** + + 6 * Get the user's preferred locale. + + 7 */ + + 8 public function preferredLocale(): string + + 9 { + + 10 return $this->locale; + + 11 } + + 12} + + + use Illuminate\Contracts\Translation\HasLocalePreference; + + class User extends Model implements HasLocalePreference + { + /** + * Get the user's preferred locale. + */ + public function preferredLocale(): string + { + 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: + + + + 1Mail::to($request->user())->send(new OrderShipped($order)); + + + Mail::to($request->user())->send(new OrderShipped($order)); + +## Testing + +### Testing Mailable Content + +Laravel provides a variety of methods for inspecting your mailable's +structure. In addition, Laravel provides several convenient methods for +testing that your mailable contains the content that you expect: + +Pest PHPUnit + + + + 1use App\Mail\InvoicePaid; + + 2use App\Models\User; + + 3  + + 4test('mailable content', function () { + + 5 $user = User::factory()->create(); + + 6  + + 7 $mailable = new InvoicePaid($user); + + 8  + + 9 $mailable->assertFrom('[[email protected]](/cdn-cgi/l/email-protection)'); + + 10 $mailable->assertTo('[[email protected]](/cdn-cgi/l/email-protection)'); + + 11 $mailable->assertHasCc('[[email protected]](/cdn-cgi/l/email-protection)'); + + 12 $mailable->assertHasBcc('[[email protected]](/cdn-cgi/l/email-protection)'); + + 13 $mailable->assertHasReplyTo('[[email protected]](/cdn-cgi/l/email-protection)'); + + 14 $mailable->assertHasSubject('Invoice Paid'); + + 15 $mailable->assertHasTag('example-tag'); + + 16 $mailable->assertHasMetadata('key', 'value'); + + 17  + + 18 $mailable->assertSeeInHtml($user->email); + + 19 $mailable->assertDontSeeInHtml('Invoice Not Paid'); + + 20 $mailable->assertSeeInOrderInHtml(['Invoice Paid', 'Thanks']); + + 21  + + 22 $mailable->assertSeeInText($user->email); + + 23 $mailable->assertDontSeeInText('Invoice Not Paid'); + + 24 $mailable->assertSeeInOrderInText(['Invoice Paid', 'Thanks']); + + 25  + + 26 $mailable->assertHasAttachment('/path/to/file'); + + 27 $mailable->assertHasAttachment(Attachment::fromPath('/path/to/file')); + + 28 $mailable->assertHasAttachedData($pdfData, 'name.pdf', ['mime' => 'application/pdf']); + + 29 $mailable->assertHasAttachmentFromStorage('/path/to/file', 'name.pdf', ['mime' => 'application/pdf']); + + 30 $mailable->assertHasAttachmentFromStorageDisk('s3', '/path/to/file', 'name.pdf', ['mime' => 'application/pdf']); + + 31}); + + + use App\Mail\InvoicePaid; + use App\Models\User; + + test('mailable content', function () { + $user = User::factory()->create(); + + $mailable = new InvoicePaid($user); + + $mailable->assertFrom('[[email protected]](/cdn-cgi/l/email-protection)'); + $mailable->assertTo('[[email protected]](/cdn-cgi/l/email-protection)'); + $mailable->assertHasCc('[[email protected]](/cdn-cgi/l/email-protection)'); + $mailable->assertHasBcc('[[email protected]](/cdn-cgi/l/email-protection)'); + $mailable->assertHasReplyTo('[[email protected]](/cdn-cgi/l/email-protection)'); + $mailable->assertHasSubject('Invoice Paid'); + $mailable->assertHasTag('example-tag'); + $mailable->assertHasMetadata('key', 'value'); + + $mailable->assertSeeInHtml($user->email); + $mailable->assertDontSeeInHtml('Invoice Not Paid'); + $mailable->assertSeeInOrderInHtml(['Invoice Paid', 'Thanks']); + + $mailable->assertSeeInText($user->email); + $mailable->assertDontSeeInText('Invoice Not Paid'); + $mailable->assertSeeInOrderInText(['Invoice Paid', 'Thanks']); + + $mailable->assertHasAttachment('/path/to/file'); + $mailable->assertHasAttachment(Attachment::fromPath('/path/to/file')); + $mailable->assertHasAttachedData($pdfData, 'name.pdf', ['mime' => 'application/pdf']); + $mailable->assertHasAttachmentFromStorage('/path/to/file', 'name.pdf', ['mime' => 'application/pdf']); + $mailable->assertHasAttachmentFromStorageDisk('s3', '/path/to/file', 'name.pdf', ['mime' => 'application/pdf']); + }); + + + 1use App\Mail\InvoicePaid; + + 2use App\Models\User; + + 3  + + 4public function test_mailable_content(): void + + 5{ + + 6 $user = User::factory()->create(); + + 7  + + 8 $mailable = new InvoicePaid($user); + + 9  + + 10 $mailable->assertFrom('[[email protected]](/cdn-cgi/l/email-protection)'); + + 11 $mailable->assertTo('[[email protected]](/cdn-cgi/l/email-protection)'); + + 12 $mailable->assertHasCc('[[email protected]](/cdn-cgi/l/email-protection)'); + + 13 $mailable->assertHasBcc('[[email protected]](/cdn-cgi/l/email-protection)'); + + 14 $mailable->assertHasReplyTo('[[email protected]](/cdn-cgi/l/email-protection)'); + + 15 $mailable->assertHasSubject('Invoice Paid'); + + 16 $mailable->assertHasTag('example-tag'); + + 17 $mailable->assertHasMetadata('key', 'value'); + + 18  + + 19 $mailable->assertSeeInHtml($user->email); + + 20 $mailable->assertDontSeeInHtml('Invoice Not Paid'); + + 21 $mailable->assertSeeInOrderInHtml(['Invoice Paid', 'Thanks']); + + 22  + + 23 $mailable->assertSeeInText($user->email); + + 24 $mailable->assertDontSeeInText('Invoice Not Paid'); + + 25 $mailable->assertSeeInOrderInText(['Invoice Paid', 'Thanks']); + + 26  + + 27 $mailable->assertHasAttachment('/path/to/file'); + + 28 $mailable->assertHasAttachment(Attachment::fromPath('/path/to/file')); + + 29 $mailable->assertHasAttachedData($pdfData, 'name.pdf', ['mime' => 'application/pdf']); + + 30 $mailable->assertHasAttachmentFromStorage('/path/to/file', 'name.pdf', ['mime' => 'application/pdf']); + + 31 $mailable->assertHasAttachmentFromStorageDisk('s3', '/path/to/file', 'name.pdf', ['mime' => 'application/pdf']); + + 32} + + + use App\Mail\InvoicePaid; + use App\Models\User; + + public function test_mailable_content(): void + { + $user = User::factory()->create(); + + $mailable = new InvoicePaid($user); + + $mailable->assertFrom('[[email protected]](/cdn-cgi/l/email-protection)'); + $mailable->assertTo('[[email protected]](/cdn-cgi/l/email-protection)'); + $mailable->assertHasCc('[[email protected]](/cdn-cgi/l/email-protection)'); + $mailable->assertHasBcc('[[email protected]](/cdn-cgi/l/email-protection)'); + $mailable->assertHasReplyTo('[[email protected]](/cdn-cgi/l/email-protection)'); + $mailable->assertHasSubject('Invoice Paid'); + $mailable->assertHasTag('example-tag'); + $mailable->assertHasMetadata('key', 'value'); + + $mailable->assertSeeInHtml($user->email); + $mailable->assertDontSeeInHtml('Invoice Not Paid'); + $mailable->assertSeeInOrderInHtml(['Invoice Paid', 'Thanks']); + + $mailable->assertSeeInText($user->email); + $mailable->assertDontSeeInText('Invoice Not Paid'); + $mailable->assertSeeInOrderInText(['Invoice Paid', 'Thanks']); + + $mailable->assertHasAttachment('/path/to/file'); + $mailable->assertHasAttachment(Attachment::fromPath('/path/to/file')); + $mailable->assertHasAttachedData($pdfData, 'name.pdf', ['mime' => 'application/pdf']); + $mailable->assertHasAttachmentFromStorage('/path/to/file', 'name.pdf', ['mime' => 'application/pdf']); + $mailable->assertHasAttachmentFromStorageDisk('s3', '/path/to/file', 'name.pdf', ['mime' => 'application/pdf']); + } + +As you might expect, the "HTML" assertions assert that the HTML version of +your mailable contains a given string, while the "text" assertions assert that +the plain-text version of your mailable contains a given string. + +### Testing Mailable Sending + +We suggest testing the content of your mailables separately from your tests +that assert that a given mailable was "sent" to a specific user. Typically, +the content of mailables is not relevant to the code you are testing, and it +is sufficient to simply assert that Laravel was instructed to send a given +mailable. + +You may use the `Mail` facade's `fake` method to prevent mail from being sent. +After calling the `Mail` facade's `fake` method, you may then assert that +mailables were instructed to be sent to users and even inspect the data the +mailables received: + +Pest PHPUnit + + + + 1order->id === $order->id; + + 3}); + + + 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: + + + + 1Mail::assertSent(OrderShipped::class, function (OrderShipped $mail) use ($user) { + + 2 return $mail->hasTo($user->email) && + + 3 $mail->hasCc('...') && + + 4 $mail->hasBcc('...') && + + 5 $mail->hasReplyTo('...') && + + 6 $mail->hasFrom('...') && + + 7 $mail->hasSubject('...') && + + 8 $mail->usesMailer('ses'); + + 9}); + + + Mail::assertSent(OrderShipped::class, function (OrderShipped $mail) use ($user) { + return $mail->hasTo($user->email) && + $mail->hasCc('...') && + $mail->hasBcc('...') && + $mail->hasReplyTo('...') && + $mail->hasFrom('...') && + $mail->hasSubject('...') && + $mail->usesMailer('ses'); + }); + +The mailable instance also includes several helpful methods for examining the +attachments on a mailable: + + + + 1use Illuminate\Mail\Mailables\Attachment; + + 2  + + 3Mail::assertSent(OrderShipped::class, function (OrderShipped $mail) { + + 4 return $mail->hasAttachment( + + 5 Attachment::fromPath('/path/to/file') + + 6 ->as('name.pdf') + + 7 ->withMime('application/pdf') + + 8 ); + + 9}); + + 10  + + 11Mail::assertSent(OrderShipped::class, function (OrderShipped $mail) { + + 12 return $mail->hasAttachment( + + 13 Attachment::fromStorageDisk('s3', '/path/to/file') + + 14 ); + + 15}); + + 16  + + 17Mail::assertSent(OrderShipped::class, function (OrderShipped $mail) use ($pdfData) { + + 18 return $mail->hasAttachment( + + 19 Attachment::fromData(fn () => $pdfData, 'name.pdf') + + 20 ); + + 21}); + + + 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::fromStorageDisk('s3', '/path/to/file') + ); + }); + + 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: + + + + 1Mail::assertNothingOutgoing(); + + 2  + + 3Mail::assertNotOutgoing(function (OrderShipped $mail) use ($order) { + + 4 return $mail->order->id === $order->id; + + 5}); + + + Mail::assertNothingOutgoing(); + + Mail::assertNotOutgoing(function (OrderShipped $mail) use ($order) { + return $mail->order->id === $order->id; + }); + +## Mail and Local Development + +When developing an application that sends email, you probably don't want to +actually send emails to live email addresses. Laravel provides several ways to +"disable" the actual sending of emails during local development. + +#### Log Driver + +Instead of sending your emails, the `log` mail driver will write all email +messages to your log files for inspection. Typically, this driver would only +be used during local development. For more information on configuring your +application per environment, check out the [configuration +documentation](/docs/12.x/configuration#environment-configuration). + +#### HELO / Mailtrap / Mailpit + +Alternatively, you may use a service like [HELO](https://usehelo.com) or +[Mailtrap](https://mailtrap.io) and the `smtp` driver to send your email +messages to a "dummy" mailbox where you may view them in a true email client. +This approach has the benefit of allowing you to actually inspect the final +emails in Mailtrap's message viewer. + +If you are using [Laravel Sail](/docs/12.x/sail), you may preview your +messages using [Mailpit](https://github.com/axllent/mailpit). When Sail is +running, you may access the Mailpit interface at: `http://localhost:8025`. + +#### Using a Global `to` Address + +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: + + + + 1use Illuminate\Support\Facades\Mail; + + 2  + + 3/** + + 4 * Bootstrap any application services. + + 5 */ + + 6public function boot(): void + + 7{ + + 8 if ($this->app->environment('local')) { + + 9 Mail::alwaysTo('[[email protected]](/cdn-cgi/l/email-protection)'); + + 10 } + + 11} + + + use Illuminate\Support\Facades\Mail; + + /** + * Bootstrap any application services. + */ + public function boot(): void + { + if ($this->app->environment('local')) { + Mail::alwaysTo('[[email protected]](/cdn-cgi/l/email-protection)'); + } + } + +## 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/12.x/events) for these events +within your application: + + + + 1use Illuminate\Mail\Events\MessageSending; + + 2// use Illuminate\Mail\Events\MessageSent; + + 3  + + 4class LogMessage + + 5{ + + 6 /** + + 7 * Handle the event. + + 8 */ + + 9 public function handle(MessageSending $event): void + + 10 { + + 11 // ... + + 12 } + + 13} + + + use Illuminate\Mail\Events\MessageSending; + // use Illuminate\Mail\Events\MessageSent; + + class LogMessage + { + /** + * Handle the 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: + + + + 1getOriginalMessage()); + + 28  + + 29 $this->client->messages->send(['message' => [ + + 30 'from_email' => $email->getFrom(), + + 31 'to' => collect($email->getTo())->map(function (Address $email) { + + 32 return ['email' => $email->getAddress(), 'type' => 'to']; + + 33 })->all(), + + 34 'subject' => $email->getSubject(), + + 35 'text' => $email->getTextBody(), + + 36 ]]); + + 37 } + + 38  + + 39 /** + + 40 * Get the string representation of the transport. + + 41 */ + + 42 public function __toString(): string + + 43 { + + 44 return 'mailchimp'; + + 45 } + + 46} + + + 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'; + } + } + +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`. 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: + + + + 1use App\Mail\MailchimpTransport; + + 2use Illuminate\Support\Facades\Mail; + + 3use MailchimpTransactional\ApiClient; + + 4  + + 5/** + + 6 * Bootstrap any application services. + + 7 */ + + 8public function boot(): void + + 9{ + + 10 Mail::extend('mailchimp', function (array $config = []) { + + 11 $client = new ApiClient; + + 12  + + 13 $client->setApiKey($config['key']); + + 14  + + 15 return new MailchimpTransport($client); + + 16 }); + + 17} + + + use App\Mail\MailchimpTransport; + use Illuminate\Support\Facades\Mail; + use MailchimpTransactional\ApiClient; + + /** + * Bootstrap any application services. + */ + public function boot(): void + { + Mail::extend('mailchimp', function (array $config = []) { + $client = new ApiClient; + + $client->setApiKey($config['key']); + + return new MailchimpTransport($client); + }); + } + +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: + + + + 1'mailchimp' => [ + + 2 'transport' => 'mailchimp', + + 3 'key' => env('MAILCHIMP_API_KEY'), + + 4 // ... + + 5], + + + 'mailchimp' => [ + 'transport' => 'mailchimp', + 'key' => env('MAILCHIMP_API_KEY'), + // ... + ], + +### Additional Symfony Transports + +Laravel includes support for some existing Symfony maintained mail transports +like Mailgun and Postmark. However, you may wish to extend Laravel with +support for additional Symfony maintained transports. You can do so by +requiring the necessary Symfony mailer via Composer and registering the +transport with Laravel. For example, you may install and register the "Brevo" +(formerly "Sendinblue") Symfony mailer: + + + + 1composer require symfony/brevo-mailer symfony/http-client + + + 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: + + + + 1'brevo' => [ + + 2 'key' => env('BREVO_API_KEY'), + + 3], + + + 'brevo' => [ + 'key' => env('BREVO_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: + + + + 1use Illuminate\Support\Facades\Mail; + + 2use Symfony\Component\Mailer\Bridge\Brevo\Transport\BrevoTransportFactory; + + 3use Symfony\Component\Mailer\Transport\Dsn; + + 4  + + 5/** + + 6 * Bootstrap any application services. + + 7 */ + + 8public function boot(): void + + 9{ + + 10 Mail::extend('brevo', function () { + + 11 return (new BrevoTransportFactory)->create( + + 12 new Dsn( + + 13 'brevo+api', + + 14 'default', + + 15 config('services.brevo.key') + + 16 ) + + 17 ); + + 18 }); + + 19} + + + 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') + ) + ); + }); + } + +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: + + + + 1'brevo' => [ + + 2 'transport' => 'brevo', + + 3 // ... + + 4], + + + 'brevo' => [ + 'transport' => 'brevo', + // ... + ], + diff --git a/output/12.x/middleware.md b/output/12.x/middleware.md new file mode 100644 index 0000000..c0ca6ea --- /dev/null +++ b/output/12.x/middleware.md @@ -0,0 +1,1095 @@ +# Middleware + + * Introduction + * Defining Middleware + * Registering Middleware + * Global Middleware + * Assigning Middleware to Routes + * Middleware Groups + * Middleware Aliases + * Sorting Middleware + * Middleware Parameters + * Terminable Middleware + +## Introduction + +Middleware provide a convenient mechanism for inspecting and filtering HTTP +requests entering your application. For example, Laravel includes a middleware +that verifies the user of your application is authenticated. If the user is +not authenticated, the middleware will redirect the user to your application's +login screen. However, if the user is authenticated, the middleware will allow +the request to proceed further into the application. + +Additional middleware can be written to perform a variety of tasks besides +authentication. For example, a logging middleware might log all incoming +requests to your application. A variety of middleware are included in Laravel, +including middleware for authentication and CSRF protection; however, all +user-defined middleware are typically located in your application's +`app/Http/Middleware` directory. + +## Defining Middleware + +To create a new middleware, use the `make:middleware` Artisan command: + + + + 1php artisan make:middleware EnsureTokenIsValid + + + 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: + + + + 1input('token') !== 'my-secret-token') { + + 19 return redirect('/home'); + + 20 } + + 21  + + 22 return $next($request); + + 23 } + + 24} + + + 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`. + +It's best to envision middleware as a series of "layers" HTTP requests must +pass through before they hit your application. Each layer can examine the +request and even reject it entirely. + +All middleware are resolved via the [service container](/docs/12.x/container), +so you may type-hint any dependencies you need within a middleware's +constructor. + +#### Middleware and Responses + +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: + + + + 1withMiddleware(function (Middleware $middleware) { + + 4 $middleware->append(EnsureTokenIsValid::class); + + 5}) + + + use App\Http\Middleware\EnsureTokenIsValid; + + ->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. + +#### Manually Managing Laravel's Default Global Middleware + +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: + + + + 1->withMiddleware(function (Middleware $middleware) { + + 2 $middleware->use([ + + 3 \Illuminate\Foundation\Http\Middleware\InvokeDeferredCallbacks::class, + + 4 // \Illuminate\Http\Middleware\TrustHosts::class, + + 5 \Illuminate\Http\Middleware\TrustProxies::class, + + 6 \Illuminate\Http\Middleware\HandleCors::class, + + 7 \Illuminate\Foundation\Http\Middleware\PreventRequestsDuringMaintenance::class, + + 8 \Illuminate\Http\Middleware\ValidatePostSize::class, + + 9 \Illuminate\Foundation\Http\Middleware\TrimStrings::class, + + 10 \Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class, + + 11 ]); + + 12}) + + + ->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: + + + + 1use App\Http\Middleware\EnsureTokenIsValid; + + 2  + + 3Route::get('/profile', function () { + + 4 // ... + + 5})->middleware(EnsureTokenIsValid::class); + + + use App\Http\Middleware\EnsureTokenIsValid; + + 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: + + + + 1Route::get('/', function () { + + 2 // ... + + 3})->middleware([First::class, Second::class]); + + + 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: + + + + 1use App\Http\Middleware\EnsureTokenIsValid; + + 2  + + 3Route::middleware([EnsureTokenIsValid::class])->group(function () { + + 4 Route::get('/', function () { + + 5 // ... + + 6 }); + + 7  + + 8 Route::get('/profile', function () { + + 9 // ... + + 10 })->withoutMiddleware([EnsureTokenIsValid::class]); + + 11}); + + + use App\Http\Middleware\EnsureTokenIsValid; + + 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/12.x/routing#route-groups) of route definitions: + + + + 1use App\Http\Middleware\EnsureTokenIsValid; + + 2  + + 3Route::withoutMiddleware([EnsureTokenIsValid::class])->group(function () { + + 4 Route::get('/profile', function () { + + 5 // ... + + 6 }); + + 7}); + + + use App\Http\Middleware\EnsureTokenIsValid; + + 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. + +### Middleware Groups + +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: + + + + 1use App\Http\Middleware\First; + + 2use App\Http\Middleware\Second; + + 3  + + 4->withMiddleware(function (Middleware $middleware) { + + 5 $middleware->appendToGroup('group-name', [ + + 6 First::class, + + 7 Second::class, + + 8 ]); + + 9  + + 10 $middleware->prependToGroup('group-name', [ + + 11 First::class, + + 12 Second::class, + + 13 ]); + + 14}) + + + use App\Http\Middleware\First; + use App\Http\Middleware\Second; + + ->withMiddleware(function (Middleware $middleware) { + $middleware->appendToGroup('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: + + + + 1Route::get('/', function () { + + 2 // ... + + 3})->middleware('group-name'); + + 4  + + 5Route::middleware(['group-name'])->group(function () { + + 6 // ... + + 7}); + + + Route::get('/', function () { + // ... + })->middleware('group-name'); + + Route::middleware(['group-name'])->group(function () { + // ... + }); + +#### Laravel's Default Middleware Groups + +Laravel includes predefined `web` and `api` middleware groups that contain +common middleware you may want to apply to your web and API routes. Remember, +Laravel automatically applies these middleware groups to the corresponding +`routes/web.php` and `routes/api.php` files: + +The `web` Middleware Group +--- +`Illuminate\Cookie\Middleware\EncryptCookies` +`Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse` +`Illuminate\Session\Middleware\StartSession` +`Illuminate\View\Middleware\ShareErrorsFromSession` +`Illuminate\Foundation\Http\Middleware\ValidateCsrfToken` +`Illuminate\Routing\Middleware\SubstituteBindings` + +The `api` Middleware Group +--- +`Illuminate\Routing\Middleware\SubstituteBindings` + +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: + + + + 1use App\Http\Middleware\EnsureTokenIsValid; + + 2use App\Http\Middleware\EnsureUserIsSubscribed; + + 3  + + 4->withMiddleware(function (Middleware $middleware) { + + 5 $middleware->web(append: [ + + 6 EnsureUserIsSubscribed::class, + + 7 ]); + + 8  + + 9 $middleware->api(prepend: [ + + 10 EnsureTokenIsValid::class, + + 11 ]); + + 12}) + + + use App\Http\Middleware\EnsureTokenIsValid; + use App\Http\Middleware\EnsureUserIsSubscribed; + + ->withMiddleware(function (Middleware $middleware) { + $middleware->web(append: [ + EnsureUserIsSubscribed::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: + + + + 1use App\Http\Middleware\StartCustomSession; + + 2use Illuminate\Session\Middleware\StartSession; + + 3  + + 4$middleware->web(replace: [ + + 5 StartSession::class => StartCustomSession::class, + + 6]); + + + use App\Http\Middleware\StartCustomSession; + use Illuminate\Session\Middleware\StartSession; + + $middleware->web(replace: [ + StartSession::class => StartCustomSession::class, + ]); + +Or, you may remove a middleware entirely: + + + + 1$middleware->web(remove: [ + + 2 StartSession::class, + + 3]); + + + $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: + + + + 1->withMiddleware(function (Middleware $middleware) { + + 2 $middleware->group('web', [ + + 3 \Illuminate\Cookie\Middleware\EncryptCookies::class, + + 4 \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class, + + 5 \Illuminate\Session\Middleware\StartSession::class, + + 6 \Illuminate\View\Middleware\ShareErrorsFromSession::class, + + 7 \Illuminate\Foundation\Http\Middleware\ValidateCsrfToken::class, + + 8 \Illuminate\Routing\Middleware\SubstituteBindings::class, + + 9 // \Illuminate\Session\Middleware\AuthenticateSession::class, + + 10 ]); + + 11  + + 12 $middleware->group('api', [ + + 13 // \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class, + + 14 // 'throttle:api', + + 15 \Illuminate\Routing\Middleware\SubstituteBindings::class, + + 16 ]); + + 17}) + + + ->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, + ]); + }) + +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. + +### Middleware Aliases + +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: + + + + 1use App\Http\Middleware\EnsureUserIsSubscribed; + + 2  + + 3->withMiddleware(function (Middleware $middleware) { + + 4 $middleware->alias([ + + 5 'subscribed' => EnsureUserIsSubscribed::class + + 6 ]); + + 7}) + + + use App\Http\Middleware\EnsureUserIsSubscribed; + + ->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: + + + + 1Route::get('/profile', function () { + + 2 // ... + + 3})->middleware('subscribed'); + + + 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: + +Alias | Middleware +---|--- +`auth` | `Illuminate\Auth\Middleware\Authenticate` +`auth.basic` | `Illuminate\Auth\Middleware\AuthenticateWithBasicAuth` +`auth.session` | `Illuminate\Session\Middleware\AuthenticateSession` +`cache.headers` | `Illuminate\Http\Middleware\SetCacheHeaders` +`can` | `Illuminate\Auth\Middleware\Authorize` +`guest` | `Illuminate\Auth\Middleware\RedirectIfAuthenticated` +`password.confirm` | `Illuminate\Auth\Middleware\RequirePassword` +`precognitive` | `Illuminate\Foundation\Http\Middleware\HandlePrecognitiveRequests` +`signed` | `Illuminate\Routing\Middleware\ValidateSignature` +`subscribed` | `\Spark\Http\Middleware\VerifyBillableIsSubscribed` +`throttle` | `Illuminate\Routing\Middleware\ThrottleRequests` or `Illuminate\Routing\Middleware\ThrottleRequestsWithRedis` +`verified` | `Illuminate\Auth\Middleware\EnsureEmailIsVerified` + +### Sorting Middleware + +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: + + + + 1->withMiddleware(function (Middleware $middleware) { + + 2 $middleware->priority([ + + 3 \Illuminate\Foundation\Http\Middleware\HandlePrecognitiveRequests::class, + + 4 \Illuminate\Cookie\Middleware\EncryptCookies::class, + + 5 \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class, + + 6 \Illuminate\Session\Middleware\StartSession::class, + + 7 \Illuminate\View\Middleware\ShareErrorsFromSession::class, + + 8 \Illuminate\Foundation\Http\Middleware\ValidateCsrfToken::class, + + 9 \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class, + + 10 \Illuminate\Routing\Middleware\ThrottleRequests::class, + + 11 \Illuminate\Routing\Middleware\ThrottleRequestsWithRedis::class, + + 12 \Illuminate\Routing\Middleware\SubstituteBindings::class, + + 13 \Illuminate\Contracts\Auth\Middleware\AuthenticatesRequests::class, + + 14 \Illuminate\Auth\Middleware\Authorize::class, + + 15 ]); + + 16}) + + + ->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 + +Middleware can also receive additional parameters. For example, if your +application needs to verify that the authenticated user has a given "role" +before performing a given action, you could create an `EnsureUserHasRole` +middleware that receives a role name as an additional argument. + +Additional middleware parameters will be passed to the middleware after the +`$next` argument: + + + + 1user()->hasRole($role)) { + + 19 // Redirect... + + 20 } + + 21  + + 22 return $next($request); + + 23 } + + 24} + + + 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 `:`: + + + + 1use App\Http\Middleware\EnsureUserHasRole; + + 2  + + 3Route::put('/post/{id}', function (string $id) { + + 4 // ... + + 5})->middleware(EnsureUserHasRole::class.':editor'); + + + use App\Http\Middleware\EnsureUserHasRole; + + Route::put('/post/{id}', function (string $id) { + // ... + })->middleware(EnsureUserHasRole::class.':editor'); + +Multiple parameters may be delimited by commas: + + + + 1Route::put('/post/{id}', function (string $id) { + + 2 // ... + + 3})->middleware(EnsureUserHasRole::class.':editor,publisher'); + + + 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](https://www.php.net/manual/en/install.fpm.php), the `terminate` +method will automatically be called after the response is sent to the browser: + + + + 1app->singleton(TerminatingMiddleware::class); + + 9} + + + use App\Http\Middleware\TerminatingMiddleware; + + /** + * Register any application services. + */ + public function register(): void + { + $this->app->singleton(TerminatingMiddleware::class); + } + diff --git a/output/12.x/migrations.md b/output/12.x/migrations.md new file mode 100644 index 0000000..16b8cab --- /dev/null +++ b/output/12.x/migrations.md @@ -0,0 +1,2293 @@ +# Database: Migrations + + * Introduction + * Generating Migrations + * Squashing Migrations + * Migration Structure + * Running Migrations + * Rolling Back Migrations + * Tables + * Creating Tables + * Updating Tables + * Renaming / Dropping Tables + * Columns + * Creating Columns + * Available Column Types + * Column Modifiers + * Modifying Columns + * Renaming Columns + * Dropping Columns + * Indexes + * Creating Indexes + * Renaming Indexes + * Dropping Indexes + * Foreign Key Constraints + * Events + +## Introduction + +Migrations are like version control for your database, allowing your team to +define and share the application's database schema definition. If you have +ever had to tell a teammate to manually add a column to their local database +schema after pulling in your changes from source control, you've faced the +problem that database migrations solve. + +The Laravel `Schema` [facade](/docs/12.x/facades) provides database agnostic +support for creating and manipulating tables across all of Laravel's supported +database systems. Typically, migrations will use this facade to create and +modify database tables and columns. + +## Generating Migrations + +You may use the `make:migration` [Artisan command](/docs/12.x/artisan) to +generate a database migration. The new migration will be placed in your +`database/migrations` directory. Each migration filename contains a timestamp +that allows Laravel to determine the order of the migrations: + + + + 1php artisan make:migration create_flights_table + + + php artisan make:migration create_flights_table + +Laravel will use the name of the migration to attempt to guess the name of the +table and whether or not the migration will be creating a new table. If +Laravel is able to determine the table name from the migration name, Laravel +will pre-fill the generated migration file with the specified table. +Otherwise, you may simply specify the table in the migration file manually. + +If you would like to specify a custom path for the generated migration, you +may use the `--path` option when executing the `make:migration` command. The +given path should be relative to your application's base path. + +Migration stubs may be customized using [stub +publishing](/docs/12.x/artisan#stub-customization). + +### Squashing Migrations + +As you build your application, you may accumulate more and more migrations +over time. This can lead to your `database/migrations` directory becoming +bloated with potentially hundreds of migrations. If you would like, you may +"squash" your migrations into a single SQL file. To get started, execute the +`schema:dump` command: + + + + 1php artisan schema:dump + + 2  + + 3# Dump the current database schema and prune all existing migrations... + + 4php artisan schema:dump --prune + + + php artisan schema:dump + + # Dump the current database schema and prune all existing migrations... + php artisan schema:dump --prune + +When you execute this command, Laravel will write a "schema" file to your +application's `database/schema` directory. The schema file's name will +correspond to the database connection. Now, when you attempt to migrate your +database and no other migrations have been executed, Laravel will first +execute the SQL statements in the schema file of the database connection you +are using. After executing the schema file's SQL statements, Laravel will +execute any remaining migrations that were not part of the schema dump. + +If your application's tests use a different database connection than the one +you typically use during local development, you should ensure you have dumped +a schema file using that database connection so that your tests are able to +build your database. You may wish to do this after dumping the database +connection you typically use during local development: + + + + 1php artisan schema:dump + + 2php artisan schema:dump --database=testing --prune + + + php artisan schema:dump + php artisan schema:dump --database=testing --prune + +You should commit your database schema file to source control so that other +new developers on your team may quickly create your application's initial +database structure. + +Migration squashing is only available for the MariaDB, MySQL, PostgreSQL, and +SQLite databases and utilizes the database's command-line client. + +## Migration Structure + +A migration class contains two methods: `up` and `down`. The `up` method is +used to add new tables, columns, or indexes to your database, while the `down` +method should reverse the operations performed by the `up` method. + +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. For example, +the following migration creates a `flights` table: + + + + 1id(); + + 16 $table->string('name'); + + 17 $table->string('airline'); + + 18 $table->timestamps(); + + 19 }); + + 20 } + + 21  + + 22 /** + + 23 * Reverse the migrations. + + 24 */ + + 25 public function down(): void + + 26 { + + 27 Schema::drop('flights'); + + 28 } + + 29}; + + + id(); + $table->string('name'); + $table->string('airline'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + 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: + + + + 1/** + + 2 * The database connection that should be used by the migration. + + 3 * + + 4 * @var string + + 5 */ + + 6protected $connection = 'pgsql'; + + 7  + + 8/** + + 9 * Run the migrations. + + 10 */ + + 11public function up(): void + + 12{ + + 13 // ... + + 14} + + + /** + * The database connection that should be used by the migration. + * + * @var string + */ + protected $connection = 'pgsql'; + + /** + * Run the migrations. + */ + public function up(): void + { + // ... + } + +#### Skipping Migrations + +Sometimes a migration might be meant to support a feature that is not yet +active and you do not want it to run yet. In this case you may define a +`shouldRun` method on the migration. If the `shouldRun` method returns +`false`, the migration will be skipped: + + + + 1use App\Models\Flights; + + 2use Laravel\Pennant\Feature; + + 3  + + 4/** + + 5 * Determine if this migration should run. + + 6 */ + + 7public function shouldRun(): bool + + 8{ + + 9 return Feature::active(Flights::class); + + 10} + + + use App\Models\Flights; + use Laravel\Pennant\Feature; + + /** + * Determine if this migration should run. + */ + public function shouldRun(): bool + { + return Feature::active(Flights::class); + } + +## Running Migrations + +To run all of your outstanding migrations, execute the `migrate` Artisan +command: + + + + 1php artisan migrate + + + php artisan migrate + +If you would like to see which migrations have run thus far, you may use the +`migrate:status` Artisan command: + + + + 1php artisan migrate:status + + + php artisan migrate:status + +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` command: + + + + 1php artisan migrate --pretend + + + php artisan migrate --pretend + +#### Isolating Migration Execution + +If you are deploying your application across multiple servers and running +migrations as part of your deployment process, you likely do not want two +servers attempting to migrate the database at the same time. To avoid this, +you may use the `isolated` option when invoking the `migrate` command. + +When the `isolated` option is provided, Laravel will acquire an atomic lock +using your application's cache driver before attempting to run your +migrations. All other attempts to run the `migrate` command while that lock is +held will not execute; however, the command will still exit with a successful +exit status code: + + + + 1php artisan migrate --isolated + + + php artisan migrate --isolated + +To utilize this feature, your application must be using the `memcached`, +`redis`, `dynamodb`, `database`, `file`, or `array` cache driver as your +application's default cache driver. In addition, all servers must be +communicating with the same central cache server. + +#### Forcing Migrations to Run in Production + +Some migration operations are destructive, which means they may cause you to +lose data. In order to protect you from running these commands against your +production database, you will be prompted for confirmation before the commands +are executed. To force the commands to run without a prompt, use the `--force` +flag: + + + + 1php artisan migrate --force + + + php artisan migrate --force + +### Rolling Back Migrations + +To roll back the latest migration operation, you may use the `rollback` +Artisan command. This command rolls back the last "batch" of migrations, which +may include multiple migration files: + + + + 1php artisan migrate:rollback + + + php artisan migrate:rollback + +You may roll back a limited number of migrations by providing the `step` +option to the `rollback` command. For example, the following command will roll +back the last five migrations: + + + + 1php artisan migrate:rollback --step=5 + + + 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: + + + + 1php artisan migrate:rollback --batch=3 + + + 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: + + + + 1php artisan migrate:rollback --pretend + + + php artisan migrate:rollback --pretend + +The `migrate:reset` command will roll back all of your application's +migrations: + + + + 1php artisan migrate:reset + + + php artisan migrate:reset + +#### Roll Back and Migrate Using a Single Command + +The `migrate:refresh` command will roll back all of your migrations and then +execute the `migrate` command. This command effectively re-creates your entire +database: + + + + 1php artisan migrate:refresh + + 2  + + 3# Refresh the database and run all database seeds... + + 4php artisan migrate:refresh --seed + + + php artisan migrate:refresh + + # Refresh the database and run all database seeds... + php artisan migrate:refresh --seed + +You may roll back and re-migrate a limited number of migrations by providing +the `step` option to the `refresh` command. For example, the following command +will roll back and re-migrate the last five migrations: + + + + 1php artisan migrate:refresh --step=5 + + + php artisan migrate:refresh --step=5 + +#### Drop All Tables and Migrate + +The `migrate:fresh` command will drop all tables from the database and then +execute the `migrate` command: + + + + 1php artisan migrate:fresh + + 2  + + 3php artisan migrate:fresh --seed + + + php artisan migrate:fresh + + php artisan migrate:fresh --seed + +By default, the `migrate:fresh` command only drops tables from the default +database connection. However, you may use the `--database` option to specify +the database connection that should be migrated. The database connection name +should correspond to a connection defined in your application's `database` +[configuration file](/docs/12.x/configuration): + + + + 1php artisan migrate:fresh --database=admin + + + php artisan migrate:fresh --database=admin + +The `migrate:fresh` command will drop all database tables regardless of their +prefix. This command should be used with caution when developing on a database +that is shared with other applications. + +## Tables + +### Creating Tables + +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: + + + + 1use Illuminate\Database\Schema\Blueprint; + + 2use Illuminate\Support\Facades\Schema; + + 3  + + 4Schema::create('users', function (Blueprint $table) { + + 5 $table->id(); + + 6 $table->string('name'); + + 7 $table->string('email'); + + 8 $table->timestamps(); + + 9}); + + + 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(); + }); + +When creating the table, you may use any of the schema builder's column +methods to define the table's columns. + +#### Determining Table / Column Existence + +You may determine the existence of a table, column, or index using the +`hasTable`, `hasColumn`, and `hasIndex` methods: + + + + 1if (Schema::hasTable('users')) { + + 2 // The "users" table exists... + + 3} + + 4  + + 5if (Schema::hasColumn('users', 'email')) { + + 6 // The "users" table exists and has an "email" column... + + 7} + + 8  + + 9if (Schema::hasIndex('users', ['email'], 'unique')) { + + 10 // The "users" table exists and has a unique index on the "email" column... + + 11} + + + if (Schema::hasTable('users')) { + // The "users" table exists... + } + + 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... + } + +#### 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: + + + + 1Schema::connection('sqlite')->create('users', function (Blueprint $table) { + + 2 $table->id(); + + 3}); + + + 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: + + + + 1Schema::create('users', function (Blueprint $table) { + + 2 $table->engine('InnoDB'); + + 3  + + 4 // ... + + 5}); + + + 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: + + + + 1Schema::create('users', function (Blueprint $table) { + + 2 $table->charset('utf8mb4'); + + 3 $table->collation('utf8mb4_unicode_ci'); + + 4  + + 5 // ... + + 6}); + + + 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: + + + + 1Schema::create('calculations', function (Blueprint $table) { + + 2 $table->temporary(); + + 3  + + 4 // ... + + 5}); + + + 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: + + + + 1Schema::create('calculations', function (Blueprint $table) { + + 2 $table->comment('Business calculations'); + + 3  + + 4 // ... + + 5}); + + + 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: + + + + 1use Illuminate\Database\Schema\Blueprint; + + 2use Illuminate\Support\Facades\Schema; + + 3  + + 4Schema::table('users', function (Blueprint $table) { + + 5 $table->integer('votes'); + + 6}); + + + use Illuminate\Database\Schema\Blueprint; + use Illuminate\Support\Facades\Schema; + + Schema::table('users', function (Blueprint $table) { + $table->integer('votes'); + }); + +### Renaming / Dropping Tables + +To rename an existing database table, use the `rename` method: + + + + 1use Illuminate\Support\Facades\Schema; + + 2  + + 3Schema::rename($from, $to); + + + use Illuminate\Support\Facades\Schema; + + Schema::rename($from, $to); + +To drop an existing table, you may use the `drop` or `dropIfExists` methods: + + + + 1Schema::drop('users'); + + 2  + + 3Schema::dropIfExists('users'); + + + Schema::drop('users'); + + Schema::dropIfExists('users'); + +#### Renaming Tables With Foreign Keys + +Before renaming a table, you should verify that any foreign key constraints on +the table have an explicit name in your migration files instead of letting +Laravel assign a convention based name. Otherwise, the foreign key constraint +name will refer to the old table name. + +## Columns + +### Creating Columns + +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: + + + + 1use Illuminate\Database\Schema\Blueprint; + + 2use Illuminate\Support\Facades\Schema; + + 3  + + 4Schema::table('users', function (Blueprint $table) { + + 5 $table->integer('votes'); + + 6}); + + + use Illuminate\Database\Schema\Blueprint; + use Illuminate\Support\Facades\Schema; + + Schema::table('users', function (Blueprint $table) { + $table->integer('votes'); + }); + +### Available Column Types + +The schema builder blueprint offers a variety of methods that correspond to +the different types of columns you can add to your database tables. Each of +the available methods are listed in the table below: + +#### Boolean Types + +boolean + +#### String & Text Types + +char longText mediumText string text tinyText + +#### Numeric Types + +bigIncrements bigInteger decimal double float id increments integer +mediumIncrements mediumInteger smallIncrements smallInteger tinyIncrements +tinyInteger unsignedBigInteger unsignedInteger unsignedMediumInteger +unsignedSmallInteger unsignedTinyInteger + +#### Date & Time Types + +dateTime dateTimeTz date time timeTz timestamp timestamps timestampsTz +softDeletes softDeletesTz year + +#### Binary Types + +binary + +#### Object & Json Types + +json jsonb + +#### UUID & ULID Types + +ulid ulidMorphs uuid uuidMorphs nullableUlidMorphs nullableUuidMorphs + +#### Spatial Types + +geography geometry + +#### Relationship Types + +foreignId foreignIdFor foreignUlid foreignUuid morphs nullableMorphs + +#### Specialty Types + +enum set macAddress ipAddress rememberToken vector + +#### `bigIncrements()` + +The `bigIncrements` method creates an auto-incrementing `UNSIGNED BIGINT` +(primary key) equivalent column: + + + + 1$table->bigIncrements('id'); + + + $table->bigIncrements('id'); + +#### `bigInteger()` + +The `bigInteger` method creates a `BIGINT` equivalent column: + + + + 1$table->bigInteger('votes'); + + + $table->bigInteger('votes'); + +#### `binary()` + +The `binary` method creates a `BLOB` equivalent column: + + + + 1$table->binary('photo'); + + + $table->binary('photo'); + +When utilizing MySQL, MariaDB, or SQL Server, you may pass `length` and +`fixed` arguments to create `VARBINARY` or `BINARY` equivalent column: + + + + 1$table->binary('data', length: 16); // VARBINARY(16) + + 2  + + 3$table->binary('data', length: 16, fixed: true); // BINARY(16) + + + $table->binary('data', length: 16); // VARBINARY(16) + + $table->binary('data', length: 16, fixed: true); // BINARY(16) + +#### `boolean()` + +The `boolean` method creates a `BOOLEAN` equivalent column: + + + + 1$table->boolean('confirmed'); + + + $table->boolean('confirmed'); + +#### `char()` + +The `char` method creates a `CHAR` equivalent column with of a given length: + + + + 1$table->char('name', length: 100); + + + $table->char('name', length: 100); + +#### `dateTimeTz()` + +The `dateTimeTz` method creates a `DATETIME` (with timezone) equivalent column +with an optional fractional seconds precision: + + + + 1$table->dateTimeTz('created_at', precision: 0); + + + $table->dateTimeTz('created_at', precision: 0); + +#### `dateTime()` + +The `dateTime` method creates a `DATETIME` equivalent column with an optional +fractional seconds precision: + + + + 1$table->dateTime('created_at', precision: 0); + + + $table->dateTime('created_at', precision: 0); + +#### `date()` + +The `date` method creates a `DATE` equivalent column: + + + + 1$table->date('created_at'); + + + $table->date('created_at'); + +#### `decimal()` + +The `decimal` method creates a `DECIMAL` equivalent column with the given +precision (total digits) and scale (decimal digits): + + + + 1$table->decimal('amount', total: 8, places: 2); + + + $table->decimal('amount', total: 8, places: 2); + +#### `double()` + +The `double` method creates a `DOUBLE` equivalent column: + + + + 1$table->double('amount'); + + + $table->double('amount'); + +#### `enum()` + +The `enum` method creates a `ENUM` equivalent column with the given valid +values: + + + + 1$table->enum('difficulty', ['easy', 'hard']); + + + $table->enum('difficulty', ['easy', 'hard']); + +Of course, you may use the `Enum::cases()` method instead of manually defining +an array of allowed values: + + + + 1use App\Enums\Difficulty; + + 2  + + 3$table->enum('difficulty', Difficulty::cases()); + + + use App\Enums\Difficulty; + + $table->enum('difficulty', Difficulty::cases()); + +#### `float()` + +The `float` method creates a `FLOAT` equivalent column with the given +precision: + + + + 1$table->float('amount', precision: 53); + + + $table->float('amount', precision: 53); + +#### `foreignId()` + +The `foreignId` method creates an `UNSIGNED BIGINT` equivalent column: + + + + 1$table->foreignId('user_id'); + + + $table->foreignId('user_id'); + +#### `foreignIdFor()` + +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: + + + + 1$table->foreignIdFor(User::class); + + + $table->foreignIdFor(User::class); + +#### `foreignUlid()` + +The `foreignUlid` method creates a `ULID` equivalent column: + + + + 1$table->foreignUlid('user_id'); + + + $table->foreignUlid('user_id'); + +#### `foreignUuid()` + +The `foreignUuid` method creates a `UUID` equivalent column: + + + + 1$table->foreignUuid('user_id'); + + + $table->foreignUuid('user_id'); + +#### `geography()` + +The `geography` method creates a `GEOGRAPHY` equivalent column with the given +spatial type and SRID (Spatial Reference System Identifier): + + + + 1$table->geography('coordinates', subtype: 'point', srid: 4326); + + + $table->geography('coordinates', subtype: 'point', srid: 4326); + +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. + +#### `geometry()` + +The `geometry` method creates a `GEOMETRY` equivalent column with the given +spatial type and SRID (Spatial Reference System Identifier): + + + + 1$table->geometry('positions', subtype: 'point', srid: 0); + + + $table->geometry('positions', subtype: 'point', srid: 0); + +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. + +#### `id()` + +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: + + + + 1$table->id(); + + + $table->id(); + +#### `increments()` + +The `increments` method creates an auto-incrementing `UNSIGNED INTEGER` +equivalent column as a primary key: + + + + 1$table->increments('id'); + + + $table->increments('id'); + +#### `integer()` + +The `integer` method creates an `INTEGER` equivalent column: + + + + 1$table->integer('votes'); + + + $table->integer('votes'); + +#### `ipAddress()` + +The `ipAddress` method creates a `VARCHAR` equivalent column: + + + + 1$table->ipAddress('visitor'); + + + $table->ipAddress('visitor'); + +When using PostgreSQL, an `INET` column will be created. + +#### `json()` + +The `json` method creates a `JSON` equivalent column: + + + + 1$table->json('options'); + + + $table->json('options'); + +When using SQLite, a `TEXT` column will be created. + +#### `jsonb()` + +The `jsonb` method creates a `JSONB` equivalent column: + + + + 1$table->jsonb('options'); + + + $table->jsonb('options'); + +When using SQLite, a `TEXT` column will be created. + +#### `longText()` + +The `longText` method creates a `LONGTEXT` equivalent column: + + + + 1$table->longText('description'); + + + $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: + + + + 1$table->longText('data')->charset('binary'); // LONGBLOB + + + $table->longText('data')->charset('binary'); // LONGBLOB + +#### `macAddress()` + +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: + + + + 1$table->macAddress('device'); + + + $table->macAddress('device'); + +#### `mediumIncrements()` + +The `mediumIncrements` method creates an auto-incrementing `UNSIGNED +MEDIUMINT` equivalent column as a primary key: + + + + 1$table->mediumIncrements('id'); + + + $table->mediumIncrements('id'); + +#### `mediumInteger()` + +The `mediumInteger` method creates a `MEDIUMINT` equivalent column: + + + + 1$table->mediumInteger('votes'); + + + $table->mediumInteger('votes'); + +#### `mediumText()` + +The `mediumText` method creates a `MEDIUMTEXT` equivalent column: + + + + 1$table->mediumText('description'); + + + $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: + + + + 1$table->mediumText('data')->charset('binary'); // MEDIUMBLOB + + + $table->mediumText('data')->charset('binary'); // MEDIUMBLOB + +#### `morphs()` + +The `morphs` method is a convenience method that adds a `{column}_id` +equivalent column and a `{column}_type` `VARCHAR` equivalent column. The +column type for the `{column}_id` will be `UNSIGNED BIGINT`, `CHAR(36)`, or +`CHAR(26)` depending on the model key type. + +This method is intended to be used when defining the columns necessary for a +polymorphic [Eloquent relationship](/docs/12.x/eloquent-relationships). In the +following example, `taggable_id` and `taggable_type` columns would be created: + + + + 1$table->morphs('taggable'); + + + $table->morphs('taggable'); + +#### `nullableMorphs()` + +The method is similar to the morphs method; however, the columns that are +created will be "nullable": + + + + 1$table->nullableMorphs('taggable'); + + + $table->nullableMorphs('taggable'); + +#### `nullableUlidMorphs()` + +The method is similar to the ulidMorphs method; however, the columns that are +created will be "nullable": + + + + 1$table->nullableUlidMorphs('taggable'); + + + $table->nullableUlidMorphs('taggable'); + +#### `nullableUuidMorphs()` + +The method is similar to the uuidMorphs method; however, the columns that are +created will be "nullable": + + + + 1$table->nullableUuidMorphs('taggable'); + + + $table->nullableUuidMorphs('taggable'); + +#### `rememberToken()` + +The `rememberToken` method creates a nullable, `VARCHAR(100)` equivalent +column that is intended to store the current "remember me" [authentication +token](/docs/12.x/authentication#remembering-users): + + + + 1$table->rememberToken(); + + + $table->rememberToken(); + +#### `set()` + +The `set` method creates a `SET` equivalent column with the given list of +valid values: + + + + 1$table->set('flavors', ['strawberry', 'vanilla']); + + + $table->set('flavors', ['strawberry', 'vanilla']); + +#### `smallIncrements()` + +The `smallIncrements` method creates an auto-incrementing `UNSIGNED SMALLINT` +equivalent column as a primary key: + + + + 1$table->smallIncrements('id'); + + + $table->smallIncrements('id'); + +#### `smallInteger()` + +The `smallInteger` method creates a `SMALLINT` equivalent column: + + + + 1$table->smallInteger('votes'); + + + $table->smallInteger('votes'); + +#### `softDeletesTz()` + +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: + + + + 1$table->softDeletesTz('deleted_at', precision: 0); + + + $table->softDeletesTz('deleted_at', precision: 0); + +#### `softDeletes()` + +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: + + + + 1$table->softDeletes('deleted_at', precision: 0); + + + $table->softDeletes('deleted_at', precision: 0); + +#### `string()` + +The `string` method creates a `VARCHAR` equivalent column of the given length: + + + + 1$table->string('name', length: 100); + + + $table->string('name', length: 100); + +#### `text()` + +The `text` method creates a `TEXT` equivalent column: + + + + 1$table->text('description'); + + + $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: + + + + 1$table->text('data')->charset('binary'); // BLOB + + + $table->text('data')->charset('binary'); // BLOB + +#### `timeTz()` + +The `timeTz` method creates a `TIME` (with timezone) equivalent column with an +optional fractional seconds precision: + + + + 1$table->timeTz('sunrise', precision: 0); + + + $table->timeTz('sunrise', precision: 0); + +#### `time()` + +The `time` method creates a `TIME` equivalent column with an optional +fractional seconds precision: + + + + 1$table->time('sunrise', precision: 0); + + + $table->time('sunrise', precision: 0); + +#### `timestampTz()` + +The `timestampTz` method creates a `TIMESTAMP` (with timezone) equivalent +column with an optional fractional seconds precision: + + + + 1$table->timestampTz('added_at', precision: 0); + + + $table->timestampTz('added_at', precision: 0); + +#### `timestamp()` + +The `timestamp` method creates a `TIMESTAMP` equivalent column with an +optional fractional seconds precision: + + + + 1$table->timestamp('added_at', precision: 0); + + + $table->timestamp('added_at', precision: 0); + +#### `timestampsTz()` + +The `timestampsTz` method creates `created_at` and `updated_at` `TIMESTAMP` +(with timezone) equivalent columns with an optional fractional seconds +precision: + + + + 1$table->timestampsTz(precision: 0); + + + $table->timestampsTz(precision: 0); + +#### `timestamps()` + +The `timestamps` method creates `created_at` and `updated_at` `TIMESTAMP` +equivalent columns with an optional fractional seconds precision: + + + + 1$table->timestamps(precision: 0); + + + $table->timestamps(precision: 0); + +#### `tinyIncrements()` + +The `tinyIncrements` method creates an auto-incrementing `UNSIGNED TINYINT` +equivalent column as a primary key: + + + + 1$table->tinyIncrements('id'); + + + $table->tinyIncrements('id'); + +#### `tinyInteger()` + +The `tinyInteger` method creates a `TINYINT` equivalent column: + + + + 1$table->tinyInteger('votes'); + + + $table->tinyInteger('votes'); + +#### `tinyText()` + +The `tinyText` method creates a `TINYTEXT` equivalent column: + + + + 1$table->tinyText('notes'); + + + $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: + + + + 1$table->tinyText('data')->charset('binary'); // TINYBLOB + + + $table->tinyText('data')->charset('binary'); // TINYBLOB + +#### `unsignedBigInteger()` + +The `unsignedBigInteger` method creates an `UNSIGNED BIGINT` equivalent +column: + + + + 1$table->unsignedBigInteger('votes'); + + + $table->unsignedBigInteger('votes'); + +#### `unsignedInteger()` + +The `unsignedInteger` method creates an `UNSIGNED INTEGER` equivalent column: + + + + 1$table->unsignedInteger('votes'); + + + $table->unsignedInteger('votes'); + +#### `unsignedMediumInteger()` + +The `unsignedMediumInteger` method creates an `UNSIGNED MEDIUMINT` equivalent +column: + + + + 1$table->unsignedMediumInteger('votes'); + + + $table->unsignedMediumInteger('votes'); + +#### `unsignedSmallInteger()` + +The `unsignedSmallInteger` method creates an `UNSIGNED SMALLINT` equivalent +column: + + + + 1$table->unsignedSmallInteger('votes'); + + + $table->unsignedSmallInteger('votes'); + +#### `unsignedTinyInteger()` + +The `unsignedTinyInteger` method creates an `UNSIGNED TINYINT` equivalent +column: + + + + 1$table->unsignedTinyInteger('votes'); + + + $table->unsignedTinyInteger('votes'); + +#### `ulidMorphs()` + +The `ulidMorphs` method is a convenience method that adds a `{column}_id` +`CHAR(26)` equivalent column and a `{column}_type` `VARCHAR` equivalent +column. + +This method is intended to be used when defining the columns necessary for a +polymorphic [Eloquent relationship](/docs/12.x/eloquent-relationships) that +use ULID identifiers. In the following example, `taggable_id` and +`taggable_type` columns would be created: + + + + 1$table->ulidMorphs('taggable'); + + + $table->ulidMorphs('taggable'); + +#### `uuidMorphs()` + +The `uuidMorphs` method is a convenience method that adds a `{column}_id` +`CHAR(36)` equivalent column and a `{column}_type` `VARCHAR` equivalent +column. + +This method is intended to be used when defining the columns necessary for a +polymorphic [Eloquent relationship](/docs/12.x/eloquent-relationships) that +use UUID identifiers. In the following example, `taggable_id` and +`taggable_type` columns would be created: + + + + 1$table->uuidMorphs('taggable'); + + + $table->uuidMorphs('taggable'); + +#### `ulid()` + +The `ulid` method creates a `ULID` equivalent column: + + + + 1$table->ulid('id'); + + + $table->ulid('id'); + +#### `uuid()` + +The `uuid` method creates a `UUID` equivalent column: + + + + 1$table->uuid('id'); + + + $table->uuid('id'); + +#### `vector()` + +The `vector` method creates a `vector` equivalent column: + + + + 1$table->vector('embedding', dimensions: 100); + + + $table->vector('embedding', dimensions: 100); + +#### `year()` + +The `year` method creates a `YEAR` equivalent column: + + + + 1$table->year('birth_year'); + + + $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: + + + + 1use Illuminate\Database\Schema\Blueprint; + + 2use Illuminate\Support\Facades\Schema; + + 3  + + 4Schema::table('users', function (Blueprint $table) { + + 5 $table->string('email')->nullable(); + + 6}); + + + use Illuminate\Database\Schema\Blueprint; + use Illuminate\Support\Facades\Schema; + + 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: + +Modifier | Description +---|--- +`->after('column')` | Place the column "after" another column (MariaDB / MySQL). +`->autoIncrement()` | Set `INTEGER` columns as auto-incrementing (primary key). +`->charset('utf8mb4')` | Specify a character set for the column (MariaDB / MySQL). +`->collation('utf8mb4_unicode_ci')` | Specify a collation for the column. +`->comment('my comment')` | Add a comment to a column (MariaDB / MySQL / PostgreSQL). +`->default($value)` | Specify a "default" value for the column. +`->first()` | Place the column "first" in the table (MariaDB / MySQL). +`->from($integer)` | Set the starting value of an auto-incrementing field (MariaDB / MySQL / PostgreSQL). +`->invisible()` | Make the column "invisible" to `SELECT *` queries (MariaDB / MySQL). +`->nullable($value = true)` | Allow `NULL` values to be inserted into the column. +`->storedAs($expression)` | Create a stored generated column (MariaDB / MySQL / PostgreSQL / SQLite). +`->unsigned()` | Set `INTEGER` columns as `UNSIGNED` (MariaDB / MySQL). +`->useCurrent()` | Set `TIMESTAMP` columns to use `CURRENT_TIMESTAMP` as default value. +`->useCurrentOnUpdate()` | Set `TIMESTAMP` columns to use `CURRENT_TIMESTAMP` when a record is updated (MariaDB / MySQL). +`->virtualAs($expression)` | Create a virtual generated column (MariaDB / MySQL / SQLite). +`->generatedAs($expression)` | Create an identity column with specified sequence options (PostgreSQL). +`->always()` | Defines the precedence of sequence values over input for an identity column (PostgreSQL). + +#### Default Expressions + +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: + + + + 1id(); + + 17 $table->json('movies')->default(new Expression('(JSON_ARRAY())')); + + 18 $table->timestamps(); + + 19 }); + + 20 } + + 21}; + + + id(); + $table->json('movies')->default(new Expression('(JSON_ARRAY())')); + $table->timestamps(); + }); + } + }; + +Support for default expressions depends on your database driver, database +version, and the field type. Please refer to your database's documentation. + +#### Column Order + +When using the MariaDB or MySQL database, the `after` method may be used to +add columns after an existing column in the schema: + + + + 1$table->after('password', function (Blueprint $table) { + + 2 $table->string('address_line1'); + + 3 $table->string('address_line2'); + + 4 $table->string('city'); + + 5}); + + + $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: + + + + 1Schema::table('users', function (Blueprint $table) { + + 2 $table->string('name', 50)->change(); + + 3}); + + + 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: + + + + 1Schema::table('users', function (Blueprint $table) { + + 2 $table->integer('votes')->unsigned()->default(1)->comment('my comment')->change(); + + 3}); + + + 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: + + + + 1// Add an index... + + 2$table->bigIncrements('id')->primary()->change(); + + 3  + + 4// Drop an index... + + 5$table->char('postal_code', 10)->unique(false)->change(); + + + // Add an index... + $table->bigIncrements('id')->primary()->change(); + + // Drop an index... + $table->char('postal_code', 10)->unique(false)->change(); + +### Renaming Columns + +To rename a column, you may use the `renameColumn` method provided by the +schema builder: + + + + 1Schema::table('users', function (Blueprint $table) { + + 2 $table->renameColumn('from', 'to'); + + 3}); + + + 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: + + + + 1Schema::table('users', function (Blueprint $table) { + + 2 $table->dropColumn('votes'); + + 3}); + + + 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: + + + + 1Schema::table('users', function (Blueprint $table) { + + 2 $table->dropColumn(['votes', 'avatar', 'location']); + + 3}); + + + Schema::table('users', function (Blueprint $table) { + $table->dropColumn(['votes', 'avatar', 'location']); + }); + +#### Available Command Aliases + +Laravel provides several convenient methods related to dropping common types +of columns. Each of these methods is described in the table below: + +Command | Description +---|--- +`$table->dropMorphs('morphable');` | Drop the `morphable_id` and `morphable_type` columns. +`$table->dropRememberToken();` | Drop the `remember_token` column. +`$table->dropSoftDeletes();` | Drop the `deleted_at` column. +`$table->dropSoftDeletesTz();` | Alias of `dropSoftDeletes()` method. +`$table->dropTimestamps();` | Drop the `created_at` and `updated_at` columns. +`$table->dropTimestampsTz();` | Alias of `dropTimestamps()` method. + +## Indexes + +### Creating Indexes + +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: + + + + 1use Illuminate\Database\Schema\Blueprint; + + 2use Illuminate\Support\Facades\Schema; + + 3  + + 4Schema::table('users', function (Blueprint $table) { + + 5 $table->string('email')->unique(); + + 6}); + + + use Illuminate\Database\Schema\Blueprint; + use Illuminate\Support\Facades\Schema; + + 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: + + + + 1$table->unique('email'); + + + $table->unique('email'); + +You may even pass an array of columns to an index method to create a compound +(or composite) index: + + + + 1$table->index(['account_id', 'created_at']); + + + $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: + + + + 1$table->unique('email', 'unique_email'); + + + $table->unique('email', 'unique_email'); + +#### Available Index Types + +Laravel's schema builder blueprint class provides methods for creating each +type of index supported by Laravel. Each index method accepts an optional +second argument to specify the name of the index. If omitted, the name will be +derived from the names of the table and column(s) used for the index, as well +as the index type. Each of the available index methods is described in the +table below: + +Command | Description +---|--- +`$table->primary('id');` | Adds a primary key. +`$table->primary(['id', 'parent_id']);` | Adds composite keys. +`$table->unique('email');` | Adds a unique index. +`$table->index('state');` | Adds an index. +`$table->fullText('body');` | Adds a full text index (MariaDB / MySQL / PostgreSQL). +`$table->fullText('body')->language('english');` | Adds a full text index of the specified language (PostgreSQL). +`$table->spatialIndex('location');` | Adds a spatial index (except SQLite). + +### Renaming Indexes + +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: + + + + 1$table->renameIndex('from', 'to') + + + $table->renameIndex('from', 'to') + +### Dropping Indexes + +To drop an index, you must specify the index's name. By default, Laravel +automatically assigns an index name based on the table name, the name of the +indexed column, and the index type. Here are some examples: + +Command | Description +---|--- +`$table->dropPrimary('users_id_primary');` | Drop a primary key from the "users" table. +`$table->dropUnique('users_email_unique');` | Drop a unique index from the "users" table. +`$table->dropIndex('geo_state_index');` | Drop a basic index from the "geo" table. +`$table->dropFullText('posts_body_fulltext');` | Drop a full text index from the "posts" table. +`$table->dropSpatialIndex('geo_location_spatialindex');` | Drop a spatial index from the "geo" table (except SQLite). + +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: + + + + 1Schema::table('geo', function (Blueprint $table) { + + 2 $table->dropIndex(['state']); // Drops index 'geo_state_index' + + 3}); + + + 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: + + + + 1use Illuminate\Database\Schema\Blueprint; + + 2use Illuminate\Support\Facades\Schema; + + 3  + + 4Schema::table('posts', function (Blueprint $table) { + + 5 $table->unsignedBigInteger('user_id'); + + 6  + + 7 $table->foreign('user_id')->references('id')->on('users'); + + 8}); + + + use Illuminate\Database\Schema\Blueprint; + use Illuminate\Support\Facades\Schema; + + Schema::table('posts', function (Blueprint $table) { + $table->unsignedBigInteger('user_id'); + + $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: + + + + 1Schema::table('posts', function (Blueprint $table) { + + 2 $table->foreignId('user_id')->constrained(); + + 3}); + + + 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: + + + + 1Schema::table('posts', function (Blueprint $table) { + + 2 $table->foreignId('user_id')->constrained( + + 3 table: 'users', indexName: 'posts_user_id' + + 4 ); + + 5}); + + + 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: + + + + 1$table->foreignId('user_id') + + 2 ->constrained() + + 3 ->onUpdate('cascade') + + 4 ->onDelete('cascade'); + + + $table->foreignId('user_id') + ->constrained() + ->onUpdate('cascade') + ->onDelete('cascade'); + +An alternative, expressive syntax is also provided for these actions: + +Method | Description +---|--- +`$table->cascadeOnUpdate();` | Updates should cascade. +`$table->restrictOnUpdate();` | Updates should be restricted. +`$table->nullOnUpdate();` | Updates should set the foreign key value to null. +`$table->noActionOnUpdate();` | No action on updates. +`$table->cascadeOnDelete();` | Deletes should cascade. +`$table->restrictOnDelete();` | Deletes should be restricted. +`$table->nullOnDelete();` | Deletes should set the foreign key value to null. +`$table->noActionOnDelete();` | Prevents deletes if child records exist. + +Any additional column modifiers must be called before the `constrained` +method: + + + + 1$table->foreignId('user_id') + + 2 ->nullable() + + 3 ->constrained(); + + + $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: + + + + 1$table->dropForeign('posts_user_id_foreign'); + + + $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: + + + + 1$table->dropForeign(['user_id']); + + + $table->dropForeign(['user_id']); + +#### Toggling Foreign Key Constraints + +You may enable or disable foreign key constraints within your migrations by +using the following methods: + + + + 1Schema::enableForeignKeyConstraints(); + + 2  + + 3Schema::disableForeignKeyConstraints(); + + 4  + + 5Schema::withoutForeignKeyConstraints(function () { + + 6 // Constraints disabled within this closure... + + 7}); + + + Schema::enableForeignKeyConstraints(); + + Schema::disableForeignKeyConstraints(); + + Schema::withoutForeignKeyConstraints(function () { + // Constraints disabled within this closure... + }); + +SQLite disables foreign key constraints by default. When using SQLite, make +sure to [enable foreign key support](/docs/12.x/database#configuration) in +your database configuration before attempting to create them in your +migrations. + +## Events + +For convenience, each migration operation will dispatch an +[event](/docs/12.x/events). All of the following events extend the base +`Illuminate\Database\Events\MigrationEvent` class: + +Class | Description +---|--- +`Illuminate\Database\Events\MigrationsStarted` | A batch of migrations is about to be executed. +`Illuminate\Database\Events\MigrationsEnded` | A batch of migrations has finished executing. +`Illuminate\Database\Events\MigrationStarted` | A single migration is about to be executed. +`Illuminate\Database\Events\MigrationEnded` | A single migration has finished executing. +`Illuminate\Database\Events\NoPendingMigrations` | A migration command found no pending migrations. +`Illuminate\Database\Events\SchemaDumped` | A database schema dump has completed. +`Illuminate\Database\Events\SchemaLoaded` | An existing database schema dump has loaded. + diff --git a/output/12.x/mix.md b/output/12.x/mix.md new file mode 100644 index 0000000..24b79e4 --- /dev/null +++ b/output/12.x/mix.md @@ -0,0 +1,39 @@ +# Laravel Mix + + * Introduction + +## Introduction + +Laravel Mix is a legacy package that is no longer actively maintained. +[Vite](/docs/12.x/vite) may be used as a modern alternative. + +[Laravel Mix](https://github.com/laravel-mix/laravel-mix), a package developed +by [Laracasts](https://laracasts.com) creator Jeffrey Way, provides a fluent +API for defining [webpack](https://webpack.js.org) build steps for your +Laravel application using several common CSS and JavaScript pre-processors. + +In other words, Mix makes it a cinch to compile and minify your application's +CSS and JavaScript files. Through simple method chaining, you can fluently +define your asset pipeline. For example: + + + + 1mix.js('resources/js/app.js', 'public/js') + + 2 .postCss('resources/css/app.css', 'public/css'); + + + mix.js('resources/js/app.js', 'public/js') + .postCss('resources/css/app.css', 'public/css'); + +If you've ever been confused and overwhelmed about getting started with +webpack and asset compilation, you will love Laravel Mix. However, you are not +required to use it while developing your application; you are free to use any +asset pipeline tool you wish, or even none at all. + +Vite has replaced Laravel Mix in new Laravel installations. For Mix +documentation, please visit the [official Laravel Mix](https://laravel- +mix.com/) website. If you would like to switch to Vite, please see our [Vite +migration guide](https://github.com/laravel/vite- +plugin/blob/main/UPGRADE.md#migrating-from-laravel-mix-to-vite). + diff --git a/output/12.x/mocking.md b/output/12.x/mocking.md new file mode 100644 index 0000000..1a9f839 --- /dev/null +++ b/output/12.x/mocking.md @@ -0,0 +1,757 @@ +# Mocking + + * Introduction + * Mocking Objects + * Mocking Facades + * Facade Spies + * Interacting With Time + +## Introduction + +When testing Laravel applications, you may wish to "mock" certain aspects of +your application so they are not actually executed during a given test. For +example, when testing a controller that dispatches an event, you may wish to +mock the event listeners so they are not actually executed during the test. +This allows you to only test the controller's HTTP response without worrying +about the execution of the event listeners since the event listeners can be +tested in their own test case. + +Laravel provides helpful methods for mocking events, jobs, and other facades +out of the box. These helpers primarily provide a convenience layer over +Mockery so you do not have to manually make complicated Mockery method calls. + +## Mocking Objects + +When mocking an object that is going to be injected into your application via +Laravel's [service container](/docs/12.x/container), you will need to bind +your mocked instance into the container as an `instance` binding. This will +instruct the container to use your mocked instance of the object instead of +constructing the object itself: + +Pest PHPUnit + + + + 1use App\Service; + + 2use Mockery; + + 3use Mockery\MockInterface; + + 4  + + 5test('something can be mocked', function () { + + 6 $this->instance( + + 7 Service::class, + + 8 Mockery::mock(Service::class, function (MockInterface $mock) { + + 9 $mock->expects('process'); + + 10 }) + + 11 ); + + 12}); + + + use App\Service; + use Mockery; + use Mockery\MockInterface; + + test('something can be mocked', function () { + $this->instance( + Service::class, + Mockery::mock(Service::class, function (MockInterface $mock) { + $mock->expects('process'); + }) + ); + }); + + + 1use App\Service; + + 2use Mockery; + + 3use Mockery\MockInterface; + + 4  + + 5public function test_something_can_be_mocked(): void + + 6{ + + 7 $this->instance( + + 8 Service::class, + + 9 Mockery::mock(Service::class, function (MockInterface $mock) { + + 10 $mock->expects('process'); + + 11 }) + + 12 ); + + 13} + + + use App\Service; + use Mockery; + use Mockery\MockInterface; + + public function test_something_can_be_mocked(): void + { + $this->instance( + Service::class, + Mockery::mock(Service::class, function (MockInterface $mock) { + $mock->expects('process'); + }) + ); + } + +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: + + + + 1use App\Service; + + 2use Mockery\MockInterface; + + 3  + + 4$mock = $this->mock(Service::class, function (MockInterface $mock) { + + 5 $mock->expects('process'); + + 6}); + + + use App\Service; + use Mockery\MockInterface; + + $mock = $this->mock(Service::class, function (MockInterface $mock) { + $mock->expects('process'); + }); + +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: + + + + 1use App\Service; + + 2use Mockery\MockInterface; + + 3  + + 4$mock = $this->partialMock(Service::class, function (MockInterface $mock) { + + 5 $mock->expects('process'); + + 6}); + + + use App\Service; + use Mockery\MockInterface; + + $mock = $this->partialMock(Service::class, function (MockInterface $mock) { + $mock->expects('process'); + }); + +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: + + + + 1use App\Service; + + 2  + + 3$spy = $this->spy(Service::class); + + 4  + + 5// ... + + 6  + + 7$spy->shouldHaveReceived('process'); + + + use App\Service; + + $spy = $this->spy(Service::class); + + // ... + + $spy->shouldHaveReceived('process'); + +## Mocking Facades + +Unlike traditional static method calls, [facades](/docs/12.x/facades) +(including [real-time facades](/docs/12.x/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: + + + + 1with('key') + + 8 ->andReturn('value'); + + 9  + + 10 $response = $this->get('/users'); + + 11  + + 12 // ... + + 13}); + + + with('key') + ->andReturn('value'); + + $response = $this->get('/users'); + + // ... + }); + + + 1with('key') + + 14 ->andReturn('value'); + + 15  + + 16 $response = $this->get('/users'); + + 17  + + 18 // ... + + 19 } + + 20} + + + with('key') + ->andReturn('value'); + + $response = $this->get('/users'); + + // ... + } + } + +You should not mock the `Request` facade. Instead, pass the input you desire +into the [HTTP testing methods](/docs/12.x/http-tests) such as `get` and +`post` when running your test. Likewise, instead of mocking the `Config` +facade, call the `Config::set` method in your tests. + +### Facade Spies + +If you would like to +[spy](http://docs.mockery.io/en/latest/reference/spies.html) on a facade, you +may call the `spy` method on the corresponding facade. 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: + +Pest PHPUnit + + + + 1get('/'); + + 9  + + 10 $response->assertStatus(200); + + 11  + + 12 Cache::shouldHaveReceived('put')->with('name', 'Taylor', 10); + + 13}); + + + get('/'); + + $response->assertStatus(200); + + Cache::shouldHaveReceived('put')->with('name', 'Taylor', 10); + }); + + + 1use Illuminate\Support\Facades\Cache; + + 2  + + 3public function test_values_are_stored_in_cache(): void + + 4{ + + 5 Cache::spy(); + + 6  + + 7 $response = $this->get('/'); + + 8  + + 9 $response->assertStatus(200); + + 10  + + 11 Cache::shouldHaveReceived('put')->with('name', 'Taylor', 10); + + 12} + + + use Illuminate\Support\Facades\Cache; + + public function test_values_are_stored_in_cache(): void + { + Cache::spy(); + + $response = $this->get('/'); + + $response->assertStatus(200); + + Cache::shouldHaveReceived('put')->with('name', 'Taylor', 10); + } + +## Interacting With Time + +When testing, you may occasionally need to modify the time returned by helpers +such as `now` or `Illuminate\Support\Carbon::now()`. Thankfully, Laravel's +base feature test class includes helpers that allow you to manipulate the +current time: + +Pest PHPUnit + + + + 1test('time can be manipulated', function () { + + 2 // Travel into the future... + + 3 $this->travel(5)->milliseconds(); + + 4 $this->travel(5)->seconds(); + + 5 $this->travel(5)->minutes(); + + 6 $this->travel(5)->hours(); + + 7 $this->travel(5)->days(); + + 8 $this->travel(5)->weeks(); + + 9 $this->travel(5)->years(); + + 10  + + 11 // Travel into the past... + + 12 $this->travel(-5)->hours(); + + 13  + + 14 // Travel to an explicit time... + + 15 $this->travelTo(now()->subHours(6)); + + 16  + + 17 // Return back to the present time... + + 18 $this->travelBack(); + + 19}); + + + test('time can be manipulated', function () { + // Travel into the future... + $this->travel(5)->milliseconds(); + $this->travel(5)->seconds(); + $this->travel(5)->minutes(); + $this->travel(5)->hours(); + $this->travel(5)->days(); + $this->travel(5)->weeks(); + $this->travel(5)->years(); + + // Travel into the past... + $this->travel(-5)->hours(); + + // Travel to an explicit time... + $this->travelTo(now()->subHours(6)); + + // Return back to the present time... + $this->travelBack(); + }); + + + 1public function test_time_can_be_manipulated(): void + + 2{ + + 3 // Travel into the future... + + 4 $this->travel(5)->milliseconds(); + + 5 $this->travel(5)->seconds(); + + 6 $this->travel(5)->minutes(); + + 7 $this->travel(5)->hours(); + + 8 $this->travel(5)->days(); + + 9 $this->travel(5)->weeks(); + + 10 $this->travel(5)->years(); + + 11  + + 12 // Travel into the past... + + 13 $this->travel(-5)->hours(); + + 14  + + 15 // Travel to an explicit time... + + 16 $this->travelTo(now()->subHours(6)); + + 17  + + 18 // Return back to the present time... + + 19 $this->travelBack(); + + 20} + + + public function test_time_can_be_manipulated(): void + { + // Travel into the future... + $this->travel(5)->milliseconds(); + $this->travel(5)->seconds(); + $this->travel(5)->minutes(); + $this->travel(5)->hours(); + $this->travel(5)->days(); + $this->travel(5)->weeks(); + $this->travel(5)->years(); + + // Travel into the past... + $this->travel(-5)->hours(); + + // Travel to an explicit time... + $this->travelTo(now()->subHours(6)); + + // Return back to the present time... + $this->travelBack(); + } + +You may also provide a closure to the various time travel methods. The closure +will be invoked with time frozen at the specified time. Once the closure has +executed, time will resume as normal: + + + + 1$this->travel(5)->days(function () { + + 2 // Test something five days into the future... + + 3}); + + 4  + + 5$this->travelTo(now()->subDays(10), function () { + + 6 // Test something during a given moment... + + 7}); + + + $this->travel(5)->days(function () { + // Test something five days into the future... + }); + + $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: + + + + 1use Illuminate\Support\Carbon; + + 2  + + 3// Freeze time and resume normal time after executing closure... + + 4$this->freezeTime(function (Carbon $time) { + + 5 // ... + + 6}); + + 7  + + 8// Freeze time at the current second and resume normal time after executing closure... + + 9$this->freezeSecond(function (Carbon $time) { + + 10 // ... + + 11}) + + + use Illuminate\Support\Carbon; + + // 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) { + // ... + }) + +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: + +Pest PHPUnit + + + + 1use App\Models\Thread; + + 2  + + 3test('forum threads lock after one week of inactivity', function () { + + 4 $thread = Thread::factory()->create(); + + 5  + + 6 $this->travel(1)->week(); + + 7  + + 8 expect($thread->isLockedByInactivity())->toBeTrue(); + + 9}); + + + use App\Models\Thread; + + test('forum threads lock after one week of inactivity', function () { + $thread = Thread::factory()->create(); + + $this->travel(1)->week(); + + expect($thread->isLockedByInactivity())->toBeTrue(); + }); + + + 1use App\Models\Thread; + + 2  + + 3public function test_forum_threads_lock_after_one_week_of_inactivity() + + 4{ + + 5 $thread = Thread::factory()->create(); + + 6  + + 7 $this->travel(1)->week(); + + 8  + + 9 $this->assertTrue($thread->isLockedByInactivity()); + + 10} + + + use App\Models\Thread; + + public function test_forum_threads_lock_after_one_week_of_inactivity() + { + $thread = Thread::factory()->create(); + + $this->travel(1)->week(); + + $this->assertTrue($thread->isLockedByInactivity()); + } + diff --git a/output/12.x/mongodb.md b/output/12.x/mongodb.md new file mode 100644 index 0000000..9813f2d --- /dev/null +++ b/output/12.x/mongodb.md @@ -0,0 +1,162 @@ +# MongoDB + + * Introduction + * Installation + * MongoDB Driver + * Starting a MongoDB Server + * Install the Laravel MongoDB Package + * Configuration + * Features + +## Introduction + +[MongoDB](https://www.mongodb.com/resources/products/fundamentals/why-use- +mongodb) is one of the most popular NoSQL document-oriented database, used for +its high write load (useful for analytics or IoT) and high availability (easy +to set replica sets with automatic failover). It can also shard the database +easily for horizontal scalability and has a powerful query language for doing +aggregation, text search or geospatial queries. + +Instead of storing data in tables of rows or columns like SQL databases, each +record in a MongoDB database is a document described in BSON, a binary +representation of the data. Applications can then retrieve this information in +a JSON format. It supports a wide variety of data types, including documents, +arrays, embedded documents, and binary data. + +Before using MongoDB with Laravel, we recommend installing and using the +`mongodb/laravel-mongodb` package via Composer. The `laravel-mongodb` package +is officially maintained by MongoDB, and while MongoDB is natively supported +by PHP through the MongoDB driver, the [Laravel +MongoDB](https://www.mongodb.com/docs/drivers/php/laravel-mongodb/) package +provides a richer integration with Eloquent and other Laravel features: + + + + 1composer require mongodb/laravel-mongodb + + + composer require mongodb/laravel-mongodb + +## Installation + +### MongoDB Driver + +To connect to a MongoDB database, the `mongodb` PHP extension is required. If +you are developing locally using [Laravel Herd](https://herd.laravel.com) or +installed PHP via `php.new`, you already have this extension installed on your +system. However, if you need to install the extension manually, you may do so +via PECL: + + + + 1pecl install mongodb + + + pecl install mongodb + +For more information on installing the MongoDB PHP extension, check out the +[MongoDB PHP extension installation +instructions](https://www.php.net/manual/en/mongodb.installation.php). + +### Starting a MongoDB Server + +The MongoDB Community Server can be used to run MongoDB locally and is +available for installation on Windows, macOS, Linux, or as a Docker container. +To learn how to install MongoDB, please refer to the [official MongoDB +Community installation +guide](https://docs.mongodb.com/manual/administration/install-community/). + +The connection string for the MongoDB server can be set in your `.env` file: + + + + 1MONGODB_URI="mongodb://localhost:27017" + + 2MONGODB_DATABASE="laravel_app" + + + MONGODB_URI="mongodb://localhost:27017" + MONGODB_DATABASE="laravel_app" + +For hosting MongoDB in the cloud, consider using [MongoDB +Atlas](https://www.mongodb.com/cloud/atlas). To access a MongoDB Atlas cluster +locally from your application, you will need to [add your own IP address in +the cluster's network +settings](https://www.mongodb.com/docs/atlas/security/add-ip-address-to-list/) +to the project's IP Access List. + +The connection string for MongoDB Atlas can also be set in your `.env` file: + + + + 1MONGODB_URI="mongodb+srv://:@.mongodb.net/?retryWrites=true&w=majority" + + 2MONGODB_DATABASE="laravel_app" + + + MONGODB_URI="mongodb+srv://:@.mongodb.net/?retryWrites=true&w=majority" + MONGODB_DATABASE="laravel_app" + +### Install the Laravel MongoDB Package + +Finally, use Composer to install the Laravel MongoDB package: + + + + 1composer require mongodb/laravel-mongodb + + + composer require mongodb/laravel-mongodb + +This installation of the package will fail if the `mongodb` PHP extension is +not installed. The PHP configuration can differ between the CLI and the web +server, so ensure the extension is enabled in both configurations. + +## Configuration + +You may configure your MongoDB connection via your application's +`config/database.php` configuration file. Within this file, add a `mongodb` +connection that utilizes the `mongodb` driver: + + + + 1'connections' => [ + + 2 'mongodb' => [ + + 3 'driver' => 'mongodb', + + 4 'dsn' => env('MONGODB_URI', 'mongodb://localhost:27017'), + + 5 'database' => env('MONGODB_DATABASE', 'laravel_app'), + + 6 ], + + 7], + + + 'connections' => [ + 'mongodb' => [ + 'driver' => 'mongodb', + 'dsn' => env('MONGODB_URI', 'mongodb://localhost:27017'), + 'database' => env('MONGODB_DATABASE', 'laravel_app'), + ], + ], + +## Features + +Once your configuration is complete, you can use the `mongodb` package and +database connection in your application to leverage a variety of powerful +features: + + * [Using Eloquent](https://www.mongodb.com/docs/drivers/php/laravel-mongodb/current/eloquent-models/), models can be stored in MongoDB collections. In addition to the standard Eloquent features, the Laravel MongoDB package provides additional features such as embedded relationships. The package also provides direct access to the MongoDB driver, which can be used to execute operations such as raw queries and aggregation pipelines. + * [Write complex queries](https://www.mongodb.com/docs/drivers/php/laravel-mongodb/current/query-builder/) using the query builder. + * The `mongodb` [cache driver](https://www.mongodb.com/docs/drivers/php/laravel-mongodb/current/cache/) is optimized to use MongoDB features such as TTL indexes to automatically clear expired cache entries. + * [Dispatch and process queued jobs](https://www.mongodb.com/docs/drivers/php/laravel-mongodb/current/queues/) with the `mongodb` queue driver. + * [Storing files in GridFS](https://www.mongodb.com/docs/drivers/php/laravel-mongodb/current/filesystems/), via the [GridFS Adapter for Flysystem](https://flysystem.thephpleague.com/docs/adapter/gridfs/). + * Most third party packages using a database connection or Eloquent can be used with MongoDB. + +To continue learning how to use MongoDB and Laravel, refer to MongoDB's [Quick +Start guide](https://www.mongodb.com/docs/drivers/php/laravel- +mongodb/current/quick-start/). + diff --git a/output/12.x/notifications.md b/output/12.x/notifications.md new file mode 100644 index 0000000..d5bf3e6 --- /dev/null +++ b/output/12.x/notifications.md @@ -0,0 +1,4346 @@ +# Notifications + + * Introduction + * Generating Notifications + * Sending Notifications + * Using the Notifiable Trait + * Using the Notification Facade + * Specifying Delivery Channels + * Queueing Notifications + * On-Demand Notifications + * Mail Notifications + * Formatting Mail Messages + * Customizing the Sender + * Customizing the Recipient + * Customizing the Subject + * Customizing the Mailer + * Customizing the Templates + * Attachments + * Adding Tags and Metadata + * Customizing the Symfony Message + * Using Mailables + * Previewing Mail Notifications + * Markdown Mail Notifications + * Generating the Message + * Writing the Message + * Customizing the Components + * Database Notifications + * Prerequisites + * Formatting Database Notifications + * Accessing the Notifications + * Marking Notifications as Read + * Broadcast Notifications + * Prerequisites + * Formatting Broadcast Notifications + * Listening for Notifications + * SMS Notifications + * Prerequisites + * Formatting SMS Notifications + * Customizing the "From" Number + * Adding a Client Reference + * Routing SMS Notifications + * Slack Notifications + * Prerequisites + * Formatting Slack Notifications + * Slack Interactivity + * Routing Slack Notifications + * Notifying External Slack Workspaces + * Localizing Notifications + * Testing + * Notification Events + * Custom Channels + +## Introduction + +In addition to support for [sending email](/docs/12.x/mail), Laravel provides +support for sending notifications across a variety of delivery channels, +including email, SMS (via [Vonage](https://www.vonage.com/communications- +apis/), formerly known as Nexmo), and [Slack](https://slack.com). In addition, +a variety of [community built notification channels](https://laravel- +notification-channels.com/about/#suggesting-a-new-channel) have been created +to send notifications over dozens of different channels! Notifications may +also be stored in a database so they may be displayed in your web interface. + +Typically, notifications should be short, informational messages that notify +users of something that occurred in your application. For example, if you are +writing a billing application, you might send an "Invoice Paid" notification +to your users via the email and SMS channels. + +## Generating Notifications + +In Laravel, each notification is represented by a single class that is +typically stored in the `app/Notifications` directory. Don't worry if you +don't see this directory in your application - it will be created for you when +you run the `make:notification` Artisan command: + + + + 1php artisan make:notification InvoicePaid + + + php artisan make:notification InvoicePaid + +This command will place a fresh notification class in your `app/Notifications` +directory. Each notification class contains a `via` method and a variable +number of message building methods, such as `toMail` or `toDatabase`, that +convert the notification to a message tailored for that particular channel. + +## Sending Notifications + +### Using the Notifiable Trait + +Notifications may be sent in two ways: using the `notify` method of the +`Notifiable` trait or using the `Notification` [facade](/docs/12.x/facades). +The `Notifiable` trait is included on your application's `App\Models\User` +model by default: + + + + 1notify(new InvoicePaid($invoice)); + + + use App\Notifications\InvoicePaid; + + $user->notify(new InvoicePaid($invoice)); + +Remember, you may use the `Notifiable` trait on any of your models. You are +not limited to only including it on your `User` model. + +### Using the Notification Facade + +Alternatively, you may send notifications via the `Notification` +[facade](/docs/12.x/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: + + + + 1use Illuminate\Support\Facades\Notification; + + 2  + + 3Notification::send($users, new InvoicePaid($invoice)); + + + use Illuminate\Support\Facades\Notification; + + 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: + + + + 1Notification::sendNow($developers, new DeploymentCompleted($deployment)); + + + Notification::sendNow($developers, new DeploymentCompleted($deployment)); + +### Specifying Delivery Channels + +Every notification class has a `via` method that determines on which channels +the notification will be delivered. Notifications may be sent on the `mail`, +`database`, `broadcast`, `vonage`, and `slack` channels. + +If you would like to use other delivery channels such as Telegram or Pusher, +check out the community driven [Laravel Notification Channels +website](http://laravel-notification-channels.com). + +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: + + + + 1/** + + 2 * Get the notification's delivery channels. + + 3 * + + 4 * @return array + + 5 */ + + 6public function via(object $notifiable): array + + 7{ + + 8 return $notifiable->prefers_sms ? ['vonage'] : ['mail', 'database']; + + 9} + + + /** + * Get the notification's delivery channels. + * + * @return array + */ + public function via(object $notifiable): array + { + return $notifiable->prefers_sms ? ['vonage'] : ['mail', 'database']; + } + +### Queueing Notifications + +Before queueing notifications, you should configure your queue and [start a +worker](/docs/12.x/queues#running-the-queue-worker). + +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: + + + + 1notify(new InvoicePaid($invoice)); + + + $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. + +#### Delaying Notifications + +If you would like to delay the delivery of the notification, you may chain the +`delay` method onto your notification instantiation: + + + + 1$delay = now()->addMinutes(10); + + 2  + + 3$user->notify((new InvoicePaid($invoice))->delay($delay)); + + + $delay = now()->addMinutes(10); + + $user->notify((new InvoicePaid($invoice))->delay($delay)); + +You may pass an array to the `delay` method to specify the delay amount for +specific channels: + + + + 1$user->notify((new InvoicePaid($invoice))->delay([ + + 2 'mail' => now()->addMinutes(5), + + 3 'sms' => now()->addMinutes(10), + + 4])); + + + $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: + + + + 1/** + + 2 * Determine the notification's delivery delay. + + 3 * + + 4 * @return array + + 5 */ + + 6public function withDelay(object $notifiable): array + + 7{ + + 8 return [ + + 9 'mail' => now()->addMinutes(5), + + 10 'sms' => now()->addMinutes(10), + + 11 ]; + + 12} + + + /** + * 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: + + + + 1onConnection('redis'); + + 19 } + + 20} + + + 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: + + + + 1/** + + 2 * Determine which connections should be used for each notification channel. + + 3 * + + 4 * @return array + + 5 */ + + 6public function viaConnections(): array + + 7{ + + 8 return [ + + 9 'mail' => 'redis', + + 10 'database' => 'sync', + + 11 ]; + + 12} + + + /** + * 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: + + + + 1/** + + 2 * Determine which queues should be used for each notification channel. + + 3 * + + 4 * @return array + + 5 */ + + 6public function viaQueues(): array + + 7{ + + 8 return [ + + 9 'mail' => 'mail-queue', + + 10 'slack' => 'slack-queue', + + 11 ]; + + 12} + + + /** + * 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/12.x/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: + + + + 1use Illuminate\Queue\Middleware\RateLimited; + + 2  + + 3/** + + 4 * Get the middleware the notification job should pass through. + + 5 * + + 6 * @return array + + 7 */ + + 8public function middleware(object $notifiable, string $channel) + + 9{ + + 10 return match ($channel) { + + 11 'mail' => [new RateLimited('postmark')], + + 12 'slack' => [new RateLimited('slack')], + + 13 default => [], + + 14 }; + + 15} + + + 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) { + 'mail' => [new RateLimited('postmark')], + 'slack' => [new RateLimited('slack')], + default => [], + }; + } + +#### Queued Notifications and Database Transactions + +When queued notifications are dispatched within database transactions, they +may be processed by the queue before the database transaction has committed. +When this happens, any updates you have made to models or database records +during the database transaction may not yet be reflected in the database. In +addition, any models or database records created within the transaction may +not exist in the database. If your notification depends on these models, +unexpected errors can occur when the job that sends the queued notification is +processed. + +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: + + + + 1use App\Notifications\InvoicePaid; + + 2  + + 3$user->notify((new InvoicePaid($invoice))->afterCommit()); + + + use App\Notifications\InvoicePaid; + + $user->notify((new InvoicePaid($invoice))->afterCommit()); + +Alternatively, you may call the `afterCommit` method from your notification's +constructor: + + + + 1afterCommit(); + + 19 } + + 20} + + + afterCommit(); + } + } + +To learn more about working around these issues, please review the +documentation regarding [queued jobs and database +transactions](/docs/12.x/queues#jobs-and-database-transactions). + +#### Determining if a Queued Notification Should Be Sent + +After a queued notification has been dispatched for the queue for background +processing, it will typically be accepted by a queue worker and sent to its +intended recipient. + +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: + + + + 1/** + + 2 * Determine if the notification should be sent. + + 3 */ + + 4public function shouldSend(object $notifiable, string $channel): bool + + 5{ + + 6 return $this->invoice->isPaid(); + + 7} + + + /** + * 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: + + + + 1use Illuminate\Broadcasting\Channel; + + 2use Illuminate\Support\Facades\Notification; + + 3  + + 4Notification::route('mail', '[[email protected]](/cdn-cgi/l/email-protection)') + + 5 ->route('vonage', '5555555555') + + 6 ->route('slack', '#slack-channel') + + 7 ->route('broadcast', [new Channel('channel-name')]) + + 8 ->notify(new InvoicePaid($invoice)); + + + use Illuminate\Broadcasting\Channel; + use Illuminate\Support\Facades\Notification; + + Notification::route('mail', '[[email protected]](/cdn-cgi/l/email-protection)') + ->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: + + + + 1Notification::route('mail', [ + + 2 '[[email protected]](/cdn-cgi/l/email-protection)' => 'Barrett Blair', + + 3])->notify(new InvoicePaid($invoice)); + + + Notification::route('mail', [ + '[[email protected]](/cdn-cgi/l/email-protection)' => 'Barrett Blair', + ])->notify(new InvoicePaid($invoice)); + +Using the `routes` method, you may provide ad-hoc routing information for +multiple notification channels at once: + + + + 1Notification::routes([ + + 2 'mail' => ['[[email protected]](/cdn-cgi/l/email-protection)' => 'Barrett Blair'], + + 3 'vonage' => '5555555555', + + 4])->notify(new InvoicePaid($invoice)); + + + Notification::routes([ + 'mail' => ['[[email protected]](/cdn-cgi/l/email-protection)' => 'Barrett Blair'], + 'vonage' => '5555555555', + ])->notify(new InvoicePaid($invoice)); + +## Mail Notifications + +### Formatting Mail Messages + +If a notification supports being sent as an email, you should define a +`toMail` method on the notification class. This method will receive a +`$notifiable` entity and should return an +`Illuminate\Notifications\Messages\MailMessage` instance. + +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: + + + + 1/** + + 2 * Get the mail representation of the notification. + + 3 */ + + 4public function toMail(object $notifiable): MailMessage + + 5{ + + 6 $url = url('/invoice/'.$this->invoice->id); + + 7  + + 8 return (new MailMessage) + + 9 ->greeting('Hello!') + + 10 ->line('One of your invoices has been paid!') + + 11 ->lineIf($this->amount > 0, "Amount paid: {$this->amount}") + + 12 ->action('View Invoice', $url) + + 13 ->line('Thank you for using our application!'); + + 14} + + + /** + * 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 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. + +In this example, we register a greeting, a line of text, a call to action, and +then another line of text. These methods provided by the `MailMessage` object +make it simple and fast to format small transactional emails. The mail channel +will then translate the message components into a beautiful, responsive HTML +email template with a plain-text counterpart. Here is an example of an email +generated by the `mail` channel: + +![](https://laravel.com/img/docs/notification-example-2.png) + +When sending mail notifications, be sure to set the `name` configuration +option in your `config/app.php` configuration file. This value will be used in +the header and footer of your mail notification messages. + +#### Error Messages + +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: + + + + 1/** + + 2 * Get the mail representation of the notification. + + 3 */ + + 4public function toMail(object $notifiable): MailMessage + + 5{ + + 6 return (new MailMessage) + + 7 ->error() + + 8 ->subject('Invoice Payment Failed') + + 9 ->line('...'); + + 10} + + + /** + * 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: + + + + 1/** + + 2 * Get the mail representation of the notification. + + 3 */ + + 4public function toMail(object $notifiable): MailMessage + + 5{ + + 6 return (new MailMessage)->view( + + 7 'mail.invoice.paid', ['invoice' => $this->invoice] + + 8 ); + + 9} + + + /** + * 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: + + + + 1/** + + 2 * Get the mail representation of the notification. + + 3 */ + + 4public function toMail(object $notifiable): MailMessage + + 5{ + + 6 return (new MailMessage)->view( + + 7 ['mail.invoice.paid', 'mail.invoice.paid-text'], + + 8 ['invoice' => $this->invoice] + + 9 ); + + 10} + + + /** + * 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: + + + + 1/** + + 2 * Get the mail representation of the notification. + + 3 */ + + 4public function toMail(object $notifiable): MailMessage + + 5{ + + 6 return (new MailMessage)->text( + + 7 'mail.invoice.paid-text', ['invoice' => $this->invoice] + + 8 ); + + 9} + + + /** + * 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: + + + + 1/** + + 2 * Get the mail representation of the notification. + + 3 */ + + 4public function toMail(object $notifiable): MailMessage + + 5{ + + 6 return (new MailMessage) + + 7 ->from('[[email protected]](/cdn-cgi/l/email-protection)', 'Barrett Blair') + + 8 ->line('...'); + + 9} + + + /** + * Get the mail representation of the notification. + */ + public function toMail(object $notifiable): MailMessage + { + return (new MailMessage) + ->from('[[email protected]](/cdn-cgi/l/email-protection)', '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: + + + + 1|string + + 17 */ + + 18 public function routeNotificationForMail(Notification $notification): array|string + + 19 { + + 20 // Return email address only... + + 21 return $this->email_address; + + 22  + + 23 // Return email address and name... + + 24 return [$this->email_address => $this->name]; + + 25 } + + 26} + + + |string + */ + public function routeNotificationForMail(Notification $notification): array|string + { + // Return email address only... + return $this->email_address; + + // 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: + + + + 1/** + + 2 * Get the mail representation of the notification. + + 3 */ + + 4public function toMail(object $notifiable): MailMessage + + 5{ + + 6 return (new MailMessage) + + 7 ->subject('Notification Subject') + + 8 ->line('...'); + + 9} + + + /** + * 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: + + + + 1/** + + 2 * Get the mail representation of the notification. + + 3 */ + + 4public function toMail(object $notifiable): MailMessage + + 5{ + + 6 return (new MailMessage) + + 7 ->mailer('postmark') + + 8 ->line('...'); + + 9} + + + /** + * Get the mail representation of the notification. + */ + public function toMail(object $notifiable): MailMessage + { + return (new MailMessage) + ->mailer('postmark') + ->line('...'); + } + +### Customizing the Templates + +You can modify the HTML and plain-text template used by mail notifications by +publishing the notification package's resources. After running this command, +the mail notification templates will be located in the +`resources/views/vendor/notifications` directory: + + + + 1php artisan vendor:publish --tag=laravel-notifications + + + php artisan vendor:publish --tag=laravel-notifications + +### Attachments + +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: + + + + 1/** + + 2 * Get the mail representation of the notification. + + 3 */ + + 4public function toMail(object $notifiable): MailMessage + + 5{ + + 6 return (new MailMessage) + + 7 ->greeting('Hello!') + + 8 ->attach('/path/to/file'); + + 9} + + + /** + * Get the mail representation of the notification. + */ + public function toMail(object $notifiable): MailMessage + { + return (new MailMessage) + ->greeting('Hello!') + ->attach('/path/to/file'); + } + +The `attach` method offered by notification mail messages also accepts +[attachable objects](/docs/12.x/mail#attachable-objects). Please consult the +comprehensive [attachable object documentation](/docs/12.x/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: + + + + 1/** + + 2 * Get the mail representation of the notification. + + 3 */ + + 4public function toMail(object $notifiable): MailMessage + + 5{ + + 6 return (new MailMessage) + + 7 ->greeting('Hello!') + + 8 ->attach('/path/to/file', [ + + 9 'as' => 'name.pdf', + + 10 'mime' => 'application/pdf', + + 11 ]); + + 12} + + + /** + * 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/12.x/mail#generating- +mailables) from the `toMail` method: + + + + 1use App\Mail\InvoicePaid as InvoicePaidMailable; + + 2  + + 3/** + + 4 * Get the mail representation of the notification. + + 5 */ + + 6public function toMail(object $notifiable): Mailable + + 7{ + + 8 return (new InvoicePaidMailable($this->invoice)) + + 9 ->to($notifiable->email) + + 10 ->attachFromStorage('/path/to/file'); + + 11} + + + 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'); + } + +When necessary, multiple files may be attached to a message using the +`attachMany` method: + + + + 1/** + + 2 * Get the mail representation of the notification. + + 3 */ + + 4public function toMail(object $notifiable): MailMessage + + 5{ + + 6 return (new MailMessage) + + 7 ->greeting('Hello!') + + 8 ->attachMany([ + + 9 '/path/to/forge.svg', + + 10 '/path/to/vapor.svg' => [ + + 11 'as' => 'Logo.svg', + + 12 'mime' => 'image/svg+xml', + + 13 ], + + 14 ]); + + 15} + + + /** + * 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: + + + + 1/** + + 2 * Get the mail representation of the notification. + + 3 */ + + 4public function toMail(object $notifiable): MailMessage + + 5{ + + 6 return (new MailMessage) + + 7 ->greeting('Hello!') + + 8 ->attachData($this->pdf, 'name.pdf', [ + + 9 'mime' => 'application/pdf', + + 10 ]); + + 11} + + + /** + * 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: + + + + 1/** + + 2 * Get the mail representation of the notification. + + 3 */ + + 4public function toMail(object $notifiable): MailMessage + + 5{ + + 6 return (new MailMessage) + + 7 ->greeting('Comment Upvoted!') + + 8 ->tag('upvote') + + 9 ->metadata('comment_id', $this->comment->id); + + 10} + + + /** + * 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/docs/mailgun/user-manual/tracking- +messages/#tags) and +[metadata](https://documentation.mailgun.com/docs/mailgun/user-manual/sending- +messages/#attaching-metadata-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). + +If your application is using Amazon SES to send emails, you should use the +`metadata` method to attach [SES +"tags"](https://docs.aws.amazon.com/ses/latest/APIReference/API_MessageTag.html) +to the message. + +### Customizing the Symfony Message + +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: + + + + 1use Symfony\Component\Mime\Email; + + 2  + + 3/** + + 4 * Get the mail representation of the notification. + + 5 */ + + 6public function toMail(object $notifiable): MailMessage + + 7{ + + 8 return (new MailMessage) + + 9 ->withSymfonyMessage(function (Email $message) { + + 10 $message->getHeaders()->addTextHeader( + + 11 'Custom-Header', 'Header Value' + + 12 ); + + 13 }); + + 14} + + + 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' + ); + }); + } + +### Using Mailables + +If needed, you may return a full [mailable object](/docs/12.x/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: + + + + 1use App\Mail\InvoicePaid as InvoicePaidMailable; + + 2use Illuminate\Mail\Mailable; + + 3  + + 4/** + + 5 * Get the mail representation of the notification. + + 6 */ + + 7public function toMail(object $notifiable): Mailable + + 8{ + + 9 return (new InvoicePaidMailable($this->invoice)) + + 10 ->to($notifiable->email); + + 11} + + + 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); + } + +#### Mailables and On-Demand Notifications + +If you are sending an on-demand notification, 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: + + + + 1use App\Mail\InvoicePaid as InvoicePaidMailable; + + 2use Illuminate\Notifications\AnonymousNotifiable; + + 3use Illuminate\Mail\Mailable; + + 4  + + 5/** + + 6 * Get the mail representation of the notification. + + 7 */ + + 8public function toMail(object $notifiable): Mailable + + 9{ + + 10 $address = $notifiable instanceof AnonymousNotifiable + + 11 ? $notifiable->routeNotificationFor('mail') + + 12 : $notifiable->email; + + 13  + + 14 return (new InvoicePaidMailable($this->invoice)) + + 15 ->to($address); + + 16} + + + 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; + + 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: + + + + 1use App\Models\Invoice; + + 2use App\Notifications\InvoicePaid; + + 3  + + 4Route::get('/notification', function () { + + 5 $invoice = Invoice::find(1); + + 6  + + 7 return (new InvoicePaid($invoice)) + + 8 ->toMail($invoice->user); + + 9}); + + + use App\Models\Invoice; + use App\Notifications\InvoicePaid; + + Route::get('/notification', function () { + $invoice = Invoice::find(1); + + return (new InvoicePaid($invoice)) + ->toMail($invoice->user); + }); + +## Markdown Mail Notifications + +Markdown mail notifications allow you to take advantage of the pre-built +templates of mail notifications, while giving you more freedom to write +longer, customized messages. Since the messages are written in Markdown, +Laravel is able to render beautiful, responsive HTML templates for the +messages while also automatically generating a plain-text counterpart. + +### Generating the Message + +To generate a notification with a corresponding Markdown template, you may use +the `--markdown` option of the `make:notification` Artisan command: + + + + 1php artisan make:notification InvoicePaid --markdown=mail.invoice.paid + + + 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: + + + + 1/** + + 2 * Get the mail representation of the notification. + + 3 */ + + 4public function toMail(object $notifiable): MailMessage + + 5{ + + 6 $url = url('/invoice/'.$this->invoice->id); + + 7  + + 8 return (new MailMessage) + + 9 ->subject('Invoice Paid') + + 10 ->markdown('mail.invoice.paid', ['url' => $url]); + + 11} + + + /** + * 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]); + } + +### Writing the Message + +Markdown mail notifications use a combination of Blade components and Markdown +syntax which allow you to easily construct notifications while leveraging +Laravel's pre-crafted notification components: + + + + 1 + + 2# Invoice Paid + + 3  + + 4Your invoice has been paid! + + 5  + + 6 + + 7View Invoice + + 8 + + 9  + + 10Thanks,
    + + 11{{ config('app.name') }} + + 12
    + + + + # Invoice Paid + + Your invoice has been paid! + + + View Invoice + + + Thanks,
    + {{ config('app.name') }} +
    + +Do not use excess indentation when writing Markdown emails. Per Markdown +standards, Markdown parsers will render indented content as code blocks. + +#### Button Component + +The button component renders a centered button link. The component accepts two +arguments, a `url` and an optional `color`. Supported colors are `primary`, +`green`, and `red`. You may add as many button components to a notification as +you wish: + + + + 1 + + 2View Invoice + + 3 + + + + View Invoice + + +#### Panel Component + +The panel component renders the given block of text in a panel that has a +slightly different background color than the rest of the notification. This +allows you to draw attention to a given block of text: + + + + 1 + + 2This is the panel content. + + 3 + + + + This is the panel content. + + +#### Table Component + +The table component allows you to transform a Markdown table into an HTML +table. The component accepts the Markdown table as its content. Table column +alignment is supported using the default Markdown table alignment syntax: + + + + 1 + + 2| Laravel | Table | Example | + + 3| ------------- | :-----------: | ------------: | + + 4| Col 2 is | Centered | $10 | + + 5| Col 3 is | Right-Aligned | $20 | + + 6 + + + + | Laravel | Table | Example | + | ------------- | :-----------: | ------------: | + | Col 2 is | Centered | $10 | + | Col 3 is | Right-Aligned | $20 | + + +### Customizing the Components + +You may export all of the Markdown notification components to your own +application for customization. To export the components, use the +`vendor:publish` Artisan command to publish the `laravel-mail` asset tag: + + + + 1php artisan vendor:publish --tag=laravel-mail + + + php artisan vendor:publish --tag=laravel-mail + +This command will publish the Markdown mail components to the +`resources/views/vendor/mail` directory. The `mail` directory will contain an +`html` and a `text` directory, each containing their respective +representations of every available component. You are free to customize these +components however you like. + +#### Customizing the CSS + +After exporting the components, the `resources/views/vendor/mail/html/themes` +directory will contain a `default.css` file. You may customize the CSS in this +file and your styles will automatically be in-lined within the HTML +representations of your Markdown notifications. + +If you would like to build an entirely new theme for Laravel's Markdown +components, you may place a CSS file within the `html/themes` directory. After +naming and saving your CSS file, update the `theme` option of the `mail` +configuration file to match the name of your new theme. + +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: + + + + 1/** + + 2 * Get the mail representation of the notification. + + 3 */ + + 4public function toMail(object $notifiable): MailMessage + + 5{ + + 6 return (new MailMessage) + + 7 ->theme('invoice') + + 8 ->subject('Invoice Paid') + + 9 ->markdown('mail.invoice.paid', ['url' => $url]); + + 10} + + + /** + * 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 + +### Prerequisites + +The `database` notification channel stores the notification information in a +database table. This table will contain information such as the notification +type as well as a JSON data structure that describes the notification. + +You can query the table to display the notifications in your application's +user interface. But, before you can do that, you will need to create a +database table to hold your notifications. You may use the +`make:notifications-table` command to generate a +[migration](/docs/12.x/migrations) with the proper table schema: + + + + 1php artisan make:notifications-table + + 2  + + 3php artisan migrate + + + php artisan make:notifications-table + + php artisan migrate + +If your notifiable models are using [UUID or ULID primary +keys](/docs/12.x/eloquent#uuid-and-ulid-keys), you should replace the `morphs` +method with [uuidMorphs](/docs/12.x/migrations#column-method-uuidMorphs) or +[ulidMorphs](/docs/12.x/migrations#column-method-ulidMorphs) in the +notification table migration. + +### Formatting Database Notifications + +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: + + + + 1/** + + 2 * Get the array representation of the notification. + + 3 * + + 4 * @return array + + 5 */ + + 6public function toArray(object $notifiable): array + + 7{ + + 8 return [ + + 9 'invoice_id' => $this->invoice->id, + + 10 'amount' => $this->invoice->amount, + + 11 ]; + + 12} + + + /** + * 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 a notification is stored in your application's database, the `type` +column will be set to the notification's class name by default, and the +`read_at` column will be `null`. However, you can customize this behavior by +defining the `databaseType` and `initialDatabaseReadAtValue` methods in your +notification class: + + + + 1use Illuminate\Support\Carbon; + + 2  + + 3/** + + 4 * Get the notification's database type. + + 5 */ + + 6public function databaseType(object $notifiable): string + + 7{ + + 8 return 'invoice-paid'; + + 9} + + 10  + + 11/** + + 12 * Get the initial value for the "read_at" column. + + 13 */ + + 14public function initialDatabaseReadAtValue(): ?Carbon + + 15{ + + 16 return null; + + 17} + + + use Illuminate\Support\Carbon; + + /** + * Get the notification's database type. + */ + public function databaseType(object $notifiable): string + { + return 'invoice-paid'; + } + + /** + * Get the initial value for the "read_at" column. + */ + public function initialDatabaseReadAtValue(): ?Carbon + { + return null; + } + +#### `toDatabase` vs. `toArray` + +The `toArray` method is also used by the `broadcast` channel to determine +which data to broadcast to your JavaScript powered frontend. If you would like +to have two different array representations for the `database` and `broadcast` +channels, you should define a `toDatabase` method instead of a `toArray` +method. + +### Accessing the Notifications + +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/12.x/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: + + + + 1$user = App\Models\User::find(1); + + 2  + + 3foreach ($user->notifications as $notification) { + + 4 echo $notification->type; + + 5} + + + $user = App\Models\User::find(1); + + 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: + + + + 1$user = App\Models\User::find(1); + + 2  + + 3foreach ($user->unreadNotifications as $notification) { + + 4 echo $notification->type; + + 5} + + + $user = App\Models\User::find(1); + + foreach ($user->unreadNotifications as $notification) { + echo $notification->type; + } + +If you want to retrieve only the "read" notifications, you may use the +`readNotifications` relationship: + + + + 1$user = App\Models\User::find(1); + + 2  + + 3foreach ($user->readNotifications as $notification) { + + 4 echo $notification->type; + + 5} + + + $user = App\Models\User::find(1); + + foreach ($user->readNotifications as $notification) { + echo $notification->type; + } + +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. + +### Marking Notifications as Read + +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: + + + + 1$user = App\Models\User::find(1); + + 2  + + 3foreach ($user->unreadNotifications as $notification) { + + 4 $notification->markAsRead(); + + 5} + + + $user = App\Models\User::find(1); + + 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: + + + + 1$user->unreadNotifications->markAsRead(); + + + $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: + + + + 1$user = App\Models\User::find(1); + + 2  + + 3$user->unreadNotifications()->update(['read_at' => now()]); + + + $user = App\Models\User::find(1); + + $user->unreadNotifications()->update(['read_at' => now()]); + +You may `delete` the notifications to remove them from the table entirely: + + + + 1$user->notifications()->delete(); + + + $user->notifications()->delete(); + +## Broadcast Notifications + +### Prerequisites + +Before broadcasting notifications, you should configure and be familiar with +Laravel's [event broadcasting](/docs/12.x/broadcasting) services. Event +broadcasting provides a way to react to server-side Laravel events from your +JavaScript powered frontend. + +### Formatting Broadcast Notifications + +The `broadcast` channel broadcasts notifications using Laravel's [event +broadcasting](/docs/12.x/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: + + + + 1use Illuminate\Notifications\Messages\BroadcastMessage; + + 2  + + 3/** + + 4 * Get the broadcastable representation of the notification. + + 5 */ + + 6public function toBroadcast(object $notifiable): BroadcastMessage + + 7{ + + 8 return new BroadcastMessage([ + + 9 'invoice_id' => $this->invoice->id, + + 10 'amount' => $this->invoice->amount, + + 11 ]); + + 12} + + + 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, + ]); + } + +#### 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`: + + + + 1return (new BroadcastMessage($data)) + + 2 ->onConnection('sqs') + + 3 ->onQueue('broadcasts'); + + + 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: + + + + 1/** + + 2 * Get the type of the notification being broadcast. + + 3 */ + + 4public function broadcastType(): string + + 5{ + + 6 return 'broadcast.message'; + + 7} + + + /** + * 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/12.x/broadcasting#client-side-installation), you may easily listen +for notifications on a channel using the `notification` method: + + + + 1Echo.private('App.Models.User.' + userId) + + 2 .notification((notification) => { + + 3 console.log(notification.type); + + 4 }); + + + Echo.private('App.Models.User.' + userId) + .notification((notification) => { + console.log(notification.type); + }); + +#### Using React or Vue + +Laravel Echo includes React and Vue hooks that make it painless to listen for +notifications. To get started, invoke the `useEchoNotification` hook, which is +used to listen for notifications. The `useEchoNotification` hook will +automatically leave channels when the consuming component is unmounted: + +React Vue + + + + 1import { useEchoNotification } from "@laravel/echo-react"; + + 2  + + 3useEchoNotification( + + 4 `App.Models.User.${userId}`, + + 5 (notification) => { + + 6 console.log(notification.type); + + 7 }, + + 8); + + + import { useEchoNotification } from "@laravel/echo-react"; + + useEchoNotification( + `App.Models.User.${userId}`, + (notification) => { + console.log(notification.type); + }, + ); + + + 1 + + + + +By default, the hook listens to all notifications. To specify the notification +types you would like to listen to, you can provide either a string or array of +types to `useEchoNotification`: + +React Vue + + + + 1import { useEchoNotification } from "@laravel/echo-react"; + + 2  + + 3useEchoNotification( + + 4 `App.Models.User.${userId}`, + + 5 (notification) => { + + 6 console.log(notification.type); + + 7 }, + + 8 'App.Notifications.InvoicePaid', + + 9); + + + import { useEchoNotification } from "@laravel/echo-react"; + + useEchoNotification( + `App.Models.User.${userId}`, + (notification) => { + console.log(notification.type); + }, + 'App.Notifications.InvoicePaid', + ); + + + 1 + + + + +You may also specify the shape of the notification payload data, providing +greater type safety and editing convenience: + + + + 1type InvoicePaidNotification = { + + 2 invoice_id: number; + + 3 created_at: string; + + 4}; + + 5  + + 6useEchoNotification( + + 7 `App.Models.User.${userId}`, + + 8 (notification) => { + + 9 console.log(notification.invoice_id); + + 10 console.log(notification.created_at); + + 11 console.log(notification.type); + + 12 }, + + 13 'App.Notifications.InvoicePaid', + + 14); + + + type InvoicePaidNotification = { + invoice_id: number; + created_at: string; + }; + + useEchoNotification( + `App.Models.User.${userId}`, + (notification) => { + console.log(notification.invoice_id); + console.log(notification.created_at); + console.log(notification.type); + }, + 'App.Notifications.InvoicePaid', + ); + +#### 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: + + + + 1id; + + 19 } + + 20} + + + id; + } + } + +## SMS Notifications + +### Prerequisites + +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: + + + + 1composer require laravel/vonage-notification-channel guzzlehttp/guzzle + + + 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: + + + + 1VONAGE_SMS_FROM=15556666666 + + + 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: + + + + 1use Illuminate\Notifications\Messages\VonageMessage; + + 2  + + 3/** + + 4 * Get the Vonage / SMS representation of the notification. + + 5 */ + + 6public function toVonage(object $notifiable): VonageMessage + + 7{ + + 8 return (new VonageMessage) + + 9 ->content('Your SMS message content'); + + 10} + + + 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'); + } + +#### Unicode Content + +If your SMS message will contain unicode characters, you should call the +`unicode` method when constructing the `VonageMessage` instance: + + + + 1use Illuminate\Notifications\Messages\VonageMessage; + + 2  + + 3/** + + 4 * Get the Vonage / SMS representation of the notification. + + 5 */ + + 6public function toVonage(object $notifiable): VonageMessage + + 7{ + + 8 return (new VonageMessage) + + 9 ->content('Your unicode message') + + 10 ->unicode(); + + 11} + + + 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(); + } + +### 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: + + + + 1use Illuminate\Notifications\Messages\VonageMessage; + + 2  + + 3/** + + 4 * Get the Vonage / SMS representation of the notification. + + 5 */ + + 6public function toVonage(object $notifiable): VonageMessage + + 7{ + + 8 return (new VonageMessage) + + 9 ->content('Your SMS message content') + + 10 ->from('15554443333'); + + 11} + + + 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'); + } + +### 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: + + + + 1use Illuminate\Notifications\Messages\VonageMessage; + + 2  + + 3/** + + 4 * Get the Vonage / SMS representation of the notification. + + 5 */ + + 6public function toVonage(object $notifiable): VonageMessage + + 7{ + + 8 return (new VonageMessage) + + 9 ->clientReference((string) $notifiable->id) + + 10 ->content('Your SMS message content'); + + 11} + + + 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'); + } + +### Routing SMS Notifications + +To route Vonage notifications to the proper phone number, define a +`routeNotificationForVonage` method on your notifiable entity: + + + + 1phone_number; + + 19 } + + 20} + + + phone_number; + } + } + +## Slack Notifications + +### Prerequisites + +Before sending Slack notifications, you should install the Slack notification +channel via Composer: + + + + 1composer require laravel/slack-notification-channel + + + composer require laravel/slack-notification-channel + +Additionally, you must create a [Slack +App](https://api.slack.com/apps?new_app=1) for your Slack workspace. + +If you only need to send notifications to the same Slack workspace that the +App is created in, you should ensure that your App has the `chat:write`, +`chat:write.public`, and `chat:write.customize` scopes. These scopes can be +added from the "OAuth & Permissions" App management tab within Slack. + +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: + + + + 1'slack' => [ + + 2 'notifications' => [ + + 3 'bot_user_oauth_token' => env('SLACK_BOT_USER_OAUTH_TOKEN'), + + 4 'channel' => env('SLACK_BOT_USER_DEFAULT_CHANNEL'), + + 5 ], + + 6], + + + 'slack' => [ + 'notifications' => [ + 'bot_user_oauth_token' => env('SLACK_BOT_USER_OAUTH_TOKEN'), + 'channel' => env('SLACK_BOT_USER_DEFAULT_CHANNEL'), + ], + ], + +#### App Distribution + +If your application will be sending notifications to external Slack workspaces +that are owned by your application's users, you will need to "distribute" your +App via Slack. App distribution can be managed from your App's "Manage +Distribution" tab within Slack. Once your App has been distributed, you may +use [Socialite](/docs/12.x/socialite) to [obtain Slack Bot +tokens](/docs/12.x/socialite#slack-bot-scopes) on behalf of your application's +users. + +### Formatting Slack Notifications + +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): + + + + 1use Illuminate\Notifications\Slack\BlockKit\Blocks\ContextBlock; + + 2use Illuminate\Notifications\Slack\BlockKit\Blocks\SectionBlock; + + 3use Illuminate\Notifications\Slack\SlackMessage; + + 4  + + 5/** + + 6 * Get the Slack representation of the notification. + + 7 */ + + 8public function toSlack(object $notifiable): SlackMessage + + 9{ + + 10 return (new SlackMessage) + + 11 ->text('One of your invoices has been paid!') + + 12 ->headerBlock('Invoice Paid') + + 13 ->contextBlock(function (ContextBlock $block) { + + 14 $block->text('Customer #1234'); + + 15 }) + + 16 ->sectionBlock(function (SectionBlock $block) { + + 17 $block->text('An invoice has been paid.'); + + 18 $block->field("*Invoice No:*\n1000")->markdown(); + + 19 $block->field("*Invoice Recipient:*\n[[email protected]](/cdn-cgi/l/email-protection)")->markdown(); + + 20 }) + + 21 ->dividerBlock() + + 22 ->sectionBlock(function (SectionBlock $block) { + + 23 $block->text('Congratulations!'); + + 24 }); + + 25} + + + 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.'); + $block->field("*Invoice No:*\n1000")->markdown(); + $block->field("*Invoice Recipient:*\[[email protected]](/cdn-cgi/l/email-protection)")->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: + + + + 1use Illuminate\Notifications\Slack\SlackMessage; + + 2use Illuminate\Support\Str; + + 3  + + 4/** + + 5 * Get the Slack representation of the notification. + + 6 */ + + 7public function toSlack(object $notifiable): SlackMessage + + 8{ + + 9 $template = <<usingBlockKitTemplate($template); + + 32} + + + 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); + } + +### Slack Interactivity + +Slack's Block Kit notification system provides powerful features to [handle +user interaction](https://api.slack.com/interactivity/handling). To utilize +these features, your Slack App should have "Interactivity" enabled and a +"Request URL" configured that points to a URL served by your application. +These settings can be managed from the "Interactivity & Shortcuts" App +management tab within Slack. + +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: + + + + 1use Illuminate\Notifications\Slack\BlockKit\Blocks\ActionsBlock; + + 2use Illuminate\Notifications\Slack\BlockKit\Blocks\ContextBlock; + + 3use Illuminate\Notifications\Slack\BlockKit\Blocks\SectionBlock; + + 4use Illuminate\Notifications\Slack\SlackMessage; + + 5  + + 6/** + + 7 * Get the Slack representation of the notification. + + 8 */ + + 9public function toSlack(object $notifiable): SlackMessage + + 10{ + + 11 return (new SlackMessage) + + 12 ->text('One of your invoices has been paid!') + + 13 ->headerBlock('Invoice Paid') + + 14 ->contextBlock(function (ContextBlock $block) { + + 15 $block->text('Customer #1234'); + + 16 }) + + 17 ->sectionBlock(function (SectionBlock $block) { + + 18 $block->text('An invoice has been paid.'); + + 19 }) + + 20 ->actionsBlock(function (ActionsBlock $block) { + + 21 // ID defaults to "button_acknowledge_invoice"... + + 22 $block->button('Acknowledge Invoice')->primary(); + + 23  + + 24 // Manually configure the ID... + + 25 $block->button('Deny')->danger()->id('deny_invoice'); + + 26 }); + + 27} + + + 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'); + }); + } + +#### 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: + + + + 1use Illuminate\Notifications\Slack\BlockKit\Blocks\ActionsBlock; + + 2use Illuminate\Notifications\Slack\BlockKit\Blocks\ContextBlock; + + 3use Illuminate\Notifications\Slack\BlockKit\Blocks\SectionBlock; + + 4use Illuminate\Notifications\Slack\BlockKit\Composites\ConfirmObject; + + 5use Illuminate\Notifications\Slack\SlackMessage; + + 6  + + 7/** + + 8 * Get the Slack representation of the notification. + + 9 */ + + 10public function toSlack(object $notifiable): SlackMessage + + 11{ + + 12 return (new SlackMessage) + + 13 ->text('One of your invoices has been paid!') + + 14 ->headerBlock('Invoice Paid') + + 15 ->contextBlock(function (ContextBlock $block) { + + 16 $block->text('Customer #1234'); + + 17 }) + + 18 ->sectionBlock(function (SectionBlock $block) { + + 19 $block->text('An invoice has been paid.'); + + 20 }) + + 21 ->actionsBlock(function (ActionsBlock $block) { + + 22 $block->button('Acknowledge Invoice') + + 23 ->primary() + + 24 ->confirm( + + 25 'Acknowledge the payment and send a thank you email?', + + 26 function (ConfirmObject $dialog) { + + 27 $dialog->confirm('Yes'); + + 28 $dialog->deny('No'); + + 29 } + + 30 ); + + 31 }); + + 32} + + + 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: + + + + 1return (new SlackMessage) + + 2 ->text('One of your invoices has been paid!') + + 3 ->headerBlock('Invoice Paid') + + 4 ->dd(); + + + return (new SlackMessage) + ->text('One of your invoices has been paid!') + ->headerBlock('Invoice Paid') + ->dd(); + +### Routing Slack Notifications + +To direct Slack notifications to the appropriate Slack team and channel, +define a `routeNotificationForSlack` method on your notifiable model. This +method can return one of three values: + + * `null` \- which defers routing to the channel configured in the notification itself. You may use the `to` method when building your `SlackMessage` to configure the channel within the notification. + * A string specifying the Slack channel to send the notification to, e.g. `#support-channel`. + * A `SlackRoute` instance, which allows you to specify an OAuth token and channel name, e.g. `SlackRoute::make($this->slack_channel, $this->slack_token)`. This method should be used to send notifications to external workspaces. + +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: + + + + 1slack_channel, $this->slack_token); + + 20 } + + 21} + + + slack_channel, $this->slack_token); + } + } + +## Localizing Notifications + +Laravel allows you to send notifications in a locale other than the HTTP +request's current locale, and will even remember this locale if the +notification is queued. + +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: + + + + 1$user->notify((new InvoicePaid($invoice))->locale('es')); + + + $user->notify((new InvoicePaid($invoice))->locale('es')); + +Localization of multiple notifiable entries may also be achieved via the +`Notification` facade: + + + + 1Notification::locale('es')->send( + + 2 $users, new InvoicePaid($invoice) + + 3); + + + 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: + + + + 1use Illuminate\Contracts\Translation\HasLocalePreference; + + 2  + + 3class User extends Model implements HasLocalePreference + + 4{ + + 5 /** + + 6 * Get the user's preferred locale. + + 7 */ + + 8 public function preferredLocale(): string + + 9 { + + 10 return $this->locale; + + 11 } + + 12} + + + use Illuminate\Contracts\Translation\HasLocalePreference; + + class User extends Model implements HasLocalePreference + { + /** + * Get the user's preferred locale. + */ + public function preferredLocale(): string + { + 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: + + + + 1$user->notify(new InvoicePaid($invoice)); + + + $user->notify(new InvoicePaid($invoice)); + +## Testing + +You may use the `Notification` facade's `fake` method to prevent notifications +from being sent. Typically, sending notifications is unrelated to the code you +are actually testing. Most likely, it is sufficient to simply assert that +Laravel was instructed to send a given notification. + +After calling the `Notification` facade's `fake` method, you may then assert +that notifications were instructed to be sent to users and even inspect the +data the notifications received: + +Pest PHPUnit + + + + 1order->id === $order->id; + + 5 } + + 6); + + + 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, you can test that +the on-demand notification was sent via the `assertSentOnDemand` method: + + + + 1Notification::assertSentOnDemand(OrderShipped::class); + + + 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: + + + + 1Notification::assertSentOnDemand( + + 2 OrderShipped::class, + + 3 function (OrderShipped $notification, array $channels, object $notifiable) use ($user) { + + 4 return $notifiable->routes['mail'] === $user->email; + + 5 } + + 6); + + + Notification::assertSentOnDemand( + OrderShipped::class, + function (OrderShipped $notification, array $channels, object $notifiable) use ($user) { + return $notifiable->routes['mail'] === $user->email; + } + ); + +## Notification Events + +#### Notification Sending Event + +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/12.x/events) for this event within your application: + + + + 1use Illuminate\Notifications\Events\NotificationSending; + + 2  + + 3class CheckNotificationStatus + + 4{ + + 5 /** + + 6 * Handle the event. + + 7 */ + + 8 public function handle(NotificationSending $event): void + + 9 { + + 10 // ... + + 11 } + + 12} + + + use Illuminate\Notifications\Events\NotificationSending; + + class CheckNotificationStatus + { + /** + * Handle the 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: + + + + 1/** + + 2 * Handle the event. + + 3 */ + + 4public function handle(NotificationSending $event): bool + + 5{ + + 6 return false; + + 7} + + + /** + * Handle the 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: + + + + 1/** + + 2 * Handle the event. + + 3 */ + + 4public function handle(NotificationSending $event): void + + 5{ + + 6 // $event->channel + + 7 // $event->notifiable + + 8 // $event->notification + + 9} + + + /** + * Handle the 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/12.x/events) +is dispatched by the notification system. This contains the "notifiable" +entity and the notification instance itself. You may create [event +listeners](/docs/12.x/events) for this event within your application: + + + + 1use Illuminate\Notifications\Events\NotificationSent; + + 2  + + 3class LogNotification + + 4{ + + 5 /** + + 6 * Handle the event. + + 7 */ + + 8 public function handle(NotificationSent $event): void + + 9 { + + 10 // ... + + 11 } + + 12} + + + use Illuminate\Notifications\Events\NotificationSent; + + class LogNotification + { + /** + * Handle the 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: + + + + 1/** + + 2 * Handle the event. + + 3 */ + + 4public function handle(NotificationSent $event): void + + 5{ + + 6 // $event->channel + + 7 // $event->notifiable + + 8 // $event->notification + + 9 // $event->response + + 10} + + + /** + * Handle the event. + */ + public function handle(NotificationSent $event): void + { + // $event->channel + // $event->notifiable + // $event->notification + // $event->response + } + +## Custom Channels + +Laravel ships with a handful of notification channels, but you may want to +write your own drivers to deliver notifications via other channels. Laravel +makes it simple. To get started, define a class that contains a `send` method. +The method should receive two arguments: a `$notifiable` and a +`$notification`. + +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: + + + + 1toVoice($notifiable); + + 15  + + 16 // Send notification to the $notifiable instance... + + 17 } + + 18} + + + toVoice($notifiable); + + // 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: + + + + 1 [ + + 2 'options' => [ + + 3 'log_file' => storage_path('logs/swoole_http.log'), + + 4 'package_max_length' => 10 * 1024 * 1024, + + 5 ], + + 6], + + + 'swoole' => [ + 'options' => [ + 'log_file' => storage_path('logs/swoole_http.log'), + 'package_max_length' => 10 * 1024 * 1024, + ], + ], + +## Serving Your Application + +The Octane server can be started via the `octane:start` Artisan command. By +default, this command will utilize the server specified by the `server` +configuration option of your application's `octane` configuration file: + + + + 1php artisan octane:start + + + php artisan octane:start + +By default, Octane will start the server on port 8000, so you may access your +application in a web browser via `http://localhost:8000`. + +### Serving Your Application via HTTPS + +By default, applications running via Octane generate links prefixed with +`http://`. The `OCTANE_HTTPS` environment variable, used within your +application's `config/octane.php` configuration file, can be set to `true` +when serving your application via HTTPS. When this configuration value is set +to `true`, Octane will instruct Laravel to prefix all generated links with +`https://`: + + + + 1'https' => env('OCTANE_HTTPS', false), + + + 'https' => env('OCTANE_HTTPS', false), + +### Serving Your Application via Nginx + +If you aren't quite ready to manage your own server configuration or aren't +comfortable configuring all of the various services needed to run a robust +Laravel Octane application, check out [Laravel +Cloud](https://cloud.laravel.com), which offers fully-managed Laravel Octane +support. + +In production environments, you should serve your Octane application behind a +traditional web server such as Nginx or Apache. Doing so will allow the web +server to serve your static assets such as images and stylesheets, as well as +manage your SSL certificate termination. + +In the Nginx configuration example below, Nginx will serve the site's static +assets and proxy requests to the Octane server that is running on port 8000: + + + + 1map $http_upgrade $connection_upgrade { + + 2 default upgrade; + + 3 '' close; + + 4} + + 5  + + 6server { + + 7 listen 80; + + 8 listen [::]:80; + + 9 server_name domain.com; + + 10 server_tokens off; + + 11 root /home/forge/domain.com/public; + + 12  + + 13 index index.php; + + 14  + + 15 charset utf-8; + + 16  + + 17 location /index.php { + + 18 try_files /not_exists @octane; + + 19 } + + 20  + + 21 location / { + + 22 try_files $uri $uri/ @octane; + + 23 } + + 24  + + 25 location = /favicon.ico { access_log off; log_not_found off; } + + 26 location = /robots.txt { access_log off; log_not_found off; } + + 27  + + 28 access_log off; + + 29 error_log /var/log/nginx/domain.com-error.log error; + + 30  + + 31 error_page 404 /index.php; + + 32  + + 33 location @octane { + + 34 set $suffix ""; + + 35  + + 36 if ($uri = /index.php) { + + 37 set $suffix ?$query_string; + + 38 } + + 39  + + 40 proxy_http_version 1.1; + + 41 proxy_set_header Host $http_host; + + 42 proxy_set_header Scheme $scheme; + + 43 proxy_set_header SERVER_PORT $server_port; + + 44 proxy_set_header REMOTE_ADDR $remote_addr; + + 45 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + + 46 proxy_set_header Upgrade $http_upgrade; + + 47 proxy_set_header Connection $connection_upgrade; + + 48  + + 49 proxy_pass http://127.0.0.1:8000$suffix; + + 50 } + + 51} + + + map $http_upgrade $connection_upgrade { + default upgrade; + '' close; + } + + server { + listen 80; + listen [::]:80; + server_name domain.com; + server_tokens off; + root /home/forge/domain.com/public; + + index index.php; + + charset utf-8; + + location /index.php { + try_files /not_exists @octane; + } + + location / { + try_files $uri $uri/ @octane; + } + + location = /favicon.ico { access_log off; log_not_found off; } + location = /robots.txt { access_log off; log_not_found off; } + + access_log off; + error_log /var/log/nginx/domain.com-error.log error; + + error_page 404 /index.php; + + location @octane { + set $suffix ""; + + if ($uri = /index.php) { + set $suffix ?$query_string; + } + + proxy_http_version 1.1; + proxy_set_header Host $http_host; + proxy_set_header Scheme $scheme; + proxy_set_header SERVER_PORT $server_port; + proxy_set_header REMOTE_ADDR $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $connection_upgrade; + + proxy_pass http://127.0.0.1:8000$suffix; + } + } + +### Watching for File Changes + +Since your application is loaded in memory once when the Octane server starts, +any changes to your application's files will not be reflected when you refresh +your browser. For example, route definitions added to your `routes/web.php` +file will not be reflected until the server is restarted. For convenience, you +may use the `--watch` flag to instruct Octane to automatically restart the +server on any file changes within your application: + + + + 1php artisan octane:start --watch + + + php artisan octane:start --watch + +Before using this feature, you should ensure that [Node](https://nodejs.org) +is installed within your local development environment. In addition, you +should install the [Chokidar](https://github.com/paulmillr/chokidar) file- +watching library within your project: + + + + 1npm install --save-dev chokidar + + + npm install --save-dev chokidar + +You may configure the directories and files that should be watched using the +`watch` configuration option within your application's `config/octane.php` +configuration file. + +### Specifying the Worker Count + +By default, Octane will start an application request worker for each CPU core +provided by your machine. These workers will then be used to serve incoming +HTTP requests as they enter your application. You may manually specify how +many workers you would like to start using the `--workers` option when +invoking the `octane:start` command: + + + + 1php artisan octane:start --workers=4 + + + php artisan octane:start --workers=4 + +If you are using the Swoole application server, you may also specify how many +"task workers" you wish to start: + + + + 1php artisan octane:start --workers=4 --task-workers=6 + + + php artisan octane:start --workers=4 --task-workers=6 + +### Specifying the Max Request Count + +To help prevent stray memory leaks, Octane gracefully restarts any worker once +it has handled 500 requests. To adjust this number, you may use the `--max- +requests` option: + + + + 1php artisan octane:start --max-requests=250 + + + php artisan octane:start --max-requests=250 + +### Specifying the Max Execution Time + +By default, Laravel Octane sets a maximum execution time of 30 seconds for +incoming requests via the `max_execution_time` option in your application's +`config/octane.php` configuration file: + + + + 1'max_execution_time' => 30, + + + 'max_execution_time' => 30, + +This setting defines the maximum number of seconds that an incoming request is +allowed to execute before being terminated. Setting this value to `0` will +disable the execution time limit entirely. This configuration option is +particularly useful for applications that handle long-running requests, such +as file uploads, data processing, or API calls to external services. + +When you modify the `max_execution_time` configuration, you must restart the +Octane server for the changes to take effect. + +### Reloading the Workers + +You may gracefully restart the Octane server's application workers using the +`octane:reload` command. Typically, this should be done after deployment so +that your newly deployed code is loaded into memory and is used to serve to +subsequent requests: + + + + 1php artisan octane:reload + + + php artisan octane:reload + +### Stopping the Server + +You may stop the Octane server using the `octane:stop` Artisan command: + + + + 1php artisan octane:stop + + + php artisan octane:stop + +#### Checking the Server Status + +You may check the current status of the Octane server using the +`octane:status` Artisan command: + + + + 1php artisan octane:status + + + php artisan octane:status + +## Dependency Injection and Octane + +Since Octane boots your application once and keeps it in memory while serving +requests, there are a few caveats you should consider while building your +application. For example, the `register` and `boot` methods of your +application's service providers will only be executed once when the request +worker initially boots. On subsequent requests, the same application instance +will be reused. + +In light of this, you should take special care when injecting the application +service container or request into any object's constructor. By doing so, that +object may have a stale version of the container or request on subsequent +requests. + +Octane will automatically handle resetting any first-party framework state +between requests. However, Octane does not always know how to reset the global +state created by your application. Therefore, you should be aware of how to +build your application in a way that is Octane friendly. Below, we will +discuss the most common situations that may cause problems while using Octane. + +### Container Injection + +In general, you should avoid injecting the application service container or +HTTP request instance into the constructors of other objects. For example, the +following binding injects the entire application service container into an +object that is bound as a singleton: + + + + 1use App\Service; + + 2use Illuminate\Contracts\Foundation\Application; + + 3  + + 4/** + + 5 * Register any application services. + + 6 */ + + 7public function register(): void + + 8{ + + 9 $this->app->singleton(Service::class, function (Application $app) { + + 10 return new Service($app); + + 11 }); + + 12} + + + use App\Service; + use Illuminate\Contracts\Foundation\Application; + + /** + * Register any application services. + */ + public function register(): void + { + $this->app->singleton(Service::class, function (Application $app) { + return new Service($app); + }); + } + +In this example, if the `Service` instance is resolved during the application +boot process, the container will be injected into the service and that same +container will be held by the `Service` instance on subsequent requests. This +**may** not be a problem for your particular application; however, it can lead +to the container unexpectedly missing bindings that were added later in the +boot cycle or by a subsequent request. + +As a work-around, you could either stop registering the binding as a +singleton, or you could inject a container resolver closure into the service +that always resolves the current container instance: + + + + 1use App\Service; + + 2use Illuminate\Container\Container; + + 3use Illuminate\Contracts\Foundation\Application; + + 4  + + 5$this->app->bind(Service::class, function (Application $app) { + + 6 return new Service($app); + + 7}); + + 8  + + 9$this->app->singleton(Service::class, function () { + + 10 return new Service(fn () => Container::getInstance()); + + 11}); + + + use App\Service; + use Illuminate\Container\Container; + use Illuminate\Contracts\Foundation\Application; + + $this->app->bind(Service::class, function (Application $app) { + return new Service($app); + }); + + $this->app->singleton(Service::class, function () { + return new Service(fn () => Container::getInstance()); + }); + +The global `app` helper and the `Container::getInstance()` method will always +return the latest version of the application container. + +### Request Injection + +In general, you should avoid injecting the application service container or +HTTP request instance into the constructors of other objects. For example, the +following binding injects the entire request instance into an object that is +bound as a singleton: + + + + 1use App\Service; + + 2use Illuminate\Contracts\Foundation\Application; + + 3  + + 4/** + + 5 * Register any application services. + + 6 */ + + 7public function register(): void + + 8{ + + 9 $this->app->singleton(Service::class, function (Application $app) { + + 10 return new Service($app['request']); + + 11 }); + + 12} + + + use App\Service; + use Illuminate\Contracts\Foundation\Application; + + /** + * Register any application services. + */ + public function register(): void + { + $this->app->singleton(Service::class, function (Application $app) { + return new Service($app['request']); + }); + } + +In this example, if the `Service` instance is resolved during the application +boot process, the HTTP request will be injected into the service and that same +request will be held by the `Service` instance on subsequent requests. +Therefore, all headers, input, and query string data will be incorrect, as +well as all other request data. + +As a work-around, you could either stop registering the binding as a +singleton, or you could inject a request resolver closure into the service +that always resolves the current request instance. Or, the most recommended +approach is simply to pass the specific request information your object needs +to one of the object's methods at runtime: + + + + 1use App\Service; + + 2use Illuminate\Contracts\Foundation\Application; + + 3  + + 4$this->app->bind(Service::class, function (Application $app) { + + 5 return new Service($app['request']); + + 6}); + + 7  + + 8$this->app->singleton(Service::class, function (Application $app) { + + 9 return new Service(fn () => $app['request']); + + 10}); + + 11  + + 12// Or... + + 13  + + 14$service->method($request->input('name')); + + + use App\Service; + use Illuminate\Contracts\Foundation\Application; + + $this->app->bind(Service::class, function (Application $app) { + return new Service($app['request']); + }); + + $this->app->singleton(Service::class, function (Application $app) { + return new Service(fn () => $app['request']); + }); + + // Or... + + $service->method($request->input('name')); + +The global `request` helper will always return the request the application is +currently handling and is therefore safe to use within your application. + +It is acceptable to type-hint the `Illuminate\Http\Request` instance on your +controller methods and route closures. + +### Configuration Repository Injection + +In general, you should avoid injecting the configuration repository instance +into the constructors of other objects. For example, the following binding +injects the configuration repository into an object that is bound as a +singleton: + + + + 1use App\Service; + + 2use Illuminate\Contracts\Foundation\Application; + + 3  + + 4/** + + 5 * Register any application services. + + 6 */ + + 7public function register(): void + + 8{ + + 9 $this->app->singleton(Service::class, function (Application $app) { + + 10 return new Service($app->make('config')); + + 11 }); + + 12} + + + use App\Service; + use Illuminate\Contracts\Foundation\Application; + + /** + * Register any application services. + */ + public function register(): void + { + $this->app->singleton(Service::class, function (Application $app) { + return new Service($app->make('config')); + }); + } + +In this example, if the configuration values change between requests, that +service will not have access to the new values because it's depending on the +original repository instance. + +As a work-around, you could either stop registering the binding as a +singleton, or you could inject a configuration repository resolver closure to +the class: + + + + 1use App\Service; + + 2use Illuminate\Container\Container; + + 3use Illuminate\Contracts\Foundation\Application; + + 4  + + 5$this->app->bind(Service::class, function (Application $app) { + + 6 return new Service($app->make('config')); + + 7}); + + 8  + + 9$this->app->singleton(Service::class, function () { + + 10 return new Service(fn () => Container::getInstance()->make('config')); + + 11}); + + + use App\Service; + use Illuminate\Container\Container; + use Illuminate\Contracts\Foundation\Application; + + $this->app->bind(Service::class, function (Application $app) { + return new Service($app->make('config')); + }); + + $this->app->singleton(Service::class, function () { + return new Service(fn () => Container::getInstance()->make('config')); + }); + +The global `config` will always return the latest version of the configuration +repository and is therefore safe to use within your application. + +### Managing Memory Leaks + +Remember, Octane keeps your application in memory between requests; therefore, +adding data to a statically maintained array will result in a memory leak. For +example, the following controller has a memory leak since each request to the +application will continue to add data to the static `$data` array: + + + + 1use App\Service; + + 2use Illuminate\Http\Request; + + 3use Illuminate\Support\Str; + + 4  + + 5/** + + 6 * Handle an incoming request. + + 7 */ + + 8public function index(Request $request): array + + 9{ + + 10 Service::$data[] = Str::random(10); + + 11  + + 12 return [ + + 13 // ... + + 14 ]; + + 15} + + + use App\Service; + use Illuminate\Http\Request; + use Illuminate\Support\Str; + + /** + * Handle an incoming request. + */ + public function index(Request $request): array + { + Service::$data[] = Str::random(10); + + return [ + // ... + ]; + } + +While building your application, you should take special care to avoid +creating these types of memory leaks. It is recommended that you monitor your +application's memory usage during local development to ensure you are not +introducing new memory leaks into your application. + +## Concurrent Tasks + +This feature requires Swoole. + +When using Swoole, you may execute operations concurrently via light-weight +background tasks. You may accomplish this using Octane's `concurrently` +method. You may combine this method with PHP array destructuring to retrieve +the results of each operation: + + + + 1use App\Models\User; + + 2use App\Models\Server; + + 3use Laravel\Octane\Facades\Octane; + + 4  + + 5[$users, $servers] = Octane::concurrently([ + + 6 fn () => User::all(), + + 7 fn () => Server::all(), + + 8]); + + + use App\Models\User; + use App\Models\Server; + use Laravel\Octane\Facades\Octane; + + [$users, $servers] = Octane::concurrently([ + fn () => User::all(), + fn () => Server::all(), + ]); + +Concurrent tasks processed by Octane utilize Swoole's "task workers", and +execute within an entirely different process than the incoming request. The +amount of workers available to process concurrent tasks is determined by the +`--task-workers` directive on the `octane:start` command: + + + + 1php artisan octane:start --workers=4 --task-workers=6 + + + php artisan octane:start --workers=4 --task-workers=6 + +When invoking the `concurrently` method, you should not provide more than 1024 +tasks due to limitations imposed by Swoole's task system. + +## Ticks and Intervals + +This feature requires Swoole. + +When using Swoole, you may register "tick" operations that will be executed +every specified number of seconds. You may register "tick" callbacks via the +`tick` method. The first argument provided to the `tick` method should be a +string that represents the name of the ticker. The second argument should be a +callable that will be invoked at the specified interval. + +In this example, we will register a closure to be invoked every 10 seconds. +Typically, the `tick` method should be called within the `boot` method of one +of your application's service providers: + + + + 1Octane::tick('simple-ticker', fn () => ray('Ticking...')) + + 2 ->seconds(10); + + + Octane::tick('simple-ticker', fn () => ray('Ticking...')) + ->seconds(10); + +Using the `immediate` method, you may instruct Octane to immediately invoke +the tick callback when the Octane server initially boots, and every N seconds +thereafter: + + + + 1Octane::tick('simple-ticker', fn () => ray('Ticking...')) + + 2 ->seconds(10) + + 3 ->immediate(); + + + Octane::tick('simple-ticker', fn () => ray('Ticking...')) + ->seconds(10) + ->immediate(); + +## The Octane Cache + +This feature requires Swoole. + +When using Swoole, you may leverage the Octane cache driver, which provides +read and write speeds of up to 2 million operations per second. Therefore, +this cache driver is an excellent choice for applications that need extreme +read / write speeds from their caching layer. + +This cache driver is powered by [Swoole +tables](https://www.swoole.co.uk/docs/modules/swoole-table). All data stored +in the cache is available to all workers on the server. However, the cached +data will be flushed when the server is restarted: + + + + 1Cache::store('octane')->put('framework', 'Laravel', 30); + + + Cache::store('octane')->put('framework', 'Laravel', 30); + +The maximum number of entries allowed in the Octane cache may be defined in +your application's `octane` configuration file. + +### Cache Intervals + +In addition to the typical methods provided by Laravel's cache system, the +Octane cache driver features interval based caches. These caches are +automatically refreshed at the specified interval and should be registered +within the `boot` method of one of your application's service providers. For +example, the following cache will be refreshed every five seconds: + + + + 1use Illuminate\Support\Str; + + 2  + + 3Cache::store('octane')->interval('random', function () { + + 4 return Str::random(10); + + 5}, seconds: 5); + + + use Illuminate\Support\Str; + + Cache::store('octane')->interval('random', function () { + return Str::random(10); + }, seconds: 5); + +## Tables + +This feature requires Swoole. + +When using Swoole, you may define and interact with your own arbitrary [Swoole +tables](https://www.swoole.co.uk/docs/modules/swoole-table). Swoole tables +provide extreme performance throughput and the data in these tables can be +accessed by all workers on the server. However, the data within them will be +lost when the server is restarted. + +Tables should be defined within the `tables` configuration array of your +application's `octane` configuration file. An example table that allows a +maximum of 1000 rows is already configured for you. The maximum size of string +columns may be configured by specifying the column size after the column type +as seen below: + + + + 1'tables' => [ + + 2 'example:1000' => [ + + 3 'name' => 'string:1000', + + 4 'votes' => 'int', + + 5 ], + + 6], + + + 'tables' => [ + 'example:1000' => [ + 'name' => 'string:1000', + 'votes' => 'int', + ], + ], + +To access a table, you may use the `Octane::table` method: + + + + 1use Laravel\Octane\Facades\Octane; + + 2  + + 3Octane::table('example')->set('uuid', [ + + 4 'name' => 'Nuno Maduro', + + 5 'votes' => 1000, + + 6]); + + 7  + + 8return Octane::table('example')->get('uuid'); + + + use Laravel\Octane\Facades\Octane; + + Octane::table('example')->set('uuid', [ + 'name' => 'Nuno Maduro', + 'votes' => 1000, + ]); + + return Octane::table('example')->get('uuid'); + +The column types supported by Swoole tables are: `string`, `int`, and `float`. + diff --git a/output/12.x/packages.md b/output/12.x/packages.md new file mode 100644 index 0000000..35c3f3e --- /dev/null +++ b/output/12.x/packages.md @@ -0,0 +1,960 @@ +# Package Development + + * Introduction + * A Note on Facades + * Package Discovery + * Service Providers + * Resources + * Configuration + * Routes + * Migrations + * Language Files + * Views + * View Components + * "About" Artisan Command + * Commands + * Optimize Commands + * Public Assets + * Publishing File Groups + +## Introduction + +Packages are the primary way of adding functionality to Laravel. Packages +might be anything from a great way to work with dates like +[Carbon](https://github.com/briannesbitt/Carbon) or a package that allows you +to associate files with Eloquent models like Spatie's [Laravel Media +Library](https://github.com/spatie/laravel-medialibrary). + +There are different types of packages. Some packages are stand-alone, meaning +they work with any PHP framework. Carbon and Pest are examples of stand-alone +packages. Any of these packages may be used with Laravel by requiring them in +your `composer.json` file. + +On the other hand, other packages are specifically intended for use with +Laravel. These packages may have routes, controllers, views, and configuration +specifically intended to enhance a Laravel application. This guide primarily +covers the development of those packages that are Laravel specific. + +### A Note on Facades + +When writing a Laravel application, it generally does not matter if you use +contracts or facades since both provide essentially equal levels of +testability. However, when writing packages, your package will not typically +have access to all of Laravel's testing helpers. If you would like to be able +to write your package tests as if the package were installed inside a typical +Laravel application, you may use the [Orchestral +Testbench](https://github.com/orchestral/testbench) package. + +## Package Discovery + +A Laravel application's `bootstrap/providers.php` file contains the list of +service providers that should be loaded by Laravel. However, instead of +requiring users to manually add your service provider to the list, you may +define the provider in the `extra` section of your package's `composer.json` +file so that it is automatically loaded by Laravel. In addition to service +providers, you may also list any [facades](/docs/12.x/facades) you would like +to be registered: + + + + 1"extra": { + + 2 "laravel": { + + 3 "providers": [ + + 4 "Barryvdh\\Debugbar\\ServiceProvider" + + 5 ], + + 6 "aliases": { + + 7 "Debugbar": "Barryvdh\\Debugbar\\Facade" + + 8 } + + 9 } + + 10}, + + + "extra": { + "laravel": { + "providers": [ + "Barryvdh\\Debugbar\\ServiceProvider" + ], + "aliases": { + "Debugbar": "Barryvdh\\Debugbar\\Facade" + } + } + }, + +Once your package has been configured for discovery, Laravel will +automatically register its service providers and facades when it is installed, +creating a convenient installation experience for your package's users. + +#### Opting Out of Package Discovery + +If you are the consumer of a package and would like to disable package +discovery for a package, you may list the package name in the `extra` section +of your application's `composer.json` file: + + + + 1"extra": { + + 2 "laravel": { + + 3 "dont-discover": [ + + 4 "barryvdh/laravel-debugbar" + + 5 ] + + 6 } + + 7}, + + + "extra": { + "laravel": { + "dont-discover": [ + "barryvdh/laravel-debugbar" + ] + } + }, + +You may disable package discovery for all packages using the `*` character +inside of your application's `dont-discover` directive: + + + + 1"extra": { + + 2 "laravel": { + + 3 "dont-discover": [ + + 4 "*" + + 5 ] + + 6 } + + 7}, + + + "extra": { + "laravel": { + "dont-discover": [ + "*" + ] + } + }, + +## Service Providers + +[Service providers](/docs/12.x/providers) are the connection point between +your package and Laravel. A service provider is responsible for binding things +into Laravel's [service container](/docs/12.x/container) and informing Laravel +where to load package resources such as views, configuration, and language +files. + +A service provider extends the `Illuminate\Support\ServiceProvider` class and +contains two methods: `register` and `boot`. The base `ServiceProvider` class +is located in the `illuminate/support` Composer package, which you should add +to your own package's dependencies. To learn more about the structure and +purpose of service providers, check out [their +documentation](/docs/12.x/providers). + +## Resources + +### Configuration + +Typically, you will need to publish your package's configuration file to the +application's `config` directory. This will allow users of your package to +easily override your default configuration options. To allow your +configuration files to be published, call the `publishes` method from the +`boot` method of your service provider: + + + + 1/** + + 2 * Bootstrap any package services. + + 3 */ + + 4public function boot(): void + + 5{ + + 6 $this->publishes([ + + 7 __DIR__.'/../config/courier.php' => config_path('courier.php'), + + 8 ]); + + 9} + + + /** + * 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: + + + + 1$value = config('courier.option'); + + + $value = config('courier.option'); + +You should not define closures in your configuration files. They cannot be +serialized correctly when users execute the `config:cache` Artisan command. + +#### Default Package Configuration + +You may also merge your own package configuration file with the application's +published copy. This will allow your users to define only the options they +actually want to override in the published copy of the configuration file. To +merge the configuration file values, use the `mergeConfigFrom` method within +your service provider's `register` method. + +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: + + + + 1/** + + 2 * Register any package services. + + 3 */ + + 4public function register(): void + + 5{ + + 6 $this->mergeConfigFrom( + + 7 __DIR__.'/../config/courier.php', 'courier' + + 8 ); + + 9} + + + /** + * Register any package services. + */ + public function register(): void + { + $this->mergeConfigFrom( + __DIR__.'/../config/courier.php', 'courier' + ); + } + +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. + +### Routes + +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: + + + + 1/** + + 2 * Bootstrap any package services. + + 3 */ + + 4public function boot(): void + + 5{ + + 6 $this->loadRoutesFrom(__DIR__.'/../routes/web.php'); + + 7} + + + /** + * Bootstrap any package services. + */ + public function boot(): void + { + $this->loadRoutesFrom(__DIR__.'/../routes/web.php'); + } + +### Migrations + +If your package contains [database migrations](/docs/12.x/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: + + + + 1/** + + 2 * Bootstrap any package services. + + 3 */ + + 4public function boot(): void + + 5{ + + 6 $this->publishesMigrations([ + + 7 __DIR__.'/../database/migrations' => database_path('migrations'), + + 8 ]); + + 9} + + + /** + * 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/12.x/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: + + + + 1/** + + 2 * Bootstrap any package services. + + 3 */ + + 4public function boot(): void + + 5{ + + 6 $this->loadTranslationsFrom(__DIR__.'/../lang', 'courier'); + + 7} + + + /** + * 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: + + + + 1echo trans('courier::messages.welcome'); + + + 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: + + + + 1/** + + 2 * Bootstrap any package services. + + 3 */ + + 4public function boot(): void + + 5{ + + 6 $this->loadJsonTranslationsFrom(__DIR__.'/../lang'); + + 7} + + + /** + * Bootstrap any package services. + */ + public function boot(): void + { + $this->loadJsonTranslationsFrom(__DIR__.'/../lang'); + } + +#### Publishing Language Files + +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: + + + + 1/** + + 2 * Bootstrap any package services. + + 3 */ + + 4public function boot(): void + + 5{ + + 6 $this->loadTranslationsFrom(__DIR__.'/../lang', 'courier'); + + 7  + + 8 $this->publishes([ + + 9 __DIR__.'/../lang' => $this->app->langPath('vendor/courier'), + + 10 ]); + + 11} + + + /** + * Bootstrap any package services. + */ + public function boot(): void + { + $this->loadTranslationsFrom(__DIR__.'/../lang', '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. + +### Views + +To register your package's [views](/docs/12.x/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: + + + + 1/** + + 2 * Bootstrap any package services. + + 3 */ + + 4public function boot(): void + + 5{ + + 6 $this->loadViewsFrom(__DIR__.'/../resources/views', 'courier'); + + 7} + + + /** + * 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: + + + + 1Route::get('/dashboard', function () { + + 2 return view('courier::dashboard'); + + 3}); + + + Route::get('/dashboard', function () { + return view('courier::dashboard'); + }); + +#### Overriding Package Views + +When you use the `loadViewsFrom` method, Laravel actually registers two +locations for your views: the application's `resources/views/vendor` directory +and the directory you specify. So, using the `courier` package as an example, +Laravel will first check if a custom version of the view has been placed in +the `resources/views/vendor/courier` directory by the developer. Then, if the +view has not been customized, Laravel will search the package view directory +you specified in your call to `loadViewsFrom`. This makes it easy for package +users to customize / override your package's views. + +#### Publishing Views + +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: + + + + 1/** + + 2 * Bootstrap the package services. + + 3 */ + + 4public function boot(): void + + 5{ + + 6 $this->loadViewsFrom(__DIR__.'/../resources/views', 'courier'); + + 7  + + 8 $this->publishes([ + + 9 __DIR__.'/../resources/views' => resource_path('views/vendor/courier'), + + 10 ]); + + 11} + + + /** + * Bootstrap the package services. + */ + public function boot(): void + { + $this->loadViewsFrom(__DIR__.'/../resources/views', '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. + +### View Components + +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: + + + + 1use Illuminate\Support\Facades\Blade; + + 2use VendorPackage\View\Components\AlertComponent; + + 3  + + 4/** + + 5 * Bootstrap your package's services. + + 6 */ + + 7public function boot(): void + + 8{ + + 9 Blade::component('package-alert', AlertComponent::class); + + 10} + + + 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); + } + +Once your component has been registered, it may be rendered using its tag +alias: + + + + 1 + + + + +#### Autoloading Package Components + +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: + + + + 1use Illuminate\Support\Facades\Blade; + + 2  + + 3/** + + 4 * Bootstrap your package's services. + + 5 */ + + 6public function boot(): void + + 7{ + + 8 Blade::componentNamespace('Nightshade\\Views\\Components', 'nightshade'); + + 9} + + + use Illuminate\Support\Facades\Blade; + + /** + * 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: + + + + 1 + + 2 + + + + + +Blade will automatically detect the class that's linked to this component by +pascal-casing the component name. Subdirectories are also supported using +"dot" notation. + +#### Anonymous Components + +If your package contains anonymous components, they must be placed within a +`components` directory of your package's "views" directory (as specified by +the loadViewsFrom method). Then, you may render them by prefixing the +component name with the package's view namespace: + + + + 1 + + + + +### "About" Artisan Command + +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: + + + + 1use Illuminate\Foundation\Console\AboutCommand; + + 2  + + 3/** + + 4 * Bootstrap any package services. + + 5 */ + + 6public function boot(): void + + 7{ + + 8 AboutCommand::add('My Package', fn () => ['Version' => '1.0.0']); + + 9} + + + use Illuminate\Foundation\Console\AboutCommand; + + /** + * Bootstrap any package 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/12.x/artisan): + + + + 1use Courier\Console\Commands\InstallCommand; + + 2use Courier\Console\Commands\NetworkCommand; + + 3  + + 4/** + + 5 * Bootstrap any package services. + + 6 */ + + 7public function boot(): void + + 8{ + + 9 if ($this->app->runningInConsole()) { + + 10 $this->commands([ + + 11 InstallCommand::class, + + 12 NetworkCommand::class, + + 13 ]); + + 14 } + + 15} + + + 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/12.x/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: + + + + 1/** + + 2 * Bootstrap any package services. + + 3 */ + + 4public function boot(): void + + 5{ + + 6 if ($this->app->runningInConsole()) { + + 7 $this->optimizes( + + 8 optimize: 'package:optimize', + + 9 clear: 'package:clear-optimizations', + + 10 ); + + 11 } + + 12} + + + /** + * 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: + + + + 1/** + + 2 * Bootstrap any package services. + + 3 */ + + 4public function boot(): void + + 5{ + + 6 $this->publishes([ + + 7 __DIR__.'/../public' => public_path('vendor/courier'), + + 8 ], 'public'); + + 9} + + + /** + * 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: + + + + 1php artisan vendor:publish --tag=public --force + + + php artisan vendor:publish --tag=public --force + +## Publishing File Groups + +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: + + + + 1/** + + 2 * Bootstrap any package services. + + 3 */ + + 4public function boot(): void + + 5{ + + 6 $this->publishes([ + + 7 __DIR__.'/../config/package.php' => config_path('package.php') + + 8 ], 'courier-config'); + + 9  + + 10 $this->publishesMigrations([ + + 11 __DIR__.'/../database/migrations/' => database_path('migrations') + + 12 ], 'courier-migrations'); + + 13} + + + /** + * 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: + + + + 1php artisan vendor:publish --tag=courier-config + + + php artisan vendor:publish --tag=courier-config + +Your users can also publish all publishable files defined by your package's +service provider using the `--provider` flag: + + + + 1php artisan vendor:publish --provider="Your\Package\ServiceProvider" + + + php artisan vendor:publish --provider="Your\Package\ServiceProvider" + diff --git a/output/12.x/pagination.md b/output/12.x/pagination.md new file mode 100644 index 0000000..9b5fd31 --- /dev/null +++ b/output/12.x/pagination.md @@ -0,0 +1,786 @@ +# Database: Pagination + + * Introduction + * Basic Usage + * Paginating Query Builder Results + * Paginating Eloquent Results + * Cursor Pagination + * Manually Creating a Paginator + * Customizing Pagination URLs + * Displaying Pagination Results + * Adjusting the Pagination Link Window + * Converting Results to JSON + * Customizing the Pagination View + * Using Bootstrap + * Paginator and LengthAwarePaginator Instance Methods + * Cursor Paginator Instance Methods + +## Introduction + +In other frameworks, pagination can be very painful. We hope Laravel's +approach to pagination will be a breath of fresh air. Laravel's paginator is +integrated with the [query builder](/docs/12.x/queries) and [Eloquent +ORM](/docs/12.x/eloquent) and provides convenient, easy-to-use pagination of +database records with zero configuration. + +By default, the HTML generated by the paginator is compatible with the +[Tailwind CSS framework](https://tailwindcss.com/); however, Bootstrap +pagination support is also available. + +#### Tailwind + +If you are using Laravel's default Tailwind pagination views with Tailwind +4.x, your application's `resources/css/app.css` file will already be properly +configured to `@source` Laravel's pagination views: + + + + 1@import 'tailwindcss'; + + 2  + + 3@source '../../vendor/laravel/framework/src/Illuminate/Pagination/resources/views/*.blade.php'; + + + @import 'tailwindcss'; + + @source '../../vendor/laravel/framework/src/Illuminate/Pagination/resources/views/*.blade.php'; + +## Basic Usage + +### Paginating Query Builder Results + +There are several ways to paginate items. The simplest is by using the +`paginate` method on the [query builder](/docs/12.x/queries) or an [Eloquent +query](/docs/12.x/eloquent). The `paginate` method automatically takes care of +setting the query's "limit" and "offset" based on the current page being +viewed by the user. By default, the current page is detected by the value of +the `page` query string argument on the HTTP request. This value is +automatically detected by Laravel, and is also automatically inserted into +links generated by the paginator. + +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: + + + + 1 DB::table('users')->paginate(15) + + 17 ]); + + 18 } + + 19} + + + DB::table('users')->paginate(15) + ]); + } + } + +#### Simple Pagination + +The `paginate` method counts the total number of records matched by the query +before retrieving the records from the database. This is done so that the +paginator knows how many pages of records there are in total. However, if you +do not plan to show the total number of pages in your application's UI then +the record count query is unnecessary. + +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: + + + + 1$users = DB::table('users')->simplePaginate(15); + + + $users = DB::table('users')->simplePaginate(15); + +### Paginating Eloquent Results + +You may also paginate [Eloquent](/docs/12.x/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: + + + + 1use App\Models\User; + + 2  + + 3$users = User::paginate(15); + + + use App\Models\User; + + $users = User::paginate(15); + +Of course, you may call the `paginate` method after setting other constraints +on the query, such as `where` clauses: + + + + 1$users = User::where('votes', '>', 100)->paginate(15); + + + $users = User::where('votes', '>', 100)->paginate(15); + +You may also use the `simplePaginate` method when paginating Eloquent models: + + + + 1$users = User::where('votes', '>', 100)->simplePaginate(15); + + + $users = User::where('votes', '>', 100)->simplePaginate(15); + +Similarly, you may use the `cursorPaginate` method to cursor paginate Eloquent +models: + + + + 1$users = User::where('votes', '>', 100)->cursorPaginate(15); + + + $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: + + + + 1use App\Models\User; + + 2  + + 3$users = User::where('votes', '>', 100)->paginate( + + 4 $perPage = 15, $columns = ['*'], $pageName = 'users' + + 5); + + + use App\Models\User; + + $users = User::where('votes', '>', 100)->paginate( + $perPage = 15, $columns = ['*'], $pageName = 'users' + ); + +### Cursor Pagination + +While `paginate` and `simplePaginate` create queries using the SQL "offset" +clause, cursor pagination works by constructing "where" clauses that compare +the values of the ordered columns contained in the query, providing the most +efficient database performance available amongst all of Laravel's pagination +methods. This method of pagination is particularly well-suited for large data- +sets and "infinite" scrolling user interfaces. + +Unlike offset based pagination, which includes a page number in the query +string of the URLs generated by the paginator, cursor-based pagination places +a "cursor" string in the query string. The cursor is an encoded string +containing the location that the next paginated query should start paginating +and the direction that it should paginate: + + + + 1http://localhost/users?cursor=eyJpZCI6MTUsIl9wb2ludHNUb05leHRJdGVtcyI6dHJ1ZX0 + + + 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`: + + + + 1$users = DB::table('users')->orderBy('id')->cursorPaginate(15); + + + $users = DB::table('users')->orderBy('id')->cursorPaginate(15); + +Once you have retrieved a cursor paginator instance, you may display the +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. + +Your query must contain an "order by" clause in order to take advantage of +cursor pagination. In addition, the columns that the query are ordered by must +belong to the table you are paginating. + +#### Cursor vs. Offset Pagination + +To illustrate the differences between offset pagination and cursor pagination, +let's examine some example SQL queries. Both of the following queries will +both display the "second page" of results for a `users` table ordered by `id`: + + + + 1# Offset Pagination... + + 2select * from users order by id asc limit 15 offset 15; + + 3  + + 4# Cursor Pagination... + + 5select * from users where id > 15 order by id asc limit 15; + + + # Offset Pagination... + select * from users order by id asc limit 15 offset 15; + + # Cursor Pagination... + select * from users where id > 15 order by id asc limit 15; + +The cursor pagination query offers the following advantages over offset +pagination: + + * For large data-sets, cursor pagination will offer better performance if the "order by" columns are indexed. This is because the "offset" clause scans through all previously matched data. + * For data-sets with frequent writes, offset pagination may skip records or show duplicates if results have been recently added to or deleted from the page a user is currently viewing. + +However, cursor pagination has the following limitations: + + * Like `simplePaginate`, cursor pagination can only be used to display "Next" and "Previous" links and does not support generating links with page numbers. + * It requires that the ordering is based on at least one unique column or a combination of columns that are unique. Columns with `null` values are not supported. + * Query expressions in "order by" clauses are supported only if they are aliased and added to the "select" clause as well. + * Query expressions with parameters are not supported. + +### Manually Creating a Paginator + +Sometimes you may wish to create a pagination instance manually, passing it an +array of items that you already have in memory. You may do so by creating +either an `Illuminate\Pagination\Paginator`, +`Illuminate\Pagination\LengthAwarePaginator` or +`Illuminate\Pagination\CursorPaginator` instance, depending on your needs. + +The `Paginator` and `CursorPaginator` classes do not need to know the total +number of items in the result set; however, because of this, these classes do +not have methods for retrieving the index of the last page. The +`LengthAwarePaginator` accepts almost the same arguments as the `Paginator`; +however, it requires a count of the total number of items in the result set. + +In other words, the `Paginator` corresponds to the `simplePaginate` method on +the query builder, the `CursorPaginator` corresponds to the `cursorPaginate` +method, and the `LengthAwarePaginator` corresponds to the `paginate` method. + +When manually creating a paginator instance, you should manually "slice" the +array of results you pass to the paginator. If you're unsure how to do this, +check out the [array_slice](https://secure.php.net/manual/en/function.array- +slice.php) PHP function. + +### Customizing Pagination URLs + +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: + + + + 1use App\Models\User; + + 2  + + 3Route::get('/users', function () { + + 4 $users = User::paginate(15); + + 5  + + 6 $users->withPath('/admin/users'); + + 7  + + 8 // ... + + 9}); + + + use App\Models\User; + + Route::get('/users', function () { + $users = User::paginate(15); + + $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`: + + + + 1use App\Models\User; + + 2  + + 3Route::get('/users', function () { + + 4 $users = User::paginate(15); + + 5  + + 6 $users->appends(['sort' => 'votes']); + + 7  + + 8 // ... + + 9}); + + + use App\Models\User; + + Route::get('/users', function () { + $users = User::paginate(15); + + $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: + + + + 1$users = User::paginate(15)->withQueryString(); + + + $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: + + + + 1$users = User::paginate(15)->fragment('users'); + + + $users = User::paginate(15)->fragment('users'); + +## Displaying Pagination Results + +When calling the `paginate` method, you will receive an instance of +`Illuminate\Pagination\LengthAwarePaginator`, while calling the +`simplePaginate` method returns an instance of +`Illuminate\Pagination\Paginator`. And, finally, calling the `cursorPaginate` +method returns an instance of `Illuminate\Pagination\CursorPaginator`. + +These objects provide several methods that describe the result set. In +addition to these helper methods, the paginator instances are iterators and +may be looped as an array. So, once you have retrieved the results, you may +display the results and render the page links using [Blade](/docs/12.x/blade): + + + + 1
    + + 2 @foreach ($users as $user) + + 3 {{ $user->name }} + + 4 @endforeach + + 5
    + + 6  + + 7{{ $users->links() }} + + +
    + @foreach ($users as $user) + {{ $user->name }} + @endforeach +
    + + {{ $users->links() }} + +The `links` method will render the links to the rest of the pages in the +result set. Each of these links will already contain the proper `page` query +string variable. Remember, the HTML generated by the `links` method is +compatible with the [Tailwind CSS framework](https://tailwindcss.com). + +### Adjusting the Pagination Link Window + +When the paginator displays pagination links, the current page number is +displayed as well as links for the three pages before and after the current +page. Using the `onEachSide` method, you may control how many additional links +are displayed on each side of the current page within the middle, sliding +window of links generated by the paginator: + + + + 1{{ $users->onEachSide(5)->links() }} + + + {{ $users->onEachSide(5)->links() }} + +### Converting Results to JSON + +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: + + + + 1use App\Models\User; + + 2  + + 3Route::get('/users', function () { + + 4 return User::paginate(); + + 5}); + + + use App\Models\User; + + 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: + + + + 1{ + + 2 "total": 50, + + 3 "per_page": 15, + + 4 "current_page": 1, + + 5 "last_page": 4, + + 6 "current_page_url": "http://laravel.app?page=1", + + 7 "first_page_url": "http://laravel.app?page=1", + + 8 "last_page_url": "http://laravel.app?page=4", + + 9 "next_page_url": "http://laravel.app?page=2", + + 10 "prev_page_url": null, + + 11 "path": "http://laravel.app", + + 12 "from": 1, + + 13 "to": 15, + + 14 "data":[ + + 15 { + + 16 // Record... + + 17 }, + + 18 { + + 19 // Record... + + 20 } + + 21 ] + + 22} + + + { + "total": 50, + "per_page": 15, + "current_page": 1, + "last_page": 4, + "current_page_url": "http://laravel.app?page=1", + "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 + +By default, the views rendered to display the pagination links are compatible +with the [Tailwind CSS](https://tailwindcss.com) framework. However, if you +are not using Tailwind, you are free to define your own views to render these +links. When calling the `links` method on a paginator instance, you may pass +the view name as the first argument to the method: + + + + 1{{ $paginator->links('view.name') }} + + 2  + + 3 + + 4{{ $paginator->links('view.name', ['foo' => 'bar']) }} + + + {{ $paginator->links('view.name') }} + + + {{ $paginator->links('view.name', ['foo' => 'bar']) }} + +However, the easiest way to customize the pagination views is by exporting +them to your `resources/views/vendor` directory using the `vendor:publish` +command: + + + + 1php artisan vendor:publish --tag=laravel-pagination + + + php artisan vendor:publish --tag=laravel-pagination + +This command will place the views in your application's +`resources/views/vendor/pagination` directory. The `tailwind.blade.php` file +within this directory corresponds to the default pagination view. You may edit +this file to modify the pagination HTML. + +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: + + + + 1count()` | Get the number of items for the current page. +`$paginator->currentPage()` | Get the current page number. +`$paginator->firstItem()` | Get the result number of the first item in the results. +`$paginator->getOptions()` | Get the paginator options. +`$paginator->getUrlRange($start, $end)` | Create a range of pagination URLs. +`$paginator->hasPages()` | Determine if there are enough items to split into multiple pages. +`$paginator->hasMorePages()` | Determine if there are more items in the data store. +`$paginator->items()` | Get the items for the current page. +`$paginator->lastItem()` | Get the result number of the last item in the results. +`$paginator->lastPage()` | Get the page number of the last available page. (Not available when using `simplePaginate`). +`$paginator->nextPageUrl()` | Get the URL for the next page. +`$paginator->onFirstPage()` | Determine if the paginator is on the first page. +`$paginator->onLastPage()` | Determine if the paginator is on the last page. +`$paginator->perPage()` | The number of items to be shown per page. +`$paginator->previousPageUrl()` | Get the URL for the previous page. +`$paginator->total()` | Determine the total number of matching items in the data store. (Not available when using `simplePaginate`). +`$paginator->url($page)` | Get the URL for a given page number. +`$paginator->getPageName()` | Get the query string variable used to store the page. +`$paginator->setPageName($name)` | Set the query string variable used to store the page. +`$paginator->through($callback)` | Transform each item using a callback. + +## Cursor Paginator Instance Methods + +Each cursor paginator instance provides additional pagination information via +the following methods: + +Method | Description +---|--- +`$paginator->count()` | Get the number of items for the current page. +`$paginator->cursor()` | Get the current cursor instance. +`$paginator->getOptions()` | Get the paginator options. +`$paginator->hasPages()` | Determine if there are enough items to split into multiple pages. +`$paginator->hasMorePages()` | Determine if there are more items in the data store. +`$paginator->getCursorName()` | Get the query string variable used to store the cursor. +`$paginator->items()` | Get the items for the current page. +`$paginator->nextCursor()` | Get the cursor instance for the next set of items. +`$paginator->nextPageUrl()` | Get the URL for the next page. +`$paginator->onFirstPage()` | Determine if the paginator is on the first page. +`$paginator->onLastPage()` | Determine if the paginator is on the last page. +`$paginator->perPage()` | The number of items to be shown per page. +`$paginator->previousCursor()` | Get the cursor instance for the previous set of items. +`$paginator->previousPageUrl()` | Get the URL for the previous page. +`$paginator->setCursorName()` | Set the query string variable used to store the cursor. +`$paginator->url($cursor)` | Get the URL for a given cursor instance. + diff --git a/output/12.x/passport.md b/output/12.x/passport.md new file mode 100644 index 0000000..3c6e4f1 --- /dev/null +++ b/output/12.x/passport.md @@ -0,0 +1,3067 @@ +# Laravel Passport + + * Introduction + * Passport or Sanctum? + * Installation + * Deploying Passport + * Upgrading Passport + * Configuration + * Token Lifetimes + * Overriding Default Models + * Overriding Routes + * Authorization Code Grant + * Managing Clients + * Requesting Tokens + * Managing Tokens + * Refreshing Tokens + * Revoking Tokens + * Purging Tokens + * Authorization Code Grant With PKCE + * Creating the Client + * Requesting Tokens + * Device Authorization Grant + * Creating a Device Code Grant Client + * Requesting Tokens + * Password Grant + * Creating a Password Grant Client + * Requesting Tokens + * Requesting All Scopes + * Customizing the User Provider + * Customizing the Username Field + * Customizing the Password Validation + * Implicit Grant + * Client Credentials Grant + * Personal Access Tokens + * Creating a Personal Access Client + * Customizing the User Provider + * Managing Personal Access Tokens + * Protecting Routes + * Via Middleware + * Passing the Access Token + * Token Scopes + * Defining Scopes + * Default Scope + * Assigning Scopes to Tokens + * Checking Scopes + * SPA Authentication + * Events + * Testing + +## Introduction + +[Laravel Passport](https://github.com/laravel/passport) provides a full OAuth2 +server implementation for your Laravel application in a matter of minutes. +Passport is built on top of the [League OAuth2 +server](https://github.com/thephpleague/oauth2-server) that is maintained by +Andy Millington and Simon Hamp. + +This documentation assumes you are already familiar with OAuth2. If you do not +know anything about OAuth2, consider familiarizing yourself with the general +[terminology](https://oauth2.thephpleague.com/terminology/) and features of +OAuth2 before continuing. + +### Passport or Sanctum? + +Before getting started, you may wish to determine if your application would be +better served by Laravel Passport or [Laravel Sanctum](/docs/12.x/sanctum). If +your application absolutely needs to support OAuth2, then you should use +Laravel Passport. + +However, if you are attempting to authenticate a single-page application, +mobile application, or issue API tokens, you should use [Laravel +Sanctum](/docs/12.x/sanctum). Laravel Sanctum does not support OAuth2; +however, it provides a much simpler API authentication development experience. + +## Installation + +You may install Laravel Passport via the `install:api` Artisan command: + + + + 1php artisan install:api --passport + + + php artisan install:api --passport + +This command will publish and run the database migrations necessary for +creating the tables your application needs to store OAuth2 clients and access +tokens. The command will also create the encryption keys required to generate +secure access tokens. + +After running the `install:api` command, add the +`Laravel\Passport\HasApiTokens` trait and +`Laravel\Passport\Contracts\OAuthenticatable` interface 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: + + + + 1 [ + + 2 'web' => [ + + 3 'driver' => 'session', + + 4 'provider' => 'users', + + 5 ], + + 6  + + 7 'api' => [ + + 8 'driver' => 'passport', + + 9 'provider' => 'users', + + 10 ], + + 11], + + + 'guards' => [ + 'web' => [ + 'driver' => 'session', + 'provider' => 'users', + ], + + 'api' => [ + 'driver' => 'passport', + 'provider' => 'users', + ], + ], + +### Deploying Passport + +When deploying Passport to your application's servers for the first time, you +will likely need to run the `passport:keys` command. This command generates +the encryption keys Passport needs in order to generate access tokens. The +generated keys are not typically kept in source control: + + + + 1php artisan passport:keys + + + 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: + + + + 1/** + + 2 * Bootstrap any application services. + + 3 */ + + 4public function boot(): void + + 5{ + + 6 Passport::loadKeysFrom(__DIR__.'/../secrets/oauth'); + + 7} + + + /** + * Bootstrap any application services. + */ + public function boot(): void + { + Passport::loadKeysFrom(__DIR__.'/../secrets/oauth'); + } + +#### Loading Keys From the Environment + +Alternatively, you may publish Passport's configuration file using the +`vendor:publish` Artisan command: + + + + 1php artisan vendor:publish --tag=passport-config + + + php artisan vendor:publish --tag=passport-config + +After the configuration file has been published, you may load your +application's encryption keys by defining them as environment variables: + + + + 1PASSPORT_PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY----- + + 2 + + 3-----END RSA PRIVATE KEY-----" + + 4  + + 5PASSPORT_PUBLIC_KEY="-----BEGIN PUBLIC KEY----- + + 6 + + 7-----END PUBLIC KEY-----" + + + PASSPORT_PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY----- + + -----END RSA PRIVATE KEY-----" + + PASSPORT_PUBLIC_KEY="-----BEGIN PUBLIC KEY----- + + -----END PUBLIC KEY-----" + +### Upgrading Passport + +When upgrading to a new major version of Passport, it's important that you +carefully review [the upgrade +guide](https://github.com/laravel/passport/blob/master/UPGRADE.md). + +## Configuration + +### Token Lifetimes + +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: + + + + 1use Carbon\CarbonInterval; + + 2  + + 3/** + + 4 * Bootstrap any application services. + + 5 */ + + 6public function boot(): void + + 7{ + + 8 Passport::tokensExpireIn(CarbonInterval::days(15)); + + 9 Passport::refreshTokensExpireIn(CarbonInterval::days(30)); + + 10 Passport::personalAccessTokensExpireIn(CarbonInterval::months(6)); + + 11} + + + use Carbon\CarbonInterval; + + /** + * Bootstrap any application services. + */ + public function boot(): void + { + Passport::tokensExpireIn(CarbonInterval::days(15)); + Passport::refreshTokensExpireIn(CarbonInterval::days(30)); + Passport::personalAccessTokensExpireIn(CarbonInterval::months(6)); + } + +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. + +### Overriding Default Models + +You are free to extend the models used internally by Passport by defining your +own model and extending the corresponding Passport model: + + + + 1use Laravel\Passport\Client as PassportClient; + + 2  + + 3class Client extends PassportClient + + 4{ + + 5 // ... + + 6} + + + use Laravel\Passport\Client as 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: + + + + 1use App\Models\Passport\AuthCode; + + 2use App\Models\Passport\Client; + + 3use App\Models\Passport\DeviceCode; + + 4use App\Models\Passport\RefreshToken; + + 5use App\Models\Passport\Token; + + 6use Laravel\Passport\Passport; + + 7  + + 8/** + + 9 * Bootstrap any application services. + + 10 */ + + 11public function boot(): void + + 12{ + + 13 Passport::useTokenModel(Token::class); + + 14 Passport::useRefreshTokenModel(RefreshToken::class); + + 15 Passport::useAuthCodeModel(AuthCode::class); + + 16 Passport::useClientModel(Client::class); + + 17 Passport::useDeviceCodeModel(DeviceCode::class); + + 18} + + + use App\Models\Passport\AuthCode; + use App\Models\Passport\Client; + use App\Models\Passport\DeviceCode; + use App\Models\Passport\RefreshToken; + use App\Models\Passport\Token; + use Laravel\Passport\Passport; + + /** + * 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::useDeviceCodeModel(DeviceCode::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`: + + + + 1use Laravel\Passport\Passport; + + 2  + + 3/** + + 4 * Register any application services. + + 5 */ + + 6public function register(): void + + 7{ + + 8 Passport::ignoreRoutes(); + + 9} + + + use Laravel\Passport\Passport; + + /** + * 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/master/routes/web.php) to your +application's `routes/web.php` file and modify them to your liking: + + + + 1Route::group([ + + 2 'as' => 'passport.', + + 3 'prefix' => config('passport.path', 'oauth'), + + 4 'namespace' => '\Laravel\Passport\Http\Controllers', + + 5], function () { + + 6 // Passport routes... + + 7}); + + + Route::group([ + 'as' => 'passport.', + 'prefix' => config('passport.path', 'oauth'), + 'namespace' => '\Laravel\Passport\Http\Controllers', + ], function () { + // Passport routes... + }); + +## Authorization Code Grant + +Using OAuth2 via authorization codes is how most developers are familiar with +OAuth2. When using authorization codes, a client application will redirect a +user to your server where they will either approve or deny the request to +issue an access token to the client. + +To get started, we need to instruct Passport how to return our "authorization" +view. + +All the authorization view's rendering logic may be customized using the +appropriate methods available via the `Laravel\Passport\Passport` class. +Typically, you should call this method from the `boot` method of your +application's `App\Providers\AppServiceProvider` class: + + + + 1use Inertia\Inertia; + + 2use Laravel\Passport\Passport; + + 3  + + 4/** + + 5 * Bootstrap any application services. + + 6 */ + + 7public function boot(): void + + 8{ + + 9 // By providing a view name... + + 10 Passport::authorizationView('auth.oauth.authorize'); + + 11  + + 12 // By providing a closure... + + 13 Passport::authorizationView( + + 14 fn ($parameters) => Inertia::render('Auth/OAuth/Authorize', [ + + 15 'request' => $parameters['request'], + + 16 'authToken' => $parameters['authToken'], + + 17 'client' => $parameters['client'], + + 18 'user' => $parameters['user'], + + 19 'scopes' => $parameters['scopes'], + + 20 ]) + + 21 ); + + 22} + + + use Inertia\Inertia; + use Laravel\Passport\Passport; + + /** + * Bootstrap any application services. + */ + public function boot(): void + { + // By providing a view name... + Passport::authorizationView('auth.oauth.authorize'); + + // By providing a closure... + Passport::authorizationView( + fn ($parameters) => Inertia::render('Auth/OAuth/Authorize', [ + 'request' => $parameters['request'], + 'authToken' => $parameters['authToken'], + 'client' => $parameters['client'], + 'user' => $parameters['user'], + 'scopes' => $parameters['scopes'], + ]) + ); + } + +Passport will automatically define the `/oauth/authorize` route that returns +this view. Your `auth.oauth.authorize` template should include a form that +makes a POST request to the `passport.authorizations.approve` route to approve +the authorization and a form that makes a DELETE request to the +`passport.authorizations.deny` route to deny the authorization. The +`passport.authorizations.approve` and `passport.authorizations.deny` routes +expect `state`, `client_id`, and `auth_token` fields. + +### Managing Clients + +Developers building applications that need to interact with your application's +API will need to register their application with yours by creating a "client". +Typically, this consists of providing the name of their application and a URI +that your application can redirect to after users approve their request for +authorization. + +#### First-Party Clients + +The simplest way to create a client is using the `passport:client` Artisan +command. This command may be used to create first-party clients or testing +your OAuth2 functionality. When you run the `passport:client` command, +Passport will prompt you for more information about your client and will +provide you with a client ID and secret: + + + + 1php artisan passport:client + + + php artisan passport:client + +If you would like to allow multiple redirect URIs for your client, you may +specify them using a comma-delimited list when prompted for the URI by the +`passport:client` command. Any URIs which contain commas should be URI +encoded: + + + + 1https://third-party-app.com/callback,https://example.com/oauth/redirect + + + https://third-party-app.com/callback,https://example.com/oauth/redirect + +#### Third-Party Clients + +Since your application's users will not be able to utilize the +`passport:client` command, you may use `createAuthorizationCodeGrantClient` +method of the `Laravel\Passport\ClientRepository` class to register a client +for a given user: + + + + 1use App\Models\User; + + 2use Laravel\Passport\ClientRepository; + + 3  + + 4$user = User::find($userId); + + 5  + + 6// Creating an OAuth app client that belongs to the given user... + + 7$client = app(ClientRepository::class)->createAuthorizationCodeGrantClient( + + 8 user: $user, + + 9 name: 'Example App', + + 10 redirectUris: ['https://third-party-app.com/callback'], + + 11 confidential: false, + + 12 enableDeviceFlow: true + + 13); + + 14  + + 15// Retrieving all the OAuth app clients that belong to the user... + + 16$clients = $user->oauthApps()->get(); + + + use App\Models\User; + use Laravel\Passport\ClientRepository; + + $user = User::find($userId); + + // Creating an OAuth app client that belongs to the given user... + $client = app(ClientRepository::class)->createAuthorizationCodeGrantClient( + user: $user, + name: 'Example App', + redirectUris: ['https://third-party-app.com/callback'], + confidential: false, + enableDeviceFlow: true + ); + + // Retrieving all the OAuth app clients that belong to the user... + $clients = $user->oauthApps()->get(); + +The `createAuthorizationCodeGrantClient` method returns an instance of +`Laravel\Passport\Client`. You may display the `$client->id` as the client ID +and `$client->plainSecret` as the client secret to the user. + +### Requesting Tokens + +#### Redirecting for Authorization + +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: + + + + 1use Illuminate\Http\Request; + + 2use Illuminate\Support\Str; + + 3  + + 4Route::get('/redirect', function (Request $request) { + + 5 $request->session()->put('state', $state = Str::random(40)); + + 6  + + 7 $query = http_build_query([ + + 8 'client_id' => 'your-client-id', + + 9 'redirect_uri' => 'https://third-party-app.com/callback', + + 10 'response_type' => 'code', + + 11 'scope' => 'user:read orders:create', + + 12 'state' => $state, + + 13 // 'prompt' => '', // "none", "consent", or "login" + + 14 ]); + + 15  + + 16 return redirect('https://passport-app.test/oauth/authorize?'.$query); + + 17}); + + + use Illuminate\Http\Request; + use Illuminate\Support\Str; + + Route::get('/redirect', function (Request $request) { + $request->session()->put('state', $state = Str::random(40)); + + $query = http_build_query([ + 'client_id' => 'your-client-id', + 'redirect_uri' => 'https://third-party-app.com/callback', + 'response_type' => 'code', + 'scope' => 'user:read orders:create', + 'state' => $state, + // 'prompt' => '', // "none", "consent", or "login" + ]); + + return redirect('https://passport-app.test/oauth/authorize?'.$query); + }); + +The `prompt` parameter may be used to specify the authentication behavior of +the Passport application. + +If the `prompt` value is `none`, Passport will always throw an authentication +error if the user is not already authenticated with the Passport application. +If the value is `consent`, Passport will always display the authorization +approval screen, even if all scopes were previously granted to the consuming +application. When the value is `login`, the Passport application will always +prompt the user to re-login to the application, even if they already have an +existing session. + +If no `prompt` value is provided, the user will be prompted for authorization +only if they have not previously authorized access to the consuming +application for the requested scopes. + +Remember, the `/oauth/authorize` route is already defined by Passport. You do +not need to manually define this route. + +#### Approving the Request + +When receiving authorization requests, Passport will automatically respond +based on the value of `prompt` parameter (if present) and may display a +template to the user allowing them to approve or deny the authorization +request. If they approve the request, they will be redirected back to the +`redirect_uri` that was specified by the consuming application. The +`redirect_uri` must match the `redirect` URL that was specified when the +client was created. + +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 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: + + + + 1firstParty(); + + 18 } + + 19} + + + 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: + + + + 1use Illuminate\Http\Request; + + 2use Illuminate\Support\Facades\Http; + + 3  + + 4Route::get('/callback', function (Request $request) { + + 5 $state = $request->session()->pull('state'); + + 6  + + 7 throw_unless( + + 8 strlen($state) > 0 && $state === $request->state, + + 9 InvalidArgumentException::class, + + 10 'Invalid state value.' + + 11 ); + + 12  + + 13 $response = Http::asForm()->post('https://passport-app.test/oauth/token', [ + + 14 'grant_type' => 'authorization_code', + + 15 'client_id' => 'your-client-id', + + 16 'client_secret' => 'your-client-secret', + + 17 'redirect_uri' => 'https://third-party-app.com/callback', + + 18 'code' => $request->code, + + 19 ]); + + 20  + + 21 return $response->json(); + + 22}); + + + use Illuminate\Http\Request; + use Illuminate\Support\Facades\Http; + + Route::get('/callback', function (Request $request) { + $state = $request->session()->pull('state'); + + throw_unless( + strlen($state) > 0 && $state === $request->state, + InvalidArgumentException::class, + 'Invalid state value.' + ); + + $response = Http::asForm()->post('https://passport-app.test/oauth/token', [ + 'grant_type' => 'authorization_code', + 'client_id' => 'your-client-id', + 'client_secret' => 'your-client-secret', + 'redirect_uri' => 'https://third-party-app.com/callback', + 'code' => $request->code, + ]); + + 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. + +Like the `/oauth/authorize` route, the `/oauth/token` route is defined for you +by Passport. There is no need to manually define this route. + +### Managing Tokens + +You may retrieve user's authorized tokens using the `tokens` method of the +`Laravel\Passport\HasApiTokens` trait. For example, this may be used to offer +your users a dashboard to keep track of their connections with third-party +applications: + + + + 1use App\Models\User; + + 2use Illuminate\Database\Eloquent\Collection; + + 3use Illuminate\Support\Facades\Date; + + 4use Laravel\Passport\Token; + + 5  + + 6$user = User::find($userId); + + 7  + + 8// Retrieving all of the valid tokens for the user... + + 9$tokens = $user->tokens() + + 10 ->where('revoked', false) + + 11 ->where('expires_at', '>', Date::now()) + + 12 ->get(); + + 13  + + 14// Retrieving all the user's connections to third-party OAuth app clients... + + 15$connections = $tokens->load('client') + + 16 ->reject(fn (Token $token) => $token->client->firstParty()) + + 17 ->groupBy('client_id') + + 18 ->map(fn (Collection $tokens) => [ + + 19 'client' => $tokens->first()->client, + + 20 'scopes' => $tokens->pluck('scopes')->flatten()->unique()->values()->all(), + + 21 'tokens_count' => $tokens->count(), + + 22 ]) + + 23 ->values(); + + + use App\Models\User; + use Illuminate\Database\Eloquent\Collection; + use Illuminate\Support\Facades\Date; + use Laravel\Passport\Token; + + $user = User::find($userId); + + // Retrieving all of the valid tokens for the user... + $tokens = $user->tokens() + ->where('revoked', false) + ->where('expires_at', '>', Date::now()) + ->get(); + + // Retrieving all the user's connections to third-party OAuth app clients... + $connections = $tokens->load('client') + ->reject(fn (Token $token) => $token->client->firstParty()) + ->groupBy('client_id') + ->map(fn (Collection $tokens) => [ + 'client' => $tokens->first()->client, + 'scopes' => $tokens->pluck('scopes')->flatten()->unique()->values()->all(), + 'tokens_count' => $tokens->count(), + ]) + ->values(); + +### Refreshing Tokens + +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: + + + + 1use Illuminate\Support\Facades\Http; + + 2  + + 3$response = Http::asForm()->post('https://passport-app.test/oauth/token', [ + + 4 'grant_type' => 'refresh_token', + + 5 'refresh_token' => 'the-refresh-token', + + 6 'client_id' => 'your-client-id', + + 7 'client_secret' => 'your-client-secret', // Required for confidential clients only... + + 8 'scope' => 'user:read orders:create', + + 9]); + + 10  + + 11return $response->json(); + + + use Illuminate\Support\Facades\Http; + + $response = Http::asForm()->post('https://passport-app.test/oauth/token', [ + 'grant_type' => 'refresh_token', + 'refresh_token' => 'the-refresh-token', + 'client_id' => 'your-client-id', + 'client_secret' => 'your-client-secret', // Required for confidential clients only... + 'scope' => 'user:read orders:create', + ]); + + 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. + +### Revoking Tokens + +You may revoke a token by using the `revoke` method on the +`Laravel\Passport\Token` model. You may revoke a token's refresh token using +the `revoke` method on the `Laravel\Passport\RefreshToken` model: + + + + 1use Laravel\Passport\Passport; + + 2use Laravel\Passport\Token; + + 3  + + 4$token = Passport::token()->find($tokenId); + + 5  + + 6// Revoke an access token... + + 7$token->revoke(); + + 8  + + 9// Revoke the token's refresh token... + + 10$token->refreshToken?->revoke(); + + 11  + + 12// Revoke all of the user's tokens... + + 13User::find($userId)->tokens()->each(function (Token $token) { + + 14 $token->revoke(); + + 15 $token->refreshToken?->revoke(); + + 16}); + + + use Laravel\Passport\Passport; + use Laravel\Passport\Token; + + $token = Passport::token()->find($tokenId); + + // Revoke an access token... + $token->revoke(); + + // Revoke the token's refresh token... + $token->refreshToken?->revoke(); + + // Revoke all of the user's tokens... + User::find($userId)->tokens()->each(function (Token $token) { + $token->revoke(); + $token->refreshToken?->revoke(); + }); + +### Purging Tokens + +When tokens have been revoked or expired, you might want to purge them from +the database. Passport's included `passport:purge` Artisan command can do this +for you: + + + + 1# Purge revoked and expired tokens, auth codes, and device codes... + + 2php artisan passport:purge + + 3  + + 4# Only purge tokens expired for more than 6 hours... + + 5php artisan passport:purge --hours=6 + + 6  + + 7# Only purge revoked tokens, auth codes, and device codes... + + 8php artisan passport:purge --revoked + + 9  + + 10# Only purge expired tokens, auth codes, and device codes... + + 11php artisan passport:purge --expired + + + # Purge revoked and expired tokens, auth codes, and device codes... + php artisan passport:purge + + # Only purge tokens expired for more than 6 hours... + php artisan passport:purge --hours=6 + + # Only purge revoked tokens, auth codes, and device codes... + php artisan passport:purge --revoked + + # Only purge expired tokens, auth codes, and device codes... + php artisan passport:purge --expired + +You may also configure a [scheduled job](/docs/12.x/scheduling) in your +application's `routes/console.php` file to automatically prune your tokens on +a schedule: + + + + 1use Illuminate\Support\Facades\Schedule; + + 2  + + 3Schedule::command('passport:purge')->hourly(); + + + use Illuminate\Support\Facades\Schedule; + + Schedule::command('passport:purge')->hourly(); + +## Authorization Code Grant With PKCE + +The Authorization Code grant with "Proof Key for Code Exchange" (PKCE) is a +secure way to authenticate single page applications or mobile applications to +access your API. This grant should be used when you can't guarantee that the +client secret will be stored confidentially or in order to mitigate the threat +of having the authorization code intercepted by an attacker. A combination of +a "code verifier" and a "code challenge" replaces the client secret when +exchanging the authorization code for an access token. + +### Creating the Client + +Before your application can issue tokens via the authorization code grant with +PKCE, you will need to create a PKCE-enabled client. You may do this using the +`passport:client` Artisan command with the `--public` option: + + + + 1php artisan passport:client --public + + + php artisan passport:client --public + +### Requesting Tokens + +#### Code Verifier and Code Challenge + +As this authorization grant does not provide a client secret, developers will +need to generate a combination of a code verifier and a code challenge in +order to request a token. + +The code verifier should be a random string of between 43 and 128 characters +containing letters, numbers, and `"-"`, `"."`, `"_"`, `"~"` characters, as +defined in the [RFC 7636 specification](https://tools.ietf.org/html/rfc7636). + +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. + + + + 1$encoded = base64_encode(hash('sha256', $codeVerifier, true)); + + 2  + + 3$codeChallenge = strtr(rtrim($encoded, '='), '+/', '-_'); + + + $encoded = base64_encode(hash('sha256', $codeVerifier, true)); + + $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: + + + + 1use Illuminate\Http\Request; + + 2use Illuminate\Support\Str; + + 3  + + 4Route::get('/redirect', function (Request $request) { + + 5 $request->session()->put('state', $state = Str::random(40)); + + 6  + + 7 $request->session()->put( + + 8 'code_verifier', $codeVerifier = Str::random(128) + + 9 ); + + 10  + + 11 $codeChallenge = strtr(rtrim( + + 12 base64_encode(hash('sha256', $codeVerifier, true)) + + 13 , '='), '+/', '-_'); + + 14  + + 15 $query = http_build_query([ + + 16 'client_id' => 'your-client-id', + + 17 'redirect_uri' => 'https://third-party-app.com/callback', + + 18 'response_type' => 'code', + + 19 'scope' => 'user:read orders:create', + + 20 'state' => $state, + + 21 'code_challenge' => $codeChallenge, + + 22 'code_challenge_method' => 'S256', + + 23 // 'prompt' => '', // "none", "consent", or "login" + + 24 ]); + + 25  + + 26 return redirect('https://passport-app.test/oauth/authorize?'.$query); + + 27}); + + + use Illuminate\Http\Request; + use Illuminate\Support\Str; + + Route::get('/redirect', function (Request $request) { + $request->session()->put('state', $state = Str::random(40)); + + $request->session()->put( + 'code_verifier', $codeVerifier = Str::random(128) + ); + + $codeChallenge = strtr(rtrim( + base64_encode(hash('sha256', $codeVerifier, true)) + , '='), '+/', '-_'); + + $query = http_build_query([ + 'client_id' => 'your-client-id', + 'redirect_uri' => 'https://third-party-app.com/callback', + 'response_type' => 'code', + 'scope' => 'user:read orders:create', + 'state' => $state, + 'code_challenge' => $codeChallenge, + 'code_challenge_method' => 'S256', + // 'prompt' => '', // "none", "consent", or "login" + ]); + + return redirect('https://passport-app.test/oauth/authorize?'.$query); + }); + +#### 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 verify the `state` parameter +against the value that was stored prior to the redirect, as in the standard +Authorization Code Grant. + +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: + + + + 1use Illuminate\Http\Request; + + 2use Illuminate\Support\Facades\Http; + + 3  + + 4Route::get('/callback', function (Request $request) { + + 5 $state = $request->session()->pull('state'); + + 6  + + 7 $codeVerifier = $request->session()->pull('code_verifier'); + + 8  + + 9 throw_unless( + + 10 strlen($state) > 0 && $state === $request->state, + + 11 InvalidArgumentException::class + + 12 ); + + 13  + + 14 $response = Http::asForm()->post('https://passport-app.test/oauth/token', [ + + 15 'grant_type' => 'authorization_code', + + 16 'client_id' => 'your-client-id', + + 17 'redirect_uri' => 'https://third-party-app.com/callback', + + 18 'code_verifier' => $codeVerifier, + + 19 'code' => $request->code, + + 20 ]); + + 21  + + 22 return $response->json(); + + 23}); + + + use Illuminate\Http\Request; + use Illuminate\Support\Facades\Http; + + Route::get('/callback', function (Request $request) { + $state = $request->session()->pull('state'); + + $codeVerifier = $request->session()->pull('code_verifier'); + + throw_unless( + strlen($state) > 0 && $state === $request->state, + InvalidArgumentException::class + ); + + $response = Http::asForm()->post('https://passport-app.test/oauth/token', [ + 'grant_type' => 'authorization_code', + 'client_id' => 'your-client-id', + 'redirect_uri' => 'https://third-party-app.com/callback', + 'code_verifier' => $codeVerifier, + 'code' => $request->code, + ]); + + return $response->json(); + }); + +## Device Authorization Grant + +The OAuth2 device authorization grant allows browserless or limited input +devices, such as TVs and game consoles, to obtain an access token by +exchanging a "device code". When using device flow, the device client will +instruct the user to use a secondary device, such as a computer or a +smartphone and connect to your server where they will enter the provided "user +code" and either approve or deny the access request. + +To get started, we need to instruct Passport how to return our "user code" and +"authorization" views. + +All the authorization view's rendering logic may be customized using the +appropriate methods available via the `Laravel\Passport\Passport` class. +Typically, you should call this method from the `boot` method of your +application's `App\Providers\AppServiceProvider` class. + + + + 1use Inertia\Inertia; + + 2use Laravel\Passport\Passport; + + 3  + + 4/** + + 5 * Bootstrap any application services. + + 6 */ + + 7public function boot(): void + + 8{ + + 9 // By providing a view name... + + 10 Passport::deviceUserCodeView('auth.oauth.device.user-code'); + + 11 Passport::deviceAuthorizationView('auth.oauth.device.authorize'); + + 12  + + 13 // By providing a closure... + + 14 Passport::deviceUserCodeView( + + 15 fn ($parameters) => Inertia::render('Auth/OAuth/Device/UserCode') + + 16 ); + + 17  + + 18 Passport::deviceAuthorizationView( + + 19 fn ($parameters) => Inertia::render('Auth/OAuth/Device/Authorize', [ + + 20 'request' => $parameters['request'], + + 21 'authToken' => $parameters['authToken'], + + 22 'client' => $parameters['client'], + + 23 'user' => $parameters['user'], + + 24 'scopes' => $parameters['scopes'], + + 25 ]) + + 26 ); + + 27  + + 28 // ... + + 29} + + + use Inertia\Inertia; + use Laravel\Passport\Passport; + + /** + * Bootstrap any application services. + */ + public function boot(): void + { + // By providing a view name... + Passport::deviceUserCodeView('auth.oauth.device.user-code'); + Passport::deviceAuthorizationView('auth.oauth.device.authorize'); + + // By providing a closure... + Passport::deviceUserCodeView( + fn ($parameters) => Inertia::render('Auth/OAuth/Device/UserCode') + ); + + Passport::deviceAuthorizationView( + fn ($parameters) => Inertia::render('Auth/OAuth/Device/Authorize', [ + 'request' => $parameters['request'], + 'authToken' => $parameters['authToken'], + 'client' => $parameters['client'], + 'user' => $parameters['user'], + 'scopes' => $parameters['scopes'], + ]) + ); + + // ... + } + +Passport will automatically define routes that return these views. Your +`auth.oauth.device.user-code` template should include a form that makes a GET +request to the `passport.device.authorizations.authorize` route. The +`passport.device.authorizations.authorize` route expects a `user_code` query +parameter. + +Your `auth.oauth.device.authorize` template should include a form that makes a +POST request to the `passport.device.authorizations.approve` route to approve +the authorization and a form that makes a DELETE request to the +`passport.device.authorizations.deny` route to deny the authorization. The +`passport.device.authorizations.approve` and +`passport.device.authorizations.deny` routes expect `state`, `client_id`, and +`auth_token` fields. + +### Creating a Device Authorization Grant Client + +Before your application can issue tokens via the device authorization grant, +you will need to create a device flow enabled client. You may do this using +the `passport:client` Artisan command with the `--device` option. This command +will create a first-party device flow enabled client and provide you with a +client ID and secret: + + + + 1php artisan passport:client --device + + + php artisan passport:client --device + +Additionally, you may use `createDeviceAuthorizationGrantClient` method on the +`ClientRepository` class to register a third-party client that belongs to the +given user: + + + + 1use App\Models\User; + + 2use Laravel\Passport\ClientRepository; + + 3  + + 4$user = User::find($userId); + + 5  + + 6$client = app(ClientRepository::class)->createDeviceAuthorizationGrantClient( + + 7 user: $user, + + 8 name: 'Example Device', + + 9 confidential: false, + + 10); + + + use App\Models\User; + use Laravel\Passport\ClientRepository; + + $user = User::find($userId); + + $client = app(ClientRepository::class)->createDeviceAuthorizationGrantClient( + user: $user, + name: 'Example Device', + confidential: false, + ); + +### Requesting Tokens + +#### Requesting a Device Code + +Once a client has been created, developers may use their client ID to request +a device code from your application. First, the consuming device should make a +`POST` request to your application's `/oauth/device/code` route to request a +device code: + + + + 1use Illuminate\Support\Facades\Http; + + 2  + + 3$response = Http::asForm()->post('https://passport-app.test/oauth/device/code', [ + + 4 'client_id' => 'your-client-id', + + 5 'scope' => 'user:read orders:create', + + 6]); + + 7  + + 8return $response->json(); + + + use Illuminate\Support\Facades\Http; + + $response = Http::asForm()->post('https://passport-app.test/oauth/device/code', [ + 'client_id' => 'your-client-id', + 'scope' => 'user:read orders:create', + ]); + + return $response->json(); + +This will return a JSON response containing `device_code`, `user_code`, +`verification_uri`, `interval`, and `expires_in` attributes. The `expires_in` +attribute contains the number of seconds until the device code expires. The +`interval` attribute contains the number of seconds the consuming device +should wait between requests when polling `/oauth/token` route to avoid rate +limit errors. + +Remember, the `/oauth/device/code` route is already defined by Passport. You +do not need to manually define this route. + +#### Displaying the Verification URI and User Code + +Once a device code request has been obtained, the consuming device should +instruct the user to use another device and visit the provided +`verification_uri` and enter the `user_code` in order to approve the +authorization request. + +#### Polling Token Request + +Since the user will be using a separate device to grant (or deny) access, the +consuming device should poll your application's `/oauth/token` route to +determine when the user has responded to the request. The consuming device +should use the minimum polling `interval` provided in the JSON response when +requesting device code to avoid rate limit errors: + + + + 1use Illuminate\Support\Facades\Http; + + 2use Illuminate\Support\Sleep; + + 3  + + 4$interval = 5; + + 5  + + 6do { + + 7 Sleep::for($interval)->seconds(); + + 8  + + 9 $response = Http::asForm()->post('https://passport-app.test/oauth/token', [ + + 10 'grant_type' => 'urn:ietf:params:oauth:grant-type:device_code', + + 11 'client_id' => 'your-client-id', + + 12 'client_secret' => 'your-client-secret', // Required for confidential clients only... + + 13 'device_code' => 'the-device-code', + + 14 ]); + + 15  + + 16 if ($response->json('error') === 'slow_down') { + + 17 $interval += 5; + + 18 } + + 19} while (in_array($response->json('error'), ['authorization_pending', 'slow_down'])); + + 20  + + 21return $response->json(); + + + use Illuminate\Support\Facades\Http; + use Illuminate\Support\Sleep; + + $interval = 5; + + do { + Sleep::for($interval)->seconds(); + + $response = Http::asForm()->post('https://passport-app.test/oauth/token', [ + 'grant_type' => 'urn:ietf:params:oauth:grant-type:device_code', + 'client_id' => 'your-client-id', + 'client_secret' => 'your-client-secret', // Required for confidential clients only... + 'device_code' => 'the-device-code', + ]); + + if ($response->json('error') === 'slow_down') { + $interval += 5; + } + } while (in_array($response->json('error'), ['authorization_pending', 'slow_down'])); + + return $response->json(); + +If the user has approved the authorization request, this 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. + +## Password Grant + +We no longer recommend using password grant tokens. Instead, you should choose +[a grant type that is currently recommended by OAuth2 +Server](https://oauth2.thephpleague.com/authorization-server/which-grant/). + +The OAuth2 password grant allows your other first-party clients, such as a +mobile application, to obtain an access token using an email address / +username and password. This allows you to issue access tokens securely to your +first-party clients without requiring your users to go through the entire +OAuth2 authorization code redirect flow. + +To enable the password grant, call the `enablePasswordGrant` method in the +`boot` method of your application's `App\Providers\AppServiceProvider` class: + + + + 1/** + + 2 * Bootstrap any application services. + + 3 */ + + 4public function boot(): void + + 5{ + + 6 Passport::enablePasswordGrant(); + + 7} + + + /** + * Bootstrap any application services. + */ + public function boot(): void + { + Passport::enablePasswordGrant(); + } + +### Creating a Password Grant Client + +Before your application can issue tokens via the password grant, you will need +to create a password grant client. You may do this using the `passport:client` +Artisan command with the `--password` option. + + + + 1php artisan passport:client --password + + + php artisan passport:client --password + +### Requesting Tokens + +Once you have enabled the grant and 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: + + + + 1use Illuminate\Support\Facades\Http; + + 2  + + 3$response = Http::asForm()->post('https://passport-app.test/oauth/token', [ + + 4 'grant_type' => 'password', + + 5 'client_id' => 'your-client-id', + + 6 'client_secret' => 'your-client-secret', // Required for confidential clients only... + + 7 'username' => '[[email protected]](/cdn-cgi/l/email-protection)', + + 8 'password' => 'my-password', + + 9 'scope' => 'user:read orders:create', + + 10]); + + 11  + + 12return $response->json(); + + + use Illuminate\Support\Facades\Http; + + $response = Http::asForm()->post('https://passport-app.test/oauth/token', [ + 'grant_type' => 'password', + 'client_id' => 'your-client-id', + 'client_secret' => 'your-client-secret', // Required for confidential clients only... + 'username' => '[[email protected]](/cdn-cgi/l/email-protection)', + 'password' => 'my-password', + 'scope' => 'user:read orders:create', + ]); + + return $response->json(); + +Remember, access tokens are long-lived by default. However, you are free to +configure your maximum access token lifetime if needed. + +### Requesting All Scopes + +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: + + + + 1use Illuminate\Support\Facades\Http; + + 2  + + 3$response = Http::asForm()->post('https://passport-app.test/oauth/token', [ + + 4 'grant_type' => 'password', + + 5 'client_id' => 'your-client-id', + + 6 'client_secret' => 'your-client-secret', // Required for confidential clients only... + + 7 'username' => '[[email protected]](/cdn-cgi/l/email-protection)', + + 8 'password' => 'my-password', + + 9 'scope' => '*', + + 10]); + + + use Illuminate\Support\Facades\Http; + + $response = Http::asForm()->post('https://passport-app.test/oauth/token', [ + 'grant_type' => 'password', + 'client_id' => 'your-client-id', + 'client_secret' => 'your-client-secret', // Required for confidential clients only... + 'username' => '[[email protected]](/cdn-cgi/l/email-protection)', + 'password' => 'my-password', + 'scope' => '*', + ]); + +### Customizing the User Provider + +If your application uses more than one [authentication user +provider](/docs/12.x/authentication#introduction), you may specify which user +provider the password grant client uses by providing a `--provider` option +when creating the client via the `artisan passport:client --password` command. +The given provider name should match a valid provider defined in your +application's `config/auth.php` configuration file. You can then protect your +route using middleware to ensure that only users from the guard's specified +provider are authorized. + +### Customizing the Username Field + +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: + + + + 1where('username', $username)->first(); + + 20 } + + 21} + + + 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: + + + + 1password); + + 21 } + + 22} + + + password); + } + } + +## Implicit Grant + +We no longer recommend using implicit grant tokens. Instead, you should choose +[a grant type that is currently recommended by OAuth2 +Server](https://oauth2.thephpleague.com/authorization-server/which-grant/). + +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: + + + + 1/** + + 2 * Bootstrap any application services. + + 3 */ + + 4public function boot(): void + + 5{ + + 6 Passport::enableImplicitGrant(); + + 7} + + + /** + * Bootstrap any application services. + */ + public function boot(): void + { + Passport::enableImplicitGrant(); + } + +Before your application can issue tokens via the implicit grant, you will need +to create an implicit grant client. You may do this using the +`passport:client` Artisan command with the `--implicit` option. + + + + 1php artisan passport:client --implicit + + + php artisan passport:client --implicit + +Once the grant has been enabled and an implicit client has been created, +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: + + + + 1use Illuminate\Http\Request; + + 2  + + 3Route::get('/redirect', function (Request $request) { + + 4 $request->session()->put('state', $state = Str::random(40)); + + 5  + + 6 $query = http_build_query([ + + 7 'client_id' => 'your-client-id', + + 8 'redirect_uri' => 'https://third-party-app.com/callback', + + 9 'response_type' => 'token', + + 10 'scope' => 'user:read orders:create', + + 11 'state' => $state, + + 12 // 'prompt' => '', // "none", "consent", or "login" + + 13 ]); + + 14  + + 15 return redirect('https://passport-app.test/oauth/authorize?'.$query); + + 16}); + + + use Illuminate\Http\Request; + + Route::get('/redirect', function (Request $request) { + $request->session()->put('state', $state = Str::random(40)); + + $query = http_build_query([ + 'client_id' => 'your-client-id', + 'redirect_uri' => 'https://third-party-app.com/callback', + 'response_type' => 'token', + 'scope' => 'user:read orders:create', + 'state' => $state, + // 'prompt' => '', // "none", "consent", or "login" + ]); + + return redirect('https://passport-app.test/oauth/authorize?'.$query); + }); + +Remember, the `/oauth/authorize` route is already defined by Passport. You do +not need to manually define this route. + +## Client Credentials Grant + +The client credentials grant is suitable for machine-to-machine +authentication. For example, you might use this grant in a scheduled job which +is performing maintenance tasks over an API. + +Before your application can issue tokens via the client credentials grant, you +will need to create a client credentials grant client. You may do this using +the `--client` option of the `passport:client` Artisan command: + + + + 1php artisan passport:client --client + + + php artisan passport:client --client + +Next, assign the +`Laravel\Passport\Http\Middleware\EnsureClientIsResourceOwner` middleware to a +route: + + + + 1use Laravel\Passport\Http\Middleware\EnsureClientIsResourceOwner; + + 2  + + 3Route::get('/orders', function (Request $request) { + + 4 // Access token is valid and the client is resource owner... + + 5})->middleware(EnsureClientIsResourceOwner::class); + + + use Laravel\Passport\Http\Middleware\EnsureClientIsResourceOwner; + + Route::get('/orders', function (Request $request) { + // Access token is valid and the client is resource owner... + })->middleware(EnsureClientIsResourceOwner::class); + +To restrict access to the route to specific scopes, you may provide a list of +the required scopes to the `using` method`: + + + + 1Route::get('/orders', function (Request $request) { + + 2 // Access token is valid, the client is resource owner, and has both "servers:read" and "servers:create" scopes... + + 3})->middleware(EnsureClientIsResourceOwner::using('servers:read', 'servers:create')); + + + Route::get('/orders', function (Request $request) { + // Access token is valid, the client is resource owner, and has both "servers:read" and "servers:create" scopes... + })->middleware(EnsureClientIsResourceOwner::using('servers:read', 'servers:create')); + +### Retrieving Tokens + +To retrieve a token using this grant type, make a request to the `oauth/token` +endpoint: + + + + 1use Illuminate\Support\Facades\Http; + + 2  + + 3$response = Http::asForm()->post('https://passport-app.test/oauth/token', [ + + 4 'grant_type' => 'client_credentials', + + 5 'client_id' => 'your-client-id', + + 6 'client_secret' => 'your-client-secret', + + 7 'scope' => 'servers:read servers:create', + + 8]); + + 9  + + 10return $response->json()['access_token']; + + + use Illuminate\Support\Facades\Http; + + $response = Http::asForm()->post('https://passport-app.test/oauth/token', [ + 'grant_type' => 'client_credentials', + 'client_id' => 'your-client-id', + 'client_secret' => 'your-client-secret', + 'scope' => 'servers:read servers:create', + ]); + + return $response->json()['access_token']; + +## Personal Access Tokens + +Sometimes, your users may want to issue access tokens to themselves without +going through the typical authorization code redirect flow. Allowing users to +issue tokens to themselves via your application's UI can be useful for +allowing users to experiment with your API or may serve as a simpler approach +to issuing access tokens in general. + +If your application is using Passport primarily to issue personal access +tokens, consider using [Laravel Sanctum](/docs/12.x/sanctum), Laravel's light- +weight first-party library for issuing API access tokens. + +### Creating a Personal Access Client + +Before your application can issue personal access tokens, you will need to +create a personal access client. You may do this by executing the +`passport:client` Artisan command with the `--personal` option. If you have +already run the `passport:install` command, you do not need to run this +command: + + + + 1php artisan passport:client --personal + + + php artisan passport:client --personal + +### Customizing the User Provider + +If your application uses more than one [authentication user +provider](/docs/12.x/authentication#introduction), you may specify which user +provider the personal access grant client uses by providing a `--provider` +option when creating the client via the `artisan passport:client --personal` +command. The given provider name should match a valid provider defined in your +application's `config/auth.php` configuration file. You can then protect your +route using middleware to ensure that only users from the guard's specified +provider are authorized. + +### Managing Personal Access Tokens + +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 as its second argument: + + + + 1use App\Models\User; + + 2use Illuminate\Support\Facades\Date; + + 3use Laravel\Passport\Token; + + 4  + + 5$user = User::find($userId); + + 6  + + 7// Creating a token without scopes... + + 8$token = $user->createToken('My Token')->accessToken; + + 9  + + 10// Creating a token with scopes... + + 11$token = $user->createToken('My Token', ['user:read', 'orders:create'])->accessToken; + + 12  + + 13// Creating a token with all scopes... + + 14$token = $user->createToken('My Token', ['*'])->accessToken; + + 15  + + 16// Retrieving all the valid personal access tokens that belong to the user... + + 17$tokens = $user->tokens() + + 18 ->with('client') + + 19 ->where('revoked', false) + + 20 ->where('expires_at', '>', Date::now()) + + 21 ->get() + + 22 ->filter(fn (Token $token) => $token->client->hasGrantType('personal_access')); + + + use App\Models\User; + use Illuminate\Support\Facades\Date; + use Laravel\Passport\Token; + + $user = User::find($userId); + + // Creating a token without scopes... + $token = $user->createToken('My Token')->accessToken; + + // Creating a token with scopes... + $token = $user->createToken('My Token', ['user:read', 'orders:create'])->accessToken; + + // Creating a token with all scopes... + $token = $user->createToken('My Token', ['*'])->accessToken; + + // Retrieving all the valid personal access tokens that belong to the user... + $tokens = $user->tokens() + ->with('client') + ->where('revoked', false) + ->where('expires_at', '>', Date::now()) + ->get() + ->filter(fn (Token $token) => $token->client->hasGrantType('personal_access')); + +## Protecting Routes + +### Via Middleware + +Passport includes an [authentication guard](/docs/12.x/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: + + + + 1Route::get('/user', function () { + + 2 // Only API authenticated users may access this route... + + 3})->middleware('auth:api'); + + + Route::get('/user', function () { + // Only API authenticated users may access this route... + })->middleware('auth:api'); + +If you are using the client credentials grant, you should use the +`Laravel\Passport\Http\Middleware\EnsureClientIsResourceOwner` middleware to +protect your routes instead of the `auth:api` middleware. + +#### Multiple Authentication Guards + +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: + + + + 1'guards' => [ + + 2 'api' => [ + + 3 'driver' => 'passport', + + 4 'provider' => 'users', + + 5 ], + + 6  + + 7 'api-customers' => [ + + 8 'driver' => 'passport', + + 9 'provider' => 'customers', + + 10 ], + + 11], + + + 'guards' => [ + 'api' => [ + 'driver' => 'passport', + 'provider' => 'users', + ], + + '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: + + + + 1Route::get('/customer', function () { + + 2 // ... + + 3})->middleware('auth:api-customers'); + + + Route::get('/customer', function () { + // ... + })->middleware('auth:api-customers'); + +For more information on using multiple user providers with Passport, please +consult the personal access tokens documentation and password grant +documentation. + +### Passing the Access Token + +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 `Http` +Facade: + + + + 1use Illuminate\Support\Facades\Http; + + 2  + + 3$response = Http::withHeaders([ + + 4 'Accept' => 'application/json', + + 5 'Authorization' => "Bearer $accessToken", + + 6])->get('https://passport-app.test/api/user'); + + 7  + + 8return $response->json(); + + + use Illuminate\Support\Facades\Http; + + $response = Http::withHeaders([ + 'Accept' => 'application/json', + 'Authorization' => "Bearer $accessToken", + ])->get('https://passport-app.test/api/user'); + + return $response->json(); + +## Token Scopes + +Scopes allow your API clients to request a specific set of permissions when +requesting authorization to access an account. For example, if you are +building an e-commerce application, not all API consumers will need the +ability to place orders. Instead, you may allow the consumers to only request +authorization to access order shipment statuses. In other words, scopes allow +your application's users to limit the actions a third-party application can +perform on their behalf. + +### Defining Scopes + +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: + + + + 1/** + + 2 * Bootstrap any application services. + + 3 */ + + 4public function boot(): void + + 5{ + + 6 Passport::tokensCan([ + + 7 'user:read' => 'Retrieve the user info', + + 8 'orders:create' => 'Place orders', + + 9 'orders:read:status' => 'Check order status', + + 10 ]); + + 11} + + + /** + * Bootstrap any application services. + */ + public function boot(): void + { + Passport::tokensCan([ + 'user:read' => 'Retrieve the user info', + 'orders:create' => 'Place orders', + 'orders:read:status' => 'Check order status', + ]); + } + +### Default Scope + +If a client does not request any specific scopes, you may configure your +Passport server to attach default scopes to the token using the +`defaultScopes` method. Typically, you should call this method from the `boot` +method of your application's `App\Providers\AppServiceProvider` class: + + + + 1use Laravel\Passport\Passport; + + 2  + + 3Passport::tokensCan([ + + 4 'user:read' => 'Retrieve the user info', + + 5 'orders:create' => 'Place orders', + + 6 'orders:read:status' => 'Check order status', + + 7]); + + 8  + + 9Passport::defaultScopes([ + + 10 'user:read', + + 11 'orders:create', + + 12]); + + + use Laravel\Passport\Passport; + + Passport::tokensCan([ + 'user:read' => 'Retrieve the user info', + 'orders:create' => 'Place orders', + 'orders:read:status' => 'Check order status', + ]); + + Passport::defaultScopes([ + 'user:read', + 'orders:create', + ]); + +### Assigning Scopes to Tokens + +#### When Requesting Authorization Codes + +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: + + + + 1Route::get('/redirect', function () { + + 2 $query = http_build_query([ + + 3 'client_id' => 'your-client-id', + + 4 'redirect_uri' => 'https://third-party-app.com/callback', + + 5 'response_type' => 'code', + + 6 'scope' => 'user:read orders:create', + + 7 ]); + + 8  + + 9 return redirect('https://passport-app.test/oauth/authorize?'.$query); + + 10}); + + + Route::get('/redirect', function () { + $query = http_build_query([ + 'client_id' => 'your-client-id', + 'redirect_uri' => 'https://third-party-app.com/callback', + 'response_type' => 'code', + 'scope' => 'user:read orders:create', + ]); + + return redirect('https://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: + + + + 1$token = $user->createToken('My Token', ['orders:create'])->accessToken; + + + $token = $user->createToken('My Token', ['orders:create'])->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. + +#### Check For All Scopes + +The `Laravel\Passport\Http\Middleware\CheckToken` middleware may be assigned +to a route to verify that the incoming request's access token has all the +listed scopes: + + + + 1use Laravel\Passport\Http\Middleware\CheckToken; + + 2  + + 3Route::get('/orders', function () { + + 4 // Access token has both "orders:read" and "orders:create" scopes... + + 5})->middleware(['auth:api', CheckToken::using('orders:read', 'orders:create')]); + + + use Laravel\Passport\Http\Middleware\CheckToken; + + Route::get('/orders', function () { + // Access token has both "orders:read" and "orders:create" scopes... + })->middleware(['auth:api', CheckToken::using('orders:read', 'orders:create')]); + +#### Check for Any Scopes + +The `Laravel\Passport\Http\Middleware\CheckTokenForAnyScope` middleware may be +assigned to a route to verify that the incoming request's access token has _at +least one_ of the listed scopes: + + + + 1use Laravel\Passport\Http\Middleware\CheckTokenForAnyScope; + + 2  + + 3Route::get('/orders', function () { + + 4 // Access token has either "orders:read" or "orders:create" scope... + + 5})->middleware(['auth:api', CheckTokenForAnyScope::using('orders:read', 'orders:create')]); + + + use Laravel\Passport\Http\Middleware\CheckTokenForAnyScope; + + Route::get('/orders', function () { + // Access token has either "orders:read" or "orders:create" scope... + })->middleware(['auth:api', CheckTokenForAnyScope::using('orders:read', 'orders:create')]); + +#### 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: + + + + 1use Illuminate\Http\Request; + + 2  + + 3Route::get('/orders', function (Request $request) { + + 4 if ($request->user()->tokenCan('orders:create')) { + + 5 // ... + + 6 } + + 7}); + + + use Illuminate\Http\Request; + + Route::get('/orders', function (Request $request) { + if ($request->user()->tokenCan('orders:create')) { + // ... + } + }); + +#### Additional Scope Methods + +The `scopeIds` method will return an array of all defined IDs / names: + + + + 1use Laravel\Passport\Passport; + + 2  + + 3Passport::scopeIds(); + + + use Laravel\Passport\Passport; + + Passport::scopeIds(); + +The `scopes` method will return an array of all defined scopes as instances of +`Laravel\Passport\Scope`: + + + + 1Passport::scopes(); + + + Passport::scopes(); + +The `scopesFor` method will return an array of `Laravel\Passport\Scope` +instances matching the given IDs / names: + + + + 1Passport::scopesFor(['user:read', 'orders:create']); + + + Passport::scopesFor(['user:read', 'orders:create']); + +You may determine if a given scope has been defined using the `hasScope` +method: + + + + 1Passport::hasScope('orders:create'); + + + Passport::hasScope('orders:create'); + +## SPA Authentication + +When building an API, it can be extremely useful to be able to consume your +own API from your JavaScript application. This approach to API development +allows your own application to consume the same API that you are sharing with +the world. The same API may be consumed by your web application, mobile +applications, third-party applications, and any SDKs that you may publish on +various package managers. + +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: + + + + 1use Laravel\Passport\Http\Middleware\CreateFreshApiToken; + + 2  + + 3->withMiddleware(function (Middleware $middleware) { + + 4 $middleware->web(append: [ + + 5 CreateFreshApiToken::class, + + 6 ]); + + 7}) + + + use Laravel\Passport\Http\Middleware\CreateFreshApiToken; + + ->withMiddleware(function (Middleware $middleware) { + $middleware->web(append: [ + CreateFreshApiToken::class, + ]); + }) + +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: + + + + 1axios.get('/api/user') + + 2 .then(response => { + + 3 console.log(response.data); + + 4 }); + + + 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: + + + + 1/** + + 2 * Bootstrap any application services. + + 3 */ + + 4public function boot(): void + + 5{ + + 6 Passport::cookie('custom_name'); + + 7} + + + /** + * Bootstrap any application services. + */ + public function boot(): void + { + Passport::cookie('custom_name'); + } + +#### CSRF Protection + +When using this method of authentication, you will need to ensure a valid CSRF +token header is included in your requests. The default Laravel JavaScript +scaffolding included with the skeleton application and all starter kits +includes an [Axios](https://github.com/axios/axios) instance, which will +automatically use the encrypted `XSRF-TOKEN` cookie value to send an `X-XSRF- +TOKEN` header on same-origin requests. + +If you choose to send the `X-CSRF-TOKEN` header instead of `X-XSRF-TOKEN`, you +will need to use the unencrypted token provided by `csrf_token()`. + +## Events + +Passport raises events when issuing access tokens and refresh tokens. You may +[listen for these events](/docs/12.x/events) to prune or revoke other access +tokens in your database: + +Event Name +--- +`Laravel\Passport\Events\AccessTokenCreated` +`Laravel\Passport\Events\AccessTokenRevoked` +`Laravel\Passport\Events\RefreshTokenCreated` + +## Testing + +Passport's `actingAs` method may be used to specify the currently +authenticated user as well as its scopes. The first argument given to the +`actingAs` method is the user instance and the second is an array of scopes +that should be granted to the user's token: + +Pest PHPUnit + + + + 1use App\Models\User; + + 2use Laravel\Passport\Passport; + + 3  + + 4test('orders can be created', function () { + + 5 Passport::actingAs( + + 6 User::factory()->create(), + + 7 ['orders:create'] + + 8 ); + + 9  + + 10 $response = $this->post('/api/orders'); + + 11  + + 12 $response->assertStatus(201); + + 13}); + + + use App\Models\User; + use Laravel\Passport\Passport; + + test('orders can be created', function () { + Passport::actingAs( + User::factory()->create(), + ['orders:create'] + ); + + $response = $this->post('/api/orders'); + + $response->assertStatus(201); + }); + + + 1use App\Models\User; + + 2use Laravel\Passport\Passport; + + 3  + + 4public function test_orders_can_be_created(): void + + 5{ + + 6 Passport::actingAs( + + 7 User::factory()->create(), + + 8 ['orders:create'] + + 9 ); + + 10  + + 11 $response = $this->post('/api/orders'); + + 12  + + 13 $response->assertStatus(201); + + 14} + + + use App\Models\User; + use Laravel\Passport\Passport; + + public function test_orders_can_be_created(): void + { + Passport::actingAs( + User::factory()->create(), + ['orders:create'] + ); + + $response = $this->post('/api/orders'); + + $response->assertStatus(201); + } + +Passport's `actingAsClient` method may be used to specify the currently +authenticated client as well as its scopes. The first argument given to the +`actingAsClient` method is the client instance and the second is an array of +scopes that should be granted to the client's token: + +Pest PHPUnit + + + + 1use Laravel\Passport\Client; + + 2use Laravel\Passport\Passport; + + 3  + + 4test('servers can be retrieved', function () { + + 5 Passport::actingAsClient( + + 6 Client::factory()->create(), + + 7 ['servers:read'] + + 8 ); + + 9  + + 10 $response = $this->get('/api/servers'); + + 11  + + 12 $response->assertStatus(200); + + 13}); + + + use Laravel\Passport\Client; + use Laravel\Passport\Passport; + + test('servers can be retrieved', function () { + Passport::actingAsClient( + Client::factory()->create(), + ['servers:read'] + ); + + $response = $this->get('/api/servers'); + + $response->assertStatus(200); + }); + + + 1use Laravel\Passport\Client; + + 2use Laravel\Passport\Passport; + + 3  + + 4public function test_servers_can_be_retrieved(): void + + 5{ + + 6 Passport::actingAsClient( + + 7 Client::factory()->create(), + + 8 ['servers:read'] + + 9 ); + + 10  + + 11 $response = $this->get('/api/servers'); + + 12  + + 13 $response->assertStatus(200); + + 14} + + + use Laravel\Passport\Client; + use Laravel\Passport\Passport; + + public function test_servers_can_be_retrieved(): void + { + Passport::actingAsClient( + Client::factory()->create(), + ['servers:read'] + ); + + $response = $this->get('/api/servers'); + + $response->assertStatus(200); + } + diff --git a/output/12.x/passwords.md b/output/12.x/passwords.md new file mode 100644 index 0000000..1eedcb8 --- /dev/null +++ b/output/12.x/passwords.md @@ -0,0 +1,534 @@ +# Resetting Passwords + + * Introduction + * Configuration + * Driver Prerequisites + * Model Preparation + * Configuring Trusted Hosts + * Routing + * Requesting the Password Reset Link + * Resetting the Password + * Deleting Expired Tokens + * Customization + +## Introduction + +Most web applications provide a way for users to reset their forgotten +passwords. Rather than forcing you to re-implement this by hand for every +application you create, Laravel provides convenient services for sending +password reset links and secure resetting passwords. + +Want to get started fast? Install a Laravel [application starter +kit](/docs/12.x/starter-kits) in a fresh Laravel application. Laravel's +starter kits will take care of scaffolding your entire authentication system, +including resetting forgotten passwords. + +### Configuration + +Your application's password reset configuration file is stored at +`config/auth.php`. Be sure to review the options available to you in this +file. By default, Laravel is configured to use the `database` password reset +driver. + +The password reset `driver` configuration option defines where password reset +data will be stored. Laravel includes two drivers: + + * `database` \- password reset data is stored in a relational database. + * `cache` \- password reset data is stored in one of your cache-based stores. + +### Driver Prerequisites + +#### Database + +When using the default `database` driver, a table must be created to store +your application's password reset tokens. Typically, this is included in +Laravel's default `0001_01_01_000000_create_users_table.php` database +migration. + +#### Cache + +There is also a cache driver available for handling password resets, which +does not require a dedicated database table. Entries are keyed by the user's +email address, so ensure you are not using email addresses as a cache key +elsewhere in your application: + + + + 1'passwords' => [ + + 2 'users' => [ + + 3 'driver' => 'cache', + + 4 'provider' => 'users', + + 5 'store' => 'passwords', // Optional... + + 6 'expire' => 60, + + 7 'throttle' => 60, + + 8 ], + + 9], + + + 'passwords' => [ + 'users' => [ + 'driver' => 'cache', + 'provider' => 'users', + 'store' => 'passwords', // Optional... + 'expire' => 60, + 'throttle' => 60, + ], + ], + +To prevent a call to `artisan cache:clear` from flushing your password reset +data, you can optionally specify a separate cache store with the `store` +configuration key. The value should correspond to a store configured in your +`config/cache.php` configuration value. + +### Model Preparation + +Before using the password reset features of Laravel, your application's +`App\Models\User` model must use the `Illuminate\Notifications\Notifiable` +trait. Typically, this trait is already included on the default +`App\Models\User` model that is created with new Laravel applications. + +Next, verify that your `App\Models\User` model implements the +`Illuminate\Contracts\Auth\CanResetPassword` contract. The `App\Models\User` +model included with the framework already implements this interface, and uses +the `Illuminate\Auth\Passwords\CanResetPassword` trait to include the methods +needed to implement the interface. + +### Configuring Trusted Hosts + +By default, Laravel will respond to all requests it receives regardless of the +content of the HTTP request's `Host` header. In addition, the `Host` header's +value will be used when generating absolute URLs to your application during a +web request. + +Typically, you should configure your web server, such as Nginx or Apache, to +only send requests to your application that match a given hostname. However, +if you do not have the ability to customize your web server directly and need +to instruct Laravel to only respond to certain hostnames, you may do so by +using the `trustHosts` middleware method in your application's +`bootstrap/app.php` file. This is particularly important when your application +offers password reset functionality. + +To learn more about this middleware method, please consult the [TrustHosts +middleware documentation](/docs/12.x/requests#configuring-trusted-hosts). + +## Routing + +To properly implement support for allowing users to reset their passwords, we +will need to define several routes. First, we will need a pair of routes to +handle allowing the user to request a password reset link via their email +address. Second, we will need a pair of routes to handle actually resetting +the password once the user visits the password reset link that is emailed to +them and completes the password reset form. + +### Requesting the Password Reset Link + +#### The Password Reset Link Request Form + +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: + + + + 1Route::get('/forgot-password', function () { + + 2 return view('auth.forgot-password'); + + 3})->middleware('guest')->name('password.request'); + + + 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. + +#### Handling the Form Submission + +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: + + + + 1use Illuminate\Http\Request; + + 2use Illuminate\Support\Facades\Password; + + 3  + + 4Route::post('/forgot-password', function (Request $request) { + + 5 $request->validate(['email' => 'required|email']); + + 6  + + 7 $status = Password::sendResetLink( + + 8 $request->only('email') + + 9 ); + + 10  + + 11 return $status === Password::ResetLinkSent + + 12 ? back()->with(['status' => __($status)]) + + 13 : back()->withErrors(['email' => __($status)]); + + 14})->middleware('guest')->name('password.email'); + + + use Illuminate\Http\Request; + use Illuminate\Support\Facades\Password; + + Route::post('/forgot-password', function (Request $request) { + $request->validate(['email' => 'required|email']); + + $status = Password::sendResetLink( + $request->only('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/12.x/notifications). + +The `sendResetLink` method returns a "status" slug. This status may be +translated using Laravel's [localization](/docs/12.x/localization) helpers in +order to display a user-friendly message to the user regarding the status of +their request. The translation of the password reset status is determined by +your application's `lang/{lang}/passwords.php` language file. An entry for +each possible value of the status slug is located within the `passwords` +language file. + +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. + +You may be wondering how Laravel knows how to retrieve the user record from +your application's database when calling the `Password` facade's +`sendResetLink` method. The Laravel password broker utilizes your +authentication system's "user providers" to retrieve database records. The +user provider used by the password broker is configured within the `passwords` +configuration array of your `config/auth.php` configuration file. To learn +more about writing custom user providers, consult the [authentication +documentation](/docs/12.x/authentication#adding-custom-user-providers). + +When manually implementing password resets, you are required to define the +contents of the views and routes yourself. If you would like scaffolding that +includes all necessary authentication and verification logic, check out the +[Laravel application starter kits](/docs/12.x/starter-kits). + +### Resetting the Password + +#### The Password Reset Form + +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: + + + + 1Route::get('/reset-password/{token}', function (string $token) { + + 2 return view('auth.reset-password', ['token' => $token]); + + 3})->middleware('guest')->name('password.reset'); + + + 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. + +#### Handling the Form Submission + +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: + + + + 1use App\Models\User; + + 2use Illuminate\Auth\Events\PasswordReset; + + 3use Illuminate\Http\Request; + + 4use Illuminate\Support\Facades\Hash; + + 5use Illuminate\Support\Facades\Password; + + 6use Illuminate\Support\Str; + + 7  + + 8Route::post('/reset-password', function (Request $request) { + + 9 $request->validate([ + + 10 'token' => 'required', + + 11 'email' => 'required|email', + + 12 'password' => 'required|min:8|confirmed', + + 13 ]); + + 14  + + 15 $status = Password::reset( + + 16 $request->only('email', 'password', 'password_confirmation', 'token'), + + 17 function (User $user, string $password) { + + 18 $user->forceFill([ + + 19 'password' => Hash::make($password) + + 20 ])->setRememberToken(Str::random(60)); + + 21  + + 22 $user->save(); + + 23  + + 24 event(new PasswordReset($user)); + + 25 } + + 26 ); + + 27  + + 28 return $status === Password::PasswordReset + + 29 ? redirect()->route('login')->with('status', __($status)) + + 30 : back()->withErrors(['email' => [__($status)]]); + + 31})->middleware('guest')->name('password.update'); + + + 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. + +If the token, email address, and password given to the password broker are +valid, the closure passed to the `reset` method will be invoked. Within this +closure, which receives the user instance and the plain-text password provided +to the password reset form, we may update the user's password in the database. + +The `reset` method returns a "status" slug. This status may be translated +using Laravel's [localization](/docs/12.x/localization) helpers in order to +display a user-friendly message to the user regarding the status of their +request. The translation of the password reset status is determined by your +application's `lang/{lang}/passwords.php` language file. An entry for each +possible value of the status slug is located within the `passwords` language +file. If your application does not contain a `lang` directory, you may create +it using the `lang:publish` Artisan command. + +Before moving on, you may be wondering how Laravel knows how to retrieve the +user record from your application's database when calling the `Password` +facade's `reset` method. The Laravel password broker utilizes your +authentication system's "user providers" to retrieve database records. The +user provider used by the password broker is configured within the `passwords` +configuration array of your `config/auth.php` configuration file. To learn +more about writing custom user providers, consult the [authentication +documentation](/docs/12.x/authentication#adding-custom-user-providers). + +## Deleting Expired Tokens + +If you are using the `database` driver, password reset tokens that have +expired will still be present within your database. However, you may easily +delete these records using the `auth:clear-resets` Artisan command: + + + + 1php artisan auth:clear-resets + + + php artisan auth:clear-resets + +If you would like to automate this process, consider adding the command to +your application's [scheduler](/docs/12.x/scheduling): + + + + 1use Illuminate\Support\Facades\Schedule; + + 2  + + 3Schedule::command('auth:clear-resets')->everyFifteenMinutes(); + + + use Illuminate\Support\Facades\Schedule; + + Schedule::command('auth:clear-resets')->everyFifteenMinutes(); + +## Customization + +#### Reset Link Customization + +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 the `boot` method of your application's `AppServiceProvider`: + + + + 1use App\Models\User; + + 2use Illuminate\Auth\Notifications\ResetPassword; + + 3  + + 4/** + + 5 * Bootstrap any application services. + + 6 */ + + 7public function boot(): void + + 8{ + + 9 ResetPassword::createUrlUsing(function (User $user, string $token) { + + 10 return 'https://example.com/reset-password?token='.$token; + + 11 }); + + 12} + + + 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/12.x/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: + + + + 1use App\Notifications\ResetPasswordNotification; + + 2  + + 3/** + + 4 * Send a password reset notification to the user. + + 5 * + + 6 * @param string $token + + 7 */ + + 8public function sendPasswordResetNotification($token): void + + 9{ + + 10 $url = 'https://example.com/reset-password?token='.$token; + + 11  + + 12 $this->notify(new ResetPasswordNotification($url)); + + 13} + + + 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; + + $this->notify(new ResetPasswordNotification($url)); + } + diff --git a/output/12.x/pennant.md b/output/12.x/pennant.md new file mode 100644 index 0000000..58e8991 --- /dev/null +++ b/output/12.x/pennant.md @@ -0,0 +1,2717 @@ +# Laravel Pennant + + * Introduction + * Installation + * Configuration + * Defining Features + * Class Based Features + * Checking Features + * Conditional Execution + * The `HasFeatures` Trait + * Blade Directive + * Middleware + * Intercepting Feature Checks + * In-Memory Cache + * Scope + * Specifying the Scope + * Default Scope + * Nullable Scope + * Identifying Scope + * Serializing Scope + * Rich Feature Values + * Retrieving Multiple Features + * Eager Loading + * Updating Values + * Bulk Updates + * Purging Features + * Testing + * Adding Custom Pennant Drivers + * Implementing the Driver + * Registering the Driver + * Defining Features Externally + * Events + +## Introduction + +[Laravel Pennant](https://github.com/laravel/pennant) is a simple and light- +weight feature flag package - without the cruft. Feature flags enable you to +incrementally roll out new application features with confidence, A/B test new +interface designs, complement a trunk-based development strategy, and much +more. + +## Installation + +First, install Pennant into your project using the Composer package manager: + + + + 1composer require laravel/pennant + + + composer require laravel/pennant + +Next, you should publish the Pennant configuration and migration files using +the `vendor:publish` Artisan command: + + + + 1php artisan vendor:publish --provider="Laravel\Pennant\PennantServiceProvider" + + + php artisan vendor:publish --provider="Laravel\Pennant\PennantServiceProvider" + +Finally, you should run your application's database migrations. This will +create a `features` table that Pennant uses to power its `database` driver: + + + + 1php artisan migrate + + + php artisan migrate + +## Configuration + +After publishing Pennant's assets, its configuration file will be located at +`config/pennant.php`. This configuration file allows you to specify the +default storage mechanism that will be used by Pennant to store resolved +feature flag values. + +Pennant includes support for storing resolved feature flag values in an in- +memory array via the `array` driver. Or, Pennant can store resolved feature +flag values persistently in a relational database via the `database` driver, +which is the default storage mechanism used by Pennant. + +## Defining Features + +To define a feature, you may use the `define` method offered by the `Feature` +facade. You will need to provide a name for the feature, as well as a closure +that will be invoked to resolve the feature's initial value. + +Typically, features are defined in a service provider using the `Feature` +facade. The closure will receive the "scope" for the feature check. Most +commonly, the scope is the currently authenticated user. In this example, we +will define a feature for incrementally rolling out a new API to our +application's users: + + + + 1 match (true) { + + 18 $user->isInternalTeamMember() => true, + + 19 $user->isHighTrafficCustomer() => false, + + 20 default => Lottery::odds(1 / 100), + + 21 }); + + 22 } + + 23} + + + match (true) { + $user->isInternalTeamMember() => true, + $user->isHighTrafficCustomer() => false, + default => Lottery::odds(1 / 100), + }); + } + } + +As you can see, we have the following rules for our feature: + + * All internal team members should be using the new API. + * Any high traffic customers should not be using the new API. + * Otherwise, the feature should be randomly assigned to users with a 1 in 100 chance of being active. + +The first time the `new-api` feature is checked for a given user, the result +of the closure will be stored by the storage driver. The next time the feature +is checked against the same user, the value will be retrieved from storage and +the closure will not be invoked. + +For convenience, if a feature definition only returns a lottery, you may omit +the closure completely: + + + + 1Feature::define('site-redesign', Lottery::odds(1, 1000)); + + + Feature::define('site-redesign', Lottery::odds(1, 1000)); + +### Class Based Features + +Pennant also allows you to define class-based features. Unlike closure-based +feature definitions, there is no need to register a class-based feature in a +service provider. To create a class-based feature, you may invoke the +`pennant:feature` Artisan command. By default, the feature class will be +placed in your application's `app/Features` directory: + + + + 1php artisan pennant:feature NewApi + + + php artisan pennant:feature NewApi + +When writing a feature class, you only need to define a `resolve` method, +which will be invoked to resolve the feature's initial value for a given +scope. Again, the scope will typically be the currently authenticated user: + + + + 1isInternalTeamMember() => true, + + 17 $user->isHighTrafficCustomer() => false, + + 18 default => Lottery::odds(1 / 100), + + 19 }; + + 20 } + + 21} + + + isInternalTeamMember() => true, + $user->isHighTrafficCustomer() => false, + default => Lottery::odds(1 / 100), + }; + } + } + +If you would like to manually resolve an instance of a class-based feature, +you may invoke the `instance` method on the `Feature` facade: + + + + 1use Illuminate\Support\Facades\Feature; + + 2  + + 3$instance = Feature::instance(NewApi::class); + + + use Illuminate\Support\Facades\Feature; + + $instance = Feature::instance(NewApi::class); + +Feature classes are resolved via the [container](/docs/12.x/container), so you +may inject dependencies into the feature class's constructor when needed. + +#### Customizing the Stored Feature Name + +By default, Pennant will store the feature class's fully qualified class name. +If you would like to decouple the stored feature name from the application's +internal structure, you may specify a `$name` property on the feature class. +The value of this property will be stored in place of the class name: + + + + 1resolveNewApiResponse($request) + + 18 : $this->resolveLegacyApiResponse($request); + + 19 } + + 20  + + 21 // ... + + 22} + + + resolveNewApiResponse($request) + : $this->resolveLegacyApiResponse($request); + } + + // ... + } + +Although features are checked against the currently authenticated user by +default, you may easily check the feature against another user or scope. To +accomplish this, use the `for` method offered by the `Feature` facade: + + + + 1return Feature::for($user)->active('new-api') + + 2 ? $this->resolveNewApiResponse($request) + + 3 : $this->resolveLegacyApiResponse($request); + + + return Feature::for($user)->active('new-api') + ? $this->resolveNewApiResponse($request) + : $this->resolveLegacyApiResponse($request); + +Pennant also offers some additional convenience methods that may prove useful +when determining if a feature is active or not: + + + + 1// Determine if all of the given features are active... + + 2Feature::allAreActive(['new-api', 'site-redesign']); + + 3  + + 4// Determine if any of the given features are active... + + 5Feature::someAreActive(['new-api', 'site-redesign']); + + 6  + + 7// Determine if a feature is inactive... + + 8Feature::inactive('new-api'); + + 9  + + 10// Determine if all of the given features are inactive... + + 11Feature::allAreInactive(['new-api', 'site-redesign']); + + 12  + + 13// Determine if any of the given features are inactive... + + 14Feature::someAreInactive(['new-api', 'site-redesign']); + + + // Determine if all of the given features are active... + Feature::allAreActive(['new-api', 'site-redesign']); + + // Determine if any of the given features are active... + Feature::someAreActive(['new-api', 'site-redesign']); + + // Determine if a feature is inactive... + Feature::inactive('new-api'); + + // Determine if all of the given features are inactive... + Feature::allAreInactive(['new-api', 'site-redesign']); + + // Determine if any of the given features are inactive... + Feature::someAreInactive(['new-api', 'site-redesign']); + +When using Pennant outside of an HTTP context, such as in an Artisan command +or a queued job, you should typically explicitly specify the feature's scope. +Alternatively, you may define a default scope that accounts for both +authenticated HTTP contexts and unauthenticated contexts. + +#### Checking Class Based Features + +For class-based features, you should provide the class name when checking the +feature: + + + + 1resolveNewApiResponse($request) + + 19 : $this->resolveLegacyApiResponse($request); + + 20 } + + 21  + + 22 // ... + + 23} + + + resolveNewApiResponse($request) + : $this->resolveLegacyApiResponse($request); + } + + // ... + } + +### Conditional Execution + +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: + + + + 1 $this->resolveNewApiResponse($request), + + 19 fn () => $this->resolveLegacyApiResponse($request), + + 20 ); + + 21 } + + 22  + + 23 // ... + + 24} + + + $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: + + + + 1return Feature::unless(NewApi::class, + + 2 fn () => $this->resolveLegacyApiResponse($request), + + 3 fn () => $this->resolveNewApiResponse($request), + + 4); + + + return Feature::unless(NewApi::class, + fn () => $this->resolveLegacyApiResponse($request), + fn () => $this->resolveNewApiResponse($request), + ); + +### The `HasFeatures` Trait + +Pennant's `HasFeatures` trait may be added to your application's `User` model +(or any other model that has features) to provide a fluent, convenient way to +check features directly from the model: + + + + 1features()->active('new-api')) { + + 2 // ... + + 3} + + + if ($user->features()->active('new-api')) { + // ... + } + +Of course, the `features` method provides access to many other convenient +methods for interacting with features: + + + + 1// Values... + + 2$value = $user->features()->value('purchase-button') + + 3$values = $user->features()->values(['new-api', 'purchase-button']); + + 4  + + 5// State... + + 6$user->features()->active('new-api'); + + 7$user->features()->allAreActive(['new-api', 'server-api']); + + 8$user->features()->someAreActive(['new-api', 'server-api']); + + 9  + + 10$user->features()->inactive('new-api'); + + 11$user->features()->allAreInactive(['new-api', 'server-api']); + + 12$user->features()->someAreInactive(['new-api', 'server-api']); + + 13  + + 14// Conditional execution... + + 15$user->features()->when('new-api', + + 16 fn () => /* ... */, + + 17 fn () => /* ... */, + + 18); + + 19  + + 20$user->features()->unless('new-api', + + 21 fn () => /* ... */, + + 22 fn () => /* ... */, + + 23); + + + // Values... + $value = $user->features()->value('purchase-button') + $values = $user->features()->values(['new-api', 'purchase-button']); + + // State... + $user->features()->active('new-api'); + $user->features()->allAreActive(['new-api', 'server-api']); + $user->features()->someAreActive(['new-api', 'server-api']); + + $user->features()->inactive('new-api'); + $user->features()->allAreInactive(['new-api', 'server-api']); + $user->features()->someAreInactive(['new-api', 'server-api']); + + // Conditional execution... + $user->features()->when('new-api', + fn () => /* ... */, + fn () => /* ... */, + ); + + $user->features()->unless('new-api', + fn () => /* ... */, + fn () => /* ... */, + ); + +### Blade Directive + +To make checking features in Blade a seamless experience, Pennant offers the +`@feature` and `@featureany` directive: + + + + 1@feature('site-redesign') + + 2 + + 3@else + + 4 + + 5@endfeature + + 6  + + 7@featureany(['site-redesign', 'beta']) + + 8 + + 9@endfeatureany + + + @feature('site-redesign') + + @else + + @endfeature + + @featureany(['site-redesign', 'beta']) + + @endfeatureany + +### Middleware + +Pennant also includes a [middleware](/docs/12.x/middleware) that may be used +to verify the currently authenticated user has access to a feature before a +route is even invoked. You may assign the middleware to a route and specify +the features that are required to access the route. If any of the specified +features are inactive for the currently authenticated user, a `400 Bad +Request` HTTP response will be returned by the route. Multiple features may be +passed to the static `using` method. + + + + 1use Illuminate\Support\Facades\Route; + + 2use Laravel\Pennant\Middleware\EnsureFeaturesAreActive; + + 3  + + 4Route::get('/api/servers', function () { + + 5 // ... + + 6})->middleware(EnsureFeaturesAreActive::using('new-api', 'servers-api')); + + + use Illuminate\Support\Facades\Route; + use Laravel\Pennant\Middleware\EnsureFeaturesAreActive; + + Route::get('/api/servers', function () { + // ... + })->middleware(EnsureFeaturesAreActive::using('new-api', 'servers-api')); + +#### Customizing the Response + +If you would like to customize the response that is returned by the middleware +when one of the listed features is inactive, you may use the `whenInactive` +method provided by the `EnsureFeaturesAreActive` middleware. Typically, this +method should be invoked within the `boot` method of one of your application's +service providers: + + + + 1use Illuminate\Http\Request; + + 2use Illuminate\Http\Response; + + 3use Laravel\Pennant\Middleware\EnsureFeaturesAreActive; + + 4  + + 5/** + + 6 * Bootstrap any application services. + + 7 */ + + 8public function boot(): void + + 9{ + + 10 EnsureFeaturesAreActive::whenInactive( + + 11 function (Request $request, array $features) { + + 12 return new Response(status: 403); + + 13 } + + 14 ); + + 15  + + 16 // ... + + 17} + + + use Illuminate\Http\Request; + use Illuminate\Http\Response; + use Laravel\Pennant\Middleware\EnsureFeaturesAreActive; + + /** + * Bootstrap any application services. + */ + public function boot(): void + { + EnsureFeaturesAreActive::whenInactive( + function (Request $request, array $features) { + return new Response(status: 403); + } + ); + + // ... + } + +### Intercepting Feature Checks + +Sometimes it can be useful to perform some in-memory checks before retrieving +the stored value of a given feature. Imagine you are developing a new API +behind a feature flag and want the ability to disable the new API without +losing any of the resolved feature values in storage. If you notice a bug in +the new API, you could easily disable it for everyone except internal team +members, fix the bug, and then re-enable the new API for the users that +previously had access to the feature. + +You can achieve this with a class-based feature's `before` method. When +present, the `before` method is always run in-memory before retrieving the +value from storage. If a non-`null` value is returned from the method, it will +be used in place of the feature's stored value for the duration of the +request: + + + + 1isInternalTeamMember(); + + 18 } + + 19 } + + 20  + + 21 /** + + 22 * Resolve the feature's initial value. + + 23 */ + + 24 public function resolve(User $user): mixed + + 25 { + + 26 return match (true) { + + 27 $user->isInternalTeamMember() => true, + + 28 $user->isHighTrafficCustomer() => false, + + 29 default => Lottery::odds(1 / 100), + + 30 }; + + 31 } + + 32} + + + isInternalTeamMember(); + } + } + + /** + * Resolve the feature's initial value. + */ + public function resolve(User $user): mixed + { + return match (true) { + $user->isInternalTeamMember() => true, + $user->isHighTrafficCustomer() => false, + default => Lottery::odds(1 / 100), + }; + } + } + +You could also use this feature to schedule the global rollout of a feature +that was previously behind a feature flag: + + + + 1isInternalTeamMember(); + + 17 } + + 18  + + 19 if (Carbon::parse(Config::get('features.new-api.rollout-date'))->isPast()) { + + 20 return true; + + 21 } + + 22 } + + 23  + + 24 // ... + + 25} + + + isInternalTeamMember(); + } + + if (Carbon::parse(Config::get('features.new-api.rollout-date'))->isPast()) { + return true; + } + } + + // ... + } + +### In-Memory Cache + +When checking a feature, Pennant will create an in-memory cache of the result. +If you are using the `database` driver, this means that re-checking the same +feature flag within a single request will not trigger additional database +queries. This also ensures that the feature has a consistent result for the +duration of the request. + +If you need to manually flush the in-memory cache, you may use the +`flushCache` method offered by the `Feature` facade: + + + + 1Feature::flushCache(); + + + Feature::flushCache(); + +## Scope + +### Specifying the Scope + +As discussed, features are typically checked against the currently +authenticated user. However, this may not always suit your needs. Therefore, +it is possible to specify the scope you would like to check a given feature +against via the `Feature` facade's `for` method: + + + + 1return Feature::for($user)->active('new-api') + + 2 ? $this->resolveNewApiResponse($request) + + 3 : $this->resolveLegacyApiResponse($request); + + + return Feature::for($user)->active('new-api') + ? $this->resolveNewApiResponse($request) + : $this->resolveLegacyApiResponse($request); + +Of course, feature scopes are not limited to "users". Imagine you have built a +new billing experience that you are rolling out to entire teams rather than +individual users. Perhaps you would like the oldest teams to have a slower +rollout than the newer teams. Your feature resolution closure might look +something like the following: + + + + 1use App\Models\Team; + + 2use Illuminate\Support\Carbon; + + 3use Illuminate\Support\Lottery; + + 4use Laravel\Pennant\Feature; + + 5  + + 6Feature::define('billing-v2', function (Team $team) { + + 7 if ($team->created_at->isAfter(new Carbon('1st Jan, 2023'))) { + + 8 return true; + + 9 } + + 10  + + 11 if ($team->created_at->isAfter(new Carbon('1st Jan, 2019'))) { + + 12 return Lottery::odds(1 / 100); + + 13 } + + 14  + + 15 return Lottery::odds(1 / 1000); + + 16}); + + + use App\Models\Team; + use Illuminate\Support\Carbon; + use Illuminate\Support\Lottery; + use Laravel\Pennant\Feature; + + Feature::define('billing-v2', function (Team $team) { + if ($team->created_at->isAfter(new Carbon('1st Jan, 2023'))) { + return true; + } + + if ($team->created_at->isAfter(new Carbon('1st Jan, 2019'))) { + return Lottery::odds(1 / 100); + } + + return Lottery::odds(1 / 1000); + }); + +You will notice that the closure we have defined is not expecting a `User`, +but is instead expecting a `Team` model. To determine if this feature is +active for a user's team, you should pass the team to the `for` method offered +by the `Feature` facade: + + + + 1if (Feature::for($user->team)->active('billing-v2')) { + + 2 return redirect('/billing/v2'); + + 3} + + 4  + + 5// ... + + + if (Feature::for($user->team)->active('billing-v2')) { + return redirect('/billing/v2'); + } + + // ... + +### Default Scope + +It is also possible to customize the default scope Pennant uses to check +features. For example, maybe all of your features are checked against the +currently authenticated user's team instead of the user. Instead of having to +call `Feature::for($user->team)` every time you check a feature, you may +instead specify the team as the default scope. Typically, this should be done +in one of your application's service providers: + + + + 1 Auth::user()?->team); + + 17  + + 18 // ... + + 19 } + + 20} + + + Auth::user()?->team); + + // ... + } + } + +If no scope is explicitly provided via the `for` method, the feature check +will now use the currently authenticated user's team as the default scope: + + + + 1Feature::active('billing-v2'); + + 2  + + 3// Is now equivalent to... + + 4  + + 5Feature::for($user->team)->active('billing-v2'); + + + Feature::active('billing-v2'); + + // Is now equivalent to... + + Feature::for($user->team)->active('billing-v2'); + +### Nullable Scope + +If the scope you provide when checking a feature is `null` and the feature's +definition does not support `null` via a nullable type or by including `null` +in a union type, Pennant will automatically return `false` as the feature's +result value. + +So, if the scope you are passing to a feature is potentially `null` and you +want the feature's value resolver to be invoked, you should account for that +in your feature's definition. A `null` scope may occur if you check a feature +within an Artisan command, queued job, or unauthenticated route. Since there +is usually not an authenticated user in these contexts, the default scope will +be `null`. + +If you do not always explicitly specify your feature scope then you should +ensure the scope's type is "nullable" and handle the `null` scope value within +your feature definition logic: + + + + 1use App\Models\User; + + 2use Illuminate\Support\Lottery; + + 3use Laravel\Pennant\Feature; + + 4  + + 5Feature::define('new-api', fn (User $user) => match (true) { + + 6Feature::define('new-api', fn (User|null $user) => match (true) { + + 7 $user === null => true, + + 8 $user->isInternalTeamMember() => true, + + 9 $user->isHighTrafficCustomer() => false, + + 10 default => Lottery::odds(1 / 100), + + 11}); + + + use App\Models\User; + use Illuminate\Support\Lottery; + use Laravel\Pennant\Feature; + + Feature::define('new-api', fn (User $user) => match (true) { + Feature::define('new-api', fn (User|null $user) => match (true) { + $user === null => true, + $user->isInternalTeamMember() => true, + $user->isHighTrafficCustomer() => false, + default => Lottery::odds(1 / 100), + }); + +### Identifying Scope + +Pennant's built-in `array` and `database` storage drivers know how to properly +store scope identifiers for all PHP data types as well as Eloquent models. +However, if your application utilizes a third-party Pennant driver, that +driver may not know how to properly store an identifier for an Eloquent model +or other custom types in your application. + +In light of this, Pennant allows you to format scope values for storage by +implementing the `FeatureScopeable` contract on the objects in your +application that are used as Pennant scopes. + +For example, imagine you are using two different feature drivers in a single +application: the built-in `database` driver and a third-party "Flag Rocket" +driver. The "Flag Rocket" driver does not know how to properly store an +Eloquent model. Instead, it requires a `FlagRocketUser` instance. By +implementing the `toFeatureIdentifier` defined by the `FeatureScopeable` +contract, we can customize the storable scope value provided to each driver +used by our application: + + + + 1 $this, + + 18 'flag-rocket' => FlagRocketUser::fromId($this->flag_rocket_id), + + 19 }; + + 20 } + + 21} + + + $this, + 'flag-rocket' => FlagRocketUser::fromId($this->flag_rocket_id), + }; + } + } + +### Serializing Scope + +By default, Pennant will use a fully qualified class name when storing a +feature associated with an Eloquent model. If you are already using an +[Eloquent morph map](/docs/12.x/eloquent-relationships#custom-polymorphic- +types), you may choose to have Pennant also use the morph map to decouple the +stored feature from your application structure. + +To achieve this, after defining your Eloquent morph map in a service provider, +you may invoke the `Feature` facade's `useMorphMap` method: + + + + 1use Illuminate\Database\Eloquent\Relations\Relation; + + 2use Laravel\Pennant\Feature; + + 3  + + 4Relation::enforceMorphMap([ + + 5 'post' => 'App\Models\Post', + + 6 'video' => 'App\Models\Video', + + 7]); + + 8  + + 9Feature::useMorphMap(); + + + use Illuminate\Database\Eloquent\Relations\Relation; + use Laravel\Pennant\Feature; + + Relation::enforceMorphMap([ + 'post' => 'App\Models\Post', + 'video' => 'App\Models\Video', + ]); + + Feature::useMorphMap(); + +## Rich Feature Values + +Until now, we have primarily shown features as being in a binary state, +meaning they are either "active" or "inactive", but Pennant also allows you to +store rich values as well. + +For example, imagine you are testing three new colors for the "Buy now" button +of your application. Instead of returning `true` or `false` from the feature +definition, you may instead return a string: + + + + 1use Illuminate\Support\Arr; + + 2use Laravel\Pennant\Feature; + + 3  + + 4Feature::define('purchase-button', fn (User $user) => Arr::random([ + + 5 'blue-sapphire', + + 6 'seafoam-green', + + 7 'tart-orange', + + 8])); + + + use Illuminate\Support\Arr; + use Laravel\Pennant\Feature; + + Feature::define('purchase-button', fn (User $user) => Arr::random([ + 'blue-sapphire', + 'seafoam-green', + 'tart-orange', + ])); + +You may retrieve the value of the `purchase-button` feature using the `value` +method: + + + + 1$color = Feature::value('purchase-button'); + + + $color = Feature::value('purchase-button'); + +Pennant's included Blade directive also makes it easy to conditionally render +content based on the current value of the feature: + + + + 1@feature('purchase-button', 'blue-sapphire') + + 2 + + 3@elsefeature('purchase-button', 'seafoam-green') + + 4 + + 5@elsefeature('purchase-button', 'tart-orange') + + 6 + + 7@endfeature + + + @feature('purchase-button', 'blue-sapphire') + + @elsefeature('purchase-button', 'seafoam-green') + + @elsefeature('purchase-button', 'tart-orange') + + @endfeature + +When using rich values, it is important to know that a feature is considered +"active" when it has any value other than `false`. + +When calling the conditional `when` method, the feature's rich value will be +provided to the first closure: + + + + 1Feature::when('purchase-button', + + 2 fn ($color) => /* ... */, + + 3 fn () => /* ... */, + + 4); + + + 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: + + + + 1Feature::unless('purchase-button', + + 2 fn () => /* ... */, + + 3 fn ($color) => /* ... */, + + 4); + + + Feature::unless('purchase-button', + fn () => /* ... */, + fn ($color) => /* ... */, + ); + +## Retrieving Multiple Features + +The `values` method allows the retrieval of multiple features for a given +scope: + + + + 1Feature::values(['billing-v2', 'purchase-button']); + + 2  + + 3// [ + + 4// 'billing-v2' => false, + + 5// 'purchase-button' => 'blue-sapphire', + + 6// ] + + + Feature::values(['billing-v2', 'purchase-button']); + + // [ + // 'billing-v2' => false, + // 'purchase-button' => 'blue-sapphire', + // ] + +Or, you may use the `all` method to retrieve the values of all defined +features for a given scope: + + + + 1Feature::all(); + + 2  + + 3// [ + + 4// 'billing-v2' => false, + + 5// 'purchase-button' => 'blue-sapphire', + + 6// 'site-redesign' => true, + + 7// ] + + + Feature::all(); + + // [ + // 'billing-v2' => false, + // 'purchase-button' => 'blue-sapphire', + // 'site-redesign' => true, + // ] + +However, class-based features are dynamically registered and are not known by +Pennant until they are explicitly checked. This means your application's +class-based features may not appear in the results returned by the `all` +method if they have not already been checked during the current request. + +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: + + + + 1 true, + + 5// 'billing-v2' => false, + + 6// 'purchase-button' => 'blue-sapphire', + + 7// 'site-redesign' => true, + + 8// ] + + + Feature::all(); + + // [ + // 'App\Features\NewApi' => true, + // 'billing-v2' => false, + // 'purchase-button' => 'blue-sapphire', + // 'site-redesign' => true, + // ] + +## Eager Loading + +Although Pennant keeps an in-memory cache of all resolved features for a +single request, it is still possible to encounter performance issues. To +alleviate this, Pennant offers the ability to eager load feature values. + +To illustrate this, imagine that we are checking if a feature is active within +a loop: + + + + 1use Laravel\Pennant\Feature; + + 2  + + 3foreach ($users as $user) { + + 4 if (Feature::for($user)->active('notifications-beta')) { + + 5 $user->notify(new RegistrationSuccess); + + 6 } + + 7} + + + use Laravel\Pennant\Feature; + + foreach ($users as $user) { + if (Feature::for($user)->active('notifications-beta')) { + $user->notify(new RegistrationSuccess); + } + } + +Assuming we are using the database driver, this code will execute a database +query for every user in the loop - executing potentially hundreds of queries. +However, using Pennant's `load` method, we can remove this potential +performance bottleneck by eager loading the feature values for a collection of +users or scopes: + + + + 1Feature::for($users)->load(['notifications-beta']); + + 2  + + 3foreach ($users as $user) { + + 4 if (Feature::for($user)->active('notifications-beta')) { + + 5 $user->notify(new RegistrationSuccess); + + 6 } + + 7} + + + Feature::for($users)->load(['notifications-beta']); + + foreach ($users as $user) { + if (Feature::for($user)->active('notifications-beta')) { + $user->notify(new RegistrationSuccess); + } + } + +To load feature values only when they have not already been loaded, you may +use the `loadMissing` method: + + + + 1Feature::for($users)->loadMissing([ + + 2 'new-api', + + 3 'purchase-button', + + 4 'notifications-beta', + + 5]); + + + Feature::for($users)->loadMissing([ + 'new-api', + 'purchase-button', + 'notifications-beta', + ]); + +You may load all defined features using the `loadAll` method: + + + + 1Feature::for($users)->loadAll(); + + + Feature::for($users)->loadAll(); + +## Updating Values + +When a feature's value is resolved for the first time, the underlying driver +will store the result in storage. This is often necessary to ensure a +consistent experience for your users across requests. However, at times, you +may want to manually update the feature's stored value. + +To accomplish this, you may use the `activate` and `deactivate` methods to +toggle a feature "on" or "off": + + + + 1use Laravel\Pennant\Feature; + + 2  + + 3// Activate the feature for the default scope... + + 4Feature::activate('new-api'); + + 5  + + 6// Deactivate the feature for the given scope... + + 7Feature::for($user->team)->deactivate('billing-v2'); + + + use Laravel\Pennant\Feature; + + // Activate the feature for the default scope... + Feature::activate('new-api'); + + // Deactivate the feature for the given scope... + Feature::for($user->team)->deactivate('billing-v2'); + +It is also possible to manually set a rich value for a feature by providing a +second argument to the `activate` method: + + + + 1Feature::activate('purchase-button', 'seafoam-green'); + + + Feature::activate('purchase-button', 'seafoam-green'); + +To instruct Pennant to forget the stored value for a feature, you may use the +`forget` method. When the feature is checked again, Pennant will resolve the +feature's value from its feature definition: + + + + 1Feature::forget('purchase-button'); + + + Feature::forget('purchase-button'); + +### Bulk Updates + +To update stored feature values in bulk, you may use the `activateForEveryone` +and `deactivateForEveryone` methods. + +For example, imagine you are now confident in the `new-api` feature's +stability and have landed on the best `'purchase-button'` color for your +checkout flow - you can update the stored value for all users accordingly: + + + + 1use Laravel\Pennant\Feature; + + 2  + + 3Feature::activateForEveryone('new-api'); + + 4  + + 5Feature::activateForEveryone('purchase-button', 'seafoam-green'); + + + use Laravel\Pennant\Feature; + + Feature::activateForEveryone('new-api'); + + Feature::activateForEveryone('purchase-button', 'seafoam-green'); + +Alternatively, you may deactivate the feature for all users: + + + + 1Feature::deactivateForEveryone('new-api'); + + + Feature::deactivateForEveryone('new-api'); + +This will only update the resolved feature values that have been stored by +Pennant's storage driver. You will also need to update the feature definition +in your application. + +### Purging Features + +Sometimes, it can be useful to purge an entire feature from storage. This is +typically necessary if you have removed the feature from your application or +you have made adjustments to the feature's definition that you would like to +rollout to all users. + +You may remove all stored values for a feature using the `purge` method: + + + + 1// Purging a single feature... + + 2Feature::purge('new-api'); + + 3  + + 4// Purging multiple features... + + 5Feature::purge(['new-api', 'purchase-button']); + + + // Purging a single feature... + Feature::purge('new-api'); + + // Purging multiple features... + Feature::purge(['new-api', 'purchase-button']); + +If you would like to purge _all_ features from storage, you may invoke the +`purge` method without any arguments: + + + + 1Feature::purge(); + + + Feature::purge(); + +As it can be useful to purge features as part of your application's deployment +pipeline, Pennant includes a `pennant:purge` Artisan command which will purge +the provided features from storage: + + + + 1php artisan pennant:purge new-api + + 2  + + 3php artisan pennant:purge new-api purchase-button + + + php artisan pennant:purge new-api + + php artisan pennant:purge new-api purchase-button + +It is also possible to purge all features _except_ those in a given feature +list. For example, imagine you wanted to purge all features but keep the +values for the "new-api" and "purchase-button" features in storage. To +accomplish this, you can pass those feature names to the `--except` option: + + + + 1php artisan pennant:purge --except=new-api --except=purchase-button + + + php artisan pennant:purge --except=new-api --except=purchase-button + +For convenience, the `pennant:purge` command also supports an `--except- +registered` flag. This flag indicates that all features except those +explicitly registered in a service provider should be purged: + + + + 1php artisan pennant:purge --except-registered + + + php artisan pennant:purge --except-registered + +## Testing + +When testing code that interacts with feature flags, the easiest way to +control the feature flag's returned value in your tests is to simply re-define +the feature. For example, imagine you have the following feature defined in +one of your application's service provider: + + + + 1use Illuminate\Support\Arr; + + 2use Laravel\Pennant\Feature; + + 3  + + 4Feature::define('purchase-button', fn () => Arr::random([ + + 5 'blue-sapphire', + + 6 'seafoam-green', + + 7 'tart-orange', + + 8])); + + + use Illuminate\Support\Arr; + use Laravel\Pennant\Feature; + + Feature::define('purchase-button', fn () => Arr::random([ + 'blue-sapphire', + 'seafoam-green', + 'tart-orange', + ])); + +To modify the feature's returned value in your tests, you may re-define the +feature at the beginning of the test. The following test will always pass, +even though the `Arr::random()` implementation is still present in the service +provider: + +Pest PHPUnit + + + + 1use Laravel\Pennant\Feature; + + 2  + + 3test('it can control feature values', function () { + + 4 Feature::define('purchase-button', 'seafoam-green'); + + 5  + + 6 expect(Feature::value('purchase-button'))->toBe('seafoam-green'); + + 7}); + + + use Laravel\Pennant\Feature; + + test('it can control feature values', function () { + Feature::define('purchase-button', 'seafoam-green'); + + expect(Feature::value('purchase-button'))->toBe('seafoam-green'); + }); + + + 1use Laravel\Pennant\Feature; + + 2  + + 3public function test_it_can_control_feature_values() + + 4{ + + 5 Feature::define('purchase-button', 'seafoam-green'); + + 6  + + 7 $this->assertSame('seafoam-green', Feature::value('purchase-button')); + + 8} + + + use Laravel\Pennant\Feature; + + public function test_it_can_control_feature_values() + { + Feature::define('purchase-button', 'seafoam-green'); + + $this->assertSame('seafoam-green', Feature::value('purchase-button')); + } + +The same approach may be used for class-based features: + +Pest PHPUnit + + + + 1use Laravel\Pennant\Feature; + + 2  + + 3test('it can control feature values', function () { + + 4 Feature::define(NewApi::class, true); + + 5  + + 6 expect(Feature::value(NewApi::class))->toBeTrue(); + + 7}); + + + use Laravel\Pennant\Feature; + + test('it can control feature values', function () { + Feature::define(NewApi::class, true); + + expect(Feature::value(NewApi::class))->toBeTrue(); + }); + + + 1use App\Features\NewApi; + + 2use Laravel\Pennant\Feature; + + 3  + + 4public function test_it_can_control_feature_values() + + 5{ + + 6 Feature::define(NewApi::class, true); + + 7  + + 8 $this->assertTrue(Feature::value(NewApi::class)); + + 9} + + + use App\Features\NewApi; + use Laravel\Pennant\Feature; + + public function test_it_can_control_feature_values() + { + Feature::define(NewApi::class, true); + + $this->assertTrue(Feature::value(NewApi::class)); + } + +If your feature is returning a `Lottery` instance, there are a handful of +useful [testing helpers available](/docs/12.x/helpers#testing-lotteries). + +#### Store Configuration + +You may configure the store that Pennant will use during testing by defining +the `PENNANT_STORE` environment variable in your application's `phpunit.xml` +file: + + + + 1 + + 2 + + 3 + + 4 + + 5 + + 6 + + 7 + + 8 + + + + + + + + + + + +## Adding Custom Pennant Drivers + +#### Implementing the Driver + +If none of Pennant's existing storage drivers fit your application's needs, +you may write your own storage driver. Your custom driver should implement the +`Laravel\Pennant\Contracts\Driver` interface: + + + + 1make('redis'), $app->make('events'), []); + + 27 }); + + 28 } + + 29} + + + make('redis'), $app->make('events'), []); + }); + } + } + +Once the driver has been registered, you may use the `redis` driver in your +application's `config/pennant.php` configuration file: + + + + 1'stores' => [ + + 2  + + 3 'redis' => [ + + 4 'driver' => 'redis', + + 5 'connection' => null, + + 6 ], + + 7  + + 8 // ... + + 9  + + 10], + + + 'stores' => [ + + 'redis' => [ + 'driver' => 'redis', + 'connection' => null, + ], + + // ... + + ], + +### Defining Features Externally + +If your driver is a wrapper around a third-party feature flag platform, you +will likely define features on the platform rather than using Pennant's +`Feature::define` method. If that is the case, your custom driver should also +implement the `Laravel\Pennant\Contracts\DefinesFeaturesExternally` interface: + + + + 1feature}]."); + + 19 }); + + 20 } + + 21} + + + feature}]."); + }); + } + } + +### `Laravel\Pennant\Events\DynamicallyRegisteringFeatureClass` + +This event is dispatched when a class-based feature is dynamically checked for +the first time during a request. + +### `Laravel\Pennant\Events\UnexpectedNullScopeEncountered` + +This event is dispatched when a `null` scope is passed to a feature definition +that doesn't support null. + +This situation is handled gracefully and the feature will return `false`. +However, if you would like to opt out of this feature's default graceful +behavior, you may register a listener for this event in the `boot` method of +your application's `AppServiceProvider`: + + + + 1use Illuminate\Support\Facades\Log; + + 2use Laravel\Pennant\Events\UnexpectedNullScopeEncountered; + + 3  + + 4/** + + 5 * Bootstrap any application services. + + 6 */ + + 7public function boot(): void + + 8{ + + 9 Event::listen(UnexpectedNullScopeEncountered::class, fn () => abort(500)); + + 10} + + + use Illuminate\Support\Facades\Log; + use Laravel\Pennant\Events\UnexpectedNullScopeEncountered; + + /** + * Bootstrap any application services. + */ + public function boot(): void + { + Event::listen(UnexpectedNullScopeEncountered::class, fn () => abort(500)); + } + +### `Laravel\Pennant\Events\FeatureUpdated` + +This event is dispatched when updating a feature for a scope, usually by +calling `activate` or `deactivate`. + +### `Laravel\Pennant\Events\FeatureUpdatedForAllScopes` + +This event is dispatched when updating a feature for all scopes, usually by +calling `activateForEveryone` or `deactivateForEveryone`. + +### `Laravel\Pennant\Events\FeatureDeleted` + +This event is dispatched when deleting a feature for a scope, usually by +calling `forget`. + +### `Laravel\Pennant\Events\FeaturesPurged` + +This event is dispatched when purging specific features. + +### `Laravel\Pennant\Events\AllFeaturesPurged` + +This event is dispatched when purging all features. + diff --git a/output/12.x/pint.md b/output/12.x/pint.md new file mode 100644 index 0000000..078d8f8 --- /dev/null +++ b/output/12.x/pint.md @@ -0,0 +1,430 @@ +# Laravel Pint + + * Introduction + * Installation + * Running Pint + * Configuring Pint + * Presets + * Rules + * Excluding Files / Folders + * Continuous Integration + * GitHub Actions + +## Introduction + +[Laravel Pint](https://github.com/laravel/pint) is an opinionated PHP code +style fixer for minimalists. Pint is built on top of [PHP CS +Fixer](https://github.com/FriendsOfPHP/PHP-CS-Fixer) and makes it simple to +ensure that your code style stays clean and consistent. + +Pint is automatically installed with all new Laravel applications so you may +start using it immediately. By default, Pint does not require any +configuration and will fix code style issues in your code by following the +opinionated coding style of Laravel. + +## Installation + +Pint is included in recent releases of the Laravel framework, so installation +is typically unnecessary. However, for older applications, you may install +Laravel Pint via Composer: + + + + 1composer require laravel/pint --dev + + + composer require laravel/pint --dev + +## Running Pint + +You can instruct Pint to fix code style issues by invoking the `pint` binary +that is available in your project's `vendor/bin` directory: + + + + 1./vendor/bin/pint + + + ./vendor/bin/pint + +If you would like Pint to run in parallel mode (experimental) for improved +performance, you may use the `--parallel` option: + + + + 1./vendor/bin/pint --parallel + + + ./vendor/bin/pint --parallel + +Parallel mode also allows you to specify the maximum number of processes to +run via the `--max-processes` option. If this option is not provided, Pint +will use every available core on your machine: + + + + 1./vendor/bin/pint --parallel --max-processes=4 + + + ./vendor/bin/pint --parallel --max-processes=4 + +You may also run Pint on specific files or directories: + + + + 1./vendor/bin/pint app/Models + + 2  + + 3./vendor/bin/pint app/Models/User.php + + + ./vendor/bin/pint app/Models + + ./vendor/bin/pint app/Models/User.php + +Pint will display a thorough list of all of the files that it updates. You can +view even more detail about Pint's changes by providing the `-v` option when +invoking Pint: + + + + 1./vendor/bin/pint -v + + + ./vendor/bin/pint -v + +If you would like Pint to simply inspect your code for style errors without +actually changing the files, you may use the `--test` option. Pint will return +a non-zero exit code if any code style errors are found: + + + + 1./vendor/bin/pint --test + + + ./vendor/bin/pint --test + +If you would like Pint to only modify the files that differ from the provided +branch according to Git, you may use the `--diff=[branch]` option. This can be +effectively used in your CI environment (like GitHub actions) to save time by +only inspecting new or modified files: + + + + 1./vendor/bin/pint --diff=main + + + ./vendor/bin/pint --diff=main + +If you would like Pint to only modify the files that have uncommitted changes +according to Git, you may use the `--dirty` option: + + + + 1./vendor/bin/pint --dirty + + + ./vendor/bin/pint --dirty + +If you would like Pint to fix any files with code style errors but also exit +with a non-zero exit code if any errors were fixed, you may use the `--repair` +option: + + + + 1./vendor/bin/pint --repair + + + ./vendor/bin/pint --repair + +## Configuring Pint + +As previously mentioned, Pint does not require any configuration. However, if +you wish to customize the presets, rules, or inspected folders, you may do so +by creating a `pint.json` file in your project's root directory: + + + + 1{ + + 2 "preset": "laravel" + + 3} + + + { + "preset": "laravel" + } + +In addition, if you wish to use a `pint.json` from a specific directory, you +may provide the `--config` option when invoking Pint: + + + + 1./vendor/bin/pint --config vendor/my-company/coding-style/pint.json + + + ./vendor/bin/pint --config vendor/my-company/coding-style/pint.json + +### Presets + +Presets define a set of rules that can be used to fix code style issues in +your code. By default, Pint uses the `laravel` preset, which fixes issues by +following the opinionated coding style of Laravel. However, you may specify a +different preset by providing the `--preset` option to Pint: + + + + 1./vendor/bin/pint --preset psr12 + + + ./vendor/bin/pint --preset psr12 + +If you wish, you may also set the preset in your project's `pint.json` file: + + + + 1{ + + 2 "preset": "psr12" + + 3} + + + { + "preset": "psr12" + } + +Pint's currently supported presets are: `laravel`, `per`, `psr12`, `symfony`, +and `empty`. + +### Rules + +Rules are style guidelines that Pint will use to fix code style issues in your +code. As mentioned above, presets are predefined groups of rules that should +be perfect for most PHP projects, so you typically will not need to worry +about the individual rules they contain. + +However, if you wish, you may enable or disable specific rules in your +`pint.json` file or use the `empty` preset and define the rules from scratch: + + + + 1{ + + 2 "preset": "laravel", + + 3 "rules": { + + 4 "simplified_null_return": true, + + 5 "array_indentation": false, + + 6 "new_with_parentheses": { + + 7 "anonymous_class": true, + + 8 "named_class": true + + 9 } + + 10 } + + 11} + + + { + "preset": "laravel", + "rules": { + "simplified_null_return": true, + "array_indentation": false, + "new_with_parentheses": { + "anonymous_class": true, + "named_class": true + } + } + } + +Pint is built on top of [PHP CS Fixer](https://github.com/FriendsOfPHP/PHP-CS- +Fixer). Therefore, you may use any of its rules to fix code style issues in +your project: [PHP CS Fixer Configurator](https://mlocati.github.io/php-cs- +fixer-configurator). + +### Excluding Files / Folders + +By default, Pint will inspect all `.php` files in your project except those in +the `vendor` directory. If you wish to exclude more folders, you may do so +using the `exclude` configuration option: + + + + 1{ + + 2 "exclude": [ + + 3 "my-specific/folder" + + 4 ] + + 5} + + + { + "exclude": [ + "my-specific/folder" + ] + } + +If you wish to exclude all files that contain a given name pattern, you may do +so using the `notName` configuration option: + + + + 1{ + + 2 "notName": [ + + 3 "*-my-file.php" + + 4 ] + + 5} + + + { + "notName": [ + "*-my-file.php" + ] + } + +If you would like to exclude a file by providing an exact path to the file, +you may do so using the `notPath` configuration option: + + + + 1{ + + 2 "notPath": [ + + 3 "path/to/excluded-file.php" + + 4 ] + + 5} + + + { + "notPath": [ + "path/to/excluded-file.php" + ] + } + +## Continuous Integration + +### GitHub Actions + +To automate linting your project with Laravel Pint, you can configure [GitHub +Actions](https://github.com/features/actions) to run Pint whenever new code is +pushed to GitHub. First, be sure to grant "Read and write permissions" to +workflows within GitHub at **Settings > Actions > General > Workflow +permissions**. Then, create a `.github/workflows/lint.yml` file with the +following content: + + + + 1name: Fix Code Style + + 2  + + 3on: [push] + + 4  + + 5jobs: + + 6 lint: + + 7 runs-on: ubuntu-latest + + 8 strategy: + + 9 fail-fast: true + + 10 matrix: + + 11 php: [8.4] + + 12  + + 13 steps: + + 14 - name: Checkout code + + 15 uses: actions/checkout@v4 + + 16  + + 17 - name: Setup PHP + + 18 uses: shivammathur/setup-php@v2 + + 19 with: + + 20 php-version: ${{ matrix.php }} + + 21 extensions: json, dom, curl, libxml, mbstring + + 22 coverage: none + + 23  + + 24 - name: Install Pint + + 25 run: composer global require laravel/pint + + 26  + + 27 - name: Run Pint + + 28 run: pint + + 29  + + 30 - name: Commit linted files + + 31 uses: stefanzweifel/git-auto-commit-action@v5 + + + name: Fix Code Style + + on: [push] + + jobs: + lint: + runs-on: ubuntu-latest + strategy: + fail-fast: true + matrix: + php: [8.4] + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + extensions: json, dom, curl, libxml, mbstring + coverage: none + + - name: Install Pint + run: composer global require laravel/pint + + - name: Run Pint + run: pint + + - name: Commit linted files + uses: stefanzweifel/git-auto-commit-action@v5 + diff --git a/output/12.x/precognition.md b/output/12.x/precognition.md new file mode 100644 index 0000000..3ee6181 --- /dev/null +++ b/output/12.x/precognition.md @@ -0,0 +1,1766 @@ +# Precognition + + * Introduction + * Live Validation + * Using Vue + * Using Vue and Inertia + * Using React + * Using React and Inertia + * Using Alpine and Blade + * Configuring Axios + * Customizing Validation Rules + * Handling File Uploads + * Managing Side-Effects + * Testing + +## Introduction + +Laravel Precognition allows you to anticipate the outcome of a future HTTP +request. One of the primary use cases of Precognition is the ability to +provide "live" validation for your frontend JavaScript application without +having to duplicate your application's backend validation rules. Precognition +pairs especially well with Laravel's Inertia-based [starter +kits](/docs/12.x/starter-kits). + +When Laravel receives a "precognitive request", it will execute all of the +route's middleware and resolve the route's controller dependencies, including +validating [form requests](/docs/12.x/validation#form-request-validation) \- +but it will not actually execute the route's controller method. + +## Live Validation + +### Using Vue + +Using Laravel Precognition, you can offer live validation experiences to your +users without having to duplicate your validation rules in your frontend Vue +application. To illustrate how it works, let's build a form for creating new +users within our application. + +First, to enable Precognition for a route, the `HandlePrecognitiveRequests` +middleware should be added to the route definition. You should also create a +[form request](/docs/12.x/validation#form-request-validation) to house the +route's validation rules: + + + + 1use App\Http\Requests\StoreUserRequest; + + 2use Illuminate\Foundation\Http\Middleware\HandlePrecognitiveRequests; + + 3  + + 4Route::post('/users', function (StoreUserRequest $request) { + + 5 // ... + + 6})->middleware([HandlePrecognitiveRequests::class]); + + + use App\Http\Requests\StoreUserRequest; + use Illuminate\Foundation\Http\Middleware\HandlePrecognitiveRequests; + + Route::post('/users', function (StoreUserRequest $request) { + // ... + })->middleware([HandlePrecognitiveRequests::class]); + +Next, you should install the Laravel Precognition frontend helpers for Vue via +NPM: + + + + 1npm install laravel-precognition-vue + + + npm install laravel-precognition-vue + +With the Laravel Precognition package installed, you can now create a form +object using Precognition's `useForm` function, providing the HTTP method +(`post`), the target URL (`/users`), and the initial form data. + +Then, to enable live validation, invoke the form's `validate` method on each +input's `change` event, providing the input's name: + + + + 1 + + 11  + + 12 + + + + + + +Now, as the form is filled by the user, Precognition will provide live +validation output powered by the validation rules in the route's form request. +When the form's inputs are changed, a debounced "precognitive" validation +request will be sent to your Laravel application. You may configure the +debounce timeout by calling the form's `setValidationTimeout` function: + + + + 1form.setValidationTimeout(3000); + + + form.setValidationTimeout(3000); + +When a validation request is in-flight, the form's `validating` property will +be `true`: + + + + 1
    + + 2 Validating... + + 3
    + + +
    + Validating... +
    + +Any validation errors returned during a validation request or a form +submission will automatically populate the form's `errors` object: + + + + 1
    + + 2 {{ form.errors.email }} + + 3
    + + +
    + {{ form.errors.email }} +
    + +You can determine if the form has any errors using the form's `hasErrors` +property: + + + + 1
    + + 2 + + 3
    + + +
    + +
    + +You may also determine if an input has passed or failed validation by passing +the input's name to the form's `valid` and `invalid` functions, respectively: + + + + 1 + + 2 ✅ + + 3 + + 4  + + 5 + + 6 ❌ + + 7 + + + + ✅ + + + + ❌ + + +A form input will only appear as valid or invalid once it has changed and a +validation response has been received. + +If you are validating a subset of a form's inputs with Precognition, it can be +useful to manually clear errors. You may use the form's `forgetError` function +to achieve this: + + + + 1 + + + + +As we have seen, you can hook into an input's `change` event and validate +individual inputs as the user interacts with them; however, you may need to +validate inputs that the user has not yet interacted with. This is common when +building a "wizard", where you want to validate all visible inputs, whether +the user has interacted with them or not, before moving to the next step. + +To do this with Precognition, you should call the `validate` method passing +the field names you wish to validate to the `only` configuration key. You may +handle the validation result with `onSuccess` or `onValidationError` +callbacks: + + + + 1 + + + + +Of course, you may also execute code in reaction to the response to the form +submission. The form's `submit` function returns an Axios request promise. +This provides a convenient way to access the response payload, reset the form +inputs on successful submission, or handle a failed request: + + + + 1const submit = () => form.submit() + + 2 .then(response => { + + 3 form.reset(); + + 4  + + 5 alert('User created.'); + + 6 }) + + 7 .catch(error => { + + 8 alert('An error occurred.'); + + 9 }); + + + const submit = () => form.submit() + .then(response => { + form.reset(); + + alert('User created.'); + }) + .catch(error => { + alert('An error occurred.'); + }); + +You may determine if a form submission request is in-flight by inspecting the +form's `processing` property: + + + + 1 + + + + +### Using Vue and Inertia + +If you would like a head start when developing your Laravel application with +Vue and Inertia, consider using one of our [starter kits](/docs/12.x/starter- +kits). Laravel's starter kits provide backend and frontend authentication +scaffolding for your new Laravel application. + +Before using Precognition with Vue and Inertia, be sure to review our general +documentation on using Precognition with Vue. When using Vue with Inertia, you +will need to install the Inertia compatible Precognition library via NPM: + + + + 1npm install laravel-precognition-vue-inertia + + + npm install laravel-precognition-vue-inertia + +Once installed, Precognition's `useForm` function will return an Inertia [form +helper](https://inertiajs.com/forms#form-helper) augmented with the validation +features discussed above. + +The form helper's `submit` method has been streamlined, removing the need to +specify the HTTP method or URL. Instead, you may pass Inertia's [visit +options](https://inertiajs.com/manual-visits) as the first and only argument. +In addition, the `submit` method does not return a Promise as seen in the Vue +example above. Instead, you may provide any of Inertia's supported [event +callbacks](https://inertiajs.com/manual-visits#event-callbacks) in the visit +options given to the `submit` method: + + + + 1 + + + + +### Using React + +Using Laravel Precognition, you can offer live validation experiences to your +users without having to duplicate your validation rules in your frontend React +application. To illustrate how it works, let's build a form for creating new +users within our application. + +First, to enable Precognition for a route, the `HandlePrecognitiveRequests` +middleware should be added to the route definition. You should also create a +[form request](/docs/12.x/validation#form-request-validation) to house the +route's validation rules: + + + + 1use App\Http\Requests\StoreUserRequest; + + 2use Illuminate\Foundation\Http\Middleware\HandlePrecognitiveRequests; + + 3  + + 4Route::post('/users', function (StoreUserRequest $request) { + + 5 // ... + + 6})->middleware([HandlePrecognitiveRequests::class]); + + + use App\Http\Requests\StoreUserRequest; + use Illuminate\Foundation\Http\Middleware\HandlePrecognitiveRequests; + + Route::post('/users', function (StoreUserRequest $request) { + // ... + })->middleware([HandlePrecognitiveRequests::class]); + +Next, you should install the Laravel Precognition frontend helpers for React +via NPM: + + + + 1npm install laravel-precognition-react + + + npm install laravel-precognition-react + +With the Laravel Precognition package installed, you can now create a form +object using Precognition's `useForm` function, providing the HTTP method +(`post`), the target URL (`/users`), and the initial form data. + +To enable live validation, you should listen to each input's `change` and +`blur` event. In the `change` event handler, you should set the form's data +with the `setData` function, passing the input's name and new value. Then, in +the `blur` event handler invoke the form's `validate` method, providing the +input's name: + + + + 1import { useForm } from 'laravel-precognition-react'; + + 2  + + 3export default function Form() { + + 4 const form = useForm('post', '/users', { + + 5 name: '', + + 6 email: '', + + 7 }); + + 8  + + 9 const submit = (e) => { + + 10 e.preventDefault(); + + 11  + + 12 form.submit(); + + 13 }; + + 14  + + 15 return ( + + 16
    + + 17 + + 18 form.setData('name', e.target.value)} + + 22 onBlur={() => form.validate('name')} + + 23 /> + + 24 {form.invalid('name') &&
    {form.errors.name}
    } + + 25  + + 26 + + 27 form.setData('email', e.target.value)} + + 31 onBlur={() => form.validate('email')} + + 32 /> + + 33 {form.invalid('email') &&
    {form.errors.email}
    } + + 34  + + 35 + + 38
    + + 39 ); + + 40}; + + + import { useForm } from 'laravel-precognition-react'; + + export default function Form() { + const form = useForm('post', '/users', { + name: '', + email: '', + }); + + const submit = (e) => { + e.preventDefault(); + + form.submit(); + }; + + return ( +
    + + form.setData('name', e.target.value)} + onBlur={() => form.validate('name')} + /> + {form.invalid('name') &&
    {form.errors.name}
    } + + + form.setData('email', e.target.value)} + onBlur={() => form.validate('email')} + /> + {form.invalid('email') &&
    {form.errors.email}
    } + + +
    + ); + }; + +Now, as the form is filled by the user, Precognition will provide live +validation output powered by the validation rules in the route's form request. +When the form's inputs are changed, a debounced "precognitive" validation +request will be sent to your Laravel application. You may configure the +debounce timeout by calling the form's `setValidationTimeout` function: + + + + 1form.setValidationTimeout(3000); + + + form.setValidationTimeout(3000); + +When a validation request is in-flight, the form's `validating` property will +be `true`: + + + + 1{form.validating &&
    Validating...
    } + + + {form.validating &&
    Validating...
    } + +Any validation errors returned during a validation request or a form +submission will automatically populate the form's `errors` object: + + + + 1{form.invalid('email') &&
    {form.errors.email}
    } + + + {form.invalid('email') &&
    {form.errors.email}
    } + +You can determine if the form has any errors using the form's `hasErrors` +property: + + + + 1{form.hasErrors &&
    } + + + {form.hasErrors &&
    } + +You may also determine if an input has passed or failed validation by passing +the input's name to the form's `valid` and `invalid` functions, respectively: + + + + 1{form.valid('email') && } + + 2  + + 3{form.invalid('email') && } + + + {form.valid('email') && } + + {form.invalid('email') && } + +A form input will only appear as valid or invalid once it has changed and a +validation response has been received. + +If you are validating a subset of a form's inputs with Precognition, it can be +useful to manually clear errors. You may use the form's `forgetError` function +to achieve this: + + + + 1 { + + 5 form.setData('avatar', e.target.value); + + 6  + + 7 form.forgetError('avatar'); + + 8 }} + + 9> + + + { + form.setData('avatar', e.target.value); + + form.forgetError('avatar'); + }} + > + +As we have seen, you can hook into an input's `blur` event and validate +individual inputs as the user interacts with them; however, you may need to +validate inputs that the user has not yet interacted with. This is common when +building a "wizard", where you want to validate all visible inputs, whether +the user has interacted with them or not, before moving to the next step. + +To do this with Precognition, you should call the `validate` method passing +the field names you wish to validate to the `only` configuration key. You may +handle the validation result with `onSuccess` or `onValidationError` +callbacks: + + + + 1 + + + + +Of course, you may also execute code in reaction to the response to the form +submission. The form's `submit` function returns an Axios request promise. +This provides a convenient way to access the response payload, reset the +form's inputs on a successful form submission, or handle a failed request: + + + + 1const submit = (e) => { + + 2 e.preventDefault(); + + 3  + + 4 form.submit() + + 5 .then(response => { + + 6 form.reset(); + + 7  + + 8 alert('User created.'); + + 9 }) + + 10 .catch(error => { + + 11 alert('An error occurred.'); + + 12 }); + + 13}; + + + const submit = (e) => { + e.preventDefault(); + + form.submit() + .then(response => { + form.reset(); + + alert('User created.'); + }) + .catch(error => { + alert('An error occurred.'); + }); + }; + +You may determine if a form submission request is in-flight by inspecting the +form's `processing` property: + + + + 1 + + + + +### Using React and Inertia + +If you would like a head start when developing your Laravel application with +React and Inertia, consider using one of our [starter +kits](/docs/12.x/starter-kits). Laravel's starter kits provide backend and +frontend authentication scaffolding for your new Laravel application. + +Before using Precognition with React and Inertia, be sure to review our +general documentation on using Precognition with React. When using React with +Inertia, you will need to install the Inertia compatible Precognition library +via NPM: + + + + 1npm install laravel-precognition-react-inertia + + + npm install laravel-precognition-react-inertia + +Once installed, Precognition's `useForm` function will return an Inertia [form +helper](https://inertiajs.com/forms#form-helper) augmented with the validation +features discussed above. + +The form helper's `submit` method has been streamlined, removing the need to +specify the HTTP method or URL. Instead, you may pass Inertia's [visit +options](https://inertiajs.com/manual-visits) as the first and only argument. +In addition, the `submit` method does not return a Promise as seen in the +React example above. Instead, you may provide any of Inertia's supported +[event callbacks](https://inertiajs.com/manual-visits#event-callbacks) in the +visit options given to the `submit` method: + + + + 1import { useForm } from 'laravel-precognition-react-inertia'; + + 2  + + 3const form = useForm('post', '/users', { + + 4 name: '', + + 5 email: '', + + 6}); + + 7  + + 8const submit = (e) => { + + 9 e.preventDefault(); + + 10  + + 11 form.submit({ + + 12 preserveScroll: true, + + 13 onSuccess: () => form.reset(), + + 14 }); + + 15}; + + + import { useForm } from 'laravel-precognition-react-inertia'; + + const form = useForm('post', '/users', { + name: '', + email: '', + }); + + const submit = (e) => { + e.preventDefault(); + + form.submit({ + preserveScroll: true, + onSuccess: () => form.reset(), + }); + }; + +### Using Alpine and Blade + +Using Laravel Precognition, you can offer live validation experiences to your +users without having to duplicate your validation rules in your frontend +Alpine application. To illustrate how it works, let's build a form for +creating new users within our application. + +First, to enable Precognition for a route, the `HandlePrecognitiveRequests` +middleware should be added to the route definition. You should also create a +[form request](/docs/12.x/validation#form-request-validation) to house the +route's validation rules: + + + + 1use App\Http\Requests\CreateUserRequest; + + 2use Illuminate\Foundation\Http\Middleware\HandlePrecognitiveRequests; + + 3  + + 4Route::post('/users', function (CreateUserRequest $request) { + + 5 // ... + + 6})->middleware([HandlePrecognitiveRequests::class]); + + + use App\Http\Requests\CreateUserRequest; + use Illuminate\Foundation\Http\Middleware\HandlePrecognitiveRequests; + + Route::post('/users', function (CreateUserRequest $request) { + // ... + })->middleware([HandlePrecognitiveRequests::class]); + +Next, you should install the Laravel Precognition frontend helpers for Alpine +via NPM: + + + + 1npm install laravel-precognition-alpine + + + npm install laravel-precognition-alpine + +Then, register the Precognition plugin with Alpine in your +`resources/js/app.js` file: + + + + 1import Alpine from 'alpinejs'; + + 2import Precognition from 'laravel-precognition-alpine'; + + 3  + + 4window.Alpine = Alpine; + + 5  + + 6Alpine.plugin(Precognition); + + 7Alpine.start(); + + + import Alpine from 'alpinejs'; + import Precognition from 'laravel-precognition-alpine'; + + window.Alpine = Alpine; + + Alpine.plugin(Precognition); + Alpine.start(); + +With the Laravel Precognition package installed and registered, you can now +create a form object using Precognition's `$form` "magic", providing the HTTP +method (`post`), the target URL (`/users`), and the initial form data. + +To enable live validation, you should bind the form's data to its relevant +input and then listen to each input's `change` event. In the `change` event +handler, you should invoke the form's `validate` method, providing the input's +name: + + + + 1
    + + 7 @csrf + + 8 + + 9 + + 15 + + 18  + + 19 + + 20 + + 26 + + 29  + + 30 + + 33
    + + +
    + @csrf + + + + + + + + + +
    + +Now, as the form is filled by the user, Precognition will provide live +validation output powered by the validation rules in the route's form request. +When the form's inputs are changed, a debounced "precognitive" validation +request will be sent to your Laravel application. You may configure the +debounce timeout by calling the form's `setValidationTimeout` function: + + + + 1form.setValidationTimeout(3000); + + + form.setValidationTimeout(3000); + +When a validation request is in-flight, the form's `validating` property will +be `true`: + + + + 1 + + + + +Any validation errors returned during a validation request or a form +submission will automatically populate the form's `errors` object: + + + + 1 + + + + +You can determine if the form has any errors using the form's `hasErrors` +property: + + + + 1 + + + + +You may also determine if an input has passed or failed validation by passing +the input's name to the form's `valid` and `invalid` functions, respectively: + + + + 1 + + 4  + + 5 + + + + + + +A form input will only appear as valid or invalid once it has changed and a +validation response has been received. + +As we have seen, you can hook into an input's `change` event and validate +individual inputs as the user interacts with them; however, you may need to +validate inputs that the user has not yet interacted with. This is common when +building a "wizard", where you want to validate all visible inputs, whether +the user has interacted with them or not, before moving to the next step. + +To do this with Precognition, you should call the `validate` method passing +the field names you wish to validate to the `only` configuration key. You may +handle the validation result with `onSuccess` or `onValidationError` +callbacks: + + + + 1 + + + + +You may determine if a form submission request is in-flight by inspecting the +form's `processing` property: + + + + 1 + + + + +#### Repopulating Old Form Data + +In the user creation example discussed above, we are using Precognition to +perform live validation; however, we are performing a traditional server-side +form submission to submit the form. So, the form should be populated with any +"old" input and validation errors returned from the server-side form +submission: + + + + 1
    + + + + +Alternatively, if you would like to submit the form via XHR you may use the +form's `submit` function, which returns an Axios request promise: + + + + 1 + + + + +### Configuring Axios + +The Precognition validation libraries use the +[Axios](https://github.com/axios/axios) HTTP client to send requests to your +application's backend. For convenience, the Axios instance may be customized +if required by your application. For example, when using the `laravel- +precognition-vue` library, you may add additional request headers to each +outgoing request in your application's `resources/js/app.js` file: + + + + 1import { client } from 'laravel-precognition-vue'; + + 2  + + 3client.axios().defaults.headers.common['Authorization'] = authToken; + + + import { client } from 'laravel-precognition-vue'; + + client.axios().defaults.headers.common['Authorization'] = authToken; + +Or, if you already have a configured Axios instance for your application, you +may tell Precognition to use that instance instead: + + + + 1import Axios from 'axios'; + + 2import { client } from 'laravel-precognition-vue'; + + 3  + + 4window.axios = Axios.create() + + 5window.axios.defaults.headers.common['Authorization'] = authToken; + + 6  + + 7client.use(window.axios) + + + import Axios from 'axios'; + import { client } from 'laravel-precognition-vue'; + + window.axios = Axios.create() + window.axios.defaults.headers.common['Authorization'] = authToken; + + client.use(window.axios) + +The Inertia flavored Precognition libraries will only use the configured Axios +instance for validation requests. Form submissions will always be sent by +Inertia. + +## Customizing Validation Rules + +It is possible to customize the validation rules executed during a +precognitive request by using the request's `isPrecognitive` method. + +For example, on a user creation form, we may want to validate that a password +is "uncompromised" only on the final form submission. For precognitive +validation requests, we will simply validate that the password is required and +has a minimum of 8 characters. Using the `isPrecognitive` method, we can +customize the rules defined by our form request: + + + + 1 [ + + 19 'required', + + 20 $this->isPrecognitive() + + 21 ? Password::min(8) + + 22 : Password::min(8)->uncompromised(), + + 23 ], + + 24 // ... + + 25 ]; + + 26 } + + 27} + + + [ + 'required', + $this->isPrecognitive() + ? Password::min(8) + : Password::min(8)->uncompromised(), + ], + // ... + ]; + } + } + +## Handling File Uploads + +By default, Laravel Precognition does not upload or validate files during a +precognitive validation request. This ensure that large files are not +unnecessarily uploaded multiple times. + +Because of this behavior, you should ensure that your application customizes +the corresponding form request's validation rules to specify the field is only +required for full form submissions: + + + + 1/** + + 2 * Get the validation rules that apply to the request. + + 3 * + + 4 * @return array + + 5 */ + + 6protected function rules() + + 7{ + + 8 return [ + + 9 'avatar' => [ + + 10 ...$this->isPrecognitive() ? [] : ['required'], + + 11 'image', + + 12 'mimes:jpg,png', + + 13 'dimensions:ratio=3/2', + + 14 ], + + 15 // ... + + 16 ]; + + 17} + + + /** + * Get the validation rules that apply to the request. + * + * @return array + */ + protected function rules() + { + return [ + 'avatar' => [ + ...$this->isPrecognitive() ? [] : ['required'], + 'image', + 'mimes:jpg,png', + 'dimensions:ratio=3/2', + ], + // ... + ]; + } + +If you would like to include files in every validation request, you may invoke +the `validateFiles` function on your client-side form instance: + + + + 1form.validateFiles(); + + + form.validateFiles(); + +## Managing Side-Effects + +When adding the `HandlePrecognitiveRequests` middleware to a route, you should +consider if there are any side-effects in _other_ middleware that should be +skipped during a precognitive request. + +For example, you may have a middleware that increments the total number of +"interactions" each user has with your application, but you may not want +precognitive requests to be counted as an interaction. To accomplish this, we +may check the request's `isPrecognitive` method before incrementing the +interaction count: + + + + 1isPrecognitive()) { + + 17 Interaction::incrementFor($request->user()); + + 18 } + + 19  + + 20 return $next($request); + + 21 } + + 22} + + + isPrecognitive()) { + Interaction::incrementFor($request->user()); + } + + return $next($request); + } + } + +## Testing + +If you would like to make precognitive requests in your tests, Laravel's +`TestCase` includes a `withPrecognition` helper which will add the +`Precognition` request header. + +Additionally, if you would like to assert that a precognitive request was +successful, e.g., did not return any validation errors, you may use the +`assertSuccessfulPrecognition` method on the response: + +Pest PHPUnit + + + + 1it('validates registration form with precognition', function () { + + 2 $response = $this->withPrecognition() + + 3 ->post('/register', [ + + 4 'name' => 'Taylor Otwell', + + 5 ]); + + 6  + + 7 $response->assertSuccessfulPrecognition(); + + 8  + + 9 expect(User::count())->toBe(0); + + 10}); + + + it('validates registration form with precognition', function () { + $response = $this->withPrecognition() + ->post('/register', [ + 'name' => 'Taylor Otwell', + ]); + + $response->assertSuccessfulPrecognition(); + + expect(User::count())->toBe(0); + }); + + + 1public function test_it_validates_registration_form_with_precognition() + + 2{ + + 3 $response = $this->withPrecognition() + + 4 ->post('/register', [ + + 5 'name' => 'Taylor Otwell', + + 6 ]); + + 7  + + 8 $response->assertSuccessfulPrecognition(); + + 9 $this->assertSame(0, User::count()); + + 10} + + + public function test_it_validates_registration_form_with_precognition() + { + $response = $this->withPrecognition() + ->post('/register', [ + 'name' => 'Taylor Otwell', + ]); + + $response->assertSuccessfulPrecognition(); + $this->assertSame(0, User::count()); + } + diff --git a/output/12.x/processes.md b/output/12.x/processes.md new file mode 100644 index 0000000..6c9a3f0 --- /dev/null +++ b/output/12.x/processes.md @@ -0,0 +1,1380 @@ +# Processes + + * Introduction + * Invoking Processes + * Process Options + * Process Output + * Pipelines + * Asynchronous Processes + * Process IDs and Signals + * Asynchronous Process Output + * Asynchronous Process Timeouts + * Concurrent Processes + * Naming Pool Processes + * Pool Process IDs and Signals + * Testing + * Faking Processes + * Faking Specific Processes + * Faking Process Sequences + * Faking Asynchronous Process Lifecycles + * Available Assertions + * Preventing Stray Processes + +## Introduction + +Laravel provides an expressive, minimal API around the [Symfony Process +component](https://symfony.com/doc/current/components/process.html), allowing +you to conveniently invoke external processes from your Laravel application. +Laravel's process features are focused on the most common use cases and a +wonderful developer experience. + +## Invoking Processes + +To invoke a process, you may use the `run` and `start` methods offered by the +`Process` facade. The `run` method will invoke a process and wait for the +process to finish executing, while the `start` method is used for asynchronous +process execution. We'll examine both approaches within this documentation. +First, let's examine how to invoke a basic, synchronous process and inspect +its result: + + + + 1use Illuminate\Support\Facades\Process; + + 2  + + 3$result = Process::run('ls -la'); + + 4  + + 5return $result->output(); + + + use Illuminate\Support\Facades\Process; + + $result = Process::run('ls -la'); + + return $result->output(); + +Of course, the `Illuminate\Contracts\Process\ProcessResult` instance returned +by the `run` method offers a variety of helpful methods that may be used to +inspect the process result: + + + + 1$result = Process::run('ls -la'); + + 2  + + 3$result->successful(); + + 4$result->failed(); + + 5$result->exitCode(); + + 6$result->output(); + + 7$result->errorOutput(); + + + $result = Process::run('ls -la'); + + $result->successful(); + $result->failed(); + $result->exitCode(); + $result->output(); + $result->errorOutput(); + +#### Throwing Exceptions + +If you have a process result and would like to throw an instance of +`Illuminate\Process\Exceptions\ProcessFailedException` if the exit code is +greater than zero (thus indicating failure), you may use the `throw` and +`throwIf` methods. If the process did not fail, the `ProcessResult` instance +will be returned: + + + + 1$result = Process::run('ls -la')->throw(); + + 2  + + 3$result = Process::run('ls -la')->throwIf($condition); + + + $result = Process::run('ls -la')->throw(); + + $result = Process::run('ls -la')->throwIf($condition); + +### Process Options + +Of course, you may need to customize the behavior of a process before invoking +it. Thankfully, Laravel allows you to tweak a variety of process features, +such as the working directory, timeout, and environment variables. + +#### Working Directory Path + +You may use the `path` method to specify the working directory of the process. +If this method is not invoked, the process will inherit the working directory +of the currently executing PHP script: + + + + 1$result = Process::path(__DIR__)->run('ls -la'); + + + $result = Process::path(__DIR__)->run('ls -la'); + +#### Input + +You may provide input via the "standard input" of the process using the +`input` method: + + + + 1$result = Process::input('Hello World')->run('cat'); + + + $result = Process::input('Hello World')->run('cat'); + +#### Timeouts + +By default, processes will throw an instance of +`Illuminate\Process\Exceptions\ProcessTimedOutException` after executing for +more than 60 seconds. However, you can customize this behavior via the +`timeout` method: + + + + 1$result = Process::timeout(120)->run('bash import.sh'); + + + $result = Process::timeout(120)->run('bash import.sh'); + +Or, if you would like to disable the process timeout entirely, you may invoke +the `forever` method: + + + + 1$result = Process::forever()->run('bash import.sh'); + + + $result = Process::forever()->run('bash import.sh'); + +The `idleTimeout` method may be used to specify the maximum number of seconds +the process may run without returning any output: + + + + 1$result = Process::timeout(60)->idleTimeout(30)->run('bash import.sh'); + + + $result = Process::timeout(60)->idleTimeout(30)->run('bash import.sh'); + +#### Environment Variables + +Environment variables may be provided to the process via the `env` method. The +invoked process will also inherit all of the environment variables defined by +your system: + + + + 1$result = Process::forever() + + 2 ->env(['IMPORT_PATH' => __DIR__]) + + 3 ->run('bash import.sh'); + + + $result = Process::forever() + ->env(['IMPORT_PATH' => __DIR__]) + ->run('bash import.sh'); + +If you wish to remove an inherited environment variable from the invoked +process, you may provide that environment variable with a value of `false`: + + + + 1$result = Process::forever() + + 2 ->env(['LOAD_PATH' => false]) + + 3 ->run('bash import.sh'); + + + $result = Process::forever() + ->env(['LOAD_PATH' => false]) + ->run('bash import.sh'); + +#### TTY Mode + +The `tty` method may be used to enable TTY mode for your process. TTY mode +connects the input and output of the process to the input and output of your +program, allowing your process to open an editor like Vim or Nano as a +process: + + + + 1Process::forever()->tty()->run('vim'); + + + Process::forever()->tty()->run('vim'); + +TTY mode is not supported on Windows. + +### Process Output + +As previously discussed, process output may be accessed using the `output` +(stdout) and `errorOutput` (stderr) methods on a process result: + + + + 1use Illuminate\Support\Facades\Process; + + 2  + + 3$result = Process::run('ls -la'); + + 4  + + 5echo $result->output(); + + 6echo $result->errorOutput(); + + + use Illuminate\Support\Facades\Process; + + $result = Process::run('ls -la'); + + echo $result->output(); + echo $result->errorOutput(); + +However, output may also be gathered in real-time by passing a closure as the +second argument to the `run` method. The closure will receive two arguments: +the "type" of output (`stdout` or `stderr`) and the output string itself: + + + + 1$result = Process::run('ls -la', function (string $type, string $output) { + + 2 echo $output; + + 3}); + + + $result = Process::run('ls -la', function (string $type, string $output) { + echo $output; + }); + +Laravel also offers the `seeInOutput` and `seeInErrorOutput` methods, which +provide a convenient way to determine if a given string was contained in the +process' output: + + + + 1if (Process::run('ls -la')->seeInOutput('laravel')) { + + 2 // ... + + 3} + + + if (Process::run('ls -la')->seeInOutput('laravel')) { + // ... + } + +#### Disabling Process Output + +If your process is writing a significant amount of output that you are not +interested in, you can conserve memory by disabling output retrieval entirely. +To accomplish this, invoke the `quietly` method while building the process: + + + + 1use Illuminate\Support\Facades\Process; + + 2  + + 3$result = Process::quietly()->run('bash import.sh'); + + + use Illuminate\Support\Facades\Process; + + $result = Process::quietly()->run('bash import.sh'); + +### Pipelines + +Sometimes you may want to make the output of one process the input of another +process. This is often referred to as "piping" the output of a process into +another. The `pipe` method provided by the `Process` facades makes this easy +to accomplish. The `pipe` method will execute the piped processes +synchronously and return the process result for the last process in the +pipeline: + + + + 1use Illuminate\Process\Pipe; + + 2use Illuminate\Support\Facades\Process; + + 3  + + 4$result = Process::pipe(function (Pipe $pipe) { + + 5 $pipe->command('cat example.txt'); + + 6 $pipe->command('grep -i "laravel"'); + + 7}); + + 8  + + 9if ($result->successful()) { + + 10 // ... + + 11} + + + use Illuminate\Process\Pipe; + use Illuminate\Support\Facades\Process; + + $result = Process::pipe(function (Pipe $pipe) { + $pipe->command('cat example.txt'); + $pipe->command('grep -i "laravel"'); + }); + + if ($result->successful()) { + // ... + } + +If you do not need to customize the individual processes that make up the +pipeline, you may simply pass an array of command strings to the `pipe` +method: + + + + 1$result = Process::pipe([ + + 2 'cat example.txt', + + 3 'grep -i "laravel"', + + 4]); + + + $result = Process::pipe([ + 'cat example.txt', + 'grep -i "laravel"', + ]); + +The process output may be gathered in real-time by passing a closure as the +second argument to the `pipe` method. The closure will receive two arguments: +the "type" of output (`stdout` or `stderr`) and the output string itself: + + + + 1$result = Process::pipe(function (Pipe $pipe) { + + 2 $pipe->command('cat example.txt'); + + 3 $pipe->command('grep -i "laravel"'); + + 4}, function (string $type, string $output) { + + 5 echo $output; + + 6}); + + + $result = Process::pipe(function (Pipe $pipe) { + $pipe->command('cat example.txt'); + $pipe->command('grep -i "laravel"'); + }, function (string $type, string $output) { + echo $output; + }); + +Laravel also allows you to assign string keys to each process within a +pipeline via the `as` method. This key will also be passed to the output +closure provided to the `pipe` method, allowing you to determine which process +the output belongs to: + + + + 1$result = Process::pipe(function (Pipe $pipe) { + + 2 $pipe->as('first')->command('cat example.txt'); + + 3 $pipe->as('second')->command('grep -i "laravel"'); + + 4}, function (string $type, string $output, string $key) { + + 5 // ... + + 6}); + + + $result = Process::pipe(function (Pipe $pipe) { + $pipe->as('first')->command('cat example.txt'); + $pipe->as('second')->command('grep -i "laravel"'); + }, function (string $type, string $output, string $key) { + // ... + }); + +## Asynchronous Processes + +While the `run` method invokes processes synchronously, the `start` method may +be used to invoke a process asynchronously. This allows your application to +continue performing other tasks while the process runs in the background. Once +the process has been invoked, you may utilize the `running` method to +determine if the process is still running: + + + + 1$process = Process::timeout(120)->start('bash import.sh'); + + 2  + + 3while ($process->running()) { + + 4 // ... + + 5} + + 6  + + 7$result = $process->wait(); + + + $process = Process::timeout(120)->start('bash import.sh'); + + while ($process->running()) { + // ... + } + + $result = $process->wait(); + +As you may have noticed, you may invoke the `wait` method to wait until the +process is finished executing and retrieve the `ProcessResult` instance: + + + + 1$process = Process::timeout(120)->start('bash import.sh'); + + 2  + + 3// ... + + 4  + + 5$result = $process->wait(); + + + $process = Process::timeout(120)->start('bash import.sh'); + + // ... + + $result = $process->wait(); + +### Process IDs and Signals + +The `id` method may be used to retrieve the operating system assigned process +ID of the running process: + + + + 1$process = Process::start('bash import.sh'); + + 2  + + 3return $process->id(); + + + $process = Process::start('bash import.sh'); + + return $process->id(); + +You may use the `signal` method to send a "signal" to the running process. A +list of predefined signal constants can be found within the [PHP +documentation](https://www.php.net/manual/en/pcntl.constants.php): + + + + 1$process->signal(SIGUSR2); + + + $process->signal(SIGUSR2); + +### Asynchronous Process Output + +While an asynchronous process is running, you may access its entire current +output using the `output` and `errorOutput` methods; however, you may utilize +the `latestOutput` and `latestErrorOutput` to access the output from the +process that has occurred since the output was last retrieved: + + + + 1$process = Process::timeout(120)->start('bash import.sh'); + + 2  + + 3while ($process->running()) { + + 4 echo $process->latestOutput(); + + 5 echo $process->latestErrorOutput(); + + 6  + + 7 sleep(1); + + 8} + + + $process = Process::timeout(120)->start('bash import.sh'); + + while ($process->running()) { + echo $process->latestOutput(); + echo $process->latestErrorOutput(); + + sleep(1); + } + +Like the `run` method, output may also be gathered in real-time from +asynchronous processes by passing a closure as the second argument to the +`start` method. The closure will receive two arguments: the "type" of output +(`stdout` or `stderr`) and the output string itself: + + + + 1$process = Process::start('bash import.sh', function (string $type, string $output) { + + 2 echo $output; + + 3}); + + 4  + + 5$result = $process->wait(); + + + $process = Process::start('bash import.sh', function (string $type, string $output) { + echo $output; + }); + + $result = $process->wait(); + +Instead of waiting until the process has finished, you may use the `waitUntil` +method to stop waiting based on the output of the process. Laravel will stop +waiting for the process to finish when the closure given to the `waitUntil` +method returns `true`: + + + + 1$process = Process::start('bash import.sh'); + + 2  + + 3$process->waitUntil(function (string $type, string $output) { + + 4 return $output === 'Ready...'; + + 5}); + + + $process = Process::start('bash import.sh'); + + $process->waitUntil(function (string $type, string $output) { + return $output === 'Ready...'; + }); + +### Asynchronous Process Timeouts + +While an asynchronous process is running, you may verify that the process has +not timed out using the `ensureNotTimedOut` method. This method will throw a +timeout exception if the process has timed out: + + + + 1$process = Process::timeout(120)->start('bash import.sh'); + + 2  + + 3while ($process->running()) { + + 4 $process->ensureNotTimedOut(); + + 5  + + 6 // ... + + 7  + + 8 sleep(1); + + 9} + + + $process = Process::timeout(120)->start('bash import.sh'); + + while ($process->running()) { + $process->ensureNotTimedOut(); + + // ... + + sleep(1); + } + +## Concurrent Processes + +Laravel also makes it a breeze to manage a pool of concurrent, asynchronous +processes, allowing you to easily execute many tasks simultaneously. To get +started, invoke the `pool` method, which accepts a closure that receives an +instance of `Illuminate\Process\Pool`. + +Within this closure, you may define the processes that belong to the pool. +Once a process pool is started via the `start` method, you may access the +[collection](/docs/12.x/collections) of running processes via the `running` +method: + + + + 1use Illuminate\Process\Pool; + + 2use Illuminate\Support\Facades\Process; + + 3  + + 4$pool = Process::pool(function (Pool $pool) { + + 5 $pool->path(__DIR__)->command('bash import-1.sh'); + + 6 $pool->path(__DIR__)->command('bash import-2.sh'); + + 7 $pool->path(__DIR__)->command('bash import-3.sh'); + + 8})->start(function (string $type, string $output, int $key) { + + 9 // ... + + 10}); + + 11  + + 12while ($pool->running()->isNotEmpty()) { + + 13 // ... + + 14} + + 15  + + 16$results = $pool->wait(); + + + use Illuminate\Process\Pool; + use Illuminate\Support\Facades\Process; + + $pool = Process::pool(function (Pool $pool) { + $pool->path(__DIR__)->command('bash import-1.sh'); + $pool->path(__DIR__)->command('bash import-2.sh'); + $pool->path(__DIR__)->command('bash import-3.sh'); + })->start(function (string $type, string $output, int $key) { + // ... + }); + + while ($pool->running()->isNotEmpty()) { + // ... + } + + $results = $pool->wait(); + +As you can see, you may wait for all of the pool processes to finish executing +and resolve their results via the `wait` method. The `wait` method returns an +array accessible object that allows you to access the `ProcessResult` instance +of each process in the pool by its key: + + + + 1$results = $pool->wait(); + + 2  + + 3echo $results[0]->output(); + + + $results = $pool->wait(); + + echo $results[0]->output(); + +Or, for convenience, the `concurrently` method may be used to start an +asynchronous process pool and immediately wait on its results. This can +provide particularly expressive syntax when combined with PHP's array +destructuring capabilities: + + + + 1[$first, $second, $third] = Process::concurrently(function (Pool $pool) { + + 2 $pool->path(__DIR__)->command('ls -la'); + + 3 $pool->path(app_path())->command('ls -la'); + + 4 $pool->path(storage_path())->command('ls -la'); + + 5}); + + 6  + + 7echo $first->output(); + + + [$first, $second, $third] = Process::concurrently(function (Pool $pool) { + $pool->path(__DIR__)->command('ls -la'); + $pool->path(app_path())->command('ls -la'); + $pool->path(storage_path())->command('ls -la'); + }); + + echo $first->output(); + +### Naming Pool Processes + +Accessing process pool results via a numeric key is not very expressive; +therefore, Laravel allows you to assign string keys to each process within a +pool via the `as` method. This key will also be passed to the closure provided +to the `start` method, allowing you to determine which process the output +belongs to: + + + + 1$pool = Process::pool(function (Pool $pool) { + + 2 $pool->as('first')->command('bash import-1.sh'); + + 3 $pool->as('second')->command('bash import-2.sh'); + + 4 $pool->as('third')->command('bash import-3.sh'); + + 5})->start(function (string $type, string $output, string $key) { + + 6 // ... + + 7}); + + 8  + + 9$results = $pool->wait(); + + 10  + + 11return $results['first']->output(); + + + $pool = Process::pool(function (Pool $pool) { + $pool->as('first')->command('bash import-1.sh'); + $pool->as('second')->command('bash import-2.sh'); + $pool->as('third')->command('bash import-3.sh'); + })->start(function (string $type, string $output, string $key) { + // ... + }); + + $results = $pool->wait(); + + return $results['first']->output(); + +### Pool Process IDs and Signals + +Since the process pool's `running` method provides a collection of all invoked +processes within the pool, you may easily access the underlying pool process +IDs: + + + + 1$processIds = $pool->running()->each->id(); + + + $processIds = $pool->running()->each->id(); + +And, for convenience, you may invoke the `signal` method on a process pool to +send a signal to every process within the pool: + + + + 1$pool->signal(SIGUSR2); + + + $pool->signal(SIGUSR2); + +## Testing + +Many Laravel services provide functionality to help you easily and +expressively write tests, and Laravel's process service is no exception. The +`Process` facade's `fake` method allows you to instruct Laravel to return +stubbed / dummy results when processes are invoked. + +### Faking Processes + +To explore Laravel's ability to fake processes, let's imagine a route that +invokes a process: + + + + 1use Illuminate\Support\Facades\Process; + + 2use Illuminate\Support\Facades\Route; + + 3  + + 4Route::get('/import', function () { + + 5 Process::run('bash import.sh'); + + 6  + + 7 return 'Import complete!'; + + 8}); + + + use Illuminate\Support\Facades\Process; + use Illuminate\Support\Facades\Route; + + Route::get('/import', function () { + Process::run('bash import.sh'); + + return 'Import complete!'; + }); + +When testing this route, we can instruct Laravel to return a fake, successful +process result for every invoked process by calling the `fake` method on the +`Process` facade with no arguments. In addition, we can even assert that a +given process was "run": + +Pest PHPUnit + + + + 1get('/import'); + + 11  + + 12 // Simple process assertion... + + 13 Process::assertRan('bash import.sh'); + + 14  + + 15 // Or, inspecting the process configuration... + + 16 Process::assertRan(function (PendingProcess $process, ProcessResult $result) { + + 17 return $process->command === 'bash import.sh' && + + 18 $process->timeout === 60; + + 19 }); + + 20}); + + + get('/import'); + + // Simple process assertion... + Process::assertRan('bash import.sh'); + + // Or, inspecting the process configuration... + Process::assertRan(function (PendingProcess $process, ProcessResult $result) { + return $process->command === 'bash import.sh' && + $process->timeout === 60; + }); + }); + + + 1get('/import'); + + 17  + + 18 // Simple process assertion... + + 19 Process::assertRan('bash import.sh'); + + 20  + + 21 // Or, inspecting the process configuration... + + 22 Process::assertRan(function (PendingProcess $process, ProcessResult $result) { + + 23 return $process->command === 'bash import.sh' && + + 24 $process->timeout === 60; + + 25 }); + + 26 } + + 27} + + + get('/import'); + + // Simple process assertion... + Process::assertRan('bash import.sh'); + + // Or, inspecting the process configuration... + Process::assertRan(function (PendingProcess $process, ProcessResult $result) { + return $process->command === 'bash import.sh' && + $process->timeout === 60; + }); + } + } + +As discussed, invoking the `fake` method on the `Process` facade will instruct +Laravel to always return a successful process result with no output. However, +you may easily specify the output and exit code for faked processes using the +`Process` facade's `result` method: + + + + 1Process::fake([ + + 2 '*' => Process::result( + + 3 output: 'Test output', + + 4 errorOutput: 'Test error output', + + 5 exitCode: 1, + + 6 ), + + 7]); + + + Process::fake([ + '*' => Process::result( + output: 'Test output', + errorOutput: 'Test error output', + exitCode: 1, + ), + ]); + +### Faking Specific Processes + +As you may have noticed in a previous example, the `Process` facade allows you +to specify different fake results per process by passing an array to the +`fake` method. + +The array's keys should represent command patterns that you wish to fake and +their associated results. The `*` character may be used as a wildcard +character. Any process commands that have not been faked will actually be +invoked. You may use the `Process` facade's `result` method to construct stub +/ fake results for these commands: + + + + 1Process::fake([ + + 2 'cat *' => Process::result( + + 3 output: 'Test "cat" output', + + 4 ), + + 5 'ls *' => Process::result( + + 6 output: 'Test "ls" output', + + 7 ), + + 8]); + + + Process::fake([ + 'cat *' => Process::result( + output: 'Test "cat" output', + ), + 'ls *' => Process::result( + output: 'Test "ls" output', + ), + ]); + +If you do not need to customize the exit code or error output of a faked +process, you may find it more convenient to specify the fake process results +as simple strings: + + + + 1Process::fake([ + + 2 'cat *' => 'Test "cat" output', + + 3 'ls *' => 'Test "ls" output', + + 4]); + + + Process::fake([ + 'cat *' => 'Test "cat" output', + 'ls *' => 'Test "ls" output', + ]); + +### Faking Process Sequences + +If the code you are testing invokes multiple processes with the same command, +you may wish to assign a different fake process result to each process +invocation. You may accomplish this via the `Process` facade's `sequence` +method: + + + + 1Process::fake([ + + 2 'ls *' => Process::sequence() + + 3 ->push(Process::result('First invocation')) + + 4 ->push(Process::result('Second invocation')), + + 5]); + + + Process::fake([ + 'ls *' => Process::sequence() + ->push(Process::result('First invocation')) + ->push(Process::result('Second invocation')), + ]); + +### Faking Asynchronous Process Lifecycles + +Thus far, we have primarily discussed faking processes which are invoked +synchronously using the `run` method. However, if you are attempting to test +code that interacts with asynchronous processes invoked via `start`, you may +need a more sophisticated approach to describing your fake processes. + +For example, let's imagine the following route which interacts with an +asynchronous process: + + + + 1use Illuminate\Support\Facades\Log; + + 2use Illuminate\Support\Facades\Route; + + 3  + + 4Route::get('/import', function () { + + 5 $process = Process::start('bash import.sh'); + + 6  + + 7 while ($process->running()) { + + 8 Log::info($process->latestOutput()); + + 9 Log::info($process->latestErrorOutput()); + + 10 } + + 11  + + 12 return 'Done'; + + 13}); + + + use Illuminate\Support\Facades\Log; + use Illuminate\Support\Facades\Route; + + Route::get('/import', function () { + $process = Process::start('bash import.sh'); + + while ($process->running()) { + Log::info($process->latestOutput()); + Log::info($process->latestErrorOutput()); + } + + return 'Done'; + }); + +To properly fake this process, we need to be able to describe how many times +the `running` method should return `true`. In addition, we may want to specify +multiple lines of output that should be returned in sequence. To accomplish +this, we can use the `Process` facade's `describe` method: + + + + 1Process::fake([ + + 2 'bash import.sh' => Process::describe() + + 3 ->output('First line of standard output') + + 4 ->errorOutput('First line of error output') + + 5 ->output('Second line of standard output') + + 6 ->exitCode(0) + + 7 ->iterations(3), + + 8]); + + + Process::fake([ + 'bash import.sh' => Process::describe() + ->output('First line of standard output') + ->errorOutput('First line of error output') + ->output('Second line of standard output') + ->exitCode(0) + ->iterations(3), + ]); + +Let's dig into the example above. Using the `output` and `errorOutput` +methods, we may specify multiple lines of output that will be returned in +sequence. The `exitCode` method may be used to specify the final exit code of +the fake process. Finally, the `iterations` method may be used to specify how +many times the `running` method should return `true`. + +### Available Assertions + +As previously discussed, Laravel provides several process assertions for your +feature tests. We'll discuss each of these assertions below. + +#### assertRan + +Assert that a given process was invoked: + + + + 1use Illuminate\Support\Facades\Process; + + 2  + + 3Process::assertRan('ls -la'); + + + use Illuminate\Support\Facades\Process; + + Process::assertRan('ls -la'); + +The `assertRan` method also accepts a closure, which will receive an instance +of a process and a process result, allowing you to inspect the process' +configured options. If this closure returns `true`, the assertion will "pass": + + + + 1Process::assertRan(fn ($process, $result) => + + 2 $process->command === 'ls -la' && + + 3 $process->path === __DIR__ && + + 4 $process->timeout === 60 + + 5); + + + Process::assertRan(fn ($process, $result) => + $process->command === 'ls -la' && + $process->path === __DIR__ && + $process->timeout === 60 + ); + +The `$process` passed to the `assertRan` closure is an instance of +`Illuminate\Process\PendingProcess`, while the `$result` is an instance of +`Illuminate\Contracts\Process\ProcessResult`. + +#### assertDidntRun + +Assert that a given process was not invoked: + + + + 1use Illuminate\Support\Facades\Process; + + 2  + + 3Process::assertDidntRun('ls -la'); + + + use Illuminate\Support\Facades\Process; + + Process::assertDidntRun('ls -la'); + +Like the `assertRan` method, the `assertDidntRun` method also accepts a +closure, which will receive an instance of a process and a process result, +allowing you to inspect the process' configured options. If this closure +returns `true`, the assertion will "fail": + + + + 1Process::assertDidntRun(fn (PendingProcess $process, ProcessResult $result) => + + 2 $process->command === 'ls -la' + + 3); + + + Process::assertDidntRun(fn (PendingProcess $process, ProcessResult $result) => + $process->command === 'ls -la' + ); + +#### assertRanTimes + +Assert that a given process was invoked a given number of times: + + + + 1use Illuminate\Support\Facades\Process; + + 2  + + 3Process::assertRanTimes('ls -la', times: 3); + + + use Illuminate\Support\Facades\Process; + + Process::assertRanTimes('ls -la', times: 3); + +The `assertRanTimes` method also accepts a closure, which will receive an +instance of `PendingProcess` and `ProcessResult`, allowing you to inspect the +process' configured options. If this closure returns `true` and the process +was invoked the specified number of times, the assertion will "pass": + + + + 1Process::assertRanTimes(function (PendingProcess $process, ProcessResult $result) { + + 2 return $process->command === 'ls -la'; + + 3}, times: 3); + + + Process::assertRanTimes(function (PendingProcess $process, ProcessResult $result) { + return $process->command === 'ls -la'; + }, times: 3); + +### Preventing Stray Processes + +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: + + + + 1use Illuminate\Support\Facades\Process; + + 2  + + 3Process::preventStrayProcesses(); + + 4  + + 5Process::fake([ + + 6 'ls *' => 'Test output...', + + 7]); + + 8  + + 9// Fake response is returned... + + 10Process::run('ls -la'); + + 11  + + 12// An exception is thrown... + + 13Process::run('bash import.sh'); + + + use Illuminate\Support\Facades\Process; + + Process::preventStrayProcesses(); + + Process::fake([ + 'ls *' => 'Test output...', + ]); + + // Fake response is returned... + Process::run('ls -la'); + + // An exception is thrown... + Process::run('bash import.sh'); + diff --git a/output/12.x/prompts.md b/output/12.x/prompts.md new file mode 100644 index 0000000..b1b9ffa --- /dev/null +++ b/output/12.x/prompts.md @@ -0,0 +1,2215 @@ +# Prompts + + * Introduction + * Installation + * Available Prompts + * Text + * Textarea + * Password + * Confirm + * Select + * Multi-select + * Suggest + * Search + * Multi-search + * Pause + * Transforming Input Before Validation + * Forms + * Informational Messages + * Tables + * Spin + * Progress Bar + * Clearing the Terminal + * Terminal Considerations + * Unsupported Environments and Fallbacks + * Testing + +## Introduction + +[Laravel Prompts](https://github.com/laravel/prompts) is a PHP package for +adding beautiful and user-friendly forms to your command-line applications, +with browser-like features including placeholder text and validation. + +![](https://laravel.com/img/docs/prompts-example.png) + +Laravel Prompts is perfect for accepting user input in your [Artisan console +commands](/docs/12.x/artisan#writing-commands), but it may also be used in any +command-line PHP project. + +Laravel Prompts supports macOS, Linux, and Windows with WSL. For more +information, please see our documentation on unsupported environments & +fallbacks. + +## Installation + +Laravel Prompts is already included with the latest release of Laravel. + +Laravel Prompts may also be installed in your other PHP projects by using the +Composer package manager: + + + + 1composer require laravel/prompts + + + composer require laravel/prompts + +## Available Prompts + +### Text + +The `text` function will prompt the user with the given question, accept their +input, and then return it: + + + + 1use function Laravel\Prompts\text; + + 2  + + 3$name = text('What is your name?'); + + + use function Laravel\Prompts\text; + + $name = text('What is your name?'); + +You may also include placeholder text, a default value, and an informational +hint: + + + + 1$name = text( + + 2 label: 'What is your name?', + + 3 placeholder: 'E.g. Taylor Otwell', + + 4 default: $user?->name, + + 5 hint: 'This will be displayed on your profile.' + + 6); + + + $name = text( + label: 'What is your name?', + placeholder: 'E.g. Taylor Otwell', + default: $user?->name, + hint: 'This will be displayed on your profile.' + ); + +#### Required Values + +If you require a value to be entered, you may pass the `required` argument: + + + + 1$name = text( + + 2 label: 'What is your name?', + + 3 required: true + + 4); + + + $name = text( + label: 'What is your name?', + required: true + ); + +If you would like to customize the validation message, you may also pass a +string: + + + + 1$name = text( + + 2 label: 'What is your name?', + + 3 required: 'Your name is required.' + + 4); + + + $name = text( + label: 'What is your name?', + required: 'Your name is required.' + ); + +#### Additional Validation + +Finally, if you would like to perform additional validation logic, you may +pass a closure to the `validate` argument: + + + + 1$name = text( + + 2 label: 'What is your name?', + + 3 validate: fn (string $value) => match (true) { + + 4 strlen($value) < 3 => 'The name must be at least 3 characters.', + + 5 strlen($value) > 255 => 'The name must not exceed 255 characters.', + + 6 default => null + + 7 } + + 8); + + + $name = text( + label: 'What is your name?', + validate: fn (string $value) => match (true) { + strlen($value) < 3 => 'The name must be at least 3 characters.', + strlen($value) > 255 => 'The name must not exceed 255 characters.', + default => null + } + ); + +The closure will receive the value that has been entered and may return an +error message, or `null` if the validation passes. + +Alternatively, you may leverage the power of Laravel's +[validator](/docs/12.x/validation). To do so, provide an array containing the +name of the attribute and the desired validation rules to the `validate` +argument: + + + + 1$name = text( + + 2 label: 'What is your name?', + + 3 validate: ['name' => 'required|max:255|unique:users'] + + 4); + + + $name = text( + label: 'What is your name?', + validate: ['name' => 'required|max:255|unique:users'] + ); + +### Textarea + +The `textarea` function will prompt the user with the given question, accept +their input via a multi-line textarea, and then return it: + + + + 1use function Laravel\Prompts\textarea; + + 2  + + 3$story = textarea('Tell me a story.'); + + + use function Laravel\Prompts\textarea; + + $story = textarea('Tell me a story.'); + +You may also include placeholder text, a default value, and an informational +hint: + + + + 1$story = textarea( + + 2 label: 'Tell me a story.', + + 3 placeholder: 'This is a story about...', + + 4 hint: 'This will be displayed on your profile.' + + 5); + + + $story = textarea( + label: 'Tell me a story.', + placeholder: 'This is a story about...', + hint: 'This will be displayed on your profile.' + ); + +#### Required Values + +If you require a value to be entered, you may pass the `required` argument: + + + + 1$story = textarea( + + 2 label: 'Tell me a story.', + + 3 required: true + + 4); + + + $story = textarea( + label: 'Tell me a story.', + required: true + ); + +If you would like to customize the validation message, you may also pass a +string: + + + + 1$story = textarea( + + 2 label: 'Tell me a story.', + + 3 required: 'A story is required.' + + 4); + + + $story = textarea( + label: 'Tell me a story.', + required: 'A story is required.' + ); + +#### Additional Validation + +Finally, if you would like to perform additional validation logic, you may +pass a closure to the `validate` argument: + + + + 1$story = textarea( + + 2 label: 'Tell me a story.', + + 3 validate: fn (string $value) => match (true) { + + 4 strlen($value) < 250 => 'The story must be at least 250 characters.', + + 5 strlen($value) > 10000 => 'The story must not exceed 10,000 characters.', + + 6 default => null + + 7 } + + 8); + + + $story = textarea( + label: 'Tell me a story.', + validate: fn (string $value) => match (true) { + strlen($value) < 250 => 'The story must be at least 250 characters.', + strlen($value) > 10000 => 'The story must not exceed 10,000 characters.', + default => null + } + ); + +The closure will receive the value that has been entered and may return an +error message, or `null` if the validation passes. + +Alternatively, you may leverage the power of Laravel's +[validator](/docs/12.x/validation). To do so, provide an array containing the +name of the attribute and the desired validation rules to the `validate` +argument: + + + + 1$story = textarea( + + 2 label: 'Tell me a story.', + + 3 validate: ['story' => 'required|max:10000'] + + 4); + + + $story = textarea( + label: 'Tell me a story.', + validate: ['story' => 'required|max:10000'] + ); + +### Password + +The `password` function is similar to the `text` function, but the user's +input will be masked as they type in the console. This is useful when asking +for sensitive information such as passwords: + + + + 1use function Laravel\Prompts\password; + + 2  + + 3$password = password('What is your password?'); + + + use function Laravel\Prompts\password; + + $password = password('What is your password?'); + +You may also include placeholder text and an informational hint: + + + + 1$password = password( + + 2 label: 'What is your password?', + + 3 placeholder: 'password', + + 4 hint: 'Minimum 8 characters.' + + 5); + + + $password = password( + label: 'What is your password?', + placeholder: 'password', + hint: 'Minimum 8 characters.' + ); + +#### Required Values + +If you require a value to be entered, you may pass the `required` argument: + + + + 1$password = password( + + 2 label: 'What is your password?', + + 3 required: true + + 4); + + + $password = password( + label: 'What is your password?', + required: true + ); + +If you would like to customize the validation message, you may also pass a +string: + + + + 1$password = password( + + 2 label: 'What is your password?', + + 3 required: 'The password is required.' + + 4); + + + $password = password( + label: 'What is your password?', + required: 'The password is required.' + ); + +#### Additional Validation + +Finally, if you would like to perform additional validation logic, you may +pass a closure to the `validate` argument: + + + + 1$password = password( + + 2 label: 'What is your password?', + + 3 validate: fn (string $value) => match (true) { + + 4 strlen($value) < 8 => 'The password must be at least 8 characters.', + + 5 default => null + + 6 } + + 7); + + + $password = password( + label: 'What is your password?', + validate: fn (string $value) => match (true) { + strlen($value) < 8 => 'The password must be at least 8 characters.', + default => null + } + ); + +The closure will receive the value that has been entered and may return an +error message, or `null` if the validation passes. + +Alternatively, you may leverage the power of Laravel's +[validator](/docs/12.x/validation). To do so, provide an array containing the +name of the attribute and the desired validation rules to the `validate` +argument: + + + + 1$password = password( + + 2 label: 'What is your password?', + + 3 validate: ['password' => 'min:8'] + + 4); + + + $password = password( + label: 'What is your password?', + validate: ['password' => 'min:8'] + ); + +### Confirm + +If you need to ask the user for a "yes or no" confirmation, you may use the +`confirm` function. Users may use the arrow keys or press `y` or `n` to select +their response. This function will return either `true` or `false`. + + + + 1use function Laravel\Prompts\confirm; + + 2  + + 3$confirmed = confirm('Do you accept the terms?'); + + + use function Laravel\Prompts\confirm; + + $confirmed = confirm('Do you accept the terms?'); + +You may also include a default value, customized wording for the "Yes" and +"No" labels, and an informational hint: + + + + 1$confirmed = confirm( + + 2 label: 'Do you accept the terms?', + + 3 default: false, + + 4 yes: 'I accept', + + 5 no: 'I decline', + + 6 hint: 'The terms must be accepted to continue.' + + 7); + + + $confirmed = confirm( + label: 'Do you accept the terms?', + default: false, + yes: 'I accept', + no: 'I decline', + hint: 'The terms must be accepted to continue.' + ); + +#### Requiring "Yes" + +If necessary, you may require your users to select "Yes" by passing the +`required` argument: + + + + 1$confirmed = confirm( + + 2 label: 'Do you accept the terms?', + + 3 required: true + + 4); + + + $confirmed = confirm( + label: 'Do you accept the terms?', + required: true + ); + +If you would like to customize the validation message, you may also pass a +string: + + + + 1$confirmed = confirm( + + 2 label: 'Do you accept the terms?', + + 3 required: 'You must accept the terms to continue.' + + 4); + + + $confirmed = confirm( + label: 'Do you accept the terms?', + required: 'You must accept the terms to continue.' + ); + +### Select + +If you need the user to select from a predefined set of choices, you may use +the `select` function: + + + + 1use function Laravel\Prompts\select; + + 2  + + 3$role = select( + + 4 label: 'What role should the user have?', + + 5 options: ['Member', 'Contributor', 'Owner'] + + 6); + + + use function Laravel\Prompts\select; + + $role = select( + label: 'What role should the user have?', + options: ['Member', 'Contributor', 'Owner'] + ); + +You may also specify the default choice and an informational hint: + + + + 1$role = select( + + 2 label: 'What role should the user have?', + + 3 options: ['Member', 'Contributor', 'Owner'], + + 4 default: 'Owner', + + 5 hint: 'The role may be changed at any time.' + + 6); + + + $role = select( + label: 'What role should the user have?', + options: ['Member', 'Contributor', 'Owner'], + default: 'Owner', + hint: 'The role may be changed at any time.' + ); + +You may also pass an associative array to the `options` argument to have the +selected key returned instead of its value: + + + + 1$role = select( + + 2 label: 'What role should the user have?', + + 3 options: [ + + 4 'member' => 'Member', + + 5 'contributor' => 'Contributor', + + 6 'owner' => 'Owner', + + 7 ], + + 8 default: 'owner' + + 9); + + + $role = select( + label: 'What role should the user have?', + options: [ + 'member' => 'Member', + 'contributor' => 'Contributor', + 'owner' => 'Owner', + ], + default: 'owner' + ); + +Up to five options will be displayed before the list begins to scroll. You may +customize this by passing the `scroll` argument: + + + + 1$role = select( + + 2 label: 'Which category would you like to assign?', + + 3 options: Category::pluck('name', 'id'), + + 4 scroll: 10 + + 5); + + + $role = select( + label: 'Which category would you like to assign?', + options: Category::pluck('name', 'id'), + scroll: 10 + ); + +#### Additional Validation + +Unlike other prompt functions, the `select` function doesn't accept the +`required` argument because it is not possible to select nothing. However, you +may pass a closure to the `validate` argument if you need to present an option +but prevent it from being selected: + + + + 1$role = select( + + 2 label: 'What role should the user have?', + + 3 options: [ + + 4 'member' => 'Member', + + 5 'contributor' => 'Contributor', + + 6 'owner' => 'Owner', + + 7 ], + + 8 validate: fn (string $value) => + + 9 $value === 'owner' && User::where('role', 'owner')->exists() + + 10 ? 'An owner already exists.' + + 11 : null + + 12); + + + $role = select( + label: 'What role should the user have?', + options: [ + 'member' => 'Member', + 'contributor' => 'Contributor', + 'owner' => 'Owner', + ], + validate: fn (string $value) => + $value === 'owner' && User::where('role', 'owner')->exists() + ? 'An owner already exists.' + : null + ); + +If the `options` argument is an associative array, then the closure will +receive the selected key, otherwise it will receive the selected value. The +closure may return an error message, or `null` if the validation passes. + +### Multi-select + +If you need the user to be able to select multiple options, you may use the +`multiselect` function: + + + + 1use function Laravel\Prompts\multiselect; + + 2  + + 3$permissions = multiselect( + + 4 label: 'What permissions should be assigned?', + + 5 options: ['Read', 'Create', 'Update', 'Delete'] + + 6); + + + use function Laravel\Prompts\multiselect; + + $permissions = multiselect( + label: 'What permissions should be assigned?', + options: ['Read', 'Create', 'Update', 'Delete'] + ); + +You may also specify default choices and an informational hint: + + + + 1use function Laravel\Prompts\multiselect; + + 2  + + 3$permissions = multiselect( + + 4 label: 'What permissions should be assigned?', + + 5 options: ['Read', 'Create', 'Update', 'Delete'], + + 6 default: ['Read', 'Create'], + + 7 hint: 'Permissions may be updated at any time.' + + 8); + + + use function Laravel\Prompts\multiselect; + + $permissions = multiselect( + label: 'What permissions should be assigned?', + options: ['Read', 'Create', 'Update', 'Delete'], + default: ['Read', 'Create'], + hint: 'Permissions may be updated at any time.' + ); + +You may also pass an associative array to the `options` argument to return the +selected options' keys instead of their values: + + + + 1$permissions = multiselect( + + 2 label: 'What permissions should be assigned?', + + 3 options: [ + + 4 'read' => 'Read', + + 5 'create' => 'Create', + + 6 'update' => 'Update', + + 7 'delete' => 'Delete', + + 8 ], + + 9 default: ['read', 'create'] + + 10); + + + $permissions = multiselect( + label: 'What permissions should be assigned?', + options: [ + 'read' => 'Read', + 'create' => 'Create', + 'update' => 'Update', + 'delete' => 'Delete', + ], + default: ['read', 'create'] + ); + +Up to five options will be displayed before the list begins to scroll. You may +customize this by passing the `scroll` argument: + + + + 1$categories = multiselect( + + 2 label: 'What categories should be assigned?', + + 3 options: Category::pluck('name', 'id'), + + 4 scroll: 10 + + 5); + + + $categories = multiselect( + label: 'What categories should be assigned?', + options: Category::pluck('name', 'id'), + scroll: 10 + ); + +#### Requiring a Value + +By default, the user may select zero or more options. You may pass the +`required` argument to enforce one or more options instead: + + + + 1$categories = multiselect( + + 2 label: 'What categories should be assigned?', + + 3 options: Category::pluck('name', 'id'), + + 4 required: true + + 5); + + + $categories = multiselect( + label: 'What categories should be assigned?', + options: Category::pluck('name', 'id'), + required: true + ); + +If you would like to customize the validation message, you may provide a +string to the `required` argument: + + + + 1$categories = multiselect( + + 2 label: 'What categories should be assigned?', + + 3 options: Category::pluck('name', 'id'), + + 4 required: 'You must select at least one category' + + 5); + + + $categories = multiselect( + label: 'What categories should be assigned?', + options: Category::pluck('name', 'id'), + required: 'You must select at least one category' + ); + +#### Additional Validation + +You may pass a closure to the `validate` argument if you need to present an +option but prevent it from being selected: + + + + 1$permissions = multiselect( + + 2 label: 'What permissions should the user have?', + + 3 options: [ + + 4 'read' => 'Read', + + 5 'create' => 'Create', + + 6 'update' => 'Update', + + 7 'delete' => 'Delete', + + 8 ], + + 9 validate: fn (array $values) => ! in_array('read', $values) + + 10 ? 'All users require the read permission.' + + 11 : null + + 12); + + + $permissions = multiselect( + label: 'What permissions should the user have?', + options: [ + 'read' => 'Read', + 'create' => 'Create', + 'update' => 'Update', + 'delete' => 'Delete', + ], + validate: fn (array $values) => ! in_array('read', $values) + ? 'All users require the read permission.' + : null + ); + +If the `options` argument is an associative array then the closure will +receive the selected keys, otherwise it will receive the selected values. The +closure may return an error message, or `null` if the validation passes. + +### Suggest + +The `suggest` function can be used to provide auto-completion for possible +choices. The user can still provide any answer, regardless of the auto- +completion hints: + + + + 1use function Laravel\Prompts\suggest; + + 2  + + 3$name = suggest('What is your name?', ['Taylor', 'Dayle']); + + + use function Laravel\Prompts\suggest; + + $name = suggest('What is your name?', ['Taylor', 'Dayle']); + +Alternatively, you may pass a closure as the second argument to the `suggest` +function. 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: + + + + 1$name = suggest( + + 2 label: 'What is your name?', + + 3 options: fn ($value) => collect(['Taylor', 'Dayle']) + + 4 ->filter(fn ($name) => Str::contains($name, $value, ignoreCase: true)) + + 5) + + + $name = suggest( + label: 'What is your name?', + options: fn ($value) => collect(['Taylor', 'Dayle']) + ->filter(fn ($name) => Str::contains($name, $value, ignoreCase: true)) + ) + +You may also include placeholder text, a default value, and an informational +hint: + + + + 1$name = suggest( + + 2 label: 'What is your name?', + + 3 options: ['Taylor', 'Dayle'], + + 4 placeholder: 'E.g. Taylor', + + 5 default: $user?->name, + + 6 hint: 'This will be displayed on your profile.' + + 7); + + + $name = suggest( + label: 'What is your name?', + options: ['Taylor', 'Dayle'], + placeholder: 'E.g. Taylor', + default: $user?->name, + hint: 'This will be displayed on your profile.' + ); + +#### Required Values + +If you require a value to be entered, you may pass the `required` argument: + + + + 1$name = suggest( + + 2 label: 'What is your name?', + + 3 options: ['Taylor', 'Dayle'], + + 4 required: true + + 5); + + + $name = suggest( + label: 'What is your name?', + options: ['Taylor', 'Dayle'], + required: true + ); + +If you would like to customize the validation message, you may also pass a +string: + + + + 1$name = suggest( + + 2 label: 'What is your name?', + + 3 options: ['Taylor', 'Dayle'], + + 4 required: 'Your name is required.' + + 5); + + + $name = suggest( + label: 'What is your name?', + options: ['Taylor', 'Dayle'], + required: 'Your name is required.' + ); + +#### Additional Validation + +Finally, if you would like to perform additional validation logic, you may +pass a closure to the `validate` argument: + + + + 1$name = suggest( + + 2 label: 'What is your name?', + + 3 options: ['Taylor', 'Dayle'], + + 4 validate: fn (string $value) => match (true) { + + 5 strlen($value) < 3 => 'The name must be at least 3 characters.', + + 6 strlen($value) > 255 => 'The name must not exceed 255 characters.', + + 7 default => null + + 8 } + + 9); + + + $name = suggest( + label: 'What is your name?', + options: ['Taylor', 'Dayle'], + validate: fn (string $value) => match (true) { + strlen($value) < 3 => 'The name must be at least 3 characters.', + strlen($value) > 255 => 'The name must not exceed 255 characters.', + default => null + } + ); + +The closure will receive the value that has been entered and may return an +error message, or `null` if the validation passes. + +Alternatively, you may leverage the power of Laravel's +[validator](/docs/12.x/validation). To do so, provide an array containing the +name of the attribute and the desired validation rules to the `validate` +argument: + + + + 1$name = suggest( + + 2 label: 'What is your name?', + + 3 options: ['Taylor', 'Dayle'], + + 4 validate: ['name' => 'required|min:3|max:255'] + + 5); + + + $name = suggest( + label: 'What is your name?', + options: ['Taylor', 'Dayle'], + validate: ['name' => 'required|min:3|max:255'] + ); + +### Search + +If you have a lot of options for the user to select from, the `search` +function allows the user to type a search query to filter the results before +using the arrow keys to select an option: + + + + 1use function Laravel\Prompts\search; + + 2  + + 3$id = search( + + 4 label: 'Search for the user that should receive the mail', + + 5 options: fn (string $value) => strlen($value) > 0 + + 6 ? User::whereLike('name', "%{$value}%")->pluck('name', 'id')->all() + + 7 : [] + + 8); + + + use function Laravel\Prompts\search; + + $id = search( + label: 'Search for the user that should receive the mail', + options: fn (string $value) => strlen($value) > 0 + ? User::whereLike('name', "%{$value}%")->pluck('name', 'id')->all() + : [] + ); + +The closure will receive the text that has been typed by the user so far and +must return an array of options. If you return an associative array then the +selected option's key will be returned, otherwise its value will be returned +instead. + +When filtering an array where you intend to return the value, you should use +the `array_values` function or the `values` Collection method to ensure the +array doesn't become associative: + + + + 1$names = collect(['Taylor', 'Abigail']); + + 2  + + 3$selected = search( + + 4 label: 'Search for the user that should receive the mail', + + 5 options: fn (string $value) => $names + + 6 ->filter(fn ($name) => Str::contains($name, $value, ignoreCase: true)) + + 7 ->values() + + 8 ->all(), + + 9); + + + $names = collect(['Taylor', 'Abigail']); + + $selected = search( + label: 'Search for the user that should receive the mail', + options: fn (string $value) => $names + ->filter(fn ($name) => Str::contains($name, $value, ignoreCase: true)) + ->values() + ->all(), + ); + +You may also include placeholder text and an informational hint: + + + + 1$id = search( + + 2 label: 'Search for the user that should receive the mail', + + 3 placeholder: 'E.g. Taylor Otwell', + + 4 options: fn (string $value) => strlen($value) > 0 + + 5 ? User::whereLike('name', "%{$value}%")->pluck('name', 'id')->all() + + 6 : [], + + 7 hint: 'The user will receive an email immediately.' + + 8); + + + $id = search( + label: 'Search for the user that should receive the mail', + placeholder: 'E.g. Taylor Otwell', + options: fn (string $value) => strlen($value) > 0 + ? User::whereLike('name', "%{$value}%")->pluck('name', 'id')->all() + : [], + hint: 'The user will receive an email immediately.' + ); + +Up to five options will be displayed before the list begins to scroll. You may +customize this by passing the `scroll` argument: + + + + 1$id = search( + + 2 label: 'Search for the user that should receive the mail', + + 3 options: fn (string $value) => strlen($value) > 0 + + 4 ? User::whereLike('name', "%{$value}%")->pluck('name', 'id')->all() + + 5 : [], + + 6 scroll: 10 + + 7); + + + $id = search( + label: 'Search for the user that should receive the mail', + options: fn (string $value) => strlen($value) > 0 + ? User::whereLike('name', "%{$value}%")->pluck('name', 'id')->all() + : [], + scroll: 10 + ); + +#### Additional Validation + +If you would like to perform additional validation logic, you may pass a +closure to the `validate` argument: + + + + 1$id = search( + + 2 label: 'Search for the user that should receive the mail', + + 3 options: fn (string $value) => strlen($value) > 0 + + 4 ? User::whereLike('name', "%{$value}%")->pluck('name', 'id')->all() + + 5 : [], + + 6 validate: function (int|string $value) { + + 7 $user = User::findOrFail($value); + + 8  + + 9 if ($user->opted_out) { + + 10 return 'This user has opted-out of receiving mail.'; + + 11 } + + 12 } + + 13); + + + $id = search( + label: 'Search for the user that should receive the mail', + options: fn (string $value) => strlen($value) > 0 + ? User::whereLike('name', "%{$value}%")->pluck('name', 'id')->all() + : [], + validate: function (int|string $value) { + $user = User::findOrFail($value); + + if ($user->opted_out) { + return 'This user has opted-out of receiving mail.'; + } + } + ); + +If the `options` closure returns an associative array, then the closure will +receive the selected key, otherwise, it will receive the selected value. The +closure may return an error message, or `null` if the validation passes. + +### Multi-search + +If you have a lot of searchable options and need the user to be able to select +multiple items, the `multisearch` function allows the user to type a search +query to filter the results before using the arrow keys and space-bar to +select options: + + + + 1use function Laravel\Prompts\multisearch; + + 2  + + 3$ids = multisearch( + + 4 'Search for the users that should receive the mail', + + 5 fn (string $value) => strlen($value) > 0 + + 6 ? User::whereLike('name', "%{$value}%")->pluck('name', 'id')->all() + + 7 : [] + + 8); + + + use function Laravel\Prompts\multisearch; + + $ids = multisearch( + 'Search for the users that should receive the mail', + fn (string $value) => strlen($value) > 0 + ? User::whereLike('name', "%{$value}%")->pluck('name', 'id')->all() + : [] + ); + +The closure will receive the text that has been typed by the user so far and +must return an array of options. If you return an associative array then the +selected options' keys will be returned; otherwise, their values will be +returned instead. + +When filtering an array where you intend to return the value, you should use +the `array_values` function or the `values` Collection method to ensure the +array doesn't become associative: + + + + 1$names = collect(['Taylor', 'Abigail']); + + 2  + + 3$selected = multisearch( + + 4 label: 'Search for the users that should receive the mail', + + 5 options: fn (string $value) => $names + + 6 ->filter(fn ($name) => Str::contains($name, $value, ignoreCase: true)) + + 7 ->values() + + 8 ->all(), + + 9); + + + $names = collect(['Taylor', 'Abigail']); + + $selected = multisearch( + label: 'Search for the users that should receive the mail', + options: fn (string $value) => $names + ->filter(fn ($name) => Str::contains($name, $value, ignoreCase: true)) + ->values() + ->all(), + ); + +You may also include placeholder text and an informational hint: + + + + 1$ids = multisearch( + + 2 label: 'Search for the users that should receive the mail', + + 3 placeholder: 'E.g. Taylor Otwell', + + 4 options: fn (string $value) => strlen($value) > 0 + + 5 ? User::whereLike('name', "%{$value}%")->pluck('name', 'id')->all() + + 6 : [], + + 7 hint: 'The user will receive an email immediately.' + + 8); + + + $ids = multisearch( + label: 'Search for the users that should receive the mail', + placeholder: 'E.g. Taylor Otwell', + options: fn (string $value) => strlen($value) > 0 + ? User::whereLike('name', "%{$value}%")->pluck('name', 'id')->all() + : [], + hint: 'The user will receive an email immediately.' + ); + +Up to five options will be displayed before the list begins to scroll. You may +customize this by providing the `scroll` argument: + + + + 1$ids = multisearch( + + 2 label: 'Search for the users that should receive the mail', + + 3 options: fn (string $value) => strlen($value) > 0 + + 4 ? User::whereLike('name', "%{$value}%")->pluck('name', 'id')->all() + + 5 : [], + + 6 scroll: 10 + + 7); + + + $ids = multisearch( + label: 'Search for the users that should receive the mail', + options: fn (string $value) => strlen($value) > 0 + ? User::whereLike('name', "%{$value}%")->pluck('name', 'id')->all() + : [], + scroll: 10 + ); + +#### Requiring a Value + +By default, the user may select zero or more options. You may pass the +`required` argument to enforce one or more options instead: + + + + 1$ids = multisearch( + + 2 label: 'Search for the users that should receive the mail', + + 3 options: fn (string $value) => strlen($value) > 0 + + 4 ? User::whereLike('name', "%{$value}%")->pluck('name', 'id')->all() + + 5 : [], + + 6 required: true + + 7); + + + $ids = multisearch( + label: 'Search for the users that should receive the mail', + options: fn (string $value) => strlen($value) > 0 + ? User::whereLike('name', "%{$value}%")->pluck('name', 'id')->all() + : [], + required: true + ); + +If you would like to customize the validation message, you may also provide a +string to the `required` argument: + + + + 1$ids = multisearch( + + 2 label: 'Search for the users that should receive the mail', + + 3 options: fn (string $value) => strlen($value) > 0 + + 4 ? User::whereLike('name', "%{$value}%")->pluck('name', 'id')->all() + + 5 : [], + + 6 required: 'You must select at least one user.' + + 7); + + + $ids = multisearch( + label: 'Search for the users that should receive the mail', + options: fn (string $value) => strlen($value) > 0 + ? User::whereLike('name', "%{$value}%")->pluck('name', 'id')->all() + : [], + required: 'You must select at least one user.' + ); + +#### Additional Validation + +If you would like to perform additional validation logic, you may pass a +closure to the `validate` argument: + + + + 1$ids = multisearch( + + 2 label: 'Search for the users that should receive the mail', + + 3 options: fn (string $value) => strlen($value) > 0 + + 4 ? User::whereLike('name', "%{$value}%")->pluck('name', 'id')->all() + + 5 : [], + + 6 validate: function (array $values) { + + 7 $optedOut = User::whereLike('name', '%a%')->findMany($values); + + 8  + + 9 if ($optedOut->isNotEmpty()) { + + 10 return $optedOut->pluck('name')->join(', ', ', and ').' have opted out.'; + + 11 } + + 12 } + + 13); + + + $ids = multisearch( + label: 'Search for the users that should receive the mail', + options: fn (string $value) => strlen($value) > 0 + ? User::whereLike('name', "%{$value}%")->pluck('name', 'id')->all() + : [], + validate: function (array $values) { + $optedOut = User::whereLike('name', '%a%')->findMany($values); + + if ($optedOut->isNotEmpty()) { + return $optedOut->pluck('name')->join(', ', ', and ').' have opted out.'; + } + } + ); + +If the `options` closure returns an associative array, then the closure will +receive the selected keys; otherwise, it will receive the selected values. The +closure may return an error message, or `null` if the validation passes. + +### Pause + +The `pause` function may be used to display informational text to the user and +wait for them to confirm their desire to proceed by pressing the Enter / +Return key: + + + + 1use function Laravel\Prompts\pause; + + 2  + + 3pause('Press ENTER to continue.'); + + + use function Laravel\Prompts\pause; + + pause('Press ENTER to continue.'); + +## Transforming Input Before Validation + +Sometimes you may want to transform the prompt input before validation takes +place. For example, you may wish to remove white space from any provided +strings. To accomplish this, many of the prompt functions provide a +`transform` argument, which accepts a closure: + + + + 1$name = text( + + 2 label: 'What is your name?', + + 3 transform: fn (string $value) => trim($value), + + 4 validate: fn (string $value) => match (true) { + + 5 strlen($value) < 3 => 'The name must be at least 3 characters.', + + 6 strlen($value) > 255 => 'The name must not exceed 255 characters.', + + 7 default => null + + 8 } + + 9); + + + $name = text( + label: 'What is your name?', + transform: fn (string $value) => trim($value), + validate: fn (string $value) => match (true) { + strlen($value) < 3 => 'The name must be at least 3 characters.', + strlen($value) > 255 => 'The name must not exceed 255 characters.', + default => null + } + ); + +## Forms + +Often, you will have multiple prompts that will be displayed in sequence to +collect information before performing additional actions. You may use the +`form` function to create a grouped set of prompts for the user to complete: + + + + 1use function Laravel\Prompts\form; + + 2  + + 3$responses = form() + + 4 ->text('What is your name?', required: true) + + 5 ->password('What is your password?', validate: ['password' => 'min:8']) + + 6 ->confirm('Do you accept the terms?') + + 7 ->submit(); + + + use function Laravel\Prompts\form; + + $responses = form() + ->text('What is your name?', required: true) + ->password('What is your password?', validate: ['password' => 'min:8']) + ->confirm('Do you accept the terms?') + ->submit(); + +The `submit` method will return a numerically indexed array containing all of +the responses from the form's prompts. However, you may provide a name for +each prompt via the `name` argument. When a name is provided, the named +prompt's response may be accessed via that name: + + + + 1use App\Models\User; + + 2use function Laravel\Prompts\form; + + 3  + + 4$responses = form() + + 5 ->text('What is your name?', required: true, name: 'name') + + 6 ->password( + + 7 label: 'What is your password?', + + 8 validate: ['password' => 'min:8'], + + 9 name: 'password' + + 10 ) + + 11 ->confirm('Do you accept the terms?') + + 12 ->submit(); + + 13  + + 14User::create([ + + 15 'name' => $responses['name'], + + 16 'password' => $responses['password'], + + 17]); + + + use App\Models\User; + use function Laravel\Prompts\form; + + $responses = form() + ->text('What is your name?', required: true, name: 'name') + ->password( + label: 'What is your password?', + validate: ['password' => 'min:8'], + name: 'password' + ) + ->confirm('Do you accept the terms?') + ->submit(); + + User::create([ + 'name' => $responses['name'], + 'password' => $responses['password'], + ]); + +The primary benefit of using the `form` function is the ability for the user +to return to previous prompts in the form using `CTRL + U`. This allows the +user to fix mistakes or alter selections without needing to cancel and restart +the entire form. + +If you need more granular control over a prompt in a form, you may invoke the +`add` method instead of calling one of the prompt functions directly. The +`add` method is passed all previous responses provided by the user: + + + + 1use function Laravel\Prompts\form; + + 2use function Laravel\Prompts\outro; + + 3use function Laravel\Prompts\text; + + 4  + + 5$responses = form() + + 6 ->text('What is your name?', required: true, name: 'name') + + 7 ->add(function ($responses) { + + 8 return text("How old are you, {$responses['name']}?"); + + 9 }, name: 'age') + + 10 ->submit(); + + 11  + + 12outro("Your name is {$responses['name']} and you are {$responses['age']} years old."); + + + use function Laravel\Prompts\form; + use function Laravel\Prompts\outro; + use function Laravel\Prompts\text; + + $responses = form() + ->text('What is your name?', required: true, name: 'name') + ->add(function ($responses) { + return text("How old are you, {$responses['name']}?"); + }, name: 'age') + ->submit(); + + outro("Your name is {$responses['name']} and you are {$responses['age']} years old."); + +## Informational Messages + +The `note`, `info`, `warning`, `error`, and `alert` functions may be used to +display informational messages: + + + + 1use function Laravel\Prompts\info; + + 2  + + 3info('Package installed successfully.'); + + + use function Laravel\Prompts\info; + + info('Package installed successfully.'); + +## Tables + +The `table` function makes it easy to display multiple rows and columns of +data. All you need to do is provide the column names and the data for the +table: + + + + 1use function Laravel\Prompts\table; + + 2  + + 3table( + + 4 headers: ['Name', 'Email'], + + 5 rows: User::all(['name', 'email'])->toArray() + + 6); + + + use function Laravel\Prompts\table; + + table( + headers: ['Name', 'Email'], + rows: User::all(['name', 'email'])->toArray() + ); + +## Spin + +The `spin` function displays a spinner along with an optional message while +executing a specified callback. It serves to indicate ongoing processes and +returns the callback's results upon completion: + + + + 1use function Laravel\Prompts\spin; + + 2  + + 3$response = spin( + + 4 callback: fn () => Http::get('http://example.com'), + + 5 message: 'Fetching response...' + + 6); + + + use function Laravel\Prompts\spin; + + $response = spin( + callback: fn () => Http::get('http://example.com'), + message: 'Fetching response...' + ); + +The `spin` function requires the +[PCNTL](https://www.php.net/manual/en/book.pcntl.php) PHP extension to animate +the spinner. When this extension is not available, a static version of the +spinner will appear instead. + +## 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 `progress` function, Laravel will +display a progress bar and advance its progress for each iteration over a +given iterable value: + + + + 1use function Laravel\Prompts\progress; + + 2  + + 3$users = progress( + + 4 label: 'Updating users', + + 5 steps: User::all(), + + 6 callback: fn ($user) => $this->performTask($user) + + 7); + + + use function Laravel\Prompts\progress; + + $users = progress( + label: 'Updating users', + steps: User::all(), + callback: fn ($user) => $this->performTask($user) + ); + +The `progress` function acts like a map function and will return an array +containing the return value of each iteration of your callback. + +The callback may also accept the `Laravel\Prompts\Progress` instance, allowing +you to modify the label and hint on each iteration: + + + + 1$users = progress( + + 2 label: 'Updating users', + + 3 steps: User::all(), + + 4 callback: function ($user, $progress) { + + 5 $progress + + 6 ->label("Updating {$user->name}") + + 7 ->hint("Created on {$user->created_at}"); + + 8  + + 9 return $this->performTask($user); + + 10 }, + + 11 hint: 'This may take some time.' + + 12); + + + $users = progress( + label: 'Updating users', + steps: User::all(), + callback: function ($user, $progress) { + $progress + ->label("Updating {$user->name}") + ->hint("Created on {$user->created_at}"); + + return $this->performTask($user); + }, + hint: 'This may take some time.' + ); + +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 via the `advance` method after +processing each item: + + + + 1$progress = progress(label: 'Updating users', steps: 10); + + 2  + + 3$users = User::all(); + + 4  + + 5$progress->start(); + + 6  + + 7foreach ($users as $user) { + + 8 $this->performTask($user); + + 9  + + 10 $progress->advance(); + + 11} + + 12  + + 13$progress->finish(); + + + $progress = progress(label: 'Updating users', steps: 10); + + $users = User::all(); + + $progress->start(); + + foreach ($users as $user) { + $this->performTask($user); + + $progress->advance(); + } + + $progress->finish(); + +## Clearing the Terminal + +The `clear` function may be used to clear the user's terminal: + + + + 1use function Laravel\Prompts\clear; + + 2  + + 3clear(); + + + use function Laravel\Prompts\clear; + + clear(); + +## Terminal Considerations + +#### Terminal Width + +If the length of any label, option, or validation message exceeds the number +of "columns" in the user's terminal, it will be automatically truncated to +fit. Consider minimizing the length of these strings if your users may be +using narrower terminals. A typically safe maximum length is 74 characters to +support an 80-character terminal. + +#### Terminal Height + +For any prompts that accept the `scroll` argument, the configured value will +automatically be reduced to fit the height of the user's terminal, including +space for a validation message. + +## Unsupported Environments and Fallbacks + +Laravel Prompts supports macOS, Linux, and Windows with WSL. Due to +limitations in the Windows version of PHP, it is not currently possible to use +Laravel Prompts on Windows outside of WSL. + +For this reason, Laravel Prompts supports falling back to an alternative +implementation such as the [Symfony Console Question +Helper](https://symfony.com/doc/current/components/console/helpers/questionhelper.html). + +When using Laravel Prompts with the Laravel framework, fallbacks for each +prompt have been configured for you and will be automatically enabled in +unsupported environments. + +#### Fallback Conditions + +If you are not using Laravel or need to customize when the fallback behavior +is used, you may pass a boolean to the `fallbackWhen` static method on the +`Prompt` class: + + + + 1use Laravel\Prompts\Prompt; + + 2  + + 3Prompt::fallbackWhen( + + 4 ! $input->isInteractive() || windows_os() || app()->runningUnitTests() + + 5); + + + use Laravel\Prompts\Prompt; + + Prompt::fallbackWhen( + ! $input->isInteractive() || windows_os() || app()->runningUnitTests() + ); + +#### Fallback Behavior + +If you are not using Laravel or need to customize the fallback behavior, you +may pass a closure to the `fallbackUsing` static method on each prompt class: + + + + 1use Laravel\Prompts\TextPrompt; + + 2use Symfony\Component\Console\Question\Question; + + 3use Symfony\Component\Console\Style\SymfonyStyle; + + 4  + + 5TextPrompt::fallbackUsing(function (TextPrompt $prompt) use ($input, $output) { + + 6 $question = (new Question($prompt->label, $prompt->default ?: null)) + + 7 ->setValidator(function ($answer) use ($prompt) { + + 8 if ($prompt->required && $answer === null) { + + 9 throw new \RuntimeException( + + 10 is_string($prompt->required) ? $prompt->required : 'Required.' + + 11 ); + + 12 } + + 13  + + 14 if ($prompt->validate) { + + 15 $error = ($prompt->validate)($answer ?? ''); + + 16  + + 17 if ($error) { + + 18 throw new \RuntimeException($error); + + 19 } + + 20 } + + 21  + + 22 return $answer; + + 23 }); + + 24  + + 25 return (new SymfonyStyle($input, $output)) + + 26 ->askQuestion($question); + + 27}); + + + use Laravel\Prompts\TextPrompt; + use Symfony\Component\Console\Question\Question; + use Symfony\Component\Console\Style\SymfonyStyle; + + TextPrompt::fallbackUsing(function (TextPrompt $prompt) use ($input, $output) { + $question = (new Question($prompt->label, $prompt->default ?: null)) + ->setValidator(function ($answer) use ($prompt) { + if ($prompt->required && $answer === null) { + throw new \RuntimeException( + is_string($prompt->required) ? $prompt->required : 'Required.' + ); + } + + if ($prompt->validate) { + $error = ($prompt->validate)($answer ?? ''); + + if ($error) { + throw new \RuntimeException($error); + } + } + + return $answer; + }); + + return (new SymfonyStyle($input, $output)) + ->askQuestion($question); + }); + +Fallbacks must be configured individually for each prompt class. The closure +will receive an instance of the prompt class and must return an appropriate +type for the prompt. + +## Testing + +Laravel provides a variety of methods for testing that your command displays +the expected Prompt messages: + +Pest PHPUnit + + + + 1test('report generation', function () { + + 2 $this->artisan('report:generate') + + 3 ->expectsPromptsInfo('Welcome to the application!') + + 4 ->expectsPromptsWarning('This action cannot be undone') + + 5 ->expectsPromptsError('Something went wrong') + + 6 ->expectsPromptsAlert('Important notice!') + + 7 ->expectsPromptsIntro('Starting process...') + + 8 ->expectsPromptsOutro('Process completed!') + + 9 ->expectsPromptsTable( + + 10 headers: ['Name', 'Email'], + + 11 rows: [ + + 12 ['Taylor Otwell', '[[email protected]](/cdn-cgi/l/email-protection)'], + + 13 ['Jason Beggs', '[[email protected]](/cdn-cgi/l/email-protection)'], + + 14 ] + + 15 ) + + 16 ->assertExitCode(0); + + 17}); + + + test('report generation', function () { + $this->artisan('report:generate') + ->expectsPromptsInfo('Welcome to the application!') + ->expectsPromptsWarning('This action cannot be undone') + ->expectsPromptsError('Something went wrong') + ->expectsPromptsAlert('Important notice!') + ->expectsPromptsIntro('Starting process...') + ->expectsPromptsOutro('Process completed!') + ->expectsPromptsTable( + headers: ['Name', 'Email'], + rows: [ + ['Taylor Otwell', '[[email protected]](/cdn-cgi/l/email-protection)'], + ['Jason Beggs', '[[email protected]](/cdn-cgi/l/email-protection)'], + ] + ) + ->assertExitCode(0); + }); + + + 1public function test_report_generation(): void + + 2{ + + 3 $this->artisan('report:generate') + + 4 ->expectsPromptsInfo('Welcome to the application!') + + 5 ->expectsPromptsWarning('This action cannot be undone') + + 6 ->expectsPromptsError('Something went wrong') + + 7 ->expectsPromptsAlert('Important notice!') + + 8 ->expectsPromptsIntro('Starting process...') + + 9 ->expectsPromptsOutro('Process completed!') + + 10 ->expectsPromptsTable( + + 11 headers: ['Name', 'Email'], + + 12 rows: [ + + 13 ['Taylor Otwell', '[[email protected]](/cdn-cgi/l/email-protection)'], + + 14 ['Jason Beggs', '[[email protected]](/cdn-cgi/l/email-protection)'], + + 15 ] + + 16 ) + + 17 ->assertExitCode(0); + + 18} + + + public function test_report_generation(): void + { + $this->artisan('report:generate') + ->expectsPromptsInfo('Welcome to the application!') + ->expectsPromptsWarning('This action cannot be undone') + ->expectsPromptsError('Something went wrong') + ->expectsPromptsAlert('Important notice!') + ->expectsPromptsIntro('Starting process...') + ->expectsPromptsOutro('Process completed!') + ->expectsPromptsTable( + headers: ['Name', 'Email'], + rows: [ + ['Taylor Otwell', '[[email protected]](/cdn-cgi/l/email-protection)'], + ['Jason Beggs', '[[email protected]](/cdn-cgi/l/email-protection)'], + ] + ) + ->assertExitCode(0); + } + diff --git a/output/12.x/providers.md b/output/12.x/providers.md new file mode 100644 index 0000000..75a4da6 --- /dev/null +++ b/output/12.x/providers.md @@ -0,0 +1,521 @@ +# Service Providers + + * Introduction + * Writing Service Providers + * The Register Method + * The Boot Method + * Registering Providers + * Deferred Providers + +## Introduction + +Service providers are the central place of all Laravel application +bootstrapping. Your own application, as well as all of Laravel's core +services, are bootstrapped via service providers. + +But, what do we mean by "bootstrapped"? In general, we mean **registering** +things, including registering service container bindings, event listeners, +middleware, and even routes. Service providers are the central place to +configure your application. + +Laravel uses dozens of service providers internally to bootstrap its core +services, such as the mailer, queue, cache, and others. Many of these +providers are "deferred" providers, meaning they will not be loaded on every +request, but only when the services they provide are actually needed. + +All user-defined service providers are registered in the +`bootstrap/providers.php` file. In the following documentation, you will learn +how to write your own service providers and register them with your Laravel +application. + +If you would like to learn more about how Laravel handles requests and works +internally, check out our documentation on the Laravel [request +lifecycle](/docs/12.x/lifecycle). + +## Writing Service Providers + +All service providers extend the `Illuminate\Support\ServiceProvider` class. +Most service providers contain a `register` and a `boot` method. Within the +`register` method, you should **only bind things into the[service +container](/docs/12.x/container)**. You should never attempt to register any +event listeners, routes, or any other piece of functionality within the +`register` method. + +The Artisan CLI can generate a new provider via the `make:provider` command. +Laravel will automatically register your new provider in your application's +`bootstrap/providers.php` file: + + + + 1php artisan make:provider RiakServiceProvider + + + php artisan make:provider RiakServiceProvider + +### The Register Method + +As mentioned previously, within the `register` method, you should only bind +things into the [service container](/docs/12.x/container). You should never +attempt to register any event listeners, routes, or any other piece of +functionality within the `register` method. Otherwise, you may accidentally +use a service that is provided by a service provider which has not loaded yet. + +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: + + + + 1app->singleton(Connection::class, function (Application $app) { + + 17 return new Connection(config('riak')); + + 18 }); + + 19 } + + 20} + + + 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/12.x/container). + +#### The `bindings` and `singletons` Properties + +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: + + + + 1 DigitalOceanServerProvider::class, + + 21 ]; + + 22  + + 23 /** + + 24 * All of the container singletons that should be registered. + + 25 * + + 26 * @var array + + 27 */ + + 28 public $singletons = [ + + 29 DowntimeNotifier::class => PingdomDowntimeNotifier::class, + + 30 ServerProvider::class => ServerToolsProvider::class, + + 31 ]; + + 32} + + + 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/12.x/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: + + + + 1macro('serialized', function (mixed $value) { + + 9 // ... + + 10 }); + + 11} + + + 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: + + + + 1app->singleton(Connection::class, function (Application $app) { + + 18 return new Connection($app['config']['riak']); + + 19 }); + + 20 } + + 21  + + 22 /** + + 23 * Get the services provided by the provider. + + 24 * + + 25 * @return array + + 26 */ + + 27 public function provides(): array + + 28 { + + 29 return [Connection::class]; + + 30 } + + 31} + + + 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]; + } + } + diff --git a/output/12.x/pulse.md b/output/12.x/pulse.md new file mode 100644 index 0000000..bf7b330 --- /dev/null +++ b/output/12.x/pulse.md @@ -0,0 +1,1564 @@ +# Laravel Pulse + + * Introduction + * Installation + * Configuration + * Dashboard + * Authorization + * Customization + * Resolving Users + * Cards + * Capturing Entries + * Recorders + * Filtering + * Performance + * Using a Different Database + * Redis Ingest + * Sampling + * Trimming + * Handling Pulse Exceptions + * Custom Cards + * Card Components + * Styling + * Data Capture and Aggregation + +## Introduction + +[Laravel Pulse](https://github.com/laravel/pulse) delivers at-a-glance +insights into your application's performance and usage. With Pulse, you can +track down bottlenecks like slow jobs and endpoints, find your most active +users, and more. + +For in-depth debugging of individual events, check out [Laravel +Telescope](/docs/12.x/telescope). + +## Installation + +Pulse's first-party storage implementation currently requires a MySQL, +MariaDB, or PostgreSQL database. If you are using a different database engine, +you will need a separate MySQL, MariaDB, or PostgreSQL database for your Pulse +data. + +You may install Pulse using the Composer package manager: + + + + 1composer require laravel/pulse + + + composer require laravel/pulse + +Next, you should publish the Pulse configuration and migration files using the +`vendor:publish` Artisan command: + + + + 1php artisan vendor:publish --provider="Laravel\Pulse\PulseServiceProvider" + + + php artisan vendor:publish --provider="Laravel\Pulse\PulseServiceProvider" + +Finally, you should run the `migrate` command in order to create the tables +needed to store Pulse's data: + + + + 1php artisan migrate + + + php artisan migrate + +Once Pulse's database migrations have been run, you may access the Pulse +dashboard via the `/pulse` route. + +If you do not want to store Pulse data in your application's primary database, +you may specify a dedicated database connection. + +### Configuration + +Many of Pulse's configuration options can be controlled using environment +variables. To see the available options, register new recorders, or configure +advanced options, you may publish the `config/pulse.php` configuration file: + + + + 1php artisan vendor:publish --tag=pulse-config + + + php artisan vendor:publish --tag=pulse-config + +## Dashboard + +### Authorization + +The Pulse dashboard may be accessed via the `/pulse` route. By default, you +will only be able to access this dashboard in the `local` environment, so you +will need to configure authorization for your production environments by +customizing the `'viewPulse'` authorization gate. You can accomplish this +within your application's `app/Providers/AppServiceProvider.php` file: + + + + 1use App\Models\User; + + 2use Illuminate\Support\Facades\Gate; + + 3  + + 4/** + + 5 * Bootstrap any application services. + + 6 */ + + 7public function boot(): void + + 8{ + + 9 Gate::define('viewPulse', function (User $user) { + + 10 return $user->isAdmin(); + + 11 }); + + 12  + + 13 // ... + + 14} + + + use App\Models\User; + use Illuminate\Support\Facades\Gate; + + /** + * Bootstrap any application services. + */ + public function boot(): void + { + Gate::define('viewPulse', function (User $user) { + return $user->isAdmin(); + }); + + // ... + } + +### Customization + +The Pulse dashboard cards and layout may be configured by publishing the +dashboard view. The dashboard view will be published to +`resources/views/vendor/pulse/dashboard.blade.php`: + + + + 1php artisan vendor:publish --tag=pulse-dashboard + + + php artisan vendor:publish --tag=pulse-dashboard + +The dashboard is powered by [Livewire](https://livewire.laravel.com/), and +allows you to customize the cards and layout without needing to rebuild any +JavaScript assets. + +Within this file, the `` component is responsible for rendering the +dashboard and provides a grid layout for the cards. If you would like the +dashboard to span the full width of the screen, you may provide the `full- +width` prop to the component: + + + + 1 + + 2 ... + + 3 + + + + ... + + +By default, the `` component will create a 12 column grid, but you +may customize this using the `cols` prop: + + + + 1 + + 2 ... + + 3 + + + + ... + + +Each card accepts a `cols` and `rows` prop to control the space and +positioning: + + + + 1 + + + + +Most cards also accept an `expand` prop to show the full card instead of +scrolling: + + + + 1 + + + + +### Resolving Users + +For cards that display information about your users, such as the Application +Usage card, Pulse will only record the user's ID. When rendering the +dashboard, Pulse will resolve the `name` and `email` fields from your default +`Authenticatable` model and display avatars using the Gravatar web service. + +You may customize the fields and avatar by invoking the `Pulse::user` method +within your application's `App\Providers\AppServiceProvider` class. + +The `user` method accepts a closure which will receive the `Authenticatable` +model to be displayed and should return an array containing `name`, `extra`, +and `avatar` information for the user: + + + + 1use Laravel\Pulse\Facades\Pulse; + + 2  + + 3/** + + 4 * Bootstrap any application services. + + 5 */ + + 6public function boot(): void + + 7{ + + 8 Pulse::user(fn ($user) => [ + + 9 'name' => $user->name, + + 10 'extra' => $user->email, + + 11 'avatar' => $user->avatar_url, + + 12 ]); + + 13  + + 14 // ... + + 15} + + + use Laravel\Pulse\Facades\Pulse; + + /** + * Bootstrap any application services. + */ + public function boot(): void + { + Pulse::user(fn ($user) => [ + 'name' => $user->name, + 'extra' => $user->email, + 'avatar' => $user->avatar_url, + ]); + + // ... + } + +You may completely customize how the authenticated user is captured and +retrieved by implementing the `Laravel\Pulse\Contracts\ResolvesUsers` contract +and binding it in Laravel's [service container](/docs/12.x/container#binding- +a-singleton). + +### Cards + +#### Servers + +The `` card displays system resource usage for all +servers running the `pulse:check` command. Please refer to the documentation +regarding the servers recorder for more information on system resource +reporting. + +If you replace a server in your infrastructure, you may wish to stop +displaying the inactive server in the Pulse dashboard after a given duration. +You may accomplish this using the `ignore-after` prop, which accepts the +number of seconds after which inactive servers should be removed from the +Pulse dashboard. Alternatively, you may provide a relative time formatted +string, such as `1 hour` or `3 days and 1 hour`: + + + + 1 + + + + +#### Application Usage + +The `` card displays the top 10 users making requests +to your application, dispatching jobs, and experiencing slow requests. + +If you wish to view all usage metrics on screen at the same time, you may +include the card multiple times and specify the `type` attribute: + + + + 1 + + 2 + + 3 + + + + + + +To learn how to customize how Pulse retrieves and displays user information, +consult our documentation on resolving users. + +If your application receives a lot of requests or dispatches a lot of jobs, +you may wish to enable sampling. See the user requests recorder, user jobs +recorder, and slow jobs recorder documentation for more information. + +#### Exceptions + +The `` card shows the frequency and recency of +exceptions occurring in your application. By default, exceptions are grouped +based on the exception class and location where it occurred. See the +exceptions recorder documentation for more information. + +#### Queues + +The `` card shows the throughput of the queues in +your application, including the number of jobs queued, processing, processed, +released, and failed. See the queues recorder documentation for more +information. + +#### Slow Requests + +The `` card shows incoming requests to your +application that exceed the configured threshold, which is 1,000ms by default. +See the slow requests recorder documentation for more information. + +#### Slow Jobs + +The `` card shows the queued jobs in your +application that exceed the configured threshold, which is 1,000ms by default. +See the slow jobs recorder documentation for more information. + +#### Slow Queries + +The `` card shows the database queries in your +application that exceed the configured threshold, which is 1,000ms by default. + +By default, slow queries are grouped based on the SQL query (without bindings) +and the location where it occurred, but you may choose to not capture the +location if you wish to group solely on the SQL query. + +If you encounter rendering performance issues due to extremely large SQL +queries receiving syntax highlighting, you may disable highlighting by adding +the `without-highlighting` prop: + + + + 1 + + + + +See the slow queries recorder documentation for more information. + +#### Slow Outgoing Requests + +The `` card shows outgoing requests +made using Laravel's [HTTP client](/docs/12.x/http-client) that exceed the +configured threshold, which is 1,000ms by default. + +By default, entries will be grouped by the full URL. However, you may wish to +normalize or group similar outgoing requests using regular expressions. See +the slow outgoing requests recorder documentation for more information. + +#### Cache + +The `` card shows the cache hit and miss statistics +for your application, both globally and for individual keys. + +By default, entries will be grouped by key. However, you may wish to normalize +or group similar keys using regular expressions. See the cache interactions +recorder documentation for more information. + +## Capturing Entries + +Most Pulse recorders will automatically capture entries based on framework +events dispatched by Laravel. However, the servers recorder and some third- +party cards must poll for information regularly. To use these cards, you must +run the `pulse:check` daemon on all of your individual application servers: + + + + 1php artisan pulse:check + + + php artisan pulse:check + +To keep the `pulse:check` process running permanently in the background, you +should use a process monitor such as Supervisor to ensure that the command +does not stop running. + +As the `pulse:check` command is a long-lived process, it will not see changes +to your codebase without being restarted. You should gracefully restart the +command by calling the `pulse:restart` command during your application's +deployment process: + + + + 1php artisan pulse:restart + + + php artisan pulse:restart + +Pulse uses the [cache](/docs/12.x/cache) to store restart signals, so you +should verify that a cache driver is properly configured for your application +before using this feature. + +### Recorders + +Recorders are responsible for capturing entries from your application to be +recorded in the Pulse database. Recorders are registered and configured in the +`recorders` section of the Pulse configuration file. + +#### Cache Interactions + +The `CacheInteractions` recorder captures information about the +[cache](/docs/12.x/cache) hits and misses occurring in your application for +display on the Cache card. + +You may optionally adjust the sample rate and ignored key patterns. + +You may also configure key grouping so that similar keys are grouped as a +single entry. For example, you may wish to remove unique IDs from keys caching +the same type of information. Groups are configured using a regular expression +to "find and replace" parts of the key. An example is included in the +configuration file: + + + + 1Recorders\CacheInteractions::class => [ + + 2 // ... + + 3 'groups' => [ + + 4 // '/:\d+/' => ':*', + + 5 ], + + 6], + + + Recorders\CacheInteractions::class => [ + // ... + 'groups' => [ + // '/:\d+/' => ':*', + ], + ], + +The first pattern that matches will be used. If no patterns match, then the +key will be captured as-is. + +#### Exceptions + +The `Exceptions` recorder captures information about reportable exceptions +occurring in your application for display on the Exceptions card. + +You may optionally adjust the sample rate and ignored exceptions patterns. You +may also configure whether to capture the location that the exception +originated from. The captured location will be displayed on the Pulse +dashboard which can help to track down the exception origin; however, if the +same exception occurs in multiple locations then it will appear multiple times +for each unique location. + +#### Queues + +The `Queues` recorder captures information about your applications queues for +display on the Queues. + +You may optionally adjust the sample rate and ignored jobs patterns. + +#### Slow Jobs + +The `SlowJobs` recorder captures information about slow jobs occurring in your +application for display on the Slow Jobs card. + +You may optionally adjust the slow job threshold, sample rate, and ignored job +patterns. + +You may have some jobs that you expect to take longer than others. In those +cases, you may configure per-job thresholds: + + + + 1Recorders\SlowJobs::class => [ + + 2 // ... + + 3 'threshold' => [ + + 4 '#^App\\Jobs\\GenerateYearlyReports$#' => 5000, + + 5 'default' => env('PULSE_SLOW_JOBS_THRESHOLD', 1000), + + 6 ], + + 7], + + + Recorders\SlowJobs::class => [ + // ... + 'threshold' => [ + '#^App\\Jobs\\GenerateYearlyReports$#' => 5000, + 'default' => env('PULSE_SLOW_JOBS_THRESHOLD', 1000), + ], + ], + +If no regular expression patterns match the job's classname, then the +`'default'` value will be used. + +#### Slow Outgoing Requests + +The `SlowOutgoingRequests` recorder captures information about outgoing HTTP +requests made using Laravel's [HTTP client](/docs/12.x/http-client) that +exceed the configured threshold for display on the Slow Outgoing Requests +card. + +You may optionally adjust the slow outgoing request threshold, sample rate, +and ignored URL patterns. + +You may have some outgoing requests that you expect to take longer than +others. In those cases, you may configure per-request thresholds: + + + + 1Recorders\SlowOutgoingRequests::class => [ + + 2 // ... + + 3 'threshold' => [ + + 4 '#backup.zip$#' => 5000, + + 5 'default' => env('PULSE_SLOW_OUTGOING_REQUESTS_THRESHOLD', 1000), + + 6 ], + + 7], + + + Recorders\SlowOutgoingRequests::class => [ + // ... + 'threshold' => [ + '#backup.zip$#' => 5000, + 'default' => env('PULSE_SLOW_OUTGOING_REQUESTS_THRESHOLD', 1000), + ], + ], + +If no regular expression patterns match the request's URL, then the +`'default'` value will be used. + +You may also configure URL grouping so that similar URLs are grouped as a +single entry. For example, you may wish to remove unique IDs from URL paths or +group by domain only. Groups are configured using a regular expression to +"find and replace" parts of the URL. Some examples are included in the +configuration file: + + + + 1Recorders\SlowOutgoingRequests::class => [ + + 2 // ... + + 3 'groups' => [ + + 4 // '#^https://api\.github\.com/repos/.*$#' => 'api.github.com/repos/*', + + 5 // '#^https?://([^/]*).*$#' => '\1', + + 6 // '#/\d+#' => '/*', + + 7 ], + + 8], + + + Recorders\SlowOutgoingRequests::class => [ + // ... + 'groups' => [ + // '#^https://api\.github\.com/repos/.*$#' => 'api.github.com/repos/*', + // '#^https?://([^/]*).*$#' => '\1', + // '#/\d+#' => '/*', + ], + ], + +The first pattern that matches will be used. If no patterns match, then the +URL will be captured as-is. + +#### Slow Queries + +The `SlowQueries` recorder captures any database queries in your application +that exceed the configured threshold for display on the Slow Queries card. + +You may optionally adjust the slow query threshold, sample rate, and ignored +query patterns. You may also configure whether to capture the query location. +The captured location will be displayed on the Pulse dashboard which can help +to track down the query origin; however, if the same query is made in multiple +locations then it will appear multiple times for each unique location. + +You may have some queries that you expect to take longer than others. In those +cases, you may configure per-query thresholds: + + + + 1Recorders\SlowQueries::class => [ + + 2 // ... + + 3 'threshold' => [ + + 4 '#^insert into `yearly_reports`#' => 5000, + + 5 'default' => env('PULSE_SLOW_QUERIES_THRESHOLD', 1000), + + 6 ], + + 7], + + + Recorders\SlowQueries::class => [ + // ... + 'threshold' => [ + '#^insert into `yearly_reports`#' => 5000, + 'default' => env('PULSE_SLOW_QUERIES_THRESHOLD', 1000), + ], + ], + +If no regular expression patterns match the query's SQL, then the `'default'` +value will be used. + +#### Slow Requests + +The `Requests` recorder captures information about requests made to your +application for display on the Slow Requests and Application Usage cards. + +You may optionally adjust the slow route threshold, sample rate, and ignored +paths. + +You may have some requests that you expect to take longer than others. In +those cases, you may configure per-request thresholds: + + + + 1Recorders\SlowRequests::class => [ + + 2 // ... + + 3 'threshold' => [ + + 4 '#^/admin/#' => 5000, + + 5 'default' => env('PULSE_SLOW_REQUESTS_THRESHOLD', 1000), + + 6 ], + + 7], + + + Recorders\SlowRequests::class => [ + // ... + 'threshold' => [ + '#^/admin/#' => 5000, + 'default' => env('PULSE_SLOW_REQUESTS_THRESHOLD', 1000), + ], + ], + +If no regular expression patterns match the request's URL, then the +`'default'` value will be used. + +#### Servers + +The `Servers` recorder captures CPU, memory, and storage usage of the servers +that power your application for display on the Servers card. This recorder +requires the pulse:check command to be running on each of the servers you wish +to monitor. + +Each reporting server must have a unique name. By default, Pulse will use the +value returned by PHP's `gethostname` function. If you wish to customize this, +you may set the `PULSE_SERVER_NAME` environment variable: + + + + 1PULSE_SERVER_NAME=load-balancer + + + PULSE_SERVER_NAME=load-balancer + +The Pulse configuration file also allows you to customize the directories that +are monitored. + +#### User Jobs + +The `UserJobs` recorder captures information about the users dispatching jobs +in your application for display on the Application Usage card. + +You may optionally adjust the sample rate and ignored job patterns. + +#### User Requests + +The `UserRequests` recorder captures information about the users making +requests to your application for display on the Application Usage card. + +You may optionally adjust the sample rate and ignored URL patterns. + +### Filtering + +As we have seen, many recorders offer the ability to, via configuration, +"ignore" incoming entries based on their value, such as a request's URL. But, +sometimes it may be useful to filter out records based on other factors, such +as the currently authenticated user. To filter out these records, you may pass +a closure to Pulse's `filter` method. Typically, the `filter` method should be +invoked within the `boot` method of your application's `AppServiceProvider`: + + + + 1use Illuminate\Support\Facades\Auth; + + 2use Laravel\Pulse\Entry; + + 3use Laravel\Pulse\Facades\Pulse; + + 4use Laravel\Pulse\Value; + + 5  + + 6/** + + 7 * Bootstrap any application services. + + 8 */ + + 9public function boot(): void + + 10{ + + 11 Pulse::filter(function (Entry|Value $entry) { + + 12 return Auth::user()->isNotAdmin(); + + 13 }); + + 14  + + 15 // ... + + 16} + + + use Illuminate\Support\Facades\Auth; + use Laravel\Pulse\Entry; + use Laravel\Pulse\Facades\Pulse; + use Laravel\Pulse\Value; + + /** + * Bootstrap any application services. + */ + public function boot(): void + { + Pulse::filter(function (Entry|Value $entry) { + return Auth::user()->isNotAdmin(); + }); + + // ... + } + +## Performance + +Pulse has been designed to drop into an existing application without requiring +any additional infrastructure. However, for high-traffic applications, there +are several ways of removing any impact Pulse may have on your application's +performance. + +### Using a Different Database + +For high-traffic applications, you may prefer to use a dedicated database +connection for Pulse to avoid impacting your application database. + +You may customize the [database connection](/docs/12.x/database#configuration) +used by Pulse by setting the `PULSE_DB_CONNECTION` environment variable. + + + + 1PULSE_DB_CONNECTION=pulse + + + PULSE_DB_CONNECTION=pulse + +### Redis Ingest + +The Redis Ingest requires Redis 6.2 or greater and `phpredis` or `predis` as +the application's configured Redis client driver. + +By default, Pulse will store entries directly to the configured database +connection after the HTTP response has been sent to the client or a job has +been processed; however, you may use Pulse's Redis ingest driver to send +entries to a Redis stream instead. This can be enabled by configuring the +`PULSE_INGEST_DRIVER` environment variable: + + + + 1PULSE_INGEST_DRIVER=redis + + + PULSE_INGEST_DRIVER=redis + +Pulse will use your default [Redis connection](/docs/12.x/redis#configuration) +by default, but you may customize this via the `PULSE_REDIS_CONNECTION` +environment variable: + + + + 1PULSE_REDIS_CONNECTION=pulse + + + PULSE_REDIS_CONNECTION=pulse + +When using the Redis ingest driver, your Pulse installation should always use +a different Redis connection than your Redis powered queue, if applicable. + +When using the Redis ingest, you will need to run the `pulse:work` command to +monitor the stream and move entries from Redis into Pulse's database tables. + + + + 1php artisan pulse:work + + + php artisan pulse:work + +To keep the `pulse:work` process running permanently in the background, you +should use a process monitor such as Supervisor to ensure that the Pulse +worker does not stop running. + +As the `pulse:work` command is a long-lived process, it will not see changes +to your codebase without being restarted. You should gracefully restart the +command by calling the `pulse:restart` command during your application's +deployment process: + + + + 1php artisan pulse:restart + + + php artisan pulse:restart + +Pulse uses the [cache](/docs/12.x/cache) to store restart signals, so you +should verify that a cache driver is properly configured for your application +before using this feature. + +### Sampling + +By default, Pulse will capture every relevant event that occurs in your +application. For high-traffic applications, this can result in needing to +aggregate millions of database rows in the dashboard, especially for longer +time periods. + +You may instead choose to enable "sampling" on certain Pulse data recorders. +For example, setting the sample rate to `0.1` on the User Requests recorder +will mean that you only record approximately 10% of the requests to your +application. In the dashboard, the values will be scaled up and prefixed with +a `~` to indicate that they are an approximation. + +In general, the more entries you have for a particular metric, the lower you +can safely set the sample rate without sacrificing too much accuracy. + +### Trimming + +Pulse will automatically trim its stored entries once they are outside of the +dashboard window. Trimming occurs when ingesting data using a lottery system +which may be customized in the Pulse configuration file. + +### Handling Pulse Exceptions + +If an exception occurs while capturing Pulse data, such as being unable to +connect to the storage database, Pulse will silently fail to avoid impacting +your application. + +If you wish to customize how these exceptions are handled, you may provide a +closure to the `handleExceptionsUsing` method: + + + + 1use Laravel\Pulse\Facades\Pulse; + + 2use Illuminate\Support\Facades\Log; + + 3  + + 4Pulse::handleExceptionsUsing(function ($e) { + + 5 Log::debug('An exception happened in Pulse', [ + + 6 'message' => $e->getMessage(), + + 7 'stack' => $e->getTraceAsString(), + + 8 ]); + + 9}); + + + use Laravel\Pulse\Facades\Pulse; + use Illuminate\Support\Facades\Log; + + Pulse::handleExceptionsUsing(function ($e) { + Log::debug('An exception happened in Pulse', [ + 'message' => $e->getMessage(), + 'stack' => $e->getTraceAsString(), + ]); + }); + +## Custom Cards + +Pulse allows you to build custom cards to display data relevant to your +application's specific needs. Pulse uses +[Livewire](https://livewire.laravel.com), so you may want to [review its +documentation](https://livewire.laravel.com/docs) before building your first +custom card. + +### Card Components + +Creating a custom card in Laravel Pulse starts with extending the base `Card` +Livewire component and defining a corresponding view: + + + + 1namespace App\Livewire\Pulse; + + 2  + + 3use Laravel\Pulse\Livewire\Card; + + 4use Livewire\Attributes\Lazy; + + 5  + + 6#[Lazy] + + 7class TopSellers extends Card + + 8{ + + 9 public function render() + + 10 { + + 11 return view('livewire.pulse.top-sellers'); + + 12 } + + 13} + + + namespace App\Livewire\Pulse; + + use Laravel\Pulse\Livewire\Card; + use Livewire\Attributes\Lazy; + + #[Lazy] + class TopSellers extends Card + { + public function render() + { + return view('livewire.pulse.top-sellers'); + } + } + +When using Livewire's [lazy loading](https://livewire.laravel.com/docs/lazy) +feature, The `Card` component will automatically provide a placeholder that +respects the `cols` and `rows` attributes passed to your component. + +When writing your Pulse card's corresponding view, you may leverage Pulse's +Blade components for a consistent look and feel: + + + + 1 + + 2 + + 3 + + 4 ... + + 5 + + 6 + + 7  + + 8 + + 9 ... + + 10 + + 11 + + + + + + ... + + + + + ... + + + +The `$cols`, `$rows`, `$class`, and `$expand` variables should be passed to +their respective Blade components so the card layout may be customized from +the dashboard view. You may also wish to include the `wire:poll.5s=""` +attribute in your view to have the card automatically update. + +Once you have defined your Livewire component and template, the card may be +included in your dashboard view: + + + + 1 + + 2 ... + + 3  + + 4 + + 5 + + + + ... + + + + +If your card is included in a package, you will need to register the component +with Livewire using the `Livewire::component` method. + +### Styling + +If your card requires additional styling beyond the classes and components +included with Pulse, there are a few options for including custom CSS for your +cards. + +#### Laravel Vite Integration + +If your custom card lives within your application's code base and you are +using Laravel's [Vite integration](/docs/12.x/vite), you may update your +`vite.config.js` file to include a dedicated CSS entry point for your card: + + + + 1laravel({ + + 2 input: [ + + 3 'resources/css/pulse/top-sellers.css', + + 4 // ... + + 5 ], + + 6}), + + + laravel({ + input: [ + 'resources/css/pulse/top-sellers.css', + // ... + ], + }), + +You may then use the `@vite` Blade directive in your dashboard view, +specifying the CSS entrypoint for your card: + + + + 1 + + 2 @vite('resources/css/pulse/top-sellers.css') + + 3  + + 4 ... + + 5 + + + + @vite('resources/css/pulse/top-sellers.css') + + ... + + +#### CSS Files + +For other use cases, including Pulse cards contained within a package, you may +instruct Pulse to load additional stylesheets by defining a `css` method on +your Livewire component that returns the file path to your CSS file: + + + + 1class TopSellers extends Card + + 2{ + + 3 // ... + + 4  + + 5 protected function css() + + 6 { + + 7 return __DIR__.'/../../dist/top-sellers.css'; + + 8 } + + 9} + + + class TopSellers extends Card + { + // ... + + protected function css() + { + return __DIR__.'/../../dist/top-sellers.css'; + } + } + +When this card is included on the dashboard, Pulse will automatically include +the contents of this file within a ` + + 10 + + 13 + + + @use('Illuminate\Support\Facades\Vite') + + + + {{-- ... --}} + + + + + +## Running Vite + +There are two ways you can run Vite. You may run the development server via +the `dev` command, which is useful while developing locally. The development +server will automatically detect changes to your files and instantly reflect +them in any open browser windows. + +Or, running the `build` command will version and bundle your application's +assets and get them ready for you to deploy to production: + + + + 1# Run the Vite development server... + + 2npm run dev + + 3  + + 4# Build and version the assets for production... + + 5npm run build + + + # Run the Vite development server... + npm run dev + + # Build and version the assets for production... + npm run build + +If you are running the development server in [Sail](/docs/12.x/sail) on WSL2, +you may need some additional configuration options. + +## Working With JavaScript + +### Aliases + +By default, The Laravel plugin provides a common alias to help you hit the +ground running and conveniently import your application's assets: + + + + 1{ + + 2 '@' => '/resources/js' + + 3} + + + { + '@' => '/resources/js' + } + +You may overwrite the `'@'` alias by adding your own to the `vite.config.js` +configuration file: + + + + 1import { defineConfig } from 'vite'; + + 2import laravel from 'laravel-vite-plugin'; + + 3  + + 4export default defineConfig({ + + 5 plugins: [ + + 6 laravel(['resources/ts/app.tsx']), + + 7 ], + + 8 resolve: { + + 9 alias: { + + 10 '@': '/resources/ts', + + 11 }, + + 12 }, + + 13}); + + + import { defineConfig } from 'vite'; + import laravel from 'laravel-vite-plugin'; + + export default defineConfig({ + plugins: [ + laravel(['resources/ts/app.tsx']), + ], + resolve: { + alias: { + '@': '/resources/ts', + }, + }, + }); + +### Vue + +If you would like to build your frontend using the [Vue](https://vuejs.org/) +framework, then you will also need to install the `@vitejs/plugin-vue` plugin: + + + + 1npm install --save-dev @vitejs/plugin-vue + + + npm install --save-dev @vitejs/plugin-vue + +You may then include the plugin in your `vite.config.js` configuration file. +There are a few additional options you will need when using the Vue plugin +with Laravel: + + + + 1import { defineConfig } from 'vite'; + + 2import laravel from 'laravel-vite-plugin'; + + 3import vue from '@vitejs/plugin-vue'; + + 4  + + 5export default defineConfig({ + + 6 plugins: [ + + 7 laravel(['resources/js/app.js']), + + 8 vue({ + + 9 template: { + + 10 transformAssetUrls: { + + 11 // The Vue plugin will re-write asset URLs, when referenced + + 12 // in Single File Components, to point to the Laravel web + + 13 // server. Setting this to `null` allows the Laravel plugin + + 14 // to instead re-write asset URLs to point to the Vite + + 15 // server instead. + + 16 base: null, + + 17  + + 18 // The Vue plugin will parse absolute URLs and treat them + + 19 // as absolute paths to files on disk. Setting this to + + 20 // `false` will leave absolute URLs un-touched so they can + + 21 // reference assets in the public directory as expected. + + 22 includeAbsolute: false, + + 23 }, + + 24 }, + + 25 }), + + 26 ], + + 27}); + + + import { defineConfig } from 'vite'; + import laravel from 'laravel-vite-plugin'; + import vue from '@vitejs/plugin-vue'; + + export default defineConfig({ + plugins: [ + laravel(['resources/js/app.js']), + vue({ + template: { + transformAssetUrls: { + // The Vue plugin will re-write asset URLs, when referenced + // in Single File Components, to point to the Laravel web + // server. Setting this to `null` allows the Laravel plugin + // to instead re-write asset URLs to point to the Vite + // server instead. + base: null, + + // The Vue plugin will parse absolute URLs and treat them + // as absolute paths to files on disk. Setting this to + // `false` will leave absolute URLs un-touched so they can + // reference assets in the public directory as expected. + includeAbsolute: false, + }, + }, + }), + ], + }); + +Laravel's [starter kits](/docs/12.x/starter-kits) already include the proper +Laravel, Vue, and Vite configuration.These starter kits offer the fastest way +to get started with Laravel, Vue, and Vite. + +### React + +If you would like to build your frontend using the +[React](https://reactjs.org/) framework, then you will also need to install +the `@vitejs/plugin-react` plugin: + + + + 1npm install --save-dev @vitejs/plugin-react + + + npm install --save-dev @vitejs/plugin-react + +You may then include the plugin in your `vite.config.js` configuration file: + + + + 1import { defineConfig } from 'vite'; + + 2import laravel from 'laravel-vite-plugin'; + + 3import react from '@vitejs/plugin-react'; + + 4  + + 5export default defineConfig({ + + 6 plugins: [ + + 7 laravel(['resources/js/app.jsx']), + + 8 react(), + + 9 ], + + 10}); + + + import { defineConfig } from 'vite'; + import laravel from 'laravel-vite-plugin'; + import react from '@vitejs/plugin-react'; + + export default defineConfig({ + plugins: [ + laravel(['resources/js/app.jsx']), + react(), + ], + }); + +You will need to ensure that any files containing JSX have a `.jsx` or `.tsx` +extension, remembering to update your entry point, if required, as shown +above. + +You will also need to include the additional `@viteReactRefresh` Blade +directive alongside your existing `@vite` directive. + + + + 1@viteReactRefresh + + 2@vite('resources/js/app.jsx') + + + @viteReactRefresh + @vite('resources/js/app.jsx') + +The `@viteReactRefresh` directive must be called before the `@vite` directive. + +Laravel's [starter kits](/docs/12.x/starter-kits) already include the proper +Laravel, React, and Vite configuration.These starter kits offer the fastest +way to get started with Laravel, React, and Vite. + +### Inertia + +The Laravel Vite plugin provides a convenient `resolvePageComponent` function +to help you resolve your Inertia page components. Below is an example of the +helper in use with Vue 3; however, you may also utilize the function in other +frameworks such as React: + + + + 1import { createApp, h } from 'vue'; + + 2import { createInertiaApp } from '@inertiajs/vue3'; + + 3import { resolvePageComponent } from 'laravel-vite-plugin/inertia-helpers'; + + 4  + + 5createInertiaApp({ + + 6 resolve: (name) => resolvePageComponent(`./Pages/${name}.vue`, import.meta.glob('./Pages/**/*.vue')), + + 7 setup({ el, App, props, plugin }) { + + 8 createApp({ render: () => h(App, props) }) + + 9 .use(plugin) + + 10 .mount(el) + + 11 }, + + 12}); + + + import { createApp, h } from 'vue'; + import { createInertiaApp } from '@inertiajs/vue3'; + import { resolvePageComponent } from 'laravel-vite-plugin/inertia-helpers'; + + createInertiaApp({ + resolve: (name) => resolvePageComponent(`./Pages/${name}.vue`, import.meta.glob('./Pages/**/*.vue')), + setup({ el, App, props, plugin }) { + createApp({ render: () => h(App, props) }) + .use(plugin) + .mount(el) + }, + }); + +If you are using Vite's code splitting feature with Inertia, we recommend +configuring asset prefetching. + +Laravel's [starter kits](/docs/12.x/starter-kits) already include the proper +Laravel, Inertia, and Vite configuration.These starter kits offer the fastest +way to get started with Laravel, Inertia, and Vite. + +### URL Processing + +When using Vite and referencing assets in your application's HTML, CSS, or JS, +there are a couple of caveats to consider. First, if you reference assets with +an absolute path, Vite will not include the asset in the build; therefore, you +should ensure that the asset is available in your public directory. You should +avoid using absolute paths when using a dedicated CSS entrypoint because, +during development, browsers will try to load these paths from the Vite +development server, where the CSS is hosted, rather than from your public +directory. + +When referencing relative asset paths, you should remember that the paths are +relative to the file where they are referenced. Any assets referenced via a +relative path will be re-written, versioned, and bundled by Vite. + +Consider the following project structure: + + + + 1public/ + + 2 taylor.png + + 3resources/ + + 4 js/ + + 5 Pages/ + + 6 Welcome.vue + + 7 images/ + + 8 abigail.png + + + public/ + taylor.png + resources/ + js/ + Pages/ + Welcome.vue + images/ + abigail.png + +The following example demonstrates how Vite will treat relative and absolute +URLs: + + + + 1 + + 2 + + 3  + + 4 + + 5 + + + + + + + + +## Working With Stylesheets + +Laravel's [starter kits](/docs/12.x/starter-kits) already include the proper +Tailwind and Vite configuration. Or, if you would like to use Tailwind and +Laravel without using one of our starter kits, check out [Tailwind's +installation guide for Laravel](https://tailwindcss.com/docs/guides/laravel). + +All Laravel applications already include Tailwind and a properly configured +`vite.config.js` file. So, you only need to start the Vite development server +or run the `dev` Composer command, which will start both the Laravel and Vite +development servers: + + + + 1composer run dev + + + composer run dev + +Your application's CSS may be placed within the `resources/css/app.css` file. + +## Working With Blade and Routes + +### Processing Static Assets With Vite + +When referencing assets in your JavaScript or CSS, Vite automatically +processes and versions them. In addition, when building Blade based +applications, Vite can also process and version static assets that you +reference solely in Blade templates. + +However, in order to accomplish this, you need to make Vite aware of your +assets by importing the static assets into the application's entry point. For +example, if you want to process and version all images stored in +`resources/images` and all fonts stored in `resources/fonts`, you should add +the following in your application's `resources/js/app.js` entry point: + + + + 1import.meta.glob([ + + 2 '../images/**', + + 3 '../fonts/**', + + 4]); + + + import.meta.glob([ + '../images/**', + '../fonts/**', + ]); + +These assets will now be processed by Vite when running `npm run build`. You +can then reference these assets in Blade templates using the `Vite::asset` +method, which will return the versioned URL for a given asset: + + + + 1 + + + + +### Refreshing on Save + +When your application is built using traditional server-side rendering with +Blade, Vite can improve your development workflow by automatically refreshing +the browser when you make changes to view files in your application. To get +started, you can simply specify the `refresh` option as `true`. + + + + 1import { defineConfig } from 'vite'; + + 2import laravel from 'laravel-vite-plugin'; + + 3  + + 4export default defineConfig({ + + 5 plugins: [ + + 6 laravel({ + + 7 // ... + + 8 refresh: true, + + 9 }), + + 10 ], + + 11}); + + + import { defineConfig } from 'vite'; + import laravel from 'laravel-vite-plugin'; + + export default defineConfig({ + plugins: [ + laravel({ + // ... + refresh: true, + }), + ], + }); + +When the `refresh` option is `true`, saving files in the following directories +will trigger the browser to perform a full page refresh while you are running +`npm run dev`: + + * `app/Livewire/**` + * `app/View/Components/**` + * `lang/**` + * `resources/lang/**` + * `resources/views/**` + * `routes/**` + +Watching the `routes/**` directory is useful if you are utilizing +[Ziggy](https://github.com/tighten/ziggy) to generate route links within your +application's frontend. + +If these default paths do not suit your needs, you can specify your own list +of paths to watch: + + + + 1import { defineConfig } from 'vite'; + + 2import laravel from 'laravel-vite-plugin'; + + 3  + + 4export default defineConfig({ + + 5 plugins: [ + + 6 laravel({ + + 7 // ... + + 8 refresh: ['resources/views/**'], + + 9 }), + + 10 ], + + 11}); + + + import { defineConfig } from 'vite'; + import laravel from 'laravel-vite-plugin'; + + export default defineConfig({ + plugins: [ + laravel({ + // ... + refresh: ['resources/views/**'], + }), + ], + }); + +Under the hood, the Laravel Vite plugin uses the [vite-plugin-full- +reload](https://github.com/ElMassimo/vite-plugin-full-reload) package, which +offers some advanced configuration options to fine-tune this feature's +behavior. If you need this level of customization, you may provide a `config` +definition: + + + + 1import { defineConfig } from 'vite'; + + 2import laravel from 'laravel-vite-plugin'; + + 3  + + 4export default defineConfig({ + + 5 plugins: [ + + 6 laravel({ + + 7 // ... + + 8 refresh: [{ + + 9 paths: ['path/to/watch/**'], + + 10 config: { delay: 300 } + + 11 }], + + 12 }), + + 13 ], + + 14}); + + + import { defineConfig } from 'vite'; + import laravel from 'laravel-vite-plugin'; + + export default defineConfig({ + plugins: [ + laravel({ + // ... + refresh: [{ + paths: ['path/to/watch/**'], + config: { delay: 300 } + }], + }), + ], + }); + +### Aliases + +It is common in JavaScript applications to create 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/12.x/providers): + + + + 1/** + + 2 * Bootstrap any application services. + + 3 */ + + 4public function boot(): void + + 5{ + + 6 Vite::macro('image', fn (string $asset) => $this->asset("resources/images/{$asset}")); + + 7} + + + /** + * 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`: + + + + 1Laravel Logo + + + Laravel Logo + +## Asset Prefetching + +When building an SPA using Vite's code splitting feature, required assets are +fetched on each page navigation. This behavior can lead to delayed UI +rendering. If this is a problem for your frontend framework of choice, Laravel +offers the ability to eagerly prefetch your application's JavaScript and CSS +assets on initial page load. + +You can instruct Laravel to eagerly prefetch your assets by invoking the +`Vite::prefetch` method in the `boot` method of a [service +provider](/docs/12.x/providers): + + + + 1 + + 2 addEventListener('load', () => setTimeout(() => { + + 3 dispatchEvent(new Event('vite:prefetch')) + + 4 }, 3000)) + + 5 + + + + +## Custom Base URLs + +If your Vite compiled assets are deployed to a domain separate from your +application, such as via a CDN, you must specify the `ASSET_URL` environment +variable within your application's `.env` file: + + + + 1ASSET_URL=https://cdn.example.com + + + ASSET_URL=https://cdn.example.com + +After configuring the asset URL, all re-written URLs to your assets will be +prefixed with the configured value: + + + + 1https://cdn.example.com/build/assets/app.9dce8d17.js + + + https://cdn.example.com/build/assets/app.9dce8d17.js + +Remember that absolute URLs are not re-written by Vite, so they will not be +prefixed. + +## Environment Variables + +You may inject environment variables into your JavaScript by prefixing them +with `VITE_` in your application's `.env` file: + + + + 1VITE_SENTRY_DSN_PUBLIC=http://example.com + + + VITE_SENTRY_DSN_PUBLIC=http://example.com + +You may access injected environment variables via the `import.meta.env` +object: + + + + 1import.meta.env.VITE_SENTRY_DSN_PUBLIC + + + import.meta.env.VITE_SENTRY_DSN_PUBLIC + +## Disabling Vite in Tests + +Laravel's Vite integration will attempt to resolve your assets while running +your tests, which requires you to either run the Vite development server or +build your assets. + +If you would prefer to mock Vite during testing, you may call the +`withoutVite` method, which is available for any tests that extend Laravel's +`TestCase` class: + +Pest PHPUnit + + + + 1test('without vite example', function () { + + 2 $this->withoutVite(); + + 3  + + 4 // ... + + 5}); + + + test('without vite example', function () { + $this->withoutVite(); + + // ... + }); + + + 1use Tests\TestCase; + + 2  + + 3class ExampleTest extends TestCase + + 4{ + + 5 public function test_without_vite_example(): void + + 6 { + + 7 $this->withoutVite(); + + 8  + + 9 // ... + + 10 } + + 11} + + + use Tests\TestCase; + + class ExampleTest extends TestCase + { + public function test_without_vite_example(): void + { + $this->withoutVite(); + + // ... + } + } + +If you would like to disable Vite for all tests, you may call the +`withoutVite` method from the `setUp` method on your base `TestCase` class: + + + + 1withoutVite(); + + 14 } + + 15} + + + withoutVite(); + } + } + +## Server-Side Rendering (SSR) + +The Laravel Vite plugin makes it painless to set up server-side rendering with +Vite. To get started, create an SSR entry point at `resources/js/ssr.js` and +specify the entry point by passing a configuration option to the Laravel +plugin: + + + + 1import { defineConfig } from 'vite'; + + 2import laravel from 'laravel-vite-plugin'; + + 3  + + 4export default defineConfig({ + + 5 plugins: [ + + 6 laravel({ + + 7 input: 'resources/js/app.js', + + 8 ssr: 'resources/js/ssr.js', + + 9 }), + + 10 ], + + 11}); + + + import { defineConfig } from 'vite'; + import laravel from 'laravel-vite-plugin'; + + export default defineConfig({ + plugins: [ + laravel({ + input: 'resources/js/app.js', + ssr: 'resources/js/ssr.js', + }), + ], + }); + +To ensure you don't forget to rebuild the SSR entry point, we recommend +augmenting the "build" script in your application's `package.json` to create +your SSR build: + + + + 1"scripts": { + + 2 "dev": "vite", + + 3 "build": "vite build" + + 4 "build": "vite build && vite build --ssr" + + 5} + + + "scripts": { + "dev": "vite", + "build": "vite build" + "build": "vite build && vite build --ssr" + } + +Then, to build and start the SSR server, you may run the following commands: + + + + 1npm run build + + 2node bootstrap/ssr/ssr.js + + + npm run build + node bootstrap/ssr/ssr.js + +If you are using [SSR with Inertia](https://inertiajs.com/server-side- +rendering), you may instead use the `inertia:start-ssr` Artisan command to +start the SSR server: + + + + 1php artisan inertia:start-ssr + + + php artisan inertia:start-ssr + +Laravel's [starter kits](/docs/12.x/starter-kits) already include the proper +Laravel, Inertia SSR, and Vite configuration.These starter kits offer the +fastest way to get started with Laravel, Inertia SSR, and Vite. + +## Script and Style Tag Attributes + +### Content Security Policy (CSP) Nonce + +If you wish to include a [nonce attribute](https://developer.mozilla.org/en- +US/docs/Web/HTML/Global_attributes/nonce) on your script and style tags as +part of your [Content Security Policy](https://developer.mozilla.org/en- +US/docs/Web/HTTP/CSP), you may generate or specify a nonce using the +`useCspNonce` method within a custom [middleware](/docs/12.x/middleware): + + + + 1withHeaders([ + + 22 'Content-Security-Policy' => "script-src 'nonce-".Vite::cspNonce()."'", + + 23 ]); + + 24 } + + 25} + + + withHeaders([ + 'Content-Security-Policy' => "script-src 'nonce-".Vite::cspNonce()."'", + ]); + } + } + +After invoking the `useCspNonce` method, Laravel will automatically include +the `nonce` attributes on all generated script and style tags. + +If you need to specify the nonce elsewhere, including the [Ziggy `@route` +directive](https://github.com/tighten/ziggy#using-routes-with-a-content- +security-policy) included with Laravel's [starter kits](/docs/12.x/starter- +kits), you may retrieve it using the `cspNonce` method: + + + + 1@routes(nonce: Vite::cspNonce()) + + + @routes(nonce: Vite::cspNonce()) + +If you already have a nonce that you would like to instruct Laravel to use, +you may pass the nonce to the `useCspNonce` method: + + + + 1Vite::useCspNonce($nonce); + + + Vite::useCspNonce($nonce); + +### Subresource Integrity (SRI) + +If your Vite manifest includes `integrity` hashes for your assets, Laravel +will automatically add the `integrity` attribute on any script and style tags +it generates in order to enforce [Subresource +Integrity](https://developer.mozilla.org/en- +US/docs/Web/Security/Subresource_Integrity). By default, Vite does not include +the `integrity` hash in its manifest, but you may enable it by installing the +[vite-plugin-manifest-sri](https://www.npmjs.com/package/vite-plugin-manifest- +sri) NPM plugin: + + + + 1npm install --save-dev vite-plugin-manifest-sri + + + npm install --save-dev vite-plugin-manifest-sri + +You may then enable this plugin in your `vite.config.js` file: + + + + 1import { defineConfig } from 'vite'; + + 2import laravel from 'laravel-vite-plugin'; + + 3import manifestSRI from 'vite-plugin-manifest-sri'; + + 4  + + 5export default defineConfig({ + + 6 plugins: [ + + 7 laravel({ + + 8 // ... + + 9 }), + + 10 manifestSRI(), + + 11 ], + + 12}); + + + import { defineConfig } from 'vite'; + import laravel from 'laravel-vite-plugin'; + import manifestSRI from 'vite-plugin-manifest-sri'; + + export default defineConfig({ + plugins: [ + laravel({ + // ... + }), + manifestSRI(), + ], + }); + +If required, you may also customize the manifest key where the integrity hash +can be found: + + + + 1use Illuminate\Support\Facades\Vite; + + 2  + + 3Vite::useIntegrityKey('custom-integrity-key'); + + + use Illuminate\Support\Facades\Vite; + + Vite::useIntegrityKey('custom-integrity-key'); + +If you would like to disable this auto-detection completely, you may pass +`false` to the `useIntegrityKey` method: + + + + 1Vite::useIntegrityKey(false); + + + Vite::useIntegrityKey(false); + +### Arbitrary Attributes + +If you need to include additional attributes on your script and style tags, +such as the [data-turbo- +track](https://turbo.hotwired.dev/handbook/drive#reloading-when-assets-change) +attribute, you may specify them via the `useScriptTagAttributes` and +`useStyleTagAttributes` methods. Typically, this methods should be invoked +from a [service provider](/docs/12.x/providers): + + + + 1use Illuminate\Support\Facades\Vite; + + 2  + + 3Vite::useScriptTagAttributes([ + + 4 'data-turbo-track' => 'reload', // Specify a value for the attribute... + + 5 'async' => true, // Specify an attribute without a value... + + 6 'integrity' => false, // Exclude an attribute that would otherwise be included... + + 7]); + + 8  + + 9Vite::useStyleTagAttributes([ + + 10 'data-turbo-track' => 'reload', + + 11]); + + + use Illuminate\Support\Facades\Vite; + + Vite::useScriptTagAttributes([ + 'data-turbo-track' => 'reload', // Specify a value for the attribute... + 'async' => true, // Specify an attribute without a value... + 'integrity' => false, // Exclude an attribute that would otherwise be included... + ]); + + Vite::useStyleTagAttributes([ + 'data-turbo-track' => 'reload', + ]); + +If you need to conditionally add attributes, you may pass a callback that will +receive the asset source path, its URL, its manifest chunk, and the entire +manifest: + + + + 1use Illuminate\Support\Facades\Vite; + + 2  + + 3Vite::useScriptTagAttributes(fn (string $src, string $url, array|null $chunk, array|null $manifest) => [ + + 4 'data-turbo-track' => $src === 'resources/js/app.js' ? 'reload' : false, + + 5]); + + 6  + + 7Vite::useStyleTagAttributes(fn (string $src, string $url, array|null $chunk, array|null $manifest) => [ + + 8 'data-turbo-track' => $chunk && $chunk['isEntry'] ? 'reload' : false, + + 9]); + + + use Illuminate\Support\Facades\Vite; + + Vite::useScriptTagAttributes(fn (string $src, string $url, array|null $chunk, array|null $manifest) => [ + 'data-turbo-track' => $src === 'resources/js/app.js' ? 'reload' : false, + ]); + + Vite::useStyleTagAttributes(fn (string $src, string $url, array|null $chunk, array|null $manifest) => [ + 'data-turbo-track' => $chunk && $chunk['isEntry'] ? 'reload' : false, + ]); + +The `$chunk` and `$manifest` arguments will be `null` while the Vite +development server is running. + +## Advanced Customization + +Out of the box, Laravel's Vite plugin uses sensible conventions that should +work for the majority of applications; however, sometimes you may need to +customize Vite's behavior. To enable additional customization options, we +offer the following methods and options which can be used in place of the +`@vite` Blade directive: + + + + 1 + + 2 + + 3 {{-- ... --}} + + 4  + + 5 {{ + + 6 Vite::useHotFile(storage_path('vite.hot')) // Customize the "hot" file... + + 7 ->useBuildDirectory('bundle') // Customize the build directory... + + 8 ->useManifestFilename('assets.json') // Customize the manifest filename... + + 9 ->withEntryPoints(['resources/js/app.js']) // Specify the entry points... + + 10 ->createAssetPathsUsing(function (string $path, ?bool $secure) { // Customize the backend path generation for built assets... + + 11 return "https://cdn.example.com/{$path}"; + + 12 }) + + 13 }} + + 14 + + + + + {{-- ... --}} + + {{ + Vite::useHotFile(storage_path('vite.hot')) // Customize the "hot" file... + ->useBuildDirectory('bundle') // Customize the build directory... + ->useManifestFilename('assets.json') // Customize the manifest filename... + ->withEntryPoints(['resources/js/app.js']) // Specify the entry points... + ->createAssetPathsUsing(function (string $path, ?bool $secure) { // Customize the backend path generation for built assets... + return "https://cdn.example.com/{$path}"; + }) + }} + + +Within the `vite.config.js` file, you should then specify the same +configuration: + + + + 1import { defineConfig } from 'vite'; + + 2import laravel from 'laravel-vite-plugin'; + + 3  + + 4export default defineConfig({ + + 5 plugins: [ + + 6 laravel({ + + 7 hotFile: 'storage/vite.hot', // Customize the "hot" file... + + 8 buildDirectory: 'bundle', // Customize the build directory... + + 9 input: ['resources/js/app.js'], // Specify the entry points... + + 10 }), + + 11 ], + + 12 build: { + + 13 manifest: 'assets.json', // Customize the manifest filename... + + 14 }, + + 15}); + + + import { defineConfig } from 'vite'; + import laravel from 'laravel-vite-plugin'; + + export default defineConfig({ + plugins: [ + laravel({ + hotFile: 'storage/vite.hot', // Customize the "hot" file... + buildDirectory: 'bundle', // Customize the build directory... + input: ['resources/js/app.js'], // Specify the entry points... + }), + ], + build: { + manifest: 'assets.json', // Customize the manifest filename... + }, + }); + +### Dev Server Cross-Origin Resource Sharing (CORS) + +If you are experiencing Cross-Origin Resource Sharing (CORS) issues in the +browser while fetching assets from the Vite dev server, you may need to grant +your custom origin access to the dev server. Vite combined with the Laravel +plugin allows the following origins without any additional configuration: + + * `::1` + * `127.0.0.1` + * `localhost` + * `*.test` + * `*.localhost` + * `APP_URL` in the project's `.env` + +The easiest way to allow a custom origin for your project is to ensure that +your application's `APP_URL` environment variable matches the origin you are +visiting in your browser. For example, if you visiting `https://my- +app.laravel`, you should update your `.env` to match: + + + + 1APP_URL=https://my-app.laravel + + + APP_URL=https://my-app.laravel + +If you need more fine-grained control over the origins, such as supporting +multiple origins, you should utilize [Vite's comprehensive and flexible built- +in CORS server configuration](https://vite.dev/config/server- +options.html#server-cors). For example, you may specify multiple origins in +the `server.cors.origin` configuration option in the project's +`vite.config.js` file: + + + + 1import { defineConfig } from 'vite'; + + 2import laravel from 'laravel-vite-plugin'; + + 3  + + 4export default defineConfig({ + + 5 plugins: [ + + 6 laravel({ + + 7 input: 'resources/js/app.js', + + 8 refresh: true, + + 9 }), + + 10 ], + + 11 server: { + + 12 cors: { + + 13 origin: [ + + 14 'https://backend.laravel', + + 15 'http://admin.laravel:8566', + + 16 ], + + 17 }, + + 18 }, + + 19}); + + + import { defineConfig } from 'vite'; + import laravel from 'laravel-vite-plugin'; + + export default defineConfig({ + plugins: [ + laravel({ + input: 'resources/js/app.js', + refresh: true, + }), + ], + server: { + cors: { + origin: [ + 'https://backend.laravel', + 'http://admin.laravel:8566', + ], + }, + }, + }); + +You may also include regex patterns, which can be helpful if you would like to +allow all origins for a given top-level domain, such as `*.laravel`: + + + + 1import { defineConfig } from 'vite'; + + 2import laravel from 'laravel-vite-plugin'; + + 3  + + 4export default defineConfig({ + + 5 plugins: [ + + 6 laravel({ + + 7 input: 'resources/js/app.js', + + 8 refresh: true, + + 9 }), + + 10 ], + + 11 server: { + + 12 cors: { + + 13 origin: [ + + 14 // Supports: SCHEME://DOMAIN.laravel[:PORT] + + 15 /^https?:\/\/.*\.laravel(:\d+)?$/, + + 16 ], + + 17 }, + + 18 }, + + 19}); + + + import { defineConfig } from 'vite'; + import laravel from 'laravel-vite-plugin'; + + export default defineConfig({ + plugins: [ + laravel({ + input: 'resources/js/app.js', + refresh: true, + }), + ], + server: { + cors: { + origin: [ + // Supports: SCHEME://DOMAIN.laravel[:PORT] + /^https?:\/\/.*\.laravel(:\d+)?$/, + ], + }, + }, + }); + +### Correcting Dev Server URLs + +Some plugins within the Vite ecosystem assume that URLs which begin with a +forward-slash will always point to the Vite dev server. However, due to the +nature of the Laravel integration, this is not the case. + +For example, the `vite-imagetools` plugin outputs URLs like the following +while Vite is serving your assets: + + + + 1 + + + + +The `vite-imagetools` plugin is expecting that the output URL will be +intercepted by Vite and the plugin may then handle all URLs that start with +`/@imagetools`. If you are using plugins that are expecting this behavior, you +will need to manually correct the URLs. You can do this in your +`vite.config.js` file by using the `transformOnServe` option. + +In this particular example, we will prepend the dev server URL to all +occurrences of `/@imagetools` within the generated code: + + + + 1import { defineConfig } from 'vite'; + + 2import laravel from 'laravel-vite-plugin'; + + 3import { imagetools } from 'vite-imagetools'; + + 4  + + 5export default defineConfig({ + + 6 plugins: [ + + 7 laravel({ + + 8 // ... + + 9 transformOnServe: (code, devServerUrl) => code.replaceAll('/@imagetools', devServerUrl+'/@imagetools'), + + 10 }), + + 11 imagetools(), + + 12 ], + + 13}); + + + import { defineConfig } from 'vite'; + import laravel from 'laravel-vite-plugin'; + import { imagetools } from 'vite-imagetools'; + + export default defineConfig({ + plugins: [ + laravel({ + // ... + transformOnServe: (code, devServerUrl) => code.replaceAll('/@imagetools', devServerUrl+'/@imagetools'), + }), + imagetools(), + ], + }); + +Now, while Vite is serving Assets, it will output URLs that point to the Vite +dev server: + + + + 1- + + 2+ + + + - + + + diff --git a/output/changelog/.md b/output/changelog/.md new file mode 100644 index 0000000..9353840 --- /dev/null +++ b/output/changelog/.md @@ -0,0 +1,309 @@ +# Changelog + +New updates and improvements to Laravel’s open source products. + +## July 2025 + +## Laravel Framework 12.x + +### Added Verbose Output for `queue:work` + +Pull requests by [@seriquynh](https://github.com/laravel/framework/pull/56086) +& [@amirhshokri](https://github.com/laravel/framework/pull/56095) + + + + 1php artisan queue:work --verbose + + 2// App\Jobs\UrgentAction 85 high + + 3// App\Jobs\NormalAction 84 default + + + php artisan queue:work --verbose + // App\Jobs\UrgentAction 85 high + // App\Jobs\NormalAction 84 default + +With `--verbose`, you’ll now see queue names alongside each job and job ID, +making multi-queue debugging straightforward and saving you time when tracing +issues. + +### `Uri` Implements `JsonSerializable` + +Pull request by +[@devajmeireles](https://github.com/laravel/framework/pull/56097) + + + + 1echo json_encode(new Uri('/path?x=1')); + + 2// "http://localhost:8000/path?x=1" + + + echo json_encode(new Uri('/path?x=1')); + // "http://localhost:8000/path?x=1" + +This release fixes a serialization bug, properly converting the URI to a +string when serializing a larger JSON object that contains the URI as a value. + +### Added `doesntStartWith()` & `doesntEndWith()` `Str` Methods + +Pull request by +[@balboacodes](https://github.com/laravel/framework/pull/56168) + + + + 1str('apple')->doesntStartWith('b'); // true + + 2str('apple')->doesntEndWith('e'); // false + + + str('apple')->doesntStartWith('b'); // true + str('apple')->doesntEndWith('e'); // false + +The inverse of `startsWith`/`endsWith`, these new methods allow you to +fluently test for starting and ending characters in your string. + +### Closure Support in `pluck()` + +Pull request by [@ralphjsmit](https://github.com/laravel/framework/pull/56188) + + + + 1$users->pluck(fn($u) => $u->email, fn($u) => strtoupper($u->name)); + + + $users->pluck(fn($u) => $u->email, fn($u) => strtoupper($u->name)); + +You can now dynamically generate keys and values via callbacks. Instead of +mapping then plucking, you get a single, flexible method that reduces steps +and keeps intent clear. + +## Pint + +### Added `--parallel` Option + +Pull request by [@nunomaduro](https://github.com/laravel/pint/pull/376) + + + + 1./vendor/bin/pint --parallel + + + ./vendor/bin/pint --parallel + +By leveraging PHP CS Fixer’s parallel runner, +[Pint](https://laravel.com/docs/12.x/pint) now formats files concurrently. On +large codebases that once took minutes, you’ll see results in seconds, +speeding up both local workflows and CI pipelines for a clear quality of life +win. + +## Telescope + +### Migrated to Vite + +Pull request by [@nckrtl](https://github.com/laravel/telescope/pull/1598) + +Telescope’s frontend now uses [Vite](https://laravel.com/docs/12.x/vite) +instead of Mix. Asset builds finish faster, hot-reloads are more reliable, and +you benefit from a modern toolchain without changing your workflow. + +## Octane + +### FrankenPHP’s File Watcher + +Pull request by [@kohenkatz](https://github.com/laravel/octane/pull/971) + +[Octane](https://laravel.com/docs/12.x/octane) now relies on +[FrankenPHP](https://frankenphp.dev)’s native file watcher for reloading the +server on file changes. This removes the NodeJS dependency and eases the local +development process significantly. + +## Inertia + +### Reset Form State and Clear Errors with a Single Method + +Pull request by +[@pascalbaljet](https://github.com/inertiajs/inertia/pull/2414) + + + + 1const form = useForm({ name: "", email: "" }); + + 2  + + 3// Instead of: + + 4form.reset(); + + 5form.clearErrors(); + + 6  + + 7// You can now: + + 8form.resetAndClearErrors(); + + + const form = useForm({ name: "", email: "" }); + + // Instead of: + form.reset(); + form.clearErrors(); + + // You can now: + form.resetAndClearErrors(); + +You can now concisely reset all fields and errors in one go, bringing your +form back to square one. + +### Prevent Minifying JavaScript Builds and Test Apps + +Pull request by +[@pascalbaljet](https://github.com/inertiajs/inertia/pull/2424) + +Inertia no longer distributes minified builds, aligning it with the strategy +of other popular libraries. This makes debugging more straightforward and +allows for local patching if the need arises. + +## June 2025 + +## Laravel Framework 12.x + +### Added `encrypt()` and `decrypt()` String Helper Methods + +Pull request by +[@KIKOmanasijev](https://github.com/laravel/framework/pull/55931) + +You can now chain `encrypt()` and `decrypt()` directly on a `Str` instance, so +instead of piping your string through separate encryption calls, you can +write: + + + + 1$value = Str::of('secret')->encrypt()->prepend('Encrypted: '); + + 2$original = Str::of($value)->decrypt(); + + + $value = Str::of('secret')->encrypt()->prepend('Encrypted: '); + $original = Str::of($value)->decrypt(); + +This keeps your string-manipulation chains concise (no need to write separate, +extra code to handle encryption) and you don’t have to manually wrap a value +in encryption calls each time. + +Learn more about [`encrypt`](https://laravel.com/docs/12.x/helpers#method- +encrypt) and [`decrypt`](https://laravel.com/docs/12.x/helpers#method-decrypt) + +### Added `broadcast_if()` and `broadcast_unless()` Utilities + +Pull request by +[@taylorotwell](https://github.com/laravel/framework/pull/55967) + +You now have two methods for conditional broadcasting in a single call: + + + + 1broadcast_if($order->isPaid(), new OrderShipped($order)); + + 2broadcast_unless($user->isActive(), new InactiveUserAlert($user)); + + + broadcast_if($order->isPaid(), new OrderShipped($order)); + broadcast_unless($user->isActive(), new InactiveUserAlert($user)); + +This replaces multi-line conditionals around `broadcast()` and makes your +event logic more readable, improving the overall developer experience. + +Read the docs about [event broadcasting in +Laravel](https://laravel.com/docs/12.x/broadcasting) + +### Added `--batched` Flag to `make:job` + +Pull request by +[@hafezdivandari](https://github.com/laravel/framework/pull/55929) + +The `php artisan make:job` command now accepts a `--batched` option: + + + + 1php artisan make:job ProcessPodcast --batched + + + php artisan make:job ProcessPodcast --batched + +This command scaffolds the job with the `Batchable` trait already applied, so +you don’t have to add it manually. It saves you a manual step and ensures +consistency. + +Learn more about [defining batchable +jobs](https://laravel.com/docs/12.x/queues#defining-batchable-jobs) + +## VS Code Extension + +![VS Code Performance Improvements](/images/changelog/2025-06/vs-code- +performance-improvements.png) + +### Memory Improvements + +Pull request by [@joetannenbaum](https://github.com/laravel/vs-code- +extension/pull/402) + +We fixed a memory-leak issue in the extension’s background processes, reducing +its long-term footprint and preventing slowdowns during extended coding +sessions. + +### Improved Startup Time + +Pull request by [@joetannenbaum](https://github.com/laravel/vs-code- +extension/pull/404) + +Initialization with the VS Code extension now completes in under one second +(down from 5–7 seconds) by deferring heavy setup until it’s actually needed. +You’ll notice the extension loads almost instantly so you can start building +faster. + +Get started with the [Laravel VS Code +extension](https://github.com/laravel/vs-code-extension) + +## Inertia + +### Allow `deepMerge` on Custom Properties + +Pull request by [@mpociot](https://github.com/inertiajs/inertia/pull/2344) + +When implementing infinite scrolling with Inertia or other paginated data +merges, you can now specify a key (`matchOn`) to do a deep merge, matching +items by ID and replacing or appending as appropriate. This provides greater +flexibility, prevents duplicated entries, and keeps your client-side data +consistent. + + + + 1Inertia::render('Users/Index', [ + + 2 'users' => Inertia::deepMerge($users)->matchOn('data.id'), + + 3]); + + + Inertia::render('Users/Index', [ + 'users' => Inertia::deepMerge($users)->matchOn('data.id'), + ]); + +[Learn more in the Inertia docs](https://inertiajs.com/merging-props#server- +side) + +### Prevent Duplicate Render in React + +Pull request by +[@pascalbaljet](https://github.com/inertiajs/inertia/pull/2377) + +We fixed a React-specific issue in `` where the initial Inertia +page would render twice. Now you get a single, clean first render without +flicker, improving perceived performance. + +Get started with the [Laravel + React Starter +Kit](https://www.youtube.com/watch?v=_sw61Ew1FHQ) + diff --git a/scrapy.cfg b/scrapy.cfg new file mode 100644 index 0000000..d0fb46e --- /dev/null +++ b/scrapy.cfg @@ -0,0 +1,11 @@ +# Automatically created by: scrapy startproject +# +# For more information about the [deploy] section see: +# https://scrapyd.readthedocs.io/en/latest/deploy.html + +[settings] +default = laravelDocScrappy.settings + +[deploy] +#url = http://localhost:6800/ +project = laravelDocScrappy